RESOLVED - issue BATCH-203: HibernateDao incorrectly returns an existing job.

http://opensource.atlassian.com/projects/spring/browse/BATCH-203
This commit is contained in:
dsyer
2007-11-14 08:49:48 +00:00
parent dbf9336e60
commit 1c95e5a625
12 changed files with 371 additions and 153 deletions

View File

@@ -1,18 +1,10 @@
package org.springframework.batch.execution.repository.dao;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.hibernate.EmptyInterceptor;
import org.springframework.batch.core.domain.JobIdentifier;
import org.springframework.batch.core.domain.JobInstance;
import org.springframework.batch.execution.runtime.DefaultJobIdentifier;
import org.springframework.batch.execution.runtime.ScheduledJobIdentifier;
import org.springframework.util.ClassUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
/**
* Hibernate interceptor that can distinguish between the various
@@ -26,56 +18,25 @@ import org.springframework.util.ClassUtils;
* @author Dave Syer
*
*/
public class EntityNameInterceptor extends EmptyInterceptor {
public class EntityNameInterceptor extends EmptyInterceptor implements InitializingBean {
private static final String SIMPLE_JOB_INSTANCE = "SimpleJobInstance";
private Map identifierTypes;
private Set entrySet;
public EntityNameInterceptor() {
Map types = new HashMap();
types.put(ScheduledJobIdentifier.class,
"ScheduledJobInstance");
types.put(DefaultJobIdentifier.class, "DefaultJobInstance");
setIdentifierTypes(types);
};
private EntityNameLocator entityNameLocator;
/**
* Public setter for the identifier types. A map from Class (the
* {@link JobIdentifier} implementation) to String (the entity name). If a
* map from String to String is provided it will be interpreted as a map
* from class name to entity name.
*
* @throws IllegalArgumentException
* if a String key is provided that is not a Class name.
*
* @param types
* the identifierTypes to set
* Public setter for the {@link EntityNameLocator} property.
*
* @param entityNameLocator the entityNameLocator to set
*/
public void setIdentifierTypes(Map types) {
this.identifierTypes = new HashMap();
for (Iterator iterator = types.entrySet().iterator(); iterator
.hasNext();) {
Map.Entry entry = (Map.Entry) iterator.next();
Object key = entry.getKey();
if (key instanceof Class) {
identifierTypes.put(key, entry.getValue());
} else {
try {
Class classKey = ClassUtils.forName(key.toString());
identifierTypes.put(classKey, entry.getValue());
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(
"Could not convert key in identifierTypes to type Class: ["
+ key + "]", e);
}
}
}
this.entrySet = new TreeSet(new ClassComparator());
entrySet.addAll(identifierTypes.keySet());
public void setEntityNameLocator(EntityNameLocator entityNameLocator) {
this.entityNameLocator = entityNameLocator;
}
/**
* Check mandatory properties ({@link #entityNameLocator}).
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(entityNameLocator, "EntityNameLocator must be provided.");
}
/**
@@ -88,38 +49,10 @@ public class EntityNameInterceptor extends EmptyInterceptor {
public String getEntityName(Object object) {
if (object instanceof JobInstance) {
JobInstance instance = (JobInstance) object;
for (Iterator iterator = entrySet.iterator(); iterator.hasNext();) {
Class key = (Class) iterator.next();
if (key.isAssignableFrom(instance.getIdentifier().getClass())) {
return (String) identifierTypes.get(key);
}
}
return SIMPLE_JOB_INSTANCE;
return entityNameLocator
.locate(instance.getIdentifier().getClass());
}
return super.getEntityName(object);
}
/**
* Comparator for classes to order by inheritance.
*
* @author Dave Syer
*
*/
private class ClassComparator implements Comparator {
/**
* @return 1 if arg0 is assignable from arg1
* @return -1 otherwise
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
public int compare(Object arg0, Object arg1) {
Class cls0 = (Class) arg0;
Class cls1 = (Class) arg1;
if (cls0.isAssignableFrom(cls1)) {
return 1;
}
return -1;
}
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.execution.repository.dao;
/**
* Locator strategy for entity names based on class.
*
* @author Dave Syer
*
*/
public interface EntityNameLocator {
/**
* Translate a concrete class into an entity name.
*
* @param clz the Class of the object to locate
* @return an entity name
*/
String locate(Class clz);
}

View File

@@ -26,6 +26,7 @@ import org.springframework.batch.core.domain.JobIdentifier;
import org.springframework.batch.core.domain.JobInstance;
import org.springframework.batch.core.repository.NoSuchBatchDomainObjectException;
import org.springframework.batch.execution.runtime.ScheduledJobIdentifier;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.util.Assert;
@@ -39,8 +40,28 @@ import org.springframework.util.Assert;
* @author Dave Syer
*/
public class HibernateJobDao extends HibernateDaoSupport implements JobDao {
public class HibernateJobDao extends HibernateDaoSupport implements JobDao, InitializingBean {
private EntityNameLocator entityNameLocator;
/**
* Public setter for the {@link EntityNameLocator} property.
*
* @param entityNameLocator the entityNameLocator to set
*/
public void setEntityNameLocator(EntityNameLocator entityNameLocator) {
this.entityNameLocator = entityNameLocator;
}
/**
* Check mandatory properties ({@link #entityNameLocator}).
* @see org.springframework.dao.support.DaoSupport#initDao()
*/
protected void initDao() throws Exception {
Assert.notNull(entityNameLocator, "An EntityNameLocator must be provided.");
super.initDao();
}
/**
* @see JobDao#createJob(JobIdentifier)
*
@@ -75,8 +96,9 @@ public class HibernateJobDao extends HibernateDaoSupport implements JobDao {
List list = this.getHibernateTemplate().executeFind(
new HibernateCallback() {
public Object doInHibernate(Session session) {
String entityName = entityNameLocator.locate(jobRuntimeInformation.getClass());
Criteria criteria = session
.createCriteria(JobInstance.class);
.createCriteria(entityName);
criteria.add(Expression.eq("identifier",
jobRuntimeInformation));
return criteria.list();

View File

@@ -0,0 +1,131 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.execution.repository.dao;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.springframework.batch.core.domain.JobIdentifier;
import org.springframework.batch.core.domain.JobInstance;
import org.springframework.batch.execution.runtime.DefaultJobIdentifier;
import org.springframework.batch.execution.runtime.ScheduledJobIdentifier;
import org.springframework.util.ClassUtils;
/**
* An {@link EntityNameLocator} that knows about {@link JobIdentifier} class
* types and translates them into entity names that are recongnized by
* Hibernate. The implementation is actually generic, and can be used as a
* general purpose {@link EntityNameLocator} by setting the
* {@link #identifierTypes} property. By default it knows about all the entity
* types that work out of the box with Spring Batch.
*
* @author Dave Syer
*
*/
public class JobIdentifierEntityNameLocator implements EntityNameLocator {
private static final String SIMPLE_JOB_INSTANCE = "SimpleJobInstance";
private Map identifierTypes;
private Set entrySet;
public JobIdentifierEntityNameLocator() {
Map types = new HashMap();
types.put(ScheduledJobIdentifier.class, "ScheduledJobInstance");
types.put(DefaultJobIdentifier.class, "DefaultJobInstance");
setIdentifierTypes(types);
};
/**
* Public setter for the identifier types. A map from Class (the
* {@link JobIdentifier} implementation) to String (the entity name). If a
* map from String to String is provided it will be interpreted as a map
* from class name to entity name.
*
* @throws IllegalArgumentException
* if a String key is provided that is not a Class name.
*
* @param types
* the identifierTypes to set
*/
public void setIdentifierTypes(Map types) {
this.identifierTypes = new HashMap();
for (Iterator iterator = types.entrySet().iterator(); iterator
.hasNext();) {
Map.Entry entry = (Map.Entry) iterator.next();
Object key = entry.getKey();
if (key instanceof Class) {
identifierTypes.put(key, entry.getValue());
} else {
try {
Class classKey = ClassUtils.forName(key.toString());
identifierTypes.put(classKey, entry.getValue());
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(
"Could not convert key in identifierTypes to type Class: ["
+ key + "]", e);
}
}
}
this.entrySet = new TreeSet(new ClassComparator());
entrySet.addAll(identifierTypes.keySet());
}
/**
* Identify a {@link JobInstance} entity type from the given
* {@link JobIdentifier} class.
*
* @see org.springframework.batch.execution.repository.dao.EntityNameLocator#locate(java.lang.Class)
*/
public String locate(Class clz) {
for (Iterator iterator = entrySet.iterator(); iterator.hasNext();) {
Class key = (Class) iterator.next();
if (key.isAssignableFrom(clz)) {
return (String) identifierTypes.get(key);
}
}
return SIMPLE_JOB_INSTANCE;
}
/**
* Comparator for classes to order by inheritance.
*
* @author Dave Syer
*
*/
private class ClassComparator implements Comparator {
/**
* @return 1 if arg0 is assignable from arg1
* @return -1 otherwise
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
public int compare(Object arg0, Object arg1) {
Class cls0 = (Class) arg0;
Class cls1 = (Class) arg1;
if (cls0.isAssignableFrom(cls1)) {
return 1;
}
return -1;
}
}
}

View File

@@ -122,6 +122,34 @@ public abstract class AbstractJobDaoTests extends
}
}
/**
* Test that ensures that if you create a job with a given name, then find a
* job with the same name, but other pieces of the identifier different, you
* get no result, not the existing one.
*/
public void testCreateJobWithExistingName() {
ScheduledJobIdentifier scheduledIdentifier = new ScheduledJobIdentifier(
"ScheduledJob");
jobDao.createJob(scheduledIdentifier);
// Modifying the key should bring back a completely different
// JobInstance
ScheduledJobIdentifier newIdentifier = new ScheduledJobIdentifier(
"ScheduledJob");
newIdentifier.setJobKey("different key");
List jobs;
jobs = jobDao.findJobs(scheduledIdentifier);
assertEquals(1, jobs.size());
JobInstance job = (JobInstance) jobs.get(0);
assertEquals(scheduledIdentifier, job.getIdentifier());
jobs = jobDao.findJobs(newIdentifier);
assertEquals(0, jobs.size());
}
public void testUpdateJob() {
// Update the returned job with a new status
job.setStatus(BatchStatus.COMPLETED);

View File

@@ -1,51 +1,29 @@
package org.springframework.batch.execution.repository.dao;
import java.util.Collections;
import junit.framework.TestCase;
import org.springframework.batch.core.domain.JobInstance;
import org.springframework.batch.core.runtime.SimpleJobIdentifier;
import org.springframework.batch.execution.runtime.DefaultJobIdentifier;
import org.springframework.batch.execution.runtime.ScheduledJobIdentifier;
public class EntityNameInterceptorTests extends TestCase {
private EntityNameInterceptor interceptor = new EntityNameInterceptor();
public void testGetEntityNameForScheduledJobIdentifier() {
JobInstance job = new JobInstance(new ScheduledJobIdentifier("foo"));
assertEquals("ScheduledJobInstance", interceptor.getEntityName(job));
/* (non-Javadoc)
* @see junit.framework.TestCase#setUp()
*/
protected void setUp() throws Exception {
super.setUp();
interceptor.setEntityNameLocator(new EntityNameLocator() {
public String locate(Class clz) {
return "foo";
}
});
}
public void testGetEntityNameForDefaultJobIdentifier() {
JobInstance job = new JobInstance(new DefaultJobIdentifier("foo"));
assertEquals("DefaultJobInstance", interceptor.getEntityName(job));
}
public void testGetEntityNameForSimpleJobIdentifier() {
JobInstance job = new JobInstance(new SimpleJobIdentifier("foo"));
assertEquals("SimpleJobInstance", interceptor.getEntityName(job));
}
public void testSetIdentifierTypesWithString() {
interceptor.setIdentifierTypes(Collections.singletonMap(ScheduledJobIdentifier.class.getName(), "foo"));
public void testGetEntityName() {
JobInstance job = new JobInstance(new ScheduledJobIdentifier("foo"));
assertEquals("foo", interceptor.getEntityName(job));
}
public void testSetIdentifierTypesWithClass() {
interceptor.setIdentifierTypes(Collections.singletonMap(ScheduledJobIdentifier.class, "foo"));
JobInstance job = new JobInstance(new ScheduledJobIdentifier("foo"));
assertEquals("foo", interceptor.getEntityName(job));
}
public void testSetIdentifierTypesWithInvalidClassName() {
try {
interceptor.setIdentifierTypes(Collections.singletonMap("FooBarNotAClass", "foo"));
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
// expected
}
}
}

View File

@@ -60,7 +60,7 @@ public class HibernateJobDaoTests extends AbstractJobDaoTests {
public void testUpdateDetachedJobExecution() {
sessionFactory.getCurrentSession().evict(jobExecution);
jobExecution.setStatus(BatchStatus.COMPLETED);
jobExecution.setEndTime(new Timestamp(System.currentTimeMillis()));
jobDao.update(jobExecution);

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.execution.repository.dao;
import java.util.Collections;
import junit.framework.TestCase;
import org.springframework.batch.core.runtime.SimpleJobIdentifier;
import org.springframework.batch.execution.runtime.DefaultJobIdentifier;
import org.springframework.batch.execution.runtime.ScheduledJobIdentifier;
/**
* @author Dave Syer
*
*/
public class JobIdentiferEntityNameLocatorTests extends TestCase {
private JobIdentifierEntityNameLocator interceptor = new JobIdentifierEntityNameLocator();
public void testGetEntityNameForScheduledJobIdentifier() {
assertEquals("ScheduledJobInstance", interceptor.locate(ScheduledJobIdentifier.class));
}
public void testGetEntityNameForDefaultJobIdentifier() {
assertEquals("DefaultJobInstance", interceptor.locate(DefaultJobIdentifier.class));
}
public void testGetEntityNameForSimpleJobIdentifier() {
assertEquals("SimpleJobInstance", interceptor.locate(SimpleJobIdentifier.class));
}
public void testSetIdentifierTypesWithString() {
interceptor.setIdentifierTypes(Collections.singletonMap(ScheduledJobIdentifier.class.getName(), "foo"));
assertEquals("foo", interceptor.locate(ScheduledJobIdentifier.class));
}
public void testSetIdentifierTypesWithClass() {
interceptor.setIdentifierTypes(Collections.singletonMap(ScheduledJobIdentifier.class, "foo"));
assertEquals("foo", interceptor.locate(ScheduledJobIdentifier.class));
}
public void testSetIdentifierTypesWithInvalidClassName() {
try {
interceptor.setIdentifierTypes(Collections.singletonMap("FooBarNotAClass", "foo"));
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
// expected
}
}
}

View File

@@ -26,12 +26,18 @@
<bean class="org.springframework.jdbc.support.lob.DefaultLobHandler"/>
</property>
<property name="entityInterceptor">
<bean class="org.springframework.batch.execution.repository.dao.EntityNameInterceptor"/>
</property>
</bean>
<bean
class="org.springframework.batch.execution.repository.dao.EntityNameInterceptor">
<property name="entityNameLocator" ref="entityNameLocator"/>
</bean>
</property>
</bean>
<bean id="entityNameLocator"
class="org.springframework.batch.execution.repository.dao.JobIdentifierEntityNameLocator" />
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
</bean>
</beans>

View File

@@ -5,10 +5,11 @@
<import resource="hibernate-context.xml" />
<bean id="jobDao" class="org.springframework.batch.execution.repository.dao.HibernateJobDao">
<property name="sessionFactory" ref="sessionFactory"></property>
<property name="sessionFactory" ref="sessionFactory"/>
<property name="entityNameLocator" ref="entityNameLocator"/>
</bean>
<bean id="stepDao" class="org.springframework.batch.execution.repository.dao.HibernateStepDao">
<property name="sessionFactory" ref="sessionFactory"></property>
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
</beans>

View File

@@ -1,18 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean" lazy-init="true">
<property name="mappingLocations">
<list>
<value>classpath:/org/springframework/batch/execution/repository/dao/JobInstance.hbm.xml</value>
<value>classpath:/org/springframework/batch/execution/repository/dao/JobExecution.hbm.xml</value>
<value>classpath:/org/springframework/batch/execution/repository/dao/StepInstance.hbm.xml</value>
<value>classpath:/org/springframework/batch/execution/repository/dao/StepExecution.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"
lazy-init="true">
<property name="mappingLocations">
<list>
<value>
classpath:/org/springframework/batch/execution/repository/dao/JobInstance.hbm.xml
</value>
<value>
classpath:/org/springframework/batch/execution/repository/dao/JobExecution.hbm.xml
</value>
<value>
classpath:/org/springframework/batch/execution/repository/dao/StepInstance.hbm.xml
</value>
<value>
classpath:/org/springframework/batch/execution/repository/dao/StepExecution.hbm.xml
</value>
</list>
</property>
<property name="hibernateProperties">
<value><![CDATA[
hibernate.show_sql=true
hibernate.format_sql=true
@@ -21,15 +31,24 @@
</property>
<property name="dataSource" ref="dataSource" />
<property name="lobHandler">
<bean class="org.springframework.jdbc.support.lob.DefaultLobHandler"/>
<bean
class="org.springframework.jdbc.support.lob.DefaultLobHandler" />
</property>
<property name="entityInterceptor">
<bean class="org.springframework.batch.execution.repository.dao.EntityNameInterceptor"/>
</property>
</bean>
<bean id="hibernateTransactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager" lazy-init="true">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean
class="org.springframework.batch.execution.repository.dao.EntityNameInterceptor">
<property name="entityNameLocator" ref="entityNameLocator"/>
</bean>
</property>
</bean>
<bean id="entityNameLocator"
class="org.springframework.batch.execution.repository.dao.JobIdentifierEntityNameLocator" />
<bean id="hibernateTransactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager"
lazy-init="true">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
</beans>

View File

@@ -89,6 +89,7 @@
<bean id="hibernateJobDao" lazy-init="true"
class="org.springframework.batch.execution.repository.dao.HibernateJobDao">
<property name="sessionFactory" ref="sessionFactory"/>
<property name="entityNameLocator" ref="entityNameLocator"/>
</bean>
<bean id="hibernateStepDao" lazy-init="true"