RESOLVED - issue BATCH-1541, BATCH-1542: Thread safety for map daos

This commit is contained in:
dsyer
2010-03-28 08:37:00 +00:00
parent cabaa53c25
commit 8ffebc3e66
23 changed files with 1067 additions and 273 deletions

View File

@@ -0,0 +1,3 @@
#Sat Mar 13 13:37:06 GMT 2010
//com.springsource.sts.config.flow.coordinates\:http\://www.springframework.org/schema/batch\:/spring-batch-core-tests/src/test/resources/org/springframework/batch/core/test/step/SplitJobMapRepositoryIntegrationTests-context.xml=<?xml version\="1.0" encoding\="UTF-8"?>\n<graph>\n<element type\="job">\n<structure end\="709" endstart\="703" start\="456" startend\="522"/>\n<bounds height\="136" width\="78" x\="93" y\="19"/>\n</element>\n<element type\="step">\n<structure end\="1133" endstart\="1126" start\="910" startend\="978"/>\n<bounds height\="34" width\="58" x\="17" y\="19"/>\n</element>\n<element type\="split">\n<structure end\="701" endstart\="693" start\="525" startend\="572"/>\n<bounds height\="189" width\="197" x\="183" y\="121"/>\n</element>\n</graph>
eclipse.preferences.version=1

View File

@@ -1,239 +1,240 @@
<?xml version="1.0" encoding="UTF-8"?>
<beansProjectDescription>
<version>1</version>
<pluginVersion><![CDATA[2.2.8.200911091054-RELEASE]]></pluginVersion>
<configSuffixes>
<configSuffix><![CDATA[xml]]></configSuffix>
</configSuffixes>
<enableImports><![CDATA[false]]></enableImports>
<configs>
<config>src/test/resources/data-source-context.xml</config>
<config>src/test/resources/simple-job-launcher-context.xml</config>
</configs>
<configSets>
<configSet>
<name><![CDATA[repositoryTest]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[beanWrapper]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[delegating]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[football]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[infiniteLoop]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[multiline]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[parallel]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[restart]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[retry]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[trade]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[batchUpdate]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[quartzLauncher]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[compositeItemWriter]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[adhocLoop]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[staging]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[common]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[jmxLauncher]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[headerFooter]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[customerFilterJob]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[hibernate]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[multiResourceJob]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[skipSample]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[iosample-hibernate]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[iosample-jdbc]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[iosample-multiline]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[iosample-multiRecordType]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[iosample-jpa]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[data-source-contex]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[ibatis]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[partition]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[multilineOrder]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[restartFile]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
</configSets>
</beansProjectDescription>
<?xml version="1.0" encoding="UTF-8"?>
<beansProjectDescription>
<version>1</version>
<pluginVersion><![CDATA[2.3.0.200912170948-RELEASE]]></pluginVersion>
<configSuffixes>
<configSuffix><![CDATA[xml]]></configSuffix>
</configSuffixes>
<enableImports><![CDATA[false]]></enableImports>
<configs>
<config>src/test/resources/data-source-context.xml</config>
<config>src/test/resources/simple-job-launcher-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/test/step/SplitJobMapRepositoryIntegrationTests-context.xml</config>
</configs>
<configSets>
<configSet>
<name><![CDATA[repositoryTest]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[beanWrapper]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[delegating]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[football]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[infiniteLoop]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[multiline]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[parallel]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[restart]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[retry]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[trade]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[batchUpdate]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[quartzLauncher]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[compositeItemWriter]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[adhocLoop]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[staging]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[common]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[jmxLauncher]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[headerFooter]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[customerFilterJob]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[hibernate]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[multiResourceJob]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[skipSample]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[iosample-hibernate]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[iosample-jdbc]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[iosample-multiline]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[iosample-multiRecordType]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[iosample-jpa]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[data-source-contex]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[ibatis]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[partition]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[multilineOrder]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
<configSet>
<name><![CDATA[restartFile]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
</configs>
</configSet>
</configSets>
</beansProjectDescription>

View File

@@ -11,6 +11,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.sql.DataSource;
@@ -121,6 +122,10 @@ public class FaultTolerantStepFactoryBeanRollbackTests {
for (int i = 0; i < MAX_COUNT; i++) {
if (i%100==0) {
logger.info("Starting step: "+i);
}
SimpleJdbcTemplate jdbcTemplate = new SimpleJdbcTemplate(dataSource);
assertEquals(0, SimpleJdbcTestUtils.countRowsInTable(jdbcTemplate, "ERROR_LOG"));
@@ -145,6 +150,9 @@ public class FaultTolerantStepFactoryBeanRollbackTests {
assertEquals("[]", writer.getCommitted().toString());
assertEquals("[]", processor.getCommitted().toString());
List<String> processed = new ArrayList<String>(processor.getProcessed());
Collections.sort(processed);
assertEquals("[1, 1, 2, 2, 3, 3, 4, 4, 5, 5]", processed.toString());
assertEquals(5, stepExecution.getSkipCount());
}
@@ -196,7 +204,7 @@ public class FaultTolerantStepFactoryBeanRollbackTests {
private static class SkipWriterStub implements ItemWriter<String> {
private List<String> written = new ArrayList<String>();
private List<String> written = new CopyOnWriteArrayList<String>();
private Collection<String> failures = Collections.emptySet();
@@ -243,7 +251,7 @@ public class FaultTolerantStepFactoryBeanRollbackTests {
private final Log logger = LogFactory.getLog(getClass());
private List<String> processed = new ArrayList<String>();
private List<String> processed = new CopyOnWriteArrayList<String>();
private SimpleJdbcTemplate jdbcTemplate;
@@ -253,6 +261,13 @@ public class FaultTolerantStepFactoryBeanRollbackTests {
public SkipProcessorStub(DataSource dataSource) {
jdbcTemplate = new SimpleJdbcTemplate(dataSource);
}
/**
* @return the processed
*/
public List<String> getProcessed() {
return processed;
}
public List<String> getCommitted() {
return jdbcTemplate.query("SELECT MESSAGE from ERROR_LOG where STEP_NAME='processed'",

View File

@@ -0,0 +1,248 @@
package org.springframework.batch.core.test.step;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean;
import org.springframework.batch.core.step.item.FaultTolerantStepFactoryBean;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.Assert;
/**
* Tests for {@link FaultTolerantStepFactoryBean}.
*/
@ContextConfiguration(locations = "/simple-job-launcher-context.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class MapRepositoryFaultTolerantStepFactoryBeanRollbackTests {
private static final int MAX_COUNT = 1000;
private final Log logger = LogFactory.getLog(getClass());
private FaultTolerantStepFactoryBean<String, String> factory;
private SkipReaderStub reader;
private SkipProcessorStub processor;
private SkipWriterStub writer;
private JobExecution jobExecution;
private StepExecution stepExecution;
private JobRepository repository;
@Autowired
private PlatformTransactionManager transactionManager;
@SuppressWarnings("unchecked")
@Before
public void setUp() throws Exception {
repository = new MapJobRepositoryFactoryBean().getJobRepository();
reader = new SkipReaderStub();
writer = new SkipWriterStub();
processor = new SkipProcessorStub();
factory = new FaultTolerantStepFactoryBean<String, String>();
factory.setBeanName("stepName");
factory.setTransactionManager(transactionManager);
factory.setJobRepository(repository);
factory.setCommitInterval(3);
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(3);
taskExecutor.setMaxPoolSize(6);
taskExecutor.setQueueCapacity(0);
taskExecutor.afterPropertiesSet();
factory.setTaskExecutor(taskExecutor);
factory.setSkipLimit(10);
factory.setSkippableExceptionClasses(getExceptionMap(Exception.class));
}
@Test
public void testUpdatesNoRollback() throws Exception {
writer.write(Arrays.asList("foo", "bar"));
processor.process("spam");
assertEquals(2, writer.getWritten().size());
assertEquals(1, processor.getProcessed().size());
writer.clear();
processor.clear();
assertEquals(0, processor.getProcessed().size());
}
@Test
public void testMultithreadedSkipInWrite() throws Throwable {
for (int i = 0; i < MAX_COUNT; i++) {
if (i%100==0) {
logger.info("Starting step: "+i);
repository = new MapJobRepositoryFactoryBean().getJobRepository();
factory.setJobRepository(repository);
jobExecution = repository.createJobExecution("vanillaJob", new JobParameters());
}
reader.clear();
reader.setItems("1", "2", "3", "4", "5");
factory.setItemReader(reader);
writer.clear();
factory.setItemWriter(writer);
processor.clear();
factory.setItemProcessor(processor);
writer.setFailures("1", "2", "3", "4", "5");
try {
Step step = (Step) factory.getObject();
stepExecution = jobExecution.createStepExecution(factory.getName());
repository.add(stepExecution);
step.execute(stepExecution);
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
assertEquals(5, stepExecution.getSkipCount());
List<String> processed = new ArrayList<String>(processor.getProcessed());
Collections.sort(processed);
assertEquals("[1, 1, 2, 2, 3, 3, 4, 4, 5, 5]", processed.toString());
}
catch (Throwable e) {
logger.info("Failed on iteration " + i + " of " + MAX_COUNT);
throw e;
}
}
}
private static class SkipReaderStub implements ItemReader<String> {
private String[] items;
private int counter = -1;
public SkipReaderStub() throws Exception {
super();
}
public void setItems(String... items) {
Assert.isTrue(counter < 0, "Items cannot be set once reading has started");
this.items = items;
}
public void clear() {
counter = -1;
}
public synchronized String read() throws Exception, UnexpectedInputException, ParseException {
counter++;
if (counter >= items.length) {
return null;
}
String item = items[counter];
return item;
}
}
private static class SkipWriterStub implements ItemWriter<String> {
private final Log logger = LogFactory.getLog(getClass());
private List<String> written = new CopyOnWriteArrayList<String>();
private Collection<String> failures = Collections.emptySet();
public void setFailures(String... failures) {
this.failures = Arrays.asList(failures);
}
public List<String> getWritten() {
return written;
}
public void clear() {
written.clear();
}
public void write(List<? extends String> items) throws Exception {
for (String item : items) {
logger.trace("Writing: "+item);
written.add(item);
checkFailure(item);
}
}
private void checkFailure(String item) {
if (failures.contains(item)) {
throw new RuntimeException("Planned failure");
}
}
}
private static class SkipProcessorStub implements ItemProcessor<String, String> {
private final Log logger = LogFactory.getLog(getClass());
private List<String> processed = new CopyOnWriteArrayList<String>();
public List<String> getProcessed() {
return processed;
}
public void clear() {
processed.clear();
}
public String process(String item) throws Exception {
processed.add(item);
logger.debug("Processed item: "+item);
return item;
}
}
private Map<Class<? extends Throwable>, Boolean> getExceptionMap(Class<? extends Throwable>... args) {
Map<Class<? extends Throwable>, Boolean> map = new HashMap<Class<? extends Throwable>, Boolean>();
for (Class<? extends Throwable> arg : args) {
map.put(arg, true);
}
return map;
}
}

View File

@@ -0,0 +1,228 @@
package org.springframework.batch.core.test.step;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean;
import org.springframework.batch.core.step.item.FaultTolerantStepFactoryBean;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.Assert;
/**
* Tests for {@link FaultTolerantStepFactoryBean}.
*/
@ContextConfiguration(locations = "/simple-job-launcher-context.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class MapRepositoryFaultTolerantStepFactoryBeanTests {
private static final int MAX_COUNT = 1000;
private final Log logger = LogFactory.getLog(getClass());
private FaultTolerantStepFactoryBean<String, String> factory;
private SkipReaderStub reader;
private SkipProcessorStub processor;
private SkipWriterStub writer;
private JobExecution jobExecution;
private StepExecution stepExecution;
private JobRepository repository;
@Autowired
private PlatformTransactionManager transactionManager;
@Before
public void setUp() throws Exception {
repository = new MapJobRepositoryFactoryBean().getJobRepository();
reader = new SkipReaderStub();
writer = new SkipWriterStub();
processor = new SkipProcessorStub();
factory = new FaultTolerantStepFactoryBean<String, String>();
factory.setBeanName("stepName");
factory.setTransactionManager(transactionManager);
factory.setJobRepository(repository);
factory.setCommitInterval(3);
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(3);
taskExecutor.setMaxPoolSize(6);
taskExecutor.setQueueCapacity(0);
taskExecutor.afterPropertiesSet();
factory.setTaskExecutor(taskExecutor);
}
@Test
public void testUpdatesNoRollback() throws Exception {
writer.write(Arrays.asList("foo", "bar"));
processor.process("spam");
assertEquals(2, writer.getWritten().size());
assertEquals(1, processor.getProcessed().size());
writer.clear();
processor.clear();
assertEquals(0, processor.getProcessed().size());
}
@Test
public void testMultithreadedSunnyDay() throws Throwable {
for (int i = 0; i < MAX_COUNT; i++) {
if (i%100==0) {
logger.info("Starting step: "+i);
repository = new MapJobRepositoryFactoryBean().getJobRepository();
factory.setJobRepository(repository);
jobExecution = repository.createJobExecution("vanillaJob", new JobParameters());
}
reader.clear();
reader.setItems("1", "2", "3", "4", "5");
factory.setItemReader(reader);
writer.clear();
factory.setItemWriter(writer);
processor.clear();
factory.setItemProcessor(processor);
try {
Step step = (Step) factory.getObject();
stepExecution = jobExecution.createStepExecution(factory.getName());
repository.add(stepExecution);
step.execute(stepExecution);
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
List<String> committed = new ArrayList<String>(writer.getWritten());
Collections.sort(committed);
assertEquals("[1, 2, 3, 4, 5]", committed.toString());
List<String> processed = new ArrayList<String>(processor.getProcessed());
Collections.sort(processed);
assertEquals("[1, 2, 3, 4, 5]", processed.toString());
assertEquals(0, stepExecution.getSkipCount());
}
catch (Throwable e) {
logger.info("Failed on iteration " + i + " of " + MAX_COUNT);
throw e;
}
}
}
private static class SkipReaderStub implements ItemReader<String> {
private String[] items;
private int counter = -1;
public SkipReaderStub() throws Exception {
super();
}
public void setItems(String... items) {
Assert.isTrue(counter < 0, "Items cannot be set once reading has started");
this.items = items;
}
public void clear() {
counter = -1;
}
public synchronized String read() throws Exception, UnexpectedInputException, ParseException {
counter++;
if (counter >= items.length) {
return null;
}
String item = items[counter];
return item;
}
}
private static class SkipWriterStub implements ItemWriter<String> {
private List<String> written = new CopyOnWriteArrayList<String>();
private Collection<String> failures = Collections.emptySet();
public List<String> getWritten() {
return written;
}
public void clear() {
written.clear();
}
public void write(List<? extends String> items) throws Exception {
for (String item : items) {
written.add(item);
checkFailure(item);
}
}
private void checkFailure(String item) {
if (failures.contains(item)) {
throw new RuntimeException("Planned failure");
}
}
}
private static class SkipProcessorStub implements ItemProcessor<String, String> {
private final Log logger = LogFactory.getLog(getClass());
private List<String> processed = new CopyOnWriteArrayList<String>();
public List<String> getProcessed() {
return processed;
}
public void clear() {
processed.clear();
}
public String process(String item) throws Exception {
processed.add(item);
logger.debug("Processed item: "+item);
return item;
}
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2006-2009 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.core.test.step;
import static org.junit.Assert.assertEquals;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author Dave Syer
*
*/
public class SplitJobMapRepositoryIntegrationTests {
private static final int MAX_COUNT = 1000;
/** Logger */
private final Log logger = LogFactory.getLog(getClass());
@Test
public void testMultithreadedSplit() throws Throwable {
JobLauncher jobLauncher = null;
Job job = null;
for (int i = 0; i < MAX_COUNT; i++) {
if (i % 100 == 0) {
logger.info("Starting job: " + i);
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(getClass().getSimpleName()
+ "-context.xml", getClass());
jobLauncher = (JobLauncher) context.getBean("jobLauncher", JobLauncher.class);
job = (Job) context.getBean("job", Job.class);
}
try {
JobExecution execution = jobLauncher.run(job, new JobParametersBuilder().addLong("count", new Long(i))
.toJobParameters());
assertEquals(BatchStatus.COMPLETED, execution.getStatus());
}
catch (Throwable e) {
logger.info("Failed on iteration " + i + " of " + MAX_COUNT);
throw e;
}
}
}
public static class CountingTasklet implements Tasklet {
private int maxCount = 10;
private AtomicInteger count = new AtomicInteger(0);
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
contribution.incrementReadCount();
contribution.incrementWriteCount(1);
return RepeatStatus.continueIf(count.incrementAndGet() < maxCount);
}
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2006-2010 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.core.test.step;
import static org.junit.Assert.assertEquals;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.support.SerializationUtils;
/**
* @author Dave Syer
*
*/
public class StepExecutionSerializationUtilsTests {
@Test
public void testCycle() throws Exception {
StepExecution stepExecution = new StepExecution("step", new JobExecution(new JobInstance(123L,
new JobParameters(), "job"), 321L), 11L);
stepExecution.getExecutionContext().put("foo.bar.spam", 123);
StepExecution result = getCopy(stepExecution);
assertEquals(stepExecution, result);
}
@Test
public void testMultipleCycles() throws Throwable {
int count = 0;
int repeats = 100;
int threads = 10;
Executor executor = Executors.newFixedThreadPool(threads);
CompletionService<StepExecution> completionService = new ExecutorCompletionService<StepExecution>(executor);
for (int i = 0; i < repeats; i++) {
final JobExecution jobExecution = new JobExecution(new JobInstance(123L, new JobParameters(), "job"), 321L);
for (int j = 0; j < threads; j++) {
completionService.submit(new Callable<StepExecution>() {
public StepExecution call() throws Exception {
final StepExecution stepExecution = jobExecution.createStepExecution("step");
stepExecution.getExecutionContext().put("foo.bar.spam", 123);
StepExecution result = getCopy(stepExecution);
assertEquals(stepExecution.getExecutionContext(), result.getExecutionContext());
return result;
}
});
}
for (int j = 0; j < threads; j++) {
Future<StepExecution> future = completionService.poll(repeats, TimeUnit.MILLISECONDS);
if (future != null) {
count++;
try {
future.get();
} catch (Throwable e) {
throw new IllegalStateException("Failed on count="+count, e);
}
}
}
}
while (count < threads*repeats) {
Future<StepExecution> future = completionService.poll();
count++;
try {
future.get();
} catch (Throwable e) {
throw new IllegalStateException("Failed on count="+count, e);
}
}
}
private StepExecution getCopy(StepExecution stepExecution) {
return (StepExecution) SerializationUtils.deserialize(SerializationUtils.serialize(stepExecution));
}
}

View File

@@ -1,4 +1,4 @@
log4j.rootCategory=INFO, stdout
log4j.rootCategory=WARN, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
@@ -7,8 +7,9 @@ log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{1}:%L - %m
log4j.category.org.apache.activemq=ERROR
# log4j.category.org.springframework=DEBUG
log4j.category.org.springframework.jdbc=INFO
log4j.category.org.springframework.context=INFO
log4j.category.org.springframework.jms=INFO
log4j.category.org.springframework.batch=INFO
# log4j.category.org.springframework.batch.core.test=DEBUG
# log4j.category.org.springframework.batch=INFO
log4j.category.org.springframework.batch.core.test=INFO
log4j.category.org.springframework.retry=INFO
# log4j.category.org.springframework.beans.factory.config=TRACE

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:beans="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.5.xsd
http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.1.xsd">
<job id="job" xmlns="http://www.springframework.org/schema/batch">
<split id="split" task-executor="taskExecutor">
<flow>
<step id="step1" parent="step" />
</flow>
<flow>
<step id="step2" parent="step" />
</flow>
</split>
</job>
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="maxPoolSize" value="6"/>
<property name="queueCapacity" value="0"/>
</bean>
<step id="step" xmlns="http://www.springframework.org/schema/batch">
<tasklet>
<beans:bean class="org.springframework.batch.core.test.step.SplitJobMapRepositoryIntegrationTests$CountingTasklet" />
</tasklet>
</step>
<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
</bean>
<bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
<property name="transactionManager" ref="transactionManager" />
</bean>
<bean id="transactionManager" class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />
</beans>

View File

@@ -0,0 +1,3 @@
#Wed Mar 24 07:48:49 GMT 2010
//com.springsource.sts.config.flow.coordinates\:http\://www.springframework.org/schema/batch\:/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobRepositoryDefaultParserTests-context.xml=<?xml version\="1.0" encoding\="UTF-8"?>\n<graph/>
eclipse.preferences.version=1

View File

@@ -20,11 +20,13 @@ import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import org.springframework.batch.item.ExecutionContext;
@@ -38,7 +40,7 @@ public class JobExecution extends Entity {
private JobInstance jobInstance;
private volatile Collection<StepExecution> stepExecutions = new LinkedHashSet<StepExecution>();
private volatile Collection<StepExecution> stepExecutions = new CopyOnWriteArraySet<StepExecution>();
private volatile BatchStatus status = BatchStatus.STARTING;
@@ -54,7 +56,7 @@ public class JobExecution extends Entity {
private volatile ExecutionContext executionContext = new ExecutionContext();
private transient volatile List<Throwable> failureExceptions = new ArrayList<Throwable>();
private transient volatile List<Throwable> failureExceptions = new CopyOnWriteArrayList<Throwable>();
/**
* Because a JobExecution isn't valid unless the job is set, this
@@ -164,7 +166,7 @@ public class JobExecution extends Entity {
* @return the step executions that were registered
*/
public Collection<StepExecution> getStepExecutions() {
return stepExecutions;
return Collections.unmodifiableList(new ArrayList<StepExecution>(stepExecutions));
}
/**
@@ -278,7 +280,7 @@ public class JobExecution extends Entity {
*
* @param t
*/
public void addFailureException(Throwable t) {
public synchronized void addFailureException(Throwable t) {
this.failureExceptions.add(t);
}
@@ -289,7 +291,7 @@ public class JobExecution extends Entity {
* @return List<Throwable> containing all exceptions causing failure for
* this JobExecution.
*/
public List<Throwable> getAllFailureExceptions() {
public synchronized List<Throwable> getAllFailureExceptions() {
Set<Throwable> allExceptions = new HashSet<Throwable>(failureExceptions);
for (StepExecution stepExecution : stepExecutions) {
@@ -320,8 +322,8 @@ public class JobExecution extends Entity {
}
/**
* Setter for the step executions. For internal use only.
* @param stepExecutions
* Add some step executions. For internal use only.
* @param stepExecutions step executions to add to the current list
*/
public void addStepExecutions(List<StepExecution> stepExecutions) {
if (stepExecutions!=null) {

View File

@@ -21,6 +21,7 @@ import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.util.Assert;
@@ -70,7 +71,7 @@ public class StepExecution extends Entity {
private volatile int filterCount;
private transient volatile List<Throwable> failureExceptions = new ArrayList<Throwable>();
private transient volatile List<Throwable> failureExceptions = new CopyOnWriteArrayList<Throwable>();
/**
* Constructor with mandatory properties.

View File

@@ -23,11 +23,11 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.support.SerializationUtils;
import org.springframework.batch.support.transaction.TransactionAwareProxyFactory;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.util.Assert;
@@ -36,7 +36,7 @@ import org.springframework.util.Assert;
*/
public class MapJobExecutionDao implements JobExecutionDao {
private Map<Long, JobExecution> executionsById = TransactionAwareProxyFactory.createTransactionalMap();
private Map<Long, JobExecution> executionsById = new ConcurrentHashMap<Long, JobExecution>();
private long currentId = 0;

View File

@@ -21,11 +21,11 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.support.transaction.TransactionAwareProxyFactory;
import org.springframework.util.Assert;
/**
@@ -33,7 +33,7 @@ import org.springframework.util.Assert;
*/
public class MapJobInstanceDao implements JobInstanceDao {
private Collection<JobInstance> jobInstances = TransactionAwareProxyFactory.createTransactionalSet();
private Collection<JobInstance> jobInstances = new CopyOnWriteArraySet<JobInstance>();
private long currentId = 0;

View File

@@ -20,12 +20,13 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.batch.core.Entity;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.support.SerializationUtils;
import org.springframework.batch.support.transaction.TransactionAwareProxyFactory;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.util.Assert;
@@ -34,13 +35,11 @@ import org.springframework.util.Assert;
*/
public class MapStepExecutionDao implements StepExecutionDao {
private Map<Long, Map<Long, StepExecution>> executionsByJobExecutionId = TransactionAwareProxyFactory
.createTransactionalMap();
private Map<Long, Map<Long, StepExecution>> executionsByJobExecutionId = new ConcurrentHashMap<Long, Map<Long, StepExecution>>();
private Map<Long, StepExecution> executionsByStepExecutionId = TransactionAwareProxyFactory
.createTransactionalMap();
private Map<Long, StepExecution> executionsByStepExecutionId = new ConcurrentHashMap<Long, StepExecution>();
private long currentId = 0;
private AtomicLong currentId = new AtomicLong();
public void clear() {
executionsByJobExecutionId.clear();
@@ -59,11 +58,11 @@ public class MapStepExecutionDao implements StepExecutionDao {
Map<Long, StepExecution> executions = executionsByJobExecutionId.get(stepExecution.getJobExecutionId());
if (executions == null) {
executions = TransactionAwareProxyFactory.createTransactionalMap();
executions = new ConcurrentHashMap<Long, StepExecution>();
executionsByJobExecutionId.put(stepExecution.getJobExecutionId(), executions);
}
stepExecution.setId(currentId++);
stepExecution.setId(currentId.incrementAndGet());
stepExecution.incrementVersion();
StepExecution copy = copy(stepExecution);
executions.put(stepExecution.getId(), copy);

View File

@@ -179,6 +179,8 @@ public class SimpleJobRepository implements JobRepository {
}
public void updateExecutionContext(StepExecution stepExecution) {
validateStepExecution(stepExecution);
Assert.notNull(stepExecution.getId(), "StepExecution must already be saved (have an id assigned)");
ecDao.updateExecutionContext(stepExecution);
}

View File

@@ -136,7 +136,7 @@ public class FlowJobTests {
@Override
public FlowExecutionStatus handle(FlowExecutor executor) throws Exception {
JobExecution jobExecution = executor.getJobExecution();
jobExecution.getStepExecutions().add(new StepExecution(getName(), jobExecution));
jobExecution.createStepExecution(getName());
if (fail) {
return FlowExecutionStatus.FAILED;
}

View File

@@ -16,7 +16,6 @@
package org.springframework.batch.core.job.flow;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.job.flow.support.state.AbstractState;
/**
@@ -42,7 +41,7 @@ public class StateSupport extends AbstractState {
public FlowExecutionStatus handle(FlowExecutor executor) throws Exception {
JobExecution jobExecution = executor.getJobExecution();
if (jobExecution != null) {
jobExecution.getStepExecutions().add(new StepExecution(getName(), jobExecution));
jobExecution.createStepExecution(getName());
}
return this.status;
}

View File

@@ -9,7 +9,7 @@ import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.repository.JobRepository;
@@ -23,13 +23,15 @@ public class SimpleStepExecutionSplitterTests {
private JobRepository jobRepository;
private StepExecution stepExecution = new StepExecution("bar", new JobExecution(11L));
private StepExecution stepExecution;
@Before
public void setUp() throws Exception {
step = new TaskletStep("step");
MapJobRepositoryFactoryBean factory = new MapJobRepositoryFactoryBean();
jobRepository = (JobRepository) factory.getObject();
stepExecution = jobRepository.createJobExecution("job", new JobParameters()).createStepExecution("bar");
jobRepository.add(stepExecution);
}
@Test

View File

@@ -39,7 +39,7 @@ public class MapExecutionContextDaoTests extends AbstractExecutionContextDaoTest
public void testPersistentCopy() throws Exception {
MapExecutionContextDao tested = new MapExecutionContextDao();
JobExecution jobExecution = new JobExecution((long)1);
StepExecution stepExecution = new StepExecution("stepName", jobExecution);
StepExecution stepExecution = new StepExecution("stepName", jobExecution, 123L);
assertTrue(stepExecution.getExecutionContext().isEmpty());
tested.updateExecutionContext(stepExecution);

View File

@@ -20,6 +20,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OptionalDataException;
/**
@@ -66,6 +67,9 @@ public class SerializationUtils {
try {
return new ObjectInputStream(new ByteArrayInputStream(bytes)).readObject();
}
catch (OptionalDataException e) {
throw new IllegalArgumentException("Could not deserialize object: eof="+e.eof+ " at length="+e.length, e);
}
catch (IOException e) {
throw new IllegalArgumentException("Could not deserialize object", e);
}

View File

@@ -23,6 +23,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
@@ -74,7 +75,7 @@ public class TransactionAwareProxyFactory<T> {
return (T) new HashSet((Set) target);
}
else if (target instanceof Map) {
return (T) new HashMap((Map) target);
return (T) new ConcurrentHashMap((Map) target);
}
else {
throw new UnsupportedOperationException("Cannot copy target for this type: " + target.getClass());

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2006-2010 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.support;
import static org.junit.Assert.assertEquals;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.junit.Test;
/**
* @author Dave Syer
*
*/
public class MapSerializationUtilsTests {
private Map<String, Object> map = new ConcurrentHashMap<String, Object>();
@Test
public void testCycle() throws Exception {
map.put("foo.bar.spam", 123);
Map<String, Object> result = getCopy(map);
assertEquals(map, result);
}
@Test
public void testMultipleCycles() throws Exception {
map.put("foo.bar.spam", 123);
for (int i = 0; i < 1000; i++) {
Map<String, Object> result = getCopy(map);
assertEquals(map, result);
}
}
@SuppressWarnings("unchecked")
private Map<String, Object> getCopy(Map<String, Object> map) {
return (Map<String, Object>) SerializationUtils.deserialize(SerializationUtils.serialize(map));
}
}