IN PROGRESS - issue BATCH-7: Remove transaction synchronization and state management from input/output sources (formerly buffering)
http://jira.springframework.org/browse/BATCH-7 Lazy-initialisation solution to the "who's in step scope" issue
This commit is contained in:
@@ -43,7 +43,7 @@ public interface Step {
|
||||
* Flag to indicate if restart data needs to be saved for this step.
|
||||
* @return true if restart data should be saved
|
||||
*/
|
||||
boolean isSaveRestartData();
|
||||
boolean isSaveStreamContext();
|
||||
|
||||
/**
|
||||
* @return the number of times a job can be started with the same
|
||||
|
||||
@@ -87,11 +87,11 @@ public class StepInstance extends Entity {
|
||||
this.stepExecutionCount = stepExecutionCount;
|
||||
}
|
||||
|
||||
public StreamContext getRestartData() {
|
||||
public StreamContext getStreamContext() {
|
||||
return streamContext;
|
||||
}
|
||||
|
||||
public void setRestartData(StreamContext streamContext) {
|
||||
public void setStreamContext(StreamContext streamContext) {
|
||||
this.streamContext = streamContext;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ public class StepSupport implements Step, BeanNameAware {
|
||||
|
||||
private boolean allowStartIfComplete;
|
||||
|
||||
private boolean saveRestartData = false;
|
||||
private boolean saveStreamContext = false;
|
||||
|
||||
/**
|
||||
* Default constructor for {@link StepSupport}.
|
||||
@@ -121,12 +121,12 @@ public class StepSupport implements Step, BeanNameAware {
|
||||
this.allowStartIfComplete = allowStartIfComplete;
|
||||
}
|
||||
|
||||
public void setSaveRestartData(boolean saveRestartData) {
|
||||
this.saveRestartData = saveRestartData;
|
||||
public void setSaveStreamContext(boolean saveStreamContext) {
|
||||
this.saveStreamContext = saveStreamContext;
|
||||
}
|
||||
|
||||
public boolean isSaveRestartData() {
|
||||
return saveRestartData;
|
||||
public boolean isSaveStreamContext() {
|
||||
return saveStreamContext;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,14 +22,16 @@ import org.springframework.batch.core.domain.JobInstance;
|
||||
import org.springframework.batch.core.domain.JobParameters;
|
||||
import org.springframework.batch.core.domain.StepExecution;
|
||||
import org.springframework.batch.core.domain.StepInstance;
|
||||
import org.springframework.batch.item.StreamContext;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Repository for storing batch jobs and steps. Before using any methods, a Job
|
||||
* must first be obtained using the findOrCreateJob method. Once a Job and it's
|
||||
* related steps are obtained, they can be updated. It should be noted that any
|
||||
* reconstituted steps are expected to contain restart data <strong>if the
|
||||
* RestartPolicy associated with the step returns true, and RestartData exists.</strong>
|
||||
* reconstituted steps are expected to contain restart data <strong>if the step
|
||||
* says it wants to be restored after a restart, and {@link StreamContext}
|
||||
* exists.</strong>
|
||||
* </p>
|
||||
*
|
||||
* Once a Job/Steps has been created, Job and Step executions can be created and
|
||||
@@ -47,23 +49,21 @@ import org.springframework.batch.core.domain.StepInstance;
|
||||
public interface JobRepository {
|
||||
|
||||
/**
|
||||
* Find or create a job execution for a given {@link JobIdentifier} and configuration.
|
||||
* If the job that is uniquely identified by {@link JobIdentifier} already
|
||||
* exists, its persisted values (including ID) will be returned in a new
|
||||
* {@link JobInstance} object. If no previous run is found, a new job will
|
||||
* be created and returned.
|
||||
* Find or create a job execution for a given {@link JobIdentifier} and
|
||||
* configuration. If the job that is uniquely identified by
|
||||
* {@link JobIdentifier} already exists, its persisted values (including ID)
|
||||
* will be returned in a new {@link JobInstance} object. If no previous run
|
||||
* is found, a new job will be created and returned.
|
||||
* @param jobParameters the runtime parameters for the job
|
||||
* @param jobConfiguration
|
||||
* describes the configuration for this job
|
||||
* @param jobConfiguration describes the configuration for this job
|
||||
*
|
||||
* @return a valid job execution for the identifier provided
|
||||
* @throws JobExecutionAlreadyRunningException
|
||||
* if there is a {@link JobExecution} alrady running for the
|
||||
* job instance that would otherwise be returned
|
||||
* @throws JobExecutionAlreadyRunningException if there is a
|
||||
* {@link JobExecution} alrady running for the job instance that would
|
||||
* otherwise be returned
|
||||
*
|
||||
*/
|
||||
public JobExecution createJobExecution(Job job,
|
||||
JobParameters jobParameters)
|
||||
public JobExecution createJobExecution(Job job, JobParameters jobParameters)
|
||||
throws JobExecutionAlreadyRunningException;
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,15 +46,15 @@ public class StepInstanceTests extends TestCase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link org.springframework.batch.core.domain.StepInstance#getRestartData()}.
|
||||
* Test method for {@link org.springframework.batch.core.domain.StepInstance#getStreamContext()}.
|
||||
*/
|
||||
public void testGetRestartData() {
|
||||
assertNotNull(instance.getRestartData());
|
||||
assertTrue(instance.getRestartData().getProperties().isEmpty());
|
||||
instance.setRestartData(new GenericStreamContext(new Properties() {{
|
||||
public void testGetStreamContext() {
|
||||
assertNotNull(instance.getStreamContext());
|
||||
assertTrue(instance.getStreamContext().getProperties().isEmpty());
|
||||
instance.setStreamContext(new GenericStreamContext(new Properties() {{
|
||||
setProperty("foo", "bar");
|
||||
}}));
|
||||
assertEquals("bar", instance.getRestartData().getProperties().getProperty("foo"));
|
||||
assertEquals("bar", instance.getStreamContext().getProperties().getProperty("foo"));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -64,9 +64,9 @@ public class StepSupportTests extends TestCase {
|
||||
}
|
||||
|
||||
public void testSaveRestartFlag() throws Exception {
|
||||
assertEquals(false, configuration.isSaveRestartData());
|
||||
configuration.setSaveRestartData(true);
|
||||
assertEquals(true, configuration.isSaveRestartData());
|
||||
assertEquals(false, configuration.isSaveStreamContext());
|
||||
configuration.setSaveStreamContext(true);
|
||||
assertEquals(true, configuration.isSaveStreamContext());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -303,8 +303,8 @@ public class SimpleJobRepository implements JobRepository {
|
||||
Step step = (Step) i.next();
|
||||
StepInstance stepInstance = stepDao.createStep(job, step.getName());
|
||||
// Ensure valid restart data is being returned.
|
||||
if (stepInstance.getRestartData() == null || stepInstance.getRestartData().getProperties() == null) {
|
||||
stepInstance.setRestartData(new GenericStreamContext(new Properties()));
|
||||
if (stepInstance.getStreamContext() == null || stepInstance.getStreamContext().getProperties() == null) {
|
||||
stepInstance.setStreamContext(new GenericStreamContext(new Properties()));
|
||||
}
|
||||
stepInstances.add(stepInstance);
|
||||
}
|
||||
@@ -326,8 +326,8 @@ public class SimpleJobRepository implements JobRepository {
|
||||
|
||||
step.setStepExecutionCount(stepDao.getStepExecutionCount(step));
|
||||
// Ensure valid restart data is being returned.
|
||||
if (step.getRestartData() == null || step.getRestartData().getProperties() == null) {
|
||||
step.setRestartData(new GenericStreamContext(new Properties()));
|
||||
if (step.getStreamContext() == null || step.getStreamContext().getProperties() == null) {
|
||||
step.setStreamContext(new GenericStreamContext(new Properties()));
|
||||
}
|
||||
stepInstances.add(step);
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ public class JdbcStepDao implements StepDao, InitializingBean {
|
||||
|
||||
StepInstance step = new StepInstance(new Long(rs.getLong(1)));
|
||||
step.setStatus(BatchStatus.getStatus(rs.getString(2)));
|
||||
step.setRestartData(new GenericStreamContext(PropertiesConverter.stringToProperties(rs.getString(3))));
|
||||
step.setStreamContext(new GenericStreamContext(PropertiesConverter.stringToProperties(rs.getString(3))));
|
||||
return step;
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ public class JdbcStepDao implements StepDao, InitializingBean {
|
||||
StepInstance step = new StepInstance(job, rs.getString(2), new Long(rs.getLong(1)));
|
||||
String status = rs.getString(3);
|
||||
step.setStatus(BatchStatus.getStatus(status));
|
||||
step.setRestartData(new GenericStreamContext(PropertiesConverter.stringToProperties(rs.getString(3))));
|
||||
step.setStreamContext(new GenericStreamContext(PropertiesConverter.stringToProperties(rs.getString(3))));
|
||||
return step;
|
||||
}
|
||||
};
|
||||
@@ -422,7 +422,7 @@ public class JdbcStepDao implements StepDao, InitializingBean {
|
||||
Assert.notNull(step.getId(), "Step Id cannot be null.");
|
||||
|
||||
Properties restartProps = null;
|
||||
StreamContext streamContext = step.getRestartData();
|
||||
StreamContext streamContext = step.getStreamContext();
|
||||
if (streamContext != null) {
|
||||
restartProps = streamContext.getProperties();
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ public class MapStepDao implements StepDao {
|
||||
return new ArrayList(steps);
|
||||
}
|
||||
|
||||
public StreamContext getRestartData(Long stepId) {
|
||||
public StreamContext getStreamContext(Long stepId) {
|
||||
return (StreamContext) restartsById.get(stepId);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,10 @@ import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.batch.core.domain.StepExecution;
|
||||
import org.springframework.batch.io.exception.BatchCriticalException;
|
||||
import org.springframework.batch.item.ItemStream;
|
||||
import org.springframework.batch.item.StreamContext;
|
||||
import org.springframework.batch.item.stream.StreamManager;
|
||||
import org.springframework.batch.repeat.context.SynchronizedAttributeAccessor;
|
||||
import org.springframework.batch.statistics.StatisticsProvider;
|
||||
import org.springframework.batch.statistics.StatisticsService;
|
||||
@@ -35,53 +39,72 @@ import org.springframework.batch.statistics.StatisticsService;
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class SimpleStepContext extends SynchronizedAttributeAccessor implements
|
||||
StepContext, StatisticsProvider {
|
||||
public class SimpleStepContext extends SynchronizedAttributeAccessor implements StepContext {
|
||||
|
||||
private Map callbacks = new HashMap();
|
||||
|
||||
private StepContext parent;
|
||||
|
||||
private StepExecution stepExecution;
|
||||
|
||||
private StatisticsService statisticsService;
|
||||
|
||||
private StreamManager streamManager;
|
||||
|
||||
private StreamContext streamContext;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public SimpleStepContext(StepExecution stepExecution) {
|
||||
this(stepExecution, null, null);
|
||||
this(stepExecution, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public SimpleStepContext(StepExecution stepExecution, StepContext parent) {
|
||||
this(stepExecution, parent, null);
|
||||
this(stepExecution, parent, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object
|
||||
*/
|
||||
public SimpleStepContext(StepExecution stepExecution, StepContext parent, StatisticsService statisticsService) {
|
||||
public SimpleStepContext(StepExecution stepExecution, StepContext parent, StatisticsService statisticsService,
|
||||
StreamManager streamManager) {
|
||||
super();
|
||||
this.parent = parent;
|
||||
this.statisticsService = statisticsService;
|
||||
this.streamManager = streamManager;
|
||||
this.stepExecution = stepExecution;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.repeat.context.SynchronizedAttributeAccessor#setAttribute(java.lang.String, java.lang.Object)
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.repeat.context.SynchronizedAttributeAccessor#setAttribute(java.lang.String,
|
||||
* java.lang.Object)
|
||||
*/
|
||||
public void setAttribute(String name, Object value) {
|
||||
super.setAttribute(name, value);
|
||||
if (statisticsService!=null && (value instanceof StatisticsProvider)) {
|
||||
if (statisticsService != null && (value instanceof StatisticsProvider)) {
|
||||
statisticsService.register(this, (StatisticsProvider) value);
|
||||
}
|
||||
if (streamManager != null && (value instanceof ItemStream)) {
|
||||
ItemStream stream = (ItemStream) value;
|
||||
streamManager.register(this, stream);
|
||||
stream.open();
|
||||
if (streamContext != null) {
|
||||
stream.restoreFrom(streamContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.statistics.StatisticsProvider#getStatistics()
|
||||
*/
|
||||
public Properties getStatistics() {
|
||||
if (statisticsService==null) {
|
||||
if (statisticsService == null) {
|
||||
return new Properties();
|
||||
}
|
||||
return statisticsService.getStatistics(this);
|
||||
@@ -100,13 +123,13 @@ public class SimpleStepContext extends SynchronizedAttributeAccessor implements
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.batch.repeat.RepeatContext#registerDestructionCallback(java.lang.String,
|
||||
* java.lang.Runnable)
|
||||
* java.lang.Runnable)
|
||||
*/
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.batch.execution.scope.StepContext#registerDestructionCallback(java.lang.String,
|
||||
* java.lang.Runnable)
|
||||
* java.lang.Runnable)
|
||||
*/
|
||||
public void registerDestructionCallback(String name, Runnable callback) {
|
||||
synchronized (callbacks) {
|
||||
@@ -119,13 +142,23 @@ public class SimpleStepContext extends SynchronizedAttributeAccessor implements
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.execution.scope.StepContext#close()
|
||||
*/
|
||||
public void close() {
|
||||
|
||||
List errors = new ArrayList();
|
||||
|
||||
try {
|
||||
if (streamManager != null) {
|
||||
streamManager.close(this);
|
||||
}
|
||||
}
|
||||
catch (Exception t) {
|
||||
errors.add(t);
|
||||
}
|
||||
|
||||
Set copy;
|
||||
|
||||
synchronized (callbacks) {
|
||||
@@ -152,7 +185,8 @@ public class SimpleStepContext extends SynchronizedAttributeAccessor implements
|
||||
*/
|
||||
try {
|
||||
callback.run();
|
||||
} catch (RuntimeException t) {
|
||||
}
|
||||
catch (RuntimeException t) {
|
||||
errors.add(t);
|
||||
}
|
||||
}
|
||||
@@ -163,7 +197,14 @@ public class SimpleStepContext extends SynchronizedAttributeAccessor implements
|
||||
return;
|
||||
}
|
||||
|
||||
throw (RuntimeException) errors.get(0);
|
||||
Exception error = (Exception) errors.get(0);
|
||||
if (error instanceof RuntimeException) {
|
||||
throw (RuntimeException) error;
|
||||
}
|
||||
else {
|
||||
throw new BatchCriticalException("Could not close step context, rethrowing first of " + errors.size()
|
||||
+ " execptions.", error);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -175,4 +216,20 @@ public class SimpleStepContext extends SynchronizedAttributeAccessor implements
|
||||
return stepExecution;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.item.ItemStream#getStreamContext()
|
||||
*/
|
||||
public StreamContext getStreamContext() {
|
||||
return streamManager.getStreamContext(this);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.execution.scope.StepContext#setInitialStreamContext(org.springframework.batch.item.StreamContext)
|
||||
*/
|
||||
public void setInitialStreamContext(StreamContext streamContext) {
|
||||
this.streamContext = streamContext;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
package org.springframework.batch.execution.scope;
|
||||
|
||||
import org.springframework.batch.core.domain.StepExecution;
|
||||
import org.springframework.batch.item.ItemStream;
|
||||
import org.springframework.batch.item.StreamContext;
|
||||
import org.springframework.batch.item.StreamContextProvider;
|
||||
import org.springframework.batch.statistics.StatisticsProvider;
|
||||
import org.springframework.core.AttributeAccessor;
|
||||
|
||||
@@ -25,7 +28,7 @@ import org.springframework.core.AttributeAccessor;
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public interface StepContext extends AttributeAccessor, StatisticsProvider {
|
||||
public interface StepContext extends AttributeAccessor, StreamContextProvider, StatisticsProvider {
|
||||
|
||||
/**
|
||||
* Accessor for the {@link StepExecution} associated with the currently
|
||||
@@ -34,7 +37,7 @@ public interface StepContext extends AttributeAccessor, StatisticsProvider {
|
||||
* @return the {@link StepExecution} associated with the current step
|
||||
*/
|
||||
StepExecution getStepExecution();
|
||||
|
||||
|
||||
/**
|
||||
* Accessor for the parent context.
|
||||
*
|
||||
@@ -51,4 +54,14 @@ public interface StepContext extends AttributeAccessor, StatisticsProvider {
|
||||
* Clean up any resources held during the context of the step.
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Provide the stream context that will be needed to restore
|
||||
* {@link ItemStream} instances. If this is not set the streams will simply
|
||||
* not be initialised and repositioned for restart (which is sometimes
|
||||
* desirable).
|
||||
*
|
||||
* @param streamContext
|
||||
*/
|
||||
void setInitialStreamContext(StreamContext streamContext);
|
||||
}
|
||||
@@ -34,8 +34,8 @@ import org.springframework.batch.execution.scope.StepScope;
|
||||
import org.springframework.batch.execution.scope.StepSynchronizationManager;
|
||||
import org.springframework.batch.io.Skippable;
|
||||
import org.springframework.batch.io.exception.BatchCriticalException;
|
||||
import org.springframework.batch.item.ItemStream;
|
||||
import org.springframework.batch.item.StreamContext;
|
||||
import org.springframework.batch.item.stream.SimpleStreamManager;
|
||||
import org.springframework.batch.item.stream.StreamManager;
|
||||
import org.springframework.batch.repeat.ExitStatus;
|
||||
import org.springframework.batch.repeat.RepeatCallback;
|
||||
import org.springframework.batch.repeat.RepeatContext;
|
||||
@@ -97,6 +97,8 @@ public class SimpleStepExecutor {
|
||||
|
||||
private AbstractStep step;
|
||||
|
||||
private StreamManager streamManager = new SimpleStreamManager();
|
||||
|
||||
/**
|
||||
* Package private constructor so the factory can create a the executor.
|
||||
*/
|
||||
@@ -118,6 +120,20 @@ public class SimpleStepExecutor {
|
||||
this.statisticsService = statisticsService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link StreamManager}. This will be used to create
|
||||
* the {@link StepContext}, and hence any component that is a
|
||||
* {@link StatisticsProvider} and in step scope will be registered with the
|
||||
* service. The {@link StepContext} is then a source of aggregate statistics
|
||||
* for the step.
|
||||
*
|
||||
* @param streamManager the {@link StreamManager} to set. Default is a
|
||||
* {@link SimpleStreamManager}.
|
||||
*/
|
||||
public void setStreamManager(StreamManager streamManager) {
|
||||
this.streamManager = streamManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injected strategy for transaction management
|
||||
* @param transactionManager
|
||||
@@ -173,8 +189,7 @@ public class SimpleStepExecutor {
|
||||
* execution
|
||||
* @see StepExecutor#execute(StepExecution)
|
||||
*/
|
||||
public void execute(final StepExecution stepExecution) throws BatchCriticalException,
|
||||
StepInterruptedException {
|
||||
public void execute(final StepExecution stepExecution) throws BatchCriticalException, StepInterruptedException {
|
||||
|
||||
final StepInstance stepInstance = stepExecution.getStep();
|
||||
boolean isRestart = stepInstance.getStepExecutionCount() > 0 ? true : false;
|
||||
@@ -182,24 +197,25 @@ public class SimpleStepExecutor {
|
||||
|
||||
ExitStatus status = ExitStatus.FAILED;
|
||||
|
||||
StepContext parentStepScopeContext = StepSynchronizationManager.getContext();
|
||||
final StepContext stepScopeContext = new SimpleStepContext(stepExecution, parentStepScopeContext,
|
||||
statisticsService);
|
||||
StepSynchronizationManager.register(stepScopeContext);
|
||||
StepContext parentStepContext = StepSynchronizationManager.getContext();
|
||||
final StepContext stepContext = new SimpleStepContext(stepExecution, parentStepContext, statisticsService,
|
||||
streamManager);
|
||||
StepSynchronizationManager.register(stepContext);
|
||||
// Add the job identifier so that it can be used to identify
|
||||
// the conversation in StepScope
|
||||
stepScopeContext.setAttribute(StepScope.ID_KEY, stepExecution.getJobExecution().getId());
|
||||
stepContext.setAttribute(StepScope.ID_KEY, stepExecution.getJobExecution().getId());
|
||||
|
||||
final boolean saveRestartData = step.isSaveStreamContext();
|
||||
|
||||
if (saveRestartData && isRestart) {
|
||||
stepContext.setInitialStreamContext(stepInstance.getStreamContext());
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
stepExecution.setStartTime(new Date(System.currentTimeMillis()));
|
||||
updateStatus(stepExecution, BatchStatus.STARTED);
|
||||
|
||||
final boolean saveRestartData = step.isSaveRestartData();
|
||||
|
||||
if (saveRestartData && isRestart) {
|
||||
restoreFromRestartData(tasklet, stepInstance.getRestartData());
|
||||
}
|
||||
|
||||
status = stepOperations.iterate(new RepeatCallback() {
|
||||
|
||||
public ExitStatus doInIteration(final RepeatContext context) throws Exception {
|
||||
@@ -229,8 +245,8 @@ public class SimpleStepExecutor {
|
||||
|
||||
// TODO: check that stepExecution can
|
||||
// aggregate these contributions if they
|
||||
// come in asnchronously.
|
||||
Properties statistics = stepScopeContext.getStatistics();
|
||||
// come in asynchronously.
|
||||
Properties statistics = stepContext.getStatistics();
|
||||
contribution.setStatistics(statistics);
|
||||
contribution.incrementCommitCount();
|
||||
// Apply the contribution to the step
|
||||
@@ -238,7 +254,7 @@ public class SimpleStepExecutor {
|
||||
stepExecution.apply(contribution);
|
||||
|
||||
if (saveRestartData) {
|
||||
stepInstance.setRestartData(getRestartData(tasklet));
|
||||
stepInstance.setStreamContext(stepContext.getStreamContext());
|
||||
jobRepository.update(stepInstance);
|
||||
}
|
||||
jobRepository.saveOrUpdate(stepExecution);
|
||||
@@ -388,26 +404,6 @@ public class SimpleStepExecutor {
|
||||
return exitStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tasklet
|
||||
* @return restart data from the {@link Tasklet} if it is
|
||||
* {@link ItemStream}
|
||||
*/
|
||||
private StreamContext getRestartData(Tasklet tasklet) {
|
||||
if (tasklet instanceof ItemStream) {
|
||||
return ((ItemStream) tasklet).getRestartData();
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void restoreFromRestartData(Tasklet tasklet, StreamContext streamContext) {
|
||||
if (tasklet instanceof ItemStream && streamContext != null) {
|
||||
((ItemStream) tasklet).restoreFrom(streamContext);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the {@link StepInterruptionPolicy}. The policy is used to
|
||||
* check whether an external request has been made to interrupt the job
|
||||
|
||||
@@ -16,14 +16,11 @@
|
||||
|
||||
package org.springframework.batch.execution.tasklet;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ItemStream;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.batch.item.StreamContext;
|
||||
import org.springframework.batch.item.stream.GenericStreamContext;
|
||||
import org.springframework.batch.support.PropertiesConverter;
|
||||
import org.springframework.batch.item.StreamException;
|
||||
|
||||
/**
|
||||
* An extension of {@link ItemOrientedTasklet} that delegates calls to
|
||||
@@ -40,95 +37,30 @@ import org.springframework.batch.support.PropertiesConverter;
|
||||
public class RestartableItemOrientedTasklet extends ItemOrientedTasklet implements ItemStream {
|
||||
|
||||
/**
|
||||
* @see ItemStream#getRestartData()
|
||||
* @see ItemStream#getStreamContext()
|
||||
*/
|
||||
public StreamContext getRestartData() {
|
||||
|
||||
StreamContext itemProviderRestartData = null;
|
||||
StreamContext itemProcessorRestartData = null;
|
||||
|
||||
if (itemProvider instanceof ItemStream) {
|
||||
itemProviderRestartData = ((ItemStream) itemProvider).getRestartData();
|
||||
}
|
||||
|
||||
if (itemWriter instanceof ItemStream) {
|
||||
itemProcessorRestartData = ((ItemStream) itemWriter).getRestartData();
|
||||
}
|
||||
|
||||
RestartableItemOrientedTaskletRestartData restartData = new RestartableItemOrientedTaskletRestartData(itemProviderRestartData, itemProcessorRestartData);
|
||||
|
||||
return restartData;
|
||||
public StreamContext getStreamContext() {
|
||||
throw new UnsupportedOperationException("This class is not used");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ItemStream#restoreFrom(StreamContext)
|
||||
*/
|
||||
public void restoreFrom(StreamContext data) {
|
||||
if (data == null || data.getProperties() == null)
|
||||
return;
|
||||
|
||||
RestartableItemOrientedTaskletRestartData moduleRestartData;
|
||||
|
||||
if (data instanceof RestartableItemOrientedTaskletRestartData) {
|
||||
moduleRestartData = (RestartableItemOrientedTaskletRestartData) data;
|
||||
}
|
||||
else {
|
||||
moduleRestartData = new RestartableItemOrientedTaskletRestartData(data.getProperties());
|
||||
}
|
||||
|
||||
if (itemProvider instanceof ItemStream) {
|
||||
((ItemStream) itemProvider).restoreFrom(moduleRestartData.readerData);
|
||||
}
|
||||
if (itemWriter instanceof ItemStream) {
|
||||
((ItemStream) itemWriter).restoreFrom(moduleRestartData.writerData);
|
||||
}
|
||||
}
|
||||
|
||||
private class RestartableItemOrientedTaskletRestartData implements StreamContext {
|
||||
|
||||
private static final String READER_KEY = "DATA_PROVIDER";
|
||||
|
||||
private static final String WRITER_KEY = "DATA_PROCESSOR";
|
||||
|
||||
private StreamContext readerData;
|
||||
|
||||
private StreamContext writerData;
|
||||
|
||||
public RestartableItemOrientedTaskletRestartData(StreamContext providerData, StreamContext writerData) {
|
||||
this.readerData = providerData;
|
||||
this.writerData = writerData;
|
||||
}
|
||||
|
||||
public RestartableItemOrientedTaskletRestartData(Properties data) {
|
||||
readerData = new GenericStreamContext(PropertiesConverter
|
||||
.stringToProperties(data.getProperty(READER_KEY)));
|
||||
writerData = new GenericStreamContext(PropertiesConverter.stringToProperties(data
|
||||
.getProperty(WRITER_KEY)));
|
||||
}
|
||||
|
||||
public Properties getProperties() {
|
||||
Properties props = new Properties();
|
||||
if (readerData != null) {
|
||||
props.setProperty(READER_KEY, PropertiesConverter.propertiesToString(readerData.getProperties()));
|
||||
}
|
||||
if (writerData != null) {
|
||||
props.setProperty(WRITER_KEY, PropertiesConverter.propertiesToString(writerData.getProperties()));
|
||||
}
|
||||
return props;
|
||||
}
|
||||
throw new UnsupportedOperationException("This class is not used");
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.item.ItemStream#open()
|
||||
*/
|
||||
public void open() throws Exception {
|
||||
public void open() throws StreamException {
|
||||
throw new UnsupportedOperationException("Not implemented.");
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.item.ItemStream#close()
|
||||
*/
|
||||
public void close() throws Exception {
|
||||
public void close() throws StreamException {
|
||||
throw new UnsupportedOperationException("Not implemented.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,10 +405,10 @@ public class SimpleJobRepositoryTests extends TestCase {
|
||||
jobDao.createJobInstance(jobConfiguration.getName(), jobParameters);
|
||||
jobDaoControl.setReturnValue(databaseJob);
|
||||
stepDao.createStep(databaseJob, "TestStep1");
|
||||
databaseStep1.setRestartData(null);
|
||||
databaseStep1.setStreamContext(null);
|
||||
stepDaoControl.setReturnValue(databaseStep1);
|
||||
stepDao.createStep(databaseJob, "TestStep2");
|
||||
databaseStep2.setRestartData(new GenericStreamContext(null));
|
||||
databaseStep2.setStreamContext(new GenericStreamContext(null));
|
||||
stepDaoControl.setReturnValue(databaseStep2);
|
||||
jobDao.save(new JobExecution(databaseJob));
|
||||
jobDaoControl.setMatcher(new ArgumentsMatcher(){
|
||||
@@ -426,10 +426,10 @@ public class SimpleJobRepositoryTests extends TestCase {
|
||||
Iterator it = jobSteps.iterator();
|
||||
StepInstance step = (StepInstance) it.next();
|
||||
assertTrue(step.equals(databaseStep1));
|
||||
assertTrue(step.getRestartData().getProperties().isEmpty());
|
||||
assertTrue(step.getStreamContext().getProperties().isEmpty());
|
||||
step = (StepInstance) it.next();
|
||||
assertTrue(step.equals(databaseStep2));
|
||||
assertTrue(step.getRestartData().getProperties().isEmpty());
|
||||
assertTrue(step.getStreamContext().getProperties().isEmpty());
|
||||
}
|
||||
|
||||
public void testFindStepsFixesInvalidRestartData() throws Exception{
|
||||
@@ -438,12 +438,12 @@ public class SimpleJobRepositoryTests extends TestCase {
|
||||
jobs.add(databaseJob);
|
||||
jobDaoControl.setReturnValue(jobs);
|
||||
stepDao.findStep(databaseJob, "TestStep1");
|
||||
databaseStep1.setRestartData(null);
|
||||
databaseStep1.setStreamContext(null);
|
||||
stepDaoControl.setReturnValue(databaseStep1);
|
||||
stepDao.getStepExecutionCount(databaseStep1);
|
||||
stepDaoControl.setReturnValue(1);
|
||||
stepDao.findStep(databaseJob, "TestStep2");
|
||||
databaseStep2.setRestartData(new GenericStreamContext(null));
|
||||
databaseStep2.setStreamContext(new GenericStreamContext(null));
|
||||
stepDaoControl.setReturnValue(databaseStep2);
|
||||
stepDao.getStepExecutionCount(databaseStep2);
|
||||
stepDaoControl.setReturnValue(1);
|
||||
@@ -470,9 +470,9 @@ public class SimpleJobRepositoryTests extends TestCase {
|
||||
Iterator it = jobSteps.iterator();
|
||||
StepInstance step = (StepInstance) it.next();
|
||||
assertTrue(step.equals(databaseStep1));
|
||||
assertTrue(step.getRestartData().getProperties().isEmpty());
|
||||
assertTrue(step.getStreamContext().getProperties().isEmpty());
|
||||
step = (StepInstance) it.next();
|
||||
assertTrue(step.getRestartData().getProperties().isEmpty());
|
||||
assertTrue(step.getStreamContext().getProperties().isEmpty());
|
||||
assertTrue(step.equals(databaseStep2));
|
||||
}
|
||||
|
||||
|
||||
@@ -153,11 +153,11 @@ public abstract class AbstractStepDaoTests extends AbstractTransactionalDataSour
|
||||
Properties data = new Properties();
|
||||
data.setProperty("restart.key1", "restartData");
|
||||
StreamContext streamContext = new GenericStreamContext(data);
|
||||
step1.setRestartData(streamContext);
|
||||
step1.setStreamContext(streamContext);
|
||||
stepDao.update(step1);
|
||||
StepInstance tempStep = stepDao.findStep(jobInstance, step1.getName());
|
||||
assertEquals(tempStep, step1);
|
||||
assertEquals(tempStep.getRestartData().getProperties().toString(),
|
||||
assertEquals(tempStep.getStreamContext().getProperties().toString(),
|
||||
streamContext.getProperties().toString());
|
||||
}
|
||||
|
||||
|
||||
@@ -107,16 +107,16 @@ public class MapStepDaoTests extends TestCase {
|
||||
}
|
||||
|
||||
public void testSaveRestartData() throws Exception {
|
||||
assertEquals(null, dao.getRestartData(step.getId()));
|
||||
assertEquals(null, dao.getStreamContext(step.getId()));
|
||||
step.setStatus(BatchStatus.COMPLETED);
|
||||
Properties data = new Properties();
|
||||
data.setProperty("restart.key1", "restartData");
|
||||
StreamContext streamContext = new GenericStreamContext(data);
|
||||
step.setRestartData(streamContext);
|
||||
step.setStreamContext(streamContext);
|
||||
dao.update(step);
|
||||
StepInstance tempStep = dao.findStep(job, step.getName());
|
||||
assertEquals(tempStep, step);
|
||||
assertEquals(tempStep.getRestartData().getProperties().toString(),
|
||||
assertEquals(tempStep.getStreamContext().getProperties().toString(),
|
||||
streamContext.getProperties().toString());
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,10 @@ import java.util.Properties;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.batch.core.domain.StepExecution;
|
||||
import org.springframework.batch.item.ItemStream;
|
||||
import org.springframework.batch.item.StreamContext;
|
||||
import org.springframework.batch.item.stream.GenericStreamContext;
|
||||
import org.springframework.batch.item.stream.StreamManager;
|
||||
import org.springframework.batch.statistics.StatisticsProvider;
|
||||
import org.springframework.batch.statistics.StatisticsService;
|
||||
import org.springframework.batch.support.PropertiesConverter;
|
||||
@@ -131,21 +135,21 @@ public class SimpleStepContextTests extends TestCase {
|
||||
assertTrue(list.contains("bar"));
|
||||
assertTrue(list.contains("spam"));
|
||||
}
|
||||
|
||||
|
||||
public void testStatisticsWithNullService() throws Exception {
|
||||
assertEquals(0, context.getStatistics().size());
|
||||
}
|
||||
|
||||
public void testStatisticsWithNotNullService() throws Exception {
|
||||
Map map = new HashMap();
|
||||
context = new SimpleStepContext(null, null, new StubStatisticsService(map));
|
||||
context = new SimpleStepContext(null, null, new StubStatisticsService(map), new StubStreamManager(map));
|
||||
assertEquals(1, context.getStatistics().size());
|
||||
assertEquals("bar", context.getStatistics().getProperty("foo"));
|
||||
}
|
||||
|
||||
public void testStatisticsServiceRegistration() throws Exception {
|
||||
Map map = new HashMap();
|
||||
context = new SimpleStepContext(null, null, new StubStatisticsService(map));
|
||||
context = new SimpleStepContext(null, null, new StubStatisticsService(map), new StubStreamManager(map));
|
||||
StubStatisticsProvider provider = new StubStatisticsProvider();
|
||||
context.setAttribute("foo", provider);
|
||||
assertEquals(1, map.size());
|
||||
@@ -155,7 +159,7 @@ public class SimpleStepContextTests extends TestCase {
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
private class StubStatisticsService implements StatisticsService {
|
||||
private final Map map;
|
||||
@@ -173,6 +177,35 @@ public class SimpleStepContextTests extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
private class StubStreamManager implements StreamManager {
|
||||
private final Map map;
|
||||
|
||||
private StubStreamManager(Map map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
public void close(Object key) {
|
||||
}
|
||||
|
||||
public StreamContext getStreamContext(Object key) {
|
||||
return new GenericStreamContext(PropertiesConverter.stringToProperties("foo=bar"));
|
||||
}
|
||||
|
||||
public void open(Object key) {
|
||||
}
|
||||
|
||||
public void register(Object key, ItemStream stream) {
|
||||
map.put(key, stream);
|
||||
}
|
||||
|
||||
public void restoreFrom(Object key, StreamContext data) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
|
||||
@@ -99,12 +99,12 @@ public class SimpleStepConfigurationTests extends TestCase {
|
||||
|
||||
/**
|
||||
* Test method for
|
||||
* {@link org.springframework.batch.execution.step.simple.AbstractStep#isSaveRestartData()}.
|
||||
* {@link org.springframework.batch.execution.step.simple.AbstractStep#isSaveStreamContext()}.
|
||||
*/
|
||||
public void testIsSaveRestartData() {
|
||||
assertEquals(false, configuration.isSaveRestartData());
|
||||
configuration.setSaveRestartData(true);
|
||||
assertEquals(true, configuration.isSaveRestartData());
|
||||
assertEquals(false, configuration.isSaveStreamContext());
|
||||
configuration.setSaveStreamContext(true);
|
||||
assertEquals(true, configuration.isSaveStreamContext());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,10 +25,10 @@ import java.util.Properties;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.batch.core.domain.JobSupport;
|
||||
import org.springframework.batch.core.domain.JobExecution;
|
||||
import org.springframework.batch.core.domain.JobInstance;
|
||||
import org.springframework.batch.core.domain.JobParameters;
|
||||
import org.springframework.batch.core.domain.JobSupport;
|
||||
import org.springframework.batch.core.domain.StepContribution;
|
||||
import org.springframework.batch.core.domain.StepExecution;
|
||||
import org.springframework.batch.core.domain.StepInstance;
|
||||
@@ -43,7 +43,9 @@ import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ItemStream;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.batch.item.StreamContext;
|
||||
import org.springframework.batch.item.StreamException;
|
||||
import org.springframework.batch.item.reader.ListItemReader;
|
||||
import org.springframework.batch.item.stream.GenericStreamContext;
|
||||
import org.springframework.batch.item.writer.AbstractItemWriter;
|
||||
import org.springframework.batch.repeat.ExitStatus;
|
||||
import org.springframework.batch.repeat.RepeatContext;
|
||||
@@ -72,7 +74,7 @@ public class SimpleStepExecutorTests extends TestCase {
|
||||
private AbstractStep stepConfiguration;
|
||||
|
||||
private RepeatTemplate template;
|
||||
|
||||
|
||||
private JobInstance jobInstance;
|
||||
|
||||
private ItemReader getReader(String[] args) {
|
||||
@@ -111,7 +113,7 @@ public class SimpleStepExecutorTests extends TestCase {
|
||||
template = new RepeatTemplate();
|
||||
template.setCompletionPolicy(new SimpleCompletionPolicy(1));
|
||||
stepExecutor.setChunkOperations(template);
|
||||
|
||||
|
||||
jobInstance = new JobInstance(new Long(0), new JobParameters());
|
||||
jobInstance.setJob(new JobSupport("FOO"));
|
||||
}
|
||||
@@ -120,8 +122,7 @@ public class SimpleStepExecutorTests extends TestCase {
|
||||
|
||||
StepInstance step = new StepInstance(new Long(9));
|
||||
JobExecution jobExecutionContext = new JobExecution(jobInstance);
|
||||
StepExecution stepExecution = new StepExecution(step,
|
||||
jobExecutionContext);
|
||||
StepExecution stepExecution = new StepExecution(step, jobExecutionContext);
|
||||
|
||||
stepExecutor.execute(stepExecution);
|
||||
assertEquals(1, processed.size());
|
||||
@@ -158,8 +159,7 @@ public class SimpleStepExecutorTests extends TestCase {
|
||||
|
||||
final StepInstance step = new StepInstance(new Long(1));
|
||||
final JobExecution jobExecution = new JobExecution(jobInstance);
|
||||
final StepExecution stepExecution = new StepExecution(step,
|
||||
jobExecution);
|
||||
final StepExecution stepExecution = new StepExecution(step, jobExecution);
|
||||
|
||||
stepConfiguration.setTasklet(new Tasklet() {
|
||||
public ExitStatus execute() throws Exception {
|
||||
@@ -186,8 +186,7 @@ public class SimpleStepExecutorTests extends TestCase {
|
||||
final StepInstance step = new StepInstance(new Long(1));
|
||||
final JobExecution jobExecution = new JobExecution(jobInstance);
|
||||
jobExecution.setId(new Long(1));
|
||||
final StepExecution stepExecution = new StepExecution(step,
|
||||
jobExecution);
|
||||
final StepExecution stepExecution = new StepExecution(step, jobExecution);
|
||||
|
||||
template.setInterceptor(new RepeatInterceptorAdapter() {
|
||||
public void open(RepeatContext context) {
|
||||
@@ -210,8 +209,7 @@ public class SimpleStepExecutorTests extends TestCase {
|
||||
|
||||
StepInstance step = new StepInstance(new Long(1));
|
||||
JobExecution jobExecutionContext = new JobExecution(jobInstance);
|
||||
StepExecution stepExecution = new StepExecution(step,
|
||||
jobExecutionContext);
|
||||
StepExecution stepExecution = new StepExecution(step, jobExecutionContext);
|
||||
|
||||
stepExecutor.execute(stepExecution);
|
||||
assertEquals(1, processed.size());
|
||||
@@ -237,8 +235,7 @@ public class SimpleStepExecutorTests extends TestCase {
|
||||
StepInstance step = new StepInstance(new Long(1));
|
||||
stepConfiguration.setTasklet(tasklet);
|
||||
JobExecution jobExecutionContext = new JobExecution(jobInstance);
|
||||
StepExecution stepExecution = new StepExecution(step,
|
||||
jobExecutionContext);
|
||||
StepExecution stepExecution = new StepExecution(step, jobExecutionContext);
|
||||
|
||||
try {
|
||||
stepExecutor.execute(stepExecution);
|
||||
@@ -269,8 +266,7 @@ public class SimpleStepExecutorTests extends TestCase {
|
||||
StepInstance step = new StepInstance(new Long(1));
|
||||
stepConfiguration.setTasklet(tasklet);
|
||||
JobExecution jobExecutionContext = new JobExecution(jobInstance);
|
||||
StepExecution stepExecution = new StepExecution(step,
|
||||
jobExecutionContext);
|
||||
StepExecution stepExecution = new StepExecution(step, jobExecutionContext);
|
||||
|
||||
try {
|
||||
stepExecutor.execute(stepExecution);
|
||||
@@ -285,21 +281,15 @@ public class SimpleStepExecutorTests extends TestCase {
|
||||
* make sure a job that has never been executed before, but does have
|
||||
* saveRestartData = true, doesn't have restoreFrom called on it.
|
||||
*/
|
||||
public void testNonRestartedJob() {
|
||||
public void testNonRestartedJob() throws Exception {
|
||||
StepInstance step = new StepInstance(new Long(1));
|
||||
MockRestartableTasklet tasklet = new MockRestartableTasklet();
|
||||
stepExecutor.setTasklet(tasklet);
|
||||
stepConfiguration.setSaveRestartData(true);
|
||||
stepConfiguration.setSaveStreamContext(true);
|
||||
JobExecution jobExecutionContext = new JobExecution(jobInstance);
|
||||
StepExecution stepExecution = new StepExecution(step,
|
||||
jobExecutionContext);
|
||||
StepExecution stepExecution = new StepExecution(step, jobExecutionContext);
|
||||
|
||||
try {
|
||||
stepExecutor.execute(stepExecution);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
fail();
|
||||
}
|
||||
stepExecutor.execute(stepExecution);
|
||||
|
||||
assertFalse(tasklet.isRestoreFromCalled());
|
||||
assertTrue(tasklet.isGetRestartDataCalled());
|
||||
@@ -309,24 +299,21 @@ public class SimpleStepExecutorTests extends TestCase {
|
||||
* make sure a job that has been executed before, and is therefore being
|
||||
* restarted, is restored.
|
||||
*/
|
||||
public void testRestartedJob() {
|
||||
public void testRestartedJob() throws Exception {
|
||||
StepInstance step = new StepInstance(new Long(1));
|
||||
step.setStepExecutionCount(1);
|
||||
MockRestartableTasklet tasklet = new MockRestartableTasklet();
|
||||
stepExecutor.setTasklet(tasklet);
|
||||
stepConfiguration.setSaveRestartData(true);
|
||||
stepConfiguration.setSaveStreamContext(true);
|
||||
JobExecution jobExecutionContext = new JobExecution(jobInstance);
|
||||
StepExecution stepExecution = new StepExecution(step,
|
||||
jobExecutionContext);
|
||||
StepExecution stepExecution = new StepExecution(step, jobExecutionContext);
|
||||
stepExecution.getStep().setStreamContext(
|
||||
new GenericStreamContext(PropertiesConverter.stringToProperties("foo=bar")));
|
||||
|
||||
try {
|
||||
stepExecutor.execute(stepExecution);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
fail();
|
||||
}
|
||||
stepExecutor.execute(stepExecution);
|
||||
|
||||
assertTrue(tasklet.isRestoreFromCalled());
|
||||
assertTrue(tasklet.isRestoreFromCalledWithSomeContext());
|
||||
assertTrue(tasklet.isGetRestartDataCalled());
|
||||
}
|
||||
|
||||
@@ -339,10 +326,9 @@ public class SimpleStepExecutorTests extends TestCase {
|
||||
step.setStepExecutionCount(1);
|
||||
MockRestartableTasklet tasklet = new MockRestartableTasklet();
|
||||
stepConfiguration.setTasklet(tasklet);
|
||||
stepConfiguration.setSaveRestartData(false);
|
||||
stepConfiguration.setSaveStreamContext(false);
|
||||
JobExecution jobExecutionContext = new JobExecution(jobInstance);
|
||||
StepExecution stepExecution = new StepExecution(step,
|
||||
jobExecutionContext);
|
||||
StepExecution stepExecution = new StepExecution(step, jobExecutionContext);
|
||||
|
||||
try {
|
||||
stepExecutor.execute(stepExecution);
|
||||
@@ -359,7 +345,7 @@ public class SimpleStepExecutorTests extends TestCase {
|
||||
* Even though the job is restarted, and saveRestartData is true, nothing
|
||||
* will be restored because the Tasklet does not implement Restartable.
|
||||
*/
|
||||
public void testRestartJobOnNonRestartableTasklet() {
|
||||
public void testRestartJobOnNonRestartableTasklet() throws Exception {
|
||||
StepInstance step = new StepInstance(new Long(1));
|
||||
step.setStepExecutionCount(1);
|
||||
stepConfiguration.setTasklet(new Tasklet() {
|
||||
@@ -367,16 +353,11 @@ public class SimpleStepExecutorTests extends TestCase {
|
||||
return ExitStatus.FINISHED;
|
||||
}
|
||||
});
|
||||
stepConfiguration.setSaveRestartData(true);
|
||||
stepConfiguration.setSaveStreamContext(true);
|
||||
JobExecution jobExecution = new JobExecution(jobInstance);
|
||||
StepExecution stepExecution = new StepExecution(step, jobExecution);
|
||||
|
||||
try {
|
||||
stepExecutor.execute(stepExecution);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
fail();
|
||||
}
|
||||
stepExecutor.execute(stepExecution);
|
||||
}
|
||||
|
||||
public void testApplyConfigurationWithExceptionHandler() throws Exception {
|
||||
@@ -426,10 +407,10 @@ public class SimpleStepExecutorTests extends TestCase {
|
||||
return ExitStatus.FINISHED;
|
||||
}
|
||||
});
|
||||
stepConfiguration.setSaveRestartData(true);
|
||||
stepConfiguration.setSaveStreamContext(true);
|
||||
JobExecution jobExecution = new JobExecution(jobInstance);
|
||||
StepExecution stepExecution = new StepExecution(step, jobExecution);
|
||||
|
||||
|
||||
assertEquals(null, stepExecution.getStatistics().getProperty("foo"));
|
||||
|
||||
final Map map = new HashMap();
|
||||
@@ -437,6 +418,7 @@ public class SimpleStepExecutorTests extends TestCase {
|
||||
public Properties getStatistics(Object key) {
|
||||
return PropertiesConverter.stringToProperties("foo=bar");
|
||||
}
|
||||
|
||||
public void register(Object key, StatisticsProvider provider) {
|
||||
map.put(key, provider);
|
||||
}
|
||||
@@ -448,8 +430,9 @@ public class SimpleStepExecutorTests extends TestCase {
|
||||
catch (Throwable t) {
|
||||
fail();
|
||||
}
|
||||
|
||||
// At least once in that process the statistics service was asked for statistics...
|
||||
|
||||
// At least once in that process the statistics service was asked for
|
||||
// statistics...
|
||||
assertEquals("bar", stepExecution.getStatistics().getProperty("foo"));
|
||||
// ...but nothing was registered because nothing with step scoped.
|
||||
assertEquals(0, map.size());
|
||||
@@ -461,17 +444,25 @@ public class SimpleStepExecutorTests extends TestCase {
|
||||
|
||||
private boolean restoreFromCalled = false;
|
||||
|
||||
private boolean restoreFromCalledWithSomeContext = false;
|
||||
|
||||
public ExitStatus execute() throws Exception {
|
||||
StepSynchronizationManager.getContext().setAttribute("TASKLET_TEST", this);
|
||||
return ExitStatus.FINISHED;
|
||||
}
|
||||
|
||||
public StreamContext getRestartData() {
|
||||
public boolean isRestoreFromCalledWithSomeContext() {
|
||||
return restoreFromCalledWithSomeContext;
|
||||
}
|
||||
|
||||
public StreamContext getStreamContext() {
|
||||
getRestartDataCalled = true;
|
||||
return null;
|
||||
return new GenericStreamContext(PropertiesConverter.stringToProperties("spam=bucket"));
|
||||
}
|
||||
|
||||
public void restoreFrom(StreamContext data) {
|
||||
restoreFromCalled = true;
|
||||
restoreFromCalledWithSomeContext = data.getProperties().size() > 0;
|
||||
}
|
||||
|
||||
public boolean isGetRestartDataCalled() {
|
||||
@@ -481,15 +472,13 @@ public class SimpleStepExecutorTests extends TestCase {
|
||||
public boolean isRestoreFromCalled() {
|
||||
return restoreFromCalled;
|
||||
}
|
||||
|
||||
public void open() throws Exception {
|
||||
throw new UnsupportedOperationException("Not implemented.");
|
||||
|
||||
public void open() throws StreamException {
|
||||
}
|
||||
|
||||
public void close() throws Exception {
|
||||
throw new UnsupportedOperationException("Not implemented.");
|
||||
|
||||
public void close() throws StreamException {
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ItemRecoverer;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.batch.item.KeyedItemReader;
|
||||
import org.springframework.batch.item.StreamException;
|
||||
import org.springframework.batch.item.reader.AbstractItemReader;
|
||||
import org.springframework.batch.item.writer.AbstractItemWriter;
|
||||
import org.springframework.batch.repeat.context.RepeatContextSupport;
|
||||
@@ -60,7 +61,7 @@ public class ItemOrientedTaskletTests extends TestCase {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void close() throws Exception {
|
||||
public void close() throws StreamException {
|
||||
}
|
||||
};
|
||||
|
||||
@@ -182,7 +183,7 @@ public class ItemOrientedTaskletTests extends TestCase {
|
||||
return "bar";
|
||||
}
|
||||
|
||||
public void close() throws Exception {
|
||||
public void close() throws StreamException {
|
||||
}
|
||||
});
|
||||
|
||||
@@ -287,7 +288,7 @@ public class ItemOrientedTaskletTests extends TestCase {
|
||||
public Properties getStatistics() {
|
||||
return PropertiesConverter.stringToProperties("foo=bar");
|
||||
}
|
||||
public void close() throws Exception {
|
||||
public void close() throws StreamException {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
/*
|
||||
* 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.tasklet;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ItemStream;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.batch.item.StreamContext;
|
||||
import org.springframework.batch.item.stream.GenericStreamContext;
|
||||
import org.springframework.batch.support.PropertiesConverter;
|
||||
|
||||
/**
|
||||
* @author Peter Zozom
|
||||
*/
|
||||
public class RestartableItemOrientedTaskletTests extends TestCase {
|
||||
|
||||
private static class MockProvider implements ItemReader, ItemStream {
|
||||
|
||||
StreamContext data = new StreamContext() {
|
||||
|
||||
public Properties getProperties() {
|
||||
return PropertiesConverter.stringToProperties("a=b");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
public Object read() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public StreamContext getRestartData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void restoreFrom(StreamContext data) {
|
||||
// restart data should be same as returned by getRestartData
|
||||
assertEquals(this.data.getProperties(), data.getProperties());
|
||||
}
|
||||
|
||||
public boolean recover(Object data, Throwable cause) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void open() throws Exception {
|
||||
throw new UnsupportedOperationException("Not implemented.");
|
||||
}
|
||||
|
||||
public void close() throws Exception {
|
||||
throw new UnsupportedOperationException("Not implemented.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MockWriter implements ItemWriter, ItemStream {
|
||||
|
||||
StreamContext data = new StreamContext() {
|
||||
public Properties getProperties() {
|
||||
return PropertiesConverter.stringToProperties("x=y");
|
||||
}
|
||||
};
|
||||
|
||||
public void write(Object data) {
|
||||
}
|
||||
|
||||
public StreamContext getRestartData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void restoreFrom(StreamContext data) {
|
||||
// restart data should be same as returned by getRestartData
|
||||
assertEquals(this.data.getProperties(), data.getProperties());
|
||||
}
|
||||
|
||||
public void open() throws Exception {
|
||||
throw new UnsupportedOperationException("Not implemented.");
|
||||
}
|
||||
|
||||
public void close() throws Exception {
|
||||
throw new UnsupportedOperationException("Not implemented.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private ItemReader itemProvider;
|
||||
|
||||
private ItemWriter itemWriter;
|
||||
|
||||
private RestartableItemOrientedTasklet module;
|
||||
|
||||
public void testRestart() {
|
||||
|
||||
// create data provider and data processor
|
||||
itemProvider = new MockProvider();
|
||||
itemWriter = new MockWriter();
|
||||
|
||||
// create and set up module
|
||||
module = new RestartableItemOrientedTasklet();
|
||||
module.setItemReader(itemProvider);
|
||||
module.setItemWriter(itemWriter);
|
||||
|
||||
// get restart data
|
||||
StreamContext data = module.getRestartData();
|
||||
assertNotNull(data);
|
||||
// restore from restart data (see asserts in mock classes)
|
||||
module.restoreFrom(data);
|
||||
}
|
||||
|
||||
public void testRestartFromGenericData() {
|
||||
|
||||
// create data provider and data processor
|
||||
itemProvider = new MockProvider();
|
||||
itemWriter = new MockWriter();
|
||||
|
||||
// create and set up module
|
||||
module = new RestartableItemOrientedTasklet();
|
||||
module.setItemReader(itemProvider);
|
||||
module.setItemWriter(itemWriter);
|
||||
|
||||
// get restart data
|
||||
StreamContext data = module.getRestartData();
|
||||
assertNotNull(data);
|
||||
data = new GenericStreamContext(data.getProperties());
|
||||
// restore from restart data (see asserts in mock classes)
|
||||
module.restoreFrom(data);
|
||||
}
|
||||
|
||||
public void testRestartFromNotRestartable() {
|
||||
|
||||
// create and set up module
|
||||
module = new RestartableItemOrientedTasklet();
|
||||
module.setItemReader(null);
|
||||
module.setItemWriter(null);
|
||||
|
||||
// get restart data
|
||||
StreamContext data = module.getRestartData();
|
||||
assertNotNull(data);
|
||||
// restore from restart data (see asserts in mock classes)
|
||||
module.restoreFrom(data);
|
||||
//System.err.println(data.getProperties());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,7 +27,7 @@
|
||||
class="org.springframework.batch.execution.step.simple.SimpleStep"
|
||||
abstract="true">
|
||||
<property name="allowStartIfComplete" value="true" />
|
||||
<property name="saveRestartData" value="false" />
|
||||
<property name="saveStreamContext" value="false" />
|
||||
<property name="exceptionHandler">
|
||||
<bean
|
||||
class="org.springframework.batch.repeat.exception.handler.SimpleLimitExceptionHandler">
|
||||
|
||||
@@ -186,7 +186,7 @@ public class HibernateCursorItemReader extends AbstractItemReader implements Ite
|
||||
/**
|
||||
* @return the current row number wrapped as <code>RestartData</code>
|
||||
*/
|
||||
public StreamContext getRestartData() {
|
||||
public StreamContext getStreamContext() {
|
||||
Properties props = new Properties();
|
||||
props.setProperty(RESTART_DATA_ROW_NUMBER_KEY, ""+currentProcessedRow);
|
||||
String skipped = skippedRows.toString();
|
||||
|
||||
@@ -405,7 +405,7 @@ public class JdbcCursorItemReader extends AbstractTransactionalIoSource
|
||||
*
|
||||
* @see org.springframework.batch.restart.Restartable#getRestartData()
|
||||
*/
|
||||
public StreamContext getRestartData() {
|
||||
public StreamContext getStreamContext() {
|
||||
String skipped = skippedRows.toString();
|
||||
Properties statistics = getStatistics();
|
||||
statistics.setProperty(SKIPPED_ROWS, skipped.substring(1,skipped.length()-1));
|
||||
|
||||
@@ -191,8 +191,8 @@ public class DrivingQueryItemReader extends AbstractTransactionalIoSource
|
||||
}
|
||||
}
|
||||
|
||||
public StreamContext getRestartData() {
|
||||
return keyGenerator.getKeyAsRestartData(getCurrentKey());
|
||||
public StreamContext getStreamContext() {
|
||||
return keyGenerator.getKeyAsStreamContext(getCurrentKey());
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
|
||||
@@ -34,5 +34,5 @@ public interface KeyGenerator {
|
||||
* @throws IllegalArgumentException if key is null.
|
||||
* @throws IllegalArgumentException if key is an incompatible type.
|
||||
*/
|
||||
StreamContext getKeyAsRestartData(Object key);
|
||||
StreamContext getKeyAsStreamContext(Object key);
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* </p>Extension of the ColumnMapRowMapper that converts a column map to RestartData and allows
|
||||
* RestartData to be converted back as a PreparedStatementSetter. This is useful in a restart
|
||||
* </p>Extension of the ColumnMapRowMapper that converts a column map to {@link StreamContext} and allows
|
||||
* {@link StreamContext} to be converted back as a PreparedStatementSetter. This is useful in a restart
|
||||
* scenario, as it allows for the standard functionality of the ColumnMapRowMapper to be used to
|
||||
* create a map representing the columns returned by a query. It should be noted that this column ordering
|
||||
* is preserved in the map using a link list version of Map.
|
||||
@@ -34,9 +34,9 @@ import org.springframework.util.ClassUtils;
|
||||
* @author Dave Syer
|
||||
* @see RestartDataRowMapper
|
||||
*/
|
||||
public class ColumnMapRestartDataRowMapper extends ColumnMapRowMapper implements RestartDataRowMapper{
|
||||
public class ColumnMapStreamContextRowMapper extends ColumnMapRowMapper implements RestartDataRowMapper{
|
||||
|
||||
static final String KEY = ClassUtils.getQualifiedName(ColumnMapRestartDataRowMapper.class) + ".KEY.";
|
||||
static final String KEY = ClassUtils.getQualifiedName(ColumnMapStreamContextRowMapper.class) + ".KEY.";
|
||||
|
||||
public PreparedStatementSetter createSetter(StreamContext streamContext) {
|
||||
|
||||
@@ -44,7 +44,7 @@ public class IbatisKeyGenerator implements KeyGenerator {
|
||||
*
|
||||
* @see org.springframework.batch.restart.Restartable#getRestartData()
|
||||
*/
|
||||
public StreamContext getKeyAsRestartData(Object key) {
|
||||
public StreamContext getKeyAsStreamContext(Object key) {
|
||||
Properties props = new Properties();
|
||||
props.setProperty(RESTART_KEY, key.toString());
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ public class MultipleColumnJdbcKeyGenerator implements
|
||||
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
private RestartDataRowMapper keyMapper = new ColumnMapRestartDataRowMapper();
|
||||
private RestartDataRowMapper keyMapper = new ColumnMapStreamContextRowMapper();
|
||||
|
||||
private String sql;
|
||||
|
||||
@@ -96,7 +96,7 @@ public class MultipleColumnJdbcKeyGenerator implements
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.restart.Restartable#getRestartData()
|
||||
*/
|
||||
public StreamContext getKeyAsRestartData(Object key) {
|
||||
public StreamContext getKeyAsStreamContext(Object key) {
|
||||
Assert.state(keyMapper != null, "RestartDataConverter must not be null.");
|
||||
return keyMapper.createRestartData(key);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ public class SingleColumnJdbcKeyGenerator implements KeyGenerator {
|
||||
* @see KeyGenerator#getKeyAsRestartData()
|
||||
* @throws IllegalArgumentException if key is null.
|
||||
*/
|
||||
public StreamContext getKeyAsRestartData(Object key) {
|
||||
public StreamContext getKeyAsStreamContext(Object key) {
|
||||
|
||||
Assert.notNull(key, "The key must not be null.");
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ public class DefaultFlatFileItemReader extends SimpleFlatFileItemReader implemen
|
||||
* current Line Count which can be used to re initialise the batch job in
|
||||
* case of restart.
|
||||
*/
|
||||
public StreamContext getRestartData() {
|
||||
public StreamContext getStreamContext() {
|
||||
return new GenericStreamContext(getStatistics());
|
||||
}
|
||||
|
||||
|
||||
@@ -257,9 +257,9 @@ public class FlatFileItemWriter extends AbstractTransactionalIoSource implements
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ItemStream#getRestartData()
|
||||
* @see ItemStream#getStreamContext()
|
||||
*/
|
||||
public StreamContext getRestartData() {
|
||||
public StreamContext getStreamContext() {
|
||||
final OutputState os = getOutputState();
|
||||
|
||||
streamContext.getProperties().setProperty(RESTART_DATA_NAME, String.valueOf(os.position()));
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.springframework.batch.io.file.transform.AbstractLineTokenizer;
|
||||
import org.springframework.batch.io.file.transform.DelimitedLineTokenizer;
|
||||
import org.springframework.batch.io.file.transform.LineTokenizer;
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.StreamException;
|
||||
import org.springframework.batch.item.reader.AbstractItemReader;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
@@ -175,7 +176,7 @@ public class SimpleFlatFileItemReader extends AbstractItemReader implements Item
|
||||
*
|
||||
* @see ResourceLifecycle
|
||||
*/
|
||||
public void close() throws Exception {
|
||||
public void close() throws StreamException {
|
||||
try {
|
||||
if (reader != null) {
|
||||
log.debug("Closing flat file for reading: "+resource);
|
||||
|
||||
@@ -191,9 +191,9 @@ public class StaxEventItemReader extends AbstractItemReader implements ItemReade
|
||||
|
||||
/**
|
||||
* @return wrapped count of records read so far.
|
||||
* @see ItemStream#getRestartData()
|
||||
* @see ItemStream#getStreamContext()
|
||||
*/
|
||||
public StreamContext getRestartData() {
|
||||
public StreamContext getStreamContext() {
|
||||
Properties restartData = new Properties();
|
||||
|
||||
restartData.setProperty(RESTART_DATA_NAME, String.valueOf(currentRecordCount));
|
||||
|
||||
@@ -379,9 +379,9 @@ public class StaxEventItemWriter implements ItemWriter, ItemStream,
|
||||
/**
|
||||
* Get the restart data.
|
||||
* @return the restart data
|
||||
* @see org.springframework.batch.item.ItemStream#getRestartData()
|
||||
* @see org.springframework.batch.item.ItemStream#getStreamContext()
|
||||
*/
|
||||
public StreamContext getRestartData() {
|
||||
public StreamContext getStreamContext() {
|
||||
|
||||
Properties properties = new Properties();
|
||||
|
||||
|
||||
@@ -58,5 +58,5 @@ public interface ItemReader {
|
||||
* TODO: this is only used in sandbox?
|
||||
*
|
||||
*/
|
||||
void close() throws Exception;
|
||||
void close() throws StreamException;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ package org.springframework.batch.item;
|
||||
* The state that is stored is represented as {@link StreamContext} which
|
||||
* enforces a requirement that any restart data can be represented by a
|
||||
* Properties object. In general, the contract is that {@link StreamContext}
|
||||
* that is returned via the {@link #getRestartData()} method will be given back
|
||||
* that is returned via the {@link #getStreamContext()} method will be given back
|
||||
* to the {@link #restoreFrom(StreamContext)} method, exactly as it was
|
||||
* provided.
|
||||
* </p>
|
||||
@@ -34,32 +34,24 @@ package org.springframework.batch.item;
|
||||
* @author Lucas Ward
|
||||
*
|
||||
*/
|
||||
public interface ItemStream {
|
||||
|
||||
/**
|
||||
* Get {@link StreamContext} representing this object's current state.
|
||||
* Should not return null even if there is no state.
|
||||
*
|
||||
* @return {@link StreamContext} representing current state.
|
||||
*/
|
||||
StreamContext getRestartData();
|
||||
public interface ItemStream extends StreamContextProvider {
|
||||
|
||||
/**
|
||||
* Restart state given the provided {@link StreamContext}.
|
||||
*
|
||||
* @param data
|
||||
* @param context
|
||||
*/
|
||||
void restoreFrom(StreamContext data);
|
||||
void restoreFrom(StreamContext context);
|
||||
|
||||
/**
|
||||
* If any resources are needed for the stream to operate they need to be
|
||||
* initialised here.
|
||||
*/
|
||||
void open() throws Exception;
|
||||
void open() throws StreamException;
|
||||
|
||||
/**
|
||||
* If any resources are needed for the stream to operate they need to be
|
||||
* destroyed here.
|
||||
*/
|
||||
void close() throws Exception;
|
||||
void close() throws StreamException;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.item;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public interface StreamContextProvider {
|
||||
|
||||
/**
|
||||
* Get {@link StreamContext} representing this object's current state.
|
||||
* Should not return null even if there is no state.
|
||||
*
|
||||
* @return {@link StreamContext} representing current state.
|
||||
*/
|
||||
StreamContext getStreamContext();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.item;
|
||||
|
||||
import org.springframework.batch.io.exception.BatchCriticalException;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class StreamException extends BatchCriticalException {
|
||||
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.batch.item.reader;
|
||||
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.StreamException;
|
||||
|
||||
/**
|
||||
* Base class for {@link ItemReader} implementations.
|
||||
@@ -29,7 +30,7 @@ public abstract class AbstractItemReader implements ItemReader {
|
||||
* Do nothing.
|
||||
* @see org.springframework.batch.item.ItemReader#close()
|
||||
*/
|
||||
public void close() throws Exception {
|
||||
public void close() throws StreamException {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.springframework.batch.io.Skippable;
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ItemStream;
|
||||
import org.springframework.batch.item.StreamContext;
|
||||
import org.springframework.batch.item.StreamException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -47,15 +48,14 @@ public class DelegatingItemReader extends AbstractItemReader implements Skippabl
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ItemStream#getRestartData()
|
||||
* @see ItemStream#getStreamContext()
|
||||
* @throws IllegalStateException if the parent template is not itself
|
||||
* {@link ItemStream}.
|
||||
*/
|
||||
public StreamContext getRestartData() {
|
||||
if (!(inputSource instanceof ItemStream)) {
|
||||
throw new IllegalStateException("Input Template is not Restartable");
|
||||
}
|
||||
return ((ItemStream) inputSource).getRestartData();
|
||||
public StreamContext getStreamContext() {
|
||||
// TODO: this is not necessary...
|
||||
Assert.state(inputSource instanceof ItemStream, "Input source is not ItemStream");
|
||||
return ((ItemStream) inputSource).getStreamContext();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,9 +64,7 @@ public class DelegatingItemReader extends AbstractItemReader implements Skippabl
|
||||
* {@link ItemStream}.
|
||||
*/
|
||||
public void restoreFrom(StreamContext data) {
|
||||
if (!(inputSource instanceof ItemStream)) {
|
||||
throw new IllegalStateException("Input Template is not Restartable");
|
||||
}
|
||||
Assert.state(inputSource instanceof ItemStream, "Input source is not ItemStream");
|
||||
((ItemStream) inputSource).restoreFrom(data);
|
||||
}
|
||||
|
||||
@@ -91,7 +89,7 @@ public class DelegatingItemReader extends AbstractItemReader implements Skippabl
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.item.ItemStream#open()
|
||||
*/
|
||||
public void open() throws Exception {
|
||||
public void open() throws StreamException {
|
||||
if (inputSource instanceof ItemStream) {
|
||||
((ItemStream) inputSource).open();
|
||||
}
|
||||
@@ -100,7 +98,7 @@ public class DelegatingItemReader extends AbstractItemReader implements Skippabl
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.item.ItemStream#open()
|
||||
*/
|
||||
public void close() throws Exception {
|
||||
public void close() throws StreamException {
|
||||
if (inputSource instanceof ItemStream) {
|
||||
((ItemStream) inputSource).close();
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.batch.item.reader;
|
||||
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.StreamException;
|
||||
import org.springframework.batch.support.AbstractMethodInvokingDelegator;
|
||||
|
||||
/**
|
||||
@@ -38,7 +39,7 @@ public class ItemReaderAdapter extends AbstractMethodInvokingDelegator implement
|
||||
*
|
||||
* @see org.springframework.batch.item.ItemReader#close()
|
||||
*/
|
||||
public void close() throws Exception {
|
||||
public void close() throws StreamException {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
/*
|
||||
* 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.item.stream;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ItemStream;
|
||||
import org.springframework.batch.item.StreamContext;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class CompositeItemStream implements ItemStream {
|
||||
|
||||
private static final String SEPARATOR = "#";
|
||||
|
||||
private List delegates;
|
||||
|
||||
public void setDelegates(List delegates) {
|
||||
this.delegates = delegates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compound restart data of all injected (Restartable) ItemProcessors,
|
||||
* property keys are prefixed with list index of the ItemProcessor.
|
||||
*/
|
||||
public StreamContext getRestartData() {
|
||||
Properties props = createCompoundProperties(new PropertiesExtractor() {
|
||||
public Properties extractProperties(Object o) {
|
||||
if (o instanceof ItemStream) {
|
||||
return ((ItemStream) o).getRestartData().getProperties();
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
});
|
||||
return new GenericStreamContext(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param data contains values of restart data, property keys are expected
|
||||
* to be prefixed with list index of the ItemProcessor.
|
||||
*/
|
||||
public void restoreFrom(StreamContext data) {
|
||||
if (data == null || data.getProperties() == null) {
|
||||
// do nothing
|
||||
return;
|
||||
}
|
||||
|
||||
List restartDataList = parseProperties(data.getProperties());
|
||||
|
||||
// iterators would make the loop below less readable
|
||||
for (int i = 0; i < delegates.size(); i++) {
|
||||
if (delegates.get(i) instanceof ItemStream) {
|
||||
((ItemStream) delegates.get(i)).restoreFrom((StreamContext) restartDataList.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses compound properties into a list of RestartData.
|
||||
*/
|
||||
private List parseProperties(Properties props) {
|
||||
List restartDataList = new ArrayList(delegates.size());
|
||||
for (int i = 0; i < delegates.size(); i++) {
|
||||
restartDataList.add(new GenericStreamContext(new Properties()));
|
||||
}
|
||||
|
||||
for (Iterator iterator = props.entrySet().iterator(); iterator.hasNext();) {
|
||||
Map.Entry entry = (Map.Entry) iterator.next();
|
||||
String key = (String) entry.getKey();
|
||||
String value = (String) entry.getValue();
|
||||
int separatorIndex = key.indexOf(SEPARATOR);
|
||||
int i = Integer.valueOf(key.substring(0, separatorIndex)).intValue();
|
||||
((StreamContext) restartDataList.get(i)).getProperties().setProperty(key.substring(separatorIndex + 1),
|
||||
value);
|
||||
}
|
||||
return restartDataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param extractor used to extract Properties from {@link ItemReader}s
|
||||
* @return compound Properties containing all the Properties from injected
|
||||
* delegates with property keys prefixed by list index.
|
||||
*/
|
||||
private Properties createCompoundProperties(PropertiesExtractor extractor) {
|
||||
Properties stats = new Properties();
|
||||
int index = 0;
|
||||
for (Iterator iterator = delegates.listIterator(); iterator.hasNext();) {
|
||||
Properties writerStats = extractor.extractProperties(iterator.next());
|
||||
if (writerStats != null) {
|
||||
for (Iterator iterator2 = writerStats.entrySet().iterator(); iterator2.hasNext();) {
|
||||
Map.Entry entry = (Map.Entry) iterator2.next();
|
||||
stats.setProperty("" + index + SEPARATOR + entry.getKey(), (String) entry.getValue());
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts information from given object in the form of {@link Properties}.
|
||||
* If the information is not available (e.g. unexpected object class) return
|
||||
* null.
|
||||
*/
|
||||
private interface PropertiesExtractor {
|
||||
Properties extractProperties(Object o);
|
||||
}
|
||||
|
||||
public void close() throws Exception {
|
||||
for (Iterator iterator = delegates.listIterator(); iterator.hasNext();) {
|
||||
Object delegate = iterator.next();
|
||||
if (delegate instanceof ItemStream) {
|
||||
((ItemStream) delegate).close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void open() throws Exception {
|
||||
for (Iterator iterator = delegates.listIterator(); iterator.hasNext();) {
|
||||
Object delegate = iterator.next();
|
||||
if (delegate instanceof ItemStream) {
|
||||
((ItemStream) delegate).open();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Public getter for the list of delegates.
|
||||
* @return the delegates
|
||||
*/
|
||||
public List getDelegates() {
|
||||
return delegates;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* 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.item.stream;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.batch.item.ItemStream;
|
||||
import org.springframework.batch.item.StreamContext;
|
||||
import org.springframework.batch.item.StreamException;
|
||||
import org.springframework.batch.statistics.StatisticsProvider;
|
||||
import org.springframework.batch.support.PropertiesConverter;
|
||||
|
||||
/**
|
||||
* Simple {@link StreamManager} that makes no attempt to aggregate or resolve
|
||||
* conflicts between key names. All the contributions registered are simply
|
||||
* polled and added "as is" to the aggregate properties.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class SimpleStreamManager implements StreamManager {
|
||||
|
||||
private Map registry = new HashMap();
|
||||
|
||||
/**
|
||||
* Simple aggregate statistics provider for the contributions registered
|
||||
* under the given key.
|
||||
*
|
||||
* @see org.springframework.batch.statistics.StatisticsService#getStatistics(java.lang.Object)
|
||||
*/
|
||||
public StreamContext getStreamContext(Object key) {
|
||||
Set set = new LinkedHashSet();
|
||||
synchronized (registry) {
|
||||
Collection collection = (Collection) registry.get(key);
|
||||
if (collection != null) {
|
||||
set = new LinkedHashSet(collection);
|
||||
}
|
||||
}
|
||||
return new GenericStreamContext(aggregate(set));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list a list of {@link StatisticsProvider}s
|
||||
* @return aggregated statistics
|
||||
*/
|
||||
private Properties aggregate(Collection list) {
|
||||
Properties result = new Properties();
|
||||
for (Iterator iterator = list.iterator(); iterator.hasNext();) {
|
||||
ItemStream provider = (ItemStream) iterator.next();
|
||||
Properties properties = provider.getStreamContext().getProperties();
|
||||
if (properties != null) {
|
||||
result.putAll(properties);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link ItemStream} as one of the interesting providers under
|
||||
* the provided key.
|
||||
*
|
||||
* @see org.springframework.batch.statistics.StreamManager#register(java.lang.Object,
|
||||
* org.springframework.batch.statistics.StatisticsProvider)
|
||||
*/
|
||||
public void register(Object key, ItemStream provider) {
|
||||
synchronized (registry) {
|
||||
Set set = (Set) registry.get(key);
|
||||
if (set == null) {
|
||||
set = new LinkedHashSet();
|
||||
registry.put(key, set);
|
||||
}
|
||||
set.add(provider);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast the call to close from this {@link StreamContext}.
|
||||
* @throws Exception
|
||||
*
|
||||
* @see StreamManager#restoreFrom(Object, StreamContext)
|
||||
*/
|
||||
public void close(Object key) throws StreamException {
|
||||
Set set = new LinkedHashSet();
|
||||
synchronized (registry) {
|
||||
Collection collection = (Collection) registry.get(key);
|
||||
if (collection != null) {
|
||||
set.addAll(collection);
|
||||
}
|
||||
}
|
||||
for (Iterator iterator = set.iterator(); iterator.hasNext();) {
|
||||
ItemStream stream = (ItemStream) iterator.next();
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: integrate this
|
||||
private class SimpleStreamManagerStreamContext implements StreamContext {
|
||||
|
||||
private static final String READER_KEY = "DATA_PROVIDER";
|
||||
|
||||
private static final String WRITER_KEY = "DATA_PROCESSOR";
|
||||
|
||||
private StreamContext readerData;
|
||||
|
||||
private StreamContext writerData;
|
||||
|
||||
public SimpleStreamManagerStreamContext(StreamContext providerData, StreamContext writerData) {
|
||||
this.readerData = providerData;
|
||||
this.writerData = writerData;
|
||||
}
|
||||
|
||||
public SimpleStreamManagerStreamContext(Properties data) {
|
||||
readerData = new GenericStreamContext(PropertiesConverter
|
||||
.stringToProperties(data.getProperty(READER_KEY)));
|
||||
writerData = new GenericStreamContext(PropertiesConverter.stringToProperties(data
|
||||
.getProperty(WRITER_KEY)));
|
||||
}
|
||||
|
||||
public Properties getProperties() {
|
||||
Properties props = new Properties();
|
||||
if (readerData != null) {
|
||||
props.setProperty(READER_KEY, PropertiesConverter.propertiesToString(readerData.getProperties()));
|
||||
}
|
||||
if (writerData != null) {
|
||||
props.setProperty(WRITER_KEY, PropertiesConverter.propertiesToString(writerData.getProperties()));
|
||||
}
|
||||
return props;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.item.stream;
|
||||
|
||||
import org.springframework.batch.item.ItemStream;
|
||||
import org.springframework.batch.item.StreamContext;
|
||||
import org.springframework.batch.item.StreamException;
|
||||
|
||||
/**
|
||||
* Generalised stream management broadcast strategy. Clients register
|
||||
* {@link ItemStream} instances under a well-known key, and then when they ask
|
||||
* for {@link ItemStream} operations by that key, we call each one in turn that
|
||||
* is registered under the key.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public interface StreamManager {
|
||||
|
||||
/**
|
||||
* Register the {@link ItemStream} instance as one of possibly several that
|
||||
* are associated with the given key.
|
||||
*
|
||||
* @param key the key under which to add the provider
|
||||
* @param stream an {@link ItemStream}
|
||||
*/
|
||||
void register(Object key, ItemStream stream);
|
||||
|
||||
/**
|
||||
* Extract and aggregate the {@link StreamContext} from all streams under
|
||||
* this key.
|
||||
*
|
||||
* @param key the key under which {@link ItemStream} instances might have
|
||||
* been registered.
|
||||
* @return {@link StreamContext} aggregating the contexts of all providers
|
||||
* registered under this key, or empty otherwise.
|
||||
*/
|
||||
StreamContext getStreamContext(Object key);
|
||||
|
||||
/**
|
||||
* If any resources are needed for the stream to operate they need to be
|
||||
* destroyed here.
|
||||
*
|
||||
* @param key the key under which {@link ItemStream} instances might have
|
||||
* been registered.
|
||||
*/
|
||||
void close(Object key) throws StreamException;
|
||||
|
||||
}
|
||||
@@ -1,22 +1,28 @@
|
||||
package org.springframework.batch.item.writer;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.batch.item.stream.CompositeItemStream;
|
||||
|
||||
/**
|
||||
* Runs a collection of ItemProcessors in fixed-order sequence.
|
||||
*
|
||||
* @author Robert Kasanicky
|
||||
*/
|
||||
public class CompositeItemWriter extends CompositeItemStream implements ItemWriter {
|
||||
public class CompositeItemWriter extends AbstractItemWriter implements ItemWriter {
|
||||
|
||||
private List delegates;
|
||||
|
||||
public void setDelegates(List delegates) {
|
||||
this.delegates = delegates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls injected ItemProcessors in order.
|
||||
*/
|
||||
public void write(Object data) throws Exception {
|
||||
for (Iterator iterator = getDelegates().listIterator(); iterator.hasNext();) {
|
||||
for (Iterator iterator = delegates.listIterator(); iterator.hasNext();) {
|
||||
((ItemWriter) iterator.next()).write(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,14 +50,14 @@ public class DelegatingItemWriter implements ItemWriter, Skippable, Initializing
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ItemStream#getRestartData()
|
||||
* @see ItemStream#getStreamContext()
|
||||
*/
|
||||
public StreamContext getRestartData() {
|
||||
public StreamContext getStreamContext() {
|
||||
|
||||
Assert.state(writer != null, "Source must not be null.");
|
||||
|
||||
if (writer instanceof ItemStream) {
|
||||
return ((ItemStream) writer).getRestartData();
|
||||
return ((ItemStream) writer).getStreamContext();
|
||||
}
|
||||
else {
|
||||
return new GenericStreamContext(new Properties());
|
||||
|
||||
@@ -76,7 +76,7 @@ public class DrivingQueryItemReaderTests extends TestCase {
|
||||
Foo foo2 = (Foo) source.read();
|
||||
assertEquals(2, foo2.getValue());
|
||||
|
||||
StreamContext streamContext = getAsRestartable(source).getRestartData();
|
||||
StreamContext streamContext = getAsRestartable(source).getStreamContext();
|
||||
|
||||
// create new input source
|
||||
source = createItemReader();
|
||||
@@ -98,7 +98,7 @@ public class DrivingQueryItemReaderTests extends TestCase {
|
||||
Foo foo2 = (Foo) source.read();
|
||||
assertEquals(2, foo2.getValue());
|
||||
|
||||
StreamContext streamContext = getAsRestartable(source).getRestartData();
|
||||
StreamContext streamContext = getAsRestartable(source).getStreamContext();
|
||||
|
||||
// create new input source
|
||||
source = createItemReader();
|
||||
@@ -198,7 +198,7 @@ public class DrivingQueryItemReaderTests extends TestCase {
|
||||
restartKeys.add(new Foo(5, "5", 5));
|
||||
}
|
||||
|
||||
public StreamContext getKeyAsRestartData(Object key) {
|
||||
public StreamContext getKeyAsStreamContext(Object key) {
|
||||
return streamContext;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ class FooItemReader extends AbstractItemReader implements ItemStream, ItemReader
|
||||
}
|
||||
}
|
||||
|
||||
public StreamContext getRestartData() {
|
||||
return inputSource.getRestartData();
|
||||
public StreamContext getStreamContext() {
|
||||
return inputSource.getStreamContext();
|
||||
}
|
||||
|
||||
public void restoreFrom(StreamContext data) {
|
||||
@@ -46,6 +46,9 @@ class FooItemReader extends AbstractItemReader implements ItemStream, ItemReader
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
}
|
||||
|
||||
public void open() throws Exception {
|
||||
public void open() {
|
||||
};
|
||||
|
||||
public void close() {
|
||||
};
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@ import org.springframework.util.ClassUtils;
|
||||
*/
|
||||
public class ColumnMapRestartDataRowMapperTests extends TestCase {
|
||||
|
||||
private static final String KEY = ClassUtils.getQualifiedName(ColumnMapRestartDataRowMapper.class) + ".KEY.";
|
||||
private static final String KEY = ClassUtils.getQualifiedName(ColumnMapStreamContextRowMapper.class) + ".KEY.";
|
||||
|
||||
ColumnMapRestartDataRowMapper mapper;
|
||||
ColumnMapStreamContextRowMapper mapper;
|
||||
|
||||
Map key;
|
||||
|
||||
@@ -34,7 +34,7 @@ public class ColumnMapRestartDataRowMapperTests extends TestCase {
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
mapper = new ColumnMapRestartDataRowMapper();
|
||||
mapper = new ColumnMapStreamContextRowMapper();
|
||||
|
||||
key = CollectionFactory.createLinkedCaseInsensitiveMapIfPossible(2);
|
||||
key.put("1", new Integer(1));
|
||||
|
||||
@@ -47,8 +47,8 @@ public class MultipleColumnJdbcKeyGeneratorIntegrationTests extends AbstractTran
|
||||
public void testRestoreKeys(){
|
||||
|
||||
Properties props = new Properties();
|
||||
props.setProperty(ColumnMapRestartDataRowMapper.KEY + "0", "3");
|
||||
props.setProperty(ColumnMapRestartDataRowMapper.KEY + "1", "3");
|
||||
props.setProperty(ColumnMapStreamContextRowMapper.KEY + "0", "3");
|
||||
props.setProperty(ColumnMapStreamContextRowMapper.KEY + "1", "3");
|
||||
StreamContext streamContext = new GenericStreamContext(props);
|
||||
|
||||
List keys = keyStrategy.restoreKeys(streamContext);
|
||||
@@ -68,18 +68,18 @@ public class MultipleColumnJdbcKeyGeneratorIntegrationTests extends AbstractTran
|
||||
key.put("ID", new Long(3));
|
||||
key.put("VALUE", new Integer(3));
|
||||
|
||||
StreamContext streamContext = keyStrategy.getKeyAsRestartData(key);
|
||||
StreamContext streamContext = keyStrategy.getKeyAsStreamContext(key);
|
||||
Properties props = streamContext.getProperties();
|
||||
|
||||
assertEquals(2, props.size());
|
||||
assertEquals("3", props.get(ColumnMapRestartDataRowMapper.KEY + "0"));
|
||||
assertEquals("3", props.get(ColumnMapRestartDataRowMapper.KEY + "1"));
|
||||
assertEquals("3", props.get(ColumnMapStreamContextRowMapper.KEY + "0"));
|
||||
assertEquals("3", props.get(ColumnMapStreamContextRowMapper.KEY + "1"));
|
||||
}
|
||||
|
||||
public void testGetNullKeyAsRestartData(){
|
||||
public void testGetNullKeyAsStreamContext(){
|
||||
|
||||
try{
|
||||
keyStrategy.getKeyAsRestartData(null);
|
||||
keyStrategy.getKeyAsStreamContext(null);
|
||||
fail();
|
||||
}catch(IllegalArgumentException ex){
|
||||
//expected
|
||||
@@ -89,7 +89,7 @@ public class MultipleColumnJdbcKeyGeneratorIntegrationTests extends AbstractTran
|
||||
public void testRestoreKeysFromNull(){
|
||||
|
||||
try{
|
||||
keyStrategy.getKeyAsRestartData(null);
|
||||
keyStrategy.getKeyAsStreamContext(null);
|
||||
}catch(IllegalArgumentException ex){
|
||||
//expected
|
||||
}
|
||||
|
||||
@@ -53,19 +53,19 @@ public class SingleColumnJdbcKeyGeneratorIntegrationTests extends AbstractTransa
|
||||
assertEquals(new Long(5), keys.get(1));
|
||||
}
|
||||
|
||||
public void testGetKeyAsRestartData(){
|
||||
public void testGetKeyAsStreamContext(){
|
||||
|
||||
StreamContext streamContext = keyStrategy.getKeyAsRestartData(new Long(3));
|
||||
StreamContext streamContext = keyStrategy.getKeyAsStreamContext(new Long(3));
|
||||
Properties props = streamContext.getProperties();
|
||||
|
||||
assertEquals(1, props.size());
|
||||
assertEquals("3", props.get(SingleColumnJdbcKeyGenerator.RESTART_KEY));
|
||||
}
|
||||
|
||||
public void testGetNullKeyAsRestartData(){
|
||||
public void testGetNullKeyAsStreamContext(){
|
||||
|
||||
try{
|
||||
keyStrategy.getKeyAsRestartData(null);
|
||||
keyStrategy.getKeyAsStreamContext(null);
|
||||
fail();
|
||||
}catch(IllegalArgumentException ex){
|
||||
//expected
|
||||
@@ -75,7 +75,7 @@ public class SingleColumnJdbcKeyGeneratorIntegrationTests extends AbstractTransa
|
||||
public void testRestoreKeysFromNull(){
|
||||
|
||||
try{
|
||||
keyStrategy.getKeyAsRestartData(null);
|
||||
keyStrategy.getKeyAsStreamContext(null);
|
||||
}catch(IllegalArgumentException ex){
|
||||
//expected
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ public class DefaultFlatFileItemReaderTests extends TestCase {
|
||||
inputSource.setResource(getInputResource(TEST_STRING));
|
||||
inputSource.setFieldSetMapper(fieldSetMapper);
|
||||
// do not open the template...
|
||||
inputSource.restoreFrom(inputSource.getRestartData());
|
||||
inputSource.restoreFrom(inputSource.getStreamContext());
|
||||
assertEquals("[FlatFileInputTemplate-TestData]", inputSource.read().toString());
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ public class DefaultFlatFileItemReaderTests extends TestCase {
|
||||
inputSource.read();
|
||||
|
||||
// get restart data
|
||||
StreamContext streamContext = inputSource.getRestartData();
|
||||
StreamContext streamContext = inputSource.getStreamContext();
|
||||
assertEquals("4", (String) streamContext.getProperties().getProperty(
|
||||
DefaultFlatFileItemReader.READ_STATISTICS_NAME));
|
||||
// close input
|
||||
|
||||
@@ -320,7 +320,7 @@ public class FlatFileItemWriterTests extends TestCase {
|
||||
commit();
|
||||
|
||||
// get restart data
|
||||
StreamContext restartData = inputSource.getRestartData();
|
||||
StreamContext streamContext = inputSource.getStreamContext();
|
||||
// close template
|
||||
inputSource.close();
|
||||
|
||||
@@ -338,7 +338,7 @@ public class FlatFileItemWriterTests extends TestCase {
|
||||
}
|
||||
|
||||
// init with correct data
|
||||
inputSource.restoreFrom(restartData);
|
||||
inputSource.restoreFrom(streamContext);
|
||||
|
||||
// write more lines
|
||||
inputSource.write("testLine6");
|
||||
@@ -371,9 +371,9 @@ public class FlatFileItemWriterTests extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public void testDefaultRestartData() throws Exception {
|
||||
public void testDefaultStreamContext() throws Exception {
|
||||
inputSource = new FlatFileItemWriter();
|
||||
StreamContext streamContext = inputSource.getRestartData();
|
||||
StreamContext streamContext = inputSource.getStreamContext();
|
||||
assertNotNull(streamContext);
|
||||
assertEquals(1, streamContext.getProperties().size());
|
||||
assertEquals("0", streamContext.getProperties().getProperty(FlatFileItemWriter.RESTART_DATA_NAME));
|
||||
|
||||
@@ -85,7 +85,7 @@ public abstract class AbstractJdbcItemReaderIntegrationTests extends AbstractTra
|
||||
Foo foo2 = (Foo) source.read();
|
||||
assertEquals(2, foo2.getValue());
|
||||
|
||||
StreamContext streamContext = getAsRestartable(source).getRestartData();
|
||||
StreamContext streamContext = getAsRestartable(source).getStreamContext();
|
||||
|
||||
// create new input source
|
||||
source = createItemReader();
|
||||
@@ -107,7 +107,7 @@ public abstract class AbstractJdbcItemReaderIntegrationTests extends AbstractTra
|
||||
Foo foo2 = (Foo) source.read();
|
||||
assertEquals(2, foo2.getValue());
|
||||
|
||||
StreamContext streamContext = getAsRestartable(source).getRestartData();
|
||||
StreamContext streamContext = getAsRestartable(source).getStreamContext();
|
||||
|
||||
// create new input source
|
||||
source = createItemReader();
|
||||
|
||||
@@ -92,7 +92,7 @@ public abstract class AbstractDataSourceItemReaderIntegrationTests extends Abstr
|
||||
Foo foo2 = (Foo) source.read();
|
||||
assertEquals(2, foo2.getValue());
|
||||
|
||||
StreamContext streamContext = getAsRestartable(source).getRestartData();
|
||||
StreamContext streamContext = getAsRestartable(source).getStreamContext();
|
||||
|
||||
// create new input source
|
||||
source = createItemReader();
|
||||
@@ -114,7 +114,7 @@ public abstract class AbstractDataSourceItemReaderIntegrationTests extends Abstr
|
||||
Foo foo2 = (Foo) source.read();
|
||||
assertEquals(2, foo2.getValue());
|
||||
|
||||
StreamContext streamContext = getAsRestartable(source).getRestartData();
|
||||
StreamContext streamContext = getAsRestartable(source).getStreamContext();
|
||||
|
||||
// create new input source
|
||||
source = createItemReader();
|
||||
@@ -217,7 +217,7 @@ public abstract class AbstractDataSourceItemReaderIntegrationTests extends Abstr
|
||||
|
||||
rollback();
|
||||
|
||||
StreamContext streamContext = getAsRestartable(source).getRestartData();
|
||||
StreamContext streamContext = getAsRestartable(source).getStreamContext();
|
||||
|
||||
// create new input source
|
||||
source = createItemReader();
|
||||
|
||||
@@ -117,7 +117,7 @@ public class StaxEventReaderItemReaderTests extends TestCase {
|
||||
*/
|
||||
public void testRestart() {
|
||||
source.read();
|
||||
StreamContext streamContext = source.getRestartData();
|
||||
StreamContext streamContext = source.getStreamContext();
|
||||
assertEquals("1", streamContext.getProperties().
|
||||
getProperty("StaxEventReaderItemReader.recordcount"));
|
||||
List expectedAfterRestart = (List) source.read();
|
||||
|
||||
@@ -92,7 +92,7 @@ public class StaxEventWriterItemWriterTests extends TestCase {
|
||||
// write records
|
||||
writer.write(record);
|
||||
writer.getSynchronization().afterCompletion(TransactionSynchronization.STATUS_COMMITTED);
|
||||
StreamContext streamContext = writer.getRestartData();
|
||||
StreamContext streamContext = writer.getStreamContext();
|
||||
|
||||
// create new writer from saved restart data and continue writing
|
||||
writer = createItemWriter();
|
||||
|
||||
@@ -75,8 +75,8 @@ public class DelegatingItemReaderTests extends TestCase {
|
||||
/**
|
||||
* Gets restart data from the input template
|
||||
*/
|
||||
public void testGetRestartData() {
|
||||
Properties props = itemProvider.getRestartData().getProperties();
|
||||
public void testGetStreamContext() {
|
||||
Properties props = itemProvider.getStreamContext().getProperties();
|
||||
assertEquals("foo", props.getProperty("value"));
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ public class DelegatingItemReaderTests extends TestCase {
|
||||
return PropertiesConverter.stringToProperties("a=b");
|
||||
}
|
||||
|
||||
public StreamContext getRestartData() {
|
||||
public StreamContext getStreamContext() {
|
||||
return new GenericStreamContext(PropertiesConverter.stringToProperties("value=foo"));
|
||||
}
|
||||
|
||||
|
||||
@@ -3,17 +3,11 @@ package org.springframework.batch.item.writer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.easymock.MockControl;
|
||||
import org.springframework.batch.item.ItemStream;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.batch.item.StreamContext;
|
||||
import org.springframework.batch.item.stream.GenericStreamContext;
|
||||
import org.springframework.batch.item.writer.CompositeItemWriter;
|
||||
import org.springframework.batch.statistics.StatisticsProvider;
|
||||
|
||||
/**
|
||||
* Tests for {@link CompositeItemWriter}
|
||||
@@ -57,37 +51,7 @@ public class CompositeItemWriterTests extends TestCase {
|
||||
control.verify();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* All Restartable processors should be restarted, not-Restartable processors should be ignored.
|
||||
*/
|
||||
public void testRestart() {
|
||||
//this mock with undefined behaviour makes sure not-Restartable processor is ignored
|
||||
MockControl p1c = MockControl.createStrictControl(ItemWriter.class);
|
||||
final ItemWriter p1 = (ItemWriter) p1c.getMock();
|
||||
|
||||
final ItemWriter p2 = new StubItemWriter();
|
||||
final ItemWriter p3 = new StubItemWriter();
|
||||
List itemProcessors = new ArrayList(){{
|
||||
add(p1);
|
||||
add(p2);
|
||||
add(p3);
|
||||
}};
|
||||
itemProcessor.setDelegates(itemProcessors);
|
||||
|
||||
StreamContext rd = itemProcessor.getRestartData();
|
||||
itemProcessor.restoreFrom(rd);
|
||||
|
||||
for (Iterator iterator = itemProcessors.iterator(); iterator.hasNext();) {
|
||||
ItemWriter processor = (ItemWriter) iterator.next();
|
||||
if (processor instanceof StubItemWriter) {
|
||||
assertTrue("Injected processors are restarted",
|
||||
((StubItemWriter)processor).restarted);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void testClose() throws Exception {
|
||||
|
||||
final int NUMBER_OF_PROCESSORS = 10;
|
||||
@@ -115,49 +79,4 @@ public class CompositeItemWriterTests extends TestCase {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Stub for testing restart. Checks the restart data received is the same that was returned by
|
||||
* <code>getRestartData()</code>
|
||||
*/
|
||||
private static class StubItemWriter implements ItemWriter, ItemStream, StatisticsProvider {
|
||||
|
||||
private static final String RESTART_KEY = "restartData";
|
||||
private static final String STATS_KEY = "stats";
|
||||
|
||||
private boolean restarted = false;
|
||||
|
||||
private final int hashCode = this.hashCode();
|
||||
|
||||
|
||||
public StreamContext getRestartData() {
|
||||
Properties props = new Properties(){{
|
||||
setProperty(RESTART_KEY, String.valueOf(hashCode));
|
||||
}};
|
||||
return new GenericStreamContext(props);
|
||||
}
|
||||
|
||||
public void restoreFrom(StreamContext data) {
|
||||
if (Integer.valueOf(data.getProperties().getProperty(RESTART_KEY)).intValue() != hashCode()) {
|
||||
fail("received restart data is not the same which was saved");
|
||||
}
|
||||
restarted = true;
|
||||
}
|
||||
|
||||
public void write(Object data) throws Exception {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public Properties getStatistics() {
|
||||
return new Properties() {{
|
||||
setProperty(STATS_KEY, String.valueOf(hashCode));
|
||||
}};
|
||||
}
|
||||
|
||||
public void close() throws Exception {
|
||||
}
|
||||
|
||||
public void open() throws Exception {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -59,8 +59,8 @@ public class ItemWriterItemProcessorTests extends TestCase {
|
||||
/**
|
||||
* Gets restart data from the input template
|
||||
*/
|
||||
public void testGetRestartData() {
|
||||
Properties props = processor.getRestartData().getProperties();
|
||||
public void testGetStreamContext() {
|
||||
Properties props = processor.getStreamContext().getProperties();
|
||||
assertEquals("foo", props.getProperty("value"));
|
||||
}
|
||||
|
||||
@@ -78,10 +78,10 @@ public class ItemWriterItemProcessorTests extends TestCase {
|
||||
* Forward restart data to input template
|
||||
* @throws Exception
|
||||
*/
|
||||
public void testGetRestartDataWithoutRestartable() throws Exception {
|
||||
public void testGetStreamContextWithoutItemStream() throws Exception {
|
||||
processor.setDelegate(null);
|
||||
try {
|
||||
processor.getRestartData();
|
||||
processor.getStreamContext();
|
||||
fail("Expected IllegalStateException");
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
@@ -152,7 +152,7 @@ public class ItemWriterItemProcessorTests extends TestCase {
|
||||
return PropertiesConverter.stringToProperties("a=b");
|
||||
}
|
||||
|
||||
public StreamContext getRestartData() {
|
||||
public StreamContext getStreamContext() {
|
||||
return new GenericStreamContext(PropertiesConverter.stringToProperties("value=foo"));
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
<bean id="playerload" parent="simpleStep">
|
||||
<property name="commitInterval" value="1"></property>
|
||||
<property name="startLimit" value="100" />
|
||||
<property name="saveRestartData" value="true" />
|
||||
<property name="allowStartIfComplete" value="false" />
|
||||
<property name="tasklet">
|
||||
<bean
|
||||
@@ -44,7 +43,6 @@
|
||||
<bean id="gameLoad" parent="simpleStep">
|
||||
<property name="commitInterval" value="1" />
|
||||
<property name="startLimit" value="100" />
|
||||
<property name="saveRestartData" value="true" />
|
||||
<property name="tasklet">
|
||||
<bean
|
||||
class="org.springframework.batch.execution.tasklet.RestartableItemOrientedTasklet">
|
||||
@@ -63,7 +61,6 @@
|
||||
<bean id="playerSummarization" parent="simpleStep">
|
||||
<property name="commitInterval" value="1" />
|
||||
<property name="startLimit" value="100" />
|
||||
<property name="saveRestartData" value="true" />
|
||||
<property name="tasklet">
|
||||
<bean
|
||||
class="org.springframework.batch.execution.tasklet.RestartableItemOrientedTasklet">
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
</property>
|
||||
<property name="commitInterval" value="2" />
|
||||
<property name="startLimit" value="100" />
|
||||
<property name="saveRestartData" value="true" />
|
||||
<property name="saveStreamContext" value="true" />
|
||||
<property name="allowStartIfComplete" value="false" />
|
||||
</bean>
|
||||
<bean id="loading" parent="repeatOperationsStep">
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<bean class="org.springframework.batch.sample.tasklet.ExceptionRestartableTasklet">
|
||||
<property name="itemReader">
|
||||
<bean
|
||||
class="org.springframework.batch.item.reader.ValidatingItemReader">
|
||||
class="org.springframework.batch.item.reader.ValidatingItemReader" scope="step">
|
||||
<property name="itemReader" ref="fileItemReader" />
|
||||
<property name="validator" ref="fixedValidator" />
|
||||
</bean>
|
||||
@@ -31,7 +31,7 @@
|
||||
</bean>
|
||||
</property>
|
||||
<property name="commitInterval" value="2" />
|
||||
<property name="saveRestartData" value="true"/>
|
||||
<property name="saveStreamContext" value="true"/>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
<property name="jobRepository" ref="jobRepository" />
|
||||
<property name="transactionManager" ref="transactionManager" />
|
||||
<property name="allowStartIfComplete" value="true" />
|
||||
<property name="saveRestartData" value="false" />
|
||||
<property name="saveStreamContext" value="false" />
|
||||
</bean>
|
||||
|
||||
<bean id="simpleStep" parent="abstractStep"
|
||||
@@ -101,13 +101,14 @@
|
||||
</bean>
|
||||
</property>
|
||||
<property name="commitInterval" value="1" />
|
||||
<property name="saveStreamContext" value="true" />
|
||||
</bean>
|
||||
|
||||
<bean id="repeatOperationsStep" parent="abstractStep"
|
||||
class="org.springframework.batch.execution.step.simple.RepeatOperationsStep"
|
||||
abstract="true">
|
||||
<property name="allowStartIfComplete" value="true" />
|
||||
<property name="saveRestartData" value="false" />
|
||||
<property name="saveStreamContext" value="false" />
|
||||
<property name="jobRepository" ref="jobRepository" />
|
||||
<property name="transactionManager" ref="transactionManager" />
|
||||
</bean>
|
||||
|
||||
@@ -75,7 +75,7 @@ public class RestartFunctionalTests extends AbstractBatchLauncherTests {
|
||||
}
|
||||
|
||||
int medium = jdbcTemplate.queryForInt("SELECT COUNT(*) FROM TRADE");
|
||||
// assert based on commit inyerval = 2
|
||||
// assert based on commit interval = 2
|
||||
assertEquals(before+2, medium);
|
||||
|
||||
runJob();
|
||||
|
||||
Reference in New Issue
Block a user