Currently i am developing an application where users are authenticated via LDAP. The criteria for successful login is based on correct username,password and group(TEST-FFSUS-CALBR-USER).
i was able to login successfully with username and password criteria , but not with group criteria.
Following are the code used for this purpose :
#Resource
LdapTemplate ldapTemplate;
public boolean login(String username, String password) {
try {
ContainerCriteria searchCriteria = getLdapFilterCriteria(username);
boolean result = ldapTemplate.authenticate("OU=User,OU=fo-id,DC=feefusde,DC=rootdom,DC=net",
searchCriteria.filter().encode(), password);
return result;
} catch (Exception e) {
return false;
}
}
private ContainerCriteria getLdapFilterCriteria(String usernameOrEmail) {
ContainerCriteria objectClassCriteria = LdapQueryBuilder.query().base("DC=rootdom,DC=net").where("objectClass")
.is("user");
ContainerCriteria mailCriteria = LdapQueryBuilder.query().where("mail").is(usernameOrEmail);
ContainerCriteria cnCriteria = LdapQueryBuilder.query().where("CN").is(usernameOrEmail);
ContainerCriteria roleCriteria = LdapQueryBuilder.query().where("memberOf=CN").is("TEST-FFSUS-CALBR-USER");
ContainerCriteria userByMailOrCnCriteria = mailCriteria.or(cnCriteria);
ContainerCriteria searchCriteria = objectClassCriteria.and(userByMailOrCnCriteria).and(roleCriteria);
return searchCriteria;
}
In application-context:
<bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
<property name="contextSource">
<bean class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
<constructor-arg>
<value>ldap://urlofldap:portno</value>
</constructor-arg>
<property name="userDn" value="${ldap.username}" />
<property name="password" value="${ldap.password}" />
</bean>
</property>
</bean>
Is there any way to fix above issue. ?
The memberOf attribute should match the group distinguished name. For example "CN=TEST-FFSUS-CALBR-USER,OU=roles,DC=feefusde,DC=rootdom,DC=net":
ContainerCriteria roleCriteria = LdapQueryBuilder.query().where("memberOf").is(groupDN);
Related
I would like to use Apache Shiro with database authentication. But I can't make database design changes. I would like to use my custom SQL command and Java logic to authenticate user. Is this possible? I tried this configuration in shiro.ini:
saltedJdbcRealm=com.crm.web.authentication.JdbcRealm
And custom Java class:
public class JdbcRealm extends AuthorizingRealm
{
#Resource(name = "jdbc/DefaultDB")
private DataSource dataSource;
protected static final String DEFAULT_AUTHENTICATION_QUERY = "select passwd from user where username = ?";
protected static final String DEFAULT_SALTED_AUTHENTICATION_QUERY = "select passwd, passwd_salt from user where username = ?";
protected static final String DEFAULT_USER_ROLES_QUERY = "select role_name from user_roles where username = ?";
protected static final String DEFAULT_PERMISSIONS_QUERY = "select permission from roles_permissions where role_name = ?";
private static final Logger log = LoggerFactory.getLogger(JdbcRealm.class);
public enum SaltStyle
{
NO_SALT, CRYPT, COLUMN, EXTERNAL
};
protected String authenticationQuery = DEFAULT_AUTHENTICATION_QUERY;
protected String userRolesQuery = DEFAULT_USER_ROLES_QUERY;
protected String permissionsQuery = DEFAULT_PERMISSIONS_QUERY;
protected boolean permissionsLookupEnabled = false;
protected SaltStyle saltStyle = SaltStyle.NO_SALT;
public void setDataSource(DataSource dataSource)
{
this.dataSource = dataSource;
}
public void setAuthenticationQuery(String authenticationQuery)
{
this.authenticationQuery = authenticationQuery;
}
public void setUserRolesQuery(String userRolesQuery)
{
this.userRolesQuery = userRolesQuery;
}
public void setPermissionsQuery(String permissionsQuery)
{
this.permissionsQuery = permissionsQuery;
}
public void setPermissionsLookupEnabled(boolean permissionsLookupEnabled)
{
this.permissionsLookupEnabled = permissionsLookupEnabled;
}
public void setSaltStyle(SaltStyle saltStyle)
{
this.saltStyle = saltStyle;
if (saltStyle == SaltStyle.COLUMN && authenticationQuery.equals(DEFAULT_AUTHENTICATION_QUERY))
{
authenticationQuery = DEFAULT_SALTED_AUTHENTICATION_QUERY;
}
}
#Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException
{
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
// Null username is invalid
if (username == null)
{
throw new AccountException("Null usernames are not allowed by this realm.");
}
Connection conn = null;
SimpleAuthenticationInfo info = null;
try
{
conn = dataSource.getConnection();
String password = null;
String salt = null;
switch (saltStyle)
{
case NO_SALT:
password = getPasswordForUser(conn, username)[0];
break;
case CRYPT:
// TODO: separate password and hash from getPasswordForUser[0]
throw new ConfigurationException("Not implemented yet");
//break;
case COLUMN:
String[] queryResults = getPasswordForUser(conn, username);
password = queryResults[0];
salt = queryResults[1];
break;
case EXTERNAL:
password = getPasswordForUser(conn, username)[0];
salt = getSaltForUser(username);
}
if (password == null)
{
throw new UnknownAccountException("No account found for user [" + username + "]");
}
info = new SimpleAuthenticationInfo(username, password.toCharArray(), getName());
if (salt != null)
{
info.setCredentialsSalt(ByteSource.Util.bytes(salt));
}
}
catch (SQLException e)
{
final String message = "There was a SQL error while authenticating user [" + username + "]";
if (log.isErrorEnabled())
{
log.error(message, e);
}
throw new AuthenticationException(message, e);
}
finally
{
JdbcUtils.closeConnection(conn);
}
return info;
}
private String[] getPasswordForUser(Connection conn, String username) throws SQLException
{
String[] result;
boolean returningSeparatedSalt = false;
switch (saltStyle)
{
case NO_SALT:
case CRYPT:
case EXTERNAL:
result = new String[1];
break;
default:
result = new String[2];
returningSeparatedSalt = true;
}
PreparedStatement ps = null;
ResultSet rs = null;
try
{
ps = conn.prepareStatement(authenticationQuery);
ps.setString(1, username);
// Execute query
rs = ps.executeQuery();
// Loop over results - although we are only expecting one result, since usernames should be unique
boolean foundResult = false;
while (rs.next())
{
// Check to ensure only one row is processed
if (foundResult)
{
throw new AuthenticationException("More than one user row found for user [" + username + "]. Usernames must be unique.");
}
result[0] = rs.getString(1);
if (returningSeparatedSalt)
{
result[1] = rs.getString(2);
}
foundResult = true;
}
}
finally
{
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(ps);
}
return result;
}
#Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)
{
//null usernames are invalid
if (principals == null)
{
throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
}
String username = (String) getAvailablePrincipal(principals);
Connection conn = null;
Set<String> roleNames = null;
Set<String> permissions = null;
try
{
conn = dataSource.getConnection();
// Retrieve roles and permissions from database
roleNames = getRoleNamesForUser(conn, username);
if (permissionsLookupEnabled)
{
permissions = getPermissions(conn, username, roleNames);
}
}
catch (SQLException e)
{
final String message = "There was a SQL error while authorizing user [" + username + "]";
if (log.isErrorEnabled())
{
log.error(message, e);
}
// Rethrow any SQL errors as an authorization exception
throw new AuthorizationException(message, e);
}
finally
{
JdbcUtils.closeConnection(conn);
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames);
info.setStringPermissions(permissions);
return info;
}
protected Set<String> getRoleNamesForUser(Connection conn, String username) throws SQLException
{
PreparedStatement ps = null;
ResultSet rs = null;
Set<String> roleNames = new LinkedHashSet<String>();
try
{
ps = conn.prepareStatement(userRolesQuery);
ps.setString(1, username);
// Execute query
rs = ps.executeQuery();
// Loop over results and add each returned role to a set
while (rs.next())
{
String roleName = rs.getString(1);
// Add the role to the list of names if it isn't null
if (roleName != null)
{
roleNames.add(roleName);
}
else
{
if (log.isWarnEnabled())
{
log.warn("Null role name found while retrieving role names for user [" + username + "]");
}
}
}
}
finally
{
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(ps);
}
return roleNames;
}
protected Set<String> getPermissions(Connection conn, String username, Collection<String> roleNames) throws SQLException
{
PreparedStatement ps = null;
Set<String> permissions = new LinkedHashSet<>();
try
{
ps = conn.prepareStatement(permissionsQuery);
for (String roleName : roleNames)
{
ps.setString(1, roleName);
ResultSet rs = null;
try
{
// Execute query
rs = ps.executeQuery();
// Loop over results and add each returned role to a set
while (rs.next())
{
String permissionString = rs.getString(1);
// Add the permission to the set of permissions
permissions.add(permissionString);
}
}
finally
{
JdbcUtils.closeResultSet(rs);
}
}
}
finally
{
JdbcUtils.closeStatement(ps);
}
return permissions;
}
protected String getSaltForUser(String username)
{
return username;
}
}
But when I run the code I get:
org.apache.shiro.authc.AuthenticationException: Authentication token of type [class org.apache.shiro.authc.UsernamePasswordToken] could not be authenticated by any configured realms. Please ensure that at least one realm can authenticate these tokens.
Am I missing some configuration in shiro.ini
This is how we do it in XML(shiro.xml) :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="logout" class="org.apache.shiro.web.filter.authc.LogoutFilter">
<property name="redirectUrl" value="YOUR_LOGIN_URL" />
</bean>
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login"/>
<property name="successUrl" value="YOUR_SUCCESS_URL"/>
<property name="unauthorizedUrl" value="YOUR_ACCESS_DENIED_URL"/>
<property name="filters">
<util:map>
<entry key="logout" value-ref="logout"/>
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
/** = authc <!--SPECIFY_OTHERS_FILTERS_CHAINS-->
</value>
</property>
</bean>
<bean id="builtInCacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager"/>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- Single realm app. If you have multiple realms, use the 'realms' property instead. -->
<property name="realm" ref="myRealm"/>
<property name="cacheManager" ref="builtInCacheManager"/>
<!-- By default the servlet container sessions will be used. Uncomment this line-->
<!-- to use shiro's native sessions (see the JavaDoc for more): -->
<!-- <property name="sessionMode" value="native"/> -->
</bean>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- Define the Shiro Realm implementation you want to use to connect to your back-end -->
<!-- security datasource: -->
<bean id="myRealm" class="org.apache.shiro.realm.jdbc.JdbcRealm">
<property name="credentialsMatcher" ref="hashMatcher"/>
<property name="authenticationQuery" value="select password from user_login where user_id = ?"/>
<property name="userRolesQuery" value="YOUR_ROLE_QUERY"/>
<property name="permissionsQuery" value="YOUR_PERMISSION_QUERY" />
<property name="permissionsLookupEnabled" value="true"></property>
<property name="dataSource" ref="YOUR_DATA_SOURCE_NAME"/> <!-- i.e. being used for the DB connection -->
</bean>
<!-- Hash Matcher Bean responsible for matching credentials of logging user -->
<bean id="hashMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!-- Algorithm name -->
<property name="hashAlgorithmName" value="SHA-512"/>
<!-- No. of Hash Iterations. Note: must match with iterations used to save password in database. -->
<property name="hashIterations" value="10000"/>
<!-- true if Stored Credentials(i.e. password and salt) are in Hexadecimal form. False denotes BASE64 encoding.-->
<property name="storedCredentialsHexEncoded" value="true"/>
</bean>
<!-- Enable Shiro Annotations for Spring-configured beans. Only run after -->
<!-- the lifecycleBeanProcessor has run: -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
</beans>
You can include it in the application config file (web.xml)
All shiro needs to mark session as authenticated is AuthenticationInfo object. How it's built is up to you.
The realm should be tied to the security manager.
I want to give you 2 suggestions. Hope it will help you.
Suggestion - 1:
Configuration file is not fully configured for Realm.
You should write a class for AuthorizingRealm, then the class will be configured to configuration file.
If you use the spring, then the configuration will look like below:
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="localRealm" />
</bean>
<bean id="localRealm" class="com.xxxx.xxxxx.infra.LocalSecurityRealm">
<constructor-arg index="0" ref="securityApplication" />
</bean>
Add authenticator in shiro.ini configuration file
authenticator = com.realm.MyRealm
Resource Link:
http://www.oschina.net/question/617087_72790#answers
Suggestion - 2:
You need to first make sure that supports() are actually reached and executed.
#Override
public boolean supports(AuthenticationToken authenticationToken) {
return (authenticationToken instanceof UsernamePasswordToken)
}
If you have multiple realms and one throws an error, the others will NOT be processed.
So if you need to work around thrown Exceptions you can do something like this for authz and this for authc.
Resource Link:
http://shiro-user.582556.n2.nabble.com/Still-having-an-issue-with-multiple-realms-td7579698.html
The following managed operation exists in the project:
#ManagedOperation(description = "Some description")
#ManagedOperationParameters({
#ManagedOperationParameter(name = "key", description = "Some description"),
})
public void foo(String key) {
// some logic
}
Also there is a property which can be used in Spring context by surrounding it with dollar sign and square brackets:
"${some.property.key}"
Is it possible to use the value of aforementioned property key in the managed operation annotation description? Something like:
#ManagedOperationParameter(name = "key",
description = "Some description, please note that the key is ${some.property.key}")
Not out-of-the-box, but it's pretty easy to customize...
public class CustomAttributeSource extends AnnotationJmxAttributeSource implements EmbeddedValueResolverAware {
private StringValueResolver embeddedValueResolver;
#Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.embeddedValueResolver = resolver;
}
#Override
public ManagedAttribute getManagedAttribute(Method method) throws InvalidMetadataException {
ManagedAttribute managedAttribute = super.getManagedAttribute(method);
if (this.embeddedValueResolver != null) {
managedAttribute
.setDescription(this.embeddedValueResolver.resolveStringValue(managedAttribute.getDescription()));
}
return managedAttribute;
}
#Override
public ManagedOperation getManagedOperation(Method method) throws InvalidMetadataException {
ManagedOperation managedOperation = super.getManagedOperation(method);
if (this.embeddedValueResolver != null) {
managedOperation
.setDescription(this.embeddedValueResolver.resolveStringValue(managedOperation.getDescription()));
}
return managedOperation;
}
}
Then...
<bean class="org.springframework.jmx.export.annotation.AnnotationMBeanExporter">
<property name="assembler">
<bean class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
<property name="attributeSource">
<bean class="foo.CustomAttributeSource" />
</property>
</bean>
</property>
</bean>
Trying to implement Spring batch,but facing a strange problem,Our ItemReader class is executing only once.
Here below is the detail.
If we have 1000 rows in DB.
Our Item reader fetch 1000 rows from DB,and pass list to ItemWriter
ItemWriter successfully delete all items.
Now ItemReader again tries to fetch the data from DB,but did not find,hence returns NULL,so execution stops.
But we have configured batch to be executed with Quartz scheduler,which is every minute.
Now if we insert let say 1000 rows in DB by dump import,the batch job should pick this data in next execution,but it is not even executing,although
JobLauncher is executing.
Configuration :-
1.We have ItemReader,ItemWriter with commit interval equals to 1.
<batch:job id="csrfTokenBatchJob">
<batch:step id="step1">
<tasklet>
<chunk reader="csrfTokenReader" writer="csrfTokenWriter" commit-interval="1"></chunk>
</tasklet>
</batch:step>
</batch:job>
2.Job is scheduled to be triggered at every minute.
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail" ref="jobDetail" />
<property name="cronExpression" value="0 0/1 * * * ?" />
</bean>
</property>
</bean>
3.Job configuration
<bean id="jobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">
<property name="jobClass" value="com.tavant.oauth.batch.job.CSRFTokenJobLauncher" />
<property name="jobDataAsMap">
<map>
<entry key="jobName" value="csrfTokenCleanUpBatchJob" />
<entry key="jobLocator" value-ref="jobRegistry" />
<entry key="jobLauncher" value-ref="jobLauncher" />
</map>
</property>
</bean>
First time it is executing successfully,but later it does not execute,but i can see in logs that JobLauncher is executing.
#Component("csrfTokenReader")
#Scope(value="step")
public class CSRFTokenReader implements ItemReader<List<CSRFToken>> {
private static final Logger logger = LoggerFactory.getLogger(CSRFTokenReader.class);
#Autowired
private CleanService cleanService;
#Override
public List<CSRFToken> read() {
List<CSRFToken> csrfTokenList = null;
try{
int keepUpto = Integer.valueOf(PropertiesContext.getInstance().getProperties().getProperty("token.keep", "1"));
Calendar calTime = Calendar.getInstance();
calTime.add(Calendar.HOUR, -keepUpto);
Date toKeep = calTime.getTime();
csrfTokenList = cleanService.getCSRFTokenByTime(toKeep);
}
catch(Throwable th){
logger.error("Exception in running job At " + new Date() + th);
}
if(CollectionUtils.isEmpty(csrfTokenList)){
return null;
}
return csrfTokenList;
}
}
EDIT:--
public class CSRFTokenJobLauncher extends QuartzJobBean {
static final String JOB_NAME = "jobName";
private JobLocator jobLocator;
private JobLauncher jobLauncher;
public void setJobLocator(JobLocator jobLocator) {
this.jobLocator = jobLocator;
}
public void setJobLauncher(JobLauncher jobLauncher) {
this.jobLauncher = jobLauncher;
}
#Override
protected void executeInternal(JobExecutionContext context) {
Map<String, Object> jobDataMap = context.getMergedJobDataMap();
String jobName = (String) jobDataMap.get(JOB_NAME);
log.info("Quartz trigger firing with Spring Batch jobName="+jobName);
JobParameters jobParameters = getJobParametersFromJobMap(jobDataMap);
try {
jobLauncher.run(jobLocator.getJob(jobName), jobParameters);
}
catch (JobExecutionException e) {
log.error("Could not execute job.", e);
}
}
private JobParameters getJobParametersFromJobMap(Map<String, Object> jobDataMap) {
JobParametersBuilder builder = new JobParametersBuilder();
for (Entry<String, Object> entry : jobDataMap.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof String && !key.equals(JOB_NAME)) {
builder.addString(key, (String) value);
}
else if (value instanceof Float || value instanceof Double) {
builder.addDouble(key, ((Number) value).doubleValue());
}
else if (value instanceof Integer || value instanceof Long) {
builder.addLong(key, ((Number)value).longValue());
}
else if (value instanceof Date) {
builder.addDate(key, (Date) value);
}
}
return builder.toJobParameters();
}
}
After hours of time wasting,the problem seems to be solved now,i have configured allow-start-if-complete="true" in tasklet.Now Batch Item Reader is executing as per schedule.
<batch:job id="csrfTokenBatchJob">
<batch:step id="step1">
<batch:tasklet allow-start-if-complete="true">
<batch:chunk reader="csrfTokenReader" writer="csrfTokenWriter" commit-interval="1"></batch:chunk>
</batch:tasklet>
</batch:step>
</batch:job>
Spring batch records every job execution in database. Which is why spring batch need to differentiate every job run. It checks whether the job is already executed on the same day and it would not start again unless any job parameter varies from previous run or allow start if complete setting is enabled.
OPTION1:- As mentioned above answer we can use allow-start-if-complete="true"
OPTION2:- Always pass a job parameter which is a current date time stamp. This way job parameter value is always unique.
JobExecution jobExecution = jobLauncher.run(reportJob, new JobParametersBuilder()
.addDate("now", new Date()).build());
OPTION3:- Use an incrementor for example RunIdIncrementer so we do not need to make sure to pass unique job parameter every time.
#Bean
public Job job1(JobBuilderFactory jobs, Step s1) {
return jobs.get("job1")
.incrementer(new RunIdIncrementer())
.flow(s1)
.end()
.build();
}
I'm trying to add a custom save event listener for hibernate. My goal is to have hibernate set the last update and created timestamp values on certain entities. I have read from other posts that JPA annotations will do it but if you are using a Hibernate Session then you need to extend DefaultSaveOrUpdateEventListener. I did this and it hasn't worked. Every example I see is using a hibernate config file. My sessionFactory is configured with Spring.
<bean id="mySessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource">
<ref bean="myDataSource"/>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
<property name="packagesToScan">
<list>
<value>com.mypackages</value>
</list>
</property>
<property name="eventListeners">
<map>
<entry key="save-update">
<ref local="saveEventListener" />
</entry>
</map>
</property>
</bean>
<bean id="saveEventListener" class="com.mypackage.event.SaveOrUpdateDateListener" />
I set a breakpoint and it doesn't go through the listener. My last updated and created fields are not being set in the database.
I have worked with the similar issue and finally resolved.
I have a base pojo with properties for Auditing , Every entity extends this base pojo. on call to save or update, the methods of Event Listeners get triggered where I update the entity with auditing information.
#Component
public class EntitySaveListener implements PersistEventListener, MergeEventListener,
PreInsertEventListener {
private static final long serialVersionUID = 1L;
static final Logger logger = LoggerFactory
.getLogger(EntitySaveListener.class);
#Autowired
private LocalEntityManagerFactoryBean entityManagerFactory;
public EntitySaveListener() {
logger.info("EntitySaveListener created");
}
public void onPersist(PersistEvent event) throws HibernateException {
if (SecurityContextHolder.getContext() != null
&& SecurityContextHolder.getContext().getAuthentication() != null) {
Object principal = SecurityContextHolder.getContext()
.getAuthentication().getPrincipal();
if (principal != null && principal instanceof V2VUserDetails) {
User user = ((V2VUserDetails) principal).getUser();
if (event.getObject() instanceof ModificationTracker &&
user != null) {
ModificationTracker entity = (ModificationTracker) event.getObject();
entity.setCreatedDate(new Date());
entity.setCreatedBy(user);
entity.setLastUpdated(new Date());
entity.setLastUpdatedBy(user);
}
}
}
}
#SuppressWarnings("rawtypes")
#Override
public void onPersist(PersistEvent event, Map arg1)
throws HibernateException {
// TODO Auto-generated method stub
}
#Override
public void onMerge(MergeEvent event) throws HibernateException {
if (SecurityContextHolder.getContext() != null
&& SecurityContextHolder.getContext().getAuthentication() != null) {
Object principal = SecurityContextHolder.getContext()
.getAuthentication().getPrincipal();
if (principal != null && principal instanceof V2VUserDetails) {
User user = ((V2VUserDetails) principal).getUser();
if (event.getEntity() instanceof ModificationTracker
&& user != null) {
ModificationTracker entity = (ModificationTracker) event
.getEntity();
entity.setLastUpdated(new Date());
entity.setLastUpdatedBy(user);
}
}
}
}
#SuppressWarnings("rawtypes")
#Override
public void onMerge(MergeEvent arg0, Map arg1) throws HibernateException {
// TODO Auto-generated method stub
}
#Override
public boolean onPreInsert(PreInsertEvent arg0) {
// TODO Auto-generated method stub
return false;
}
}
I'm trying to test my optimistic locking implementation of my application. However the result is not what I expect. The steps I take to test are the following
load an entity from the database
set the version attribute to one less than present in the database
change another attribute thats just a string to something else
save the entity
I expected a staleException now, however the entity just gets saved and the version increases to the next in line.
Here is a small extract of my save code
public <T extends DomainObject> T save(T objectToSave) {
Session currentSession = null;
try {
currentSession = sessionFactory.getCurrentSession();
currentSession.save(objectToSave);
return objectToSave;
} catch (Exception ex) {
logger.error("save error",ex);
}
return null;
}
I load objects by id with named queries thru my entire application with following code
#SuppressWarnings("unchecked")
public <T extends Object> List<T> query(Class<T> returnClass, String query, List<String> namedParams, List<? extends Object> params, Integer limit) {
Session currentSession = null;
try {
currentSession = sessionFactory.getCurrentSession();
Query namedQuery = currentSession.getNamedQuery(query);
if(limit != null){
namedQuery.setMaxResults(limit);
}
namedQuery.setCacheable(true);
if (namedParams != null && namedParams.size() > 0) {
addParams(namedQuery, namedParams, (List<Object>) params);
}
return namedQuery.list();
} catch (Exception ex) {
logger.error("query error",ex);
}
return null;
}
And this is the configuration of my sessionfactory
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="entityInterceptor" ref="auditInterceptor" />
<property name="dataSource" ref="dataSource" />
<property name="hibernateProperties">
<value>
hibernate.dialect=${hibernate.dialect}
hibernate.show_sql=${hibernate.show_sql}
hibernate.cache.provider_class=org.hibernate.cache.EhCacheProvider
hibernate.cache.region.factory_class=net.sf.ehcache.hibernate.EhCacheRegionFactory
hibernate.cache.use_query_cache=true
hibernate.cache.use_second_level_cache=true
hibernate.cache.provider_configuration_file_resource_path=ehcache.xml
hibernate.generate_statistics=true
hbm2ddl.auto=${hbm2ddl.auto}
hibernate.c3p0.min_size=${hibernate.c3p0.min_size}
hibernate.c3p0.max_size=${hibernate.c3p0.max_size}
hibernate.c3p0.timeout=${hibernate.c3p0.timeout}
hibernate.c3p0.max_statements=${hibernate.c3p0.max_statements}
hibernate.c3p0.idle_test_period=${hibernate.c3p0.idle_test_period}
</value>
</property>
<property name="schemaUpdate">
<value>true</value>
</property>
<property name="annotatedClasses">
<list>
<value>com.mbalogos.mba.domain.site.Site</value>
</list>
</property>
</bean>
Is there anything I missed on the configuration of the sessionFactory or do I test it completly wrong and should I test it in a different way ?
Thanks in advance
Regressing the version field is dangerous as it is Hibernate controlled (if you're using the JPA versioning functionality). Load in an entity, detach it (maybe use session.evict(obj)), alter an attribute. Load in the same entity, keep it attached, alter it, save it. Reattach the first entity and attempt to save it (I think Merge will do this). You should then see the StaleObjectStateException exception.