diff --git a/spring-batch-execution/src/main/java/org/springframework/batch/execution/bootstrap/support/CommandLineJobRunner.java b/spring-batch-execution/src/main/java/org/springframework/batch/execution/bootstrap/support/CommandLineJobRunner.java
deleted file mode 100644
index a0cdde6ea..000000000
--- a/spring-batch-execution/src/main/java/org/springframework/batch/execution/bootstrap/support/CommandLineJobRunner.java
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * Copyright 2006-2008 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.bootstrap.support;
-
-import java.util.Properties;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.springframework.batch.core.domain.Job;
-import org.springframework.batch.core.domain.JobExecution;
-import org.springframework.batch.core.domain.JobLocator;
-import org.springframework.batch.core.domain.JobParameters;
-import org.springframework.batch.core.executor.ExitCodeExceptionClassifier;
-import org.springframework.batch.core.runtime.JobParametersFactory;
-import org.springframework.batch.execution.launch.JobLauncher;
-import org.springframework.batch.execution.step.simple.SimpleExitCodeExceptionClassifier;
-import org.springframework.beans.factory.BeanDefinitionStoreException;
-import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.support.ClassPathXmlApplicationContext;
-import org.springframework.util.StringUtils;
-
-/**
- *
- * Basic launcher for starting jobs from the command line. In general, it is
- * assumed that this launcher will primarily be used to start a job via a script
- * from an Enterprise Scheduler. Therefore, exit codes are mapped to integers so
- * that schedulers can use the returned values to determine the next course of
- * action. The returned values can also be useful to operations teams in
- * determining what should happen upon failure. For example, a returned code of
- * 5 might mean that some resource wasn't available and the job should be
- * restarted. However, a code of 10 might mean that something critical has
- * happened and the issue should be escalated.
- *
- *
- *
- * With any launch of a batch job within Spring Batch, a Spring context
- * containing the Job and the 'Execution Environment' has to be created. This
- * command line launcher can be used to load that context from a single
- * location. It can also load the job as well All dependencies of the launcher
- * will then be satisfied by autowiring by type from the combined application
- * context. Default values are provided for all fields except the
- * {@link JobLauncher} and {@link JobLocator}. Therefore, if autowiring fails
- * to set it (it should be noted that dependency checking is disabled because
- * most of the fields have default values and thus don't require dependencies to
- * be fulfilled via autowiring) then an exception will be thrown. It should also
- * be noted that even if an exception is thrown by this class, it will be mapped
- * to an integer and returned.
- *
- *
- *
- * Notice a property is available to set the {@link SystemExiter}. This class
- * is used to exit from the main method, rather than calling System.exit()
- * directly. This is because unit testing a class the calls System.exit() is
- * impossible without kicking off the test within a new Jvm, which it is
- * possible to do, however it is a complex solution, much more so than
- * strategizing the exiter.
- *
- *
- *
- * The arguments to this class are roughly as follows:
- *
- *
- *
- * java jobPath jobName jobLauncherPath jobParameters...
- *
- *
- *
- *
- * - jobPath: the xml application context containing a {@link Job}
- *
- jobName: the bean id of the job.
- *
- jobLauncherPath: the xml application context containing a
- * {@link JobLauncher}
- *
- jobParameters: 0 to many parameters that will be used to launch a job.
- *
- *
- *
- *
- * The combined application context must only contain one instance of a
- * {@link JobLauncher}. The job parameters passed in to the command line will
- * be converted to {@link Properties} by assuming that each individual element
- * is one parameter that is separated by an equals sign. For example,
- * "vendor.id=290232". Below is an example arguments list: "
- *
- *
- *
- * java org.springframework.batch.execution.bootstrap.support.CommandLineJobRunner testJob.xml
- * testJob standard-job-launcher.xml schedule.date=2008/01/24 vendor.id=3902483920
- *
- *
- * Once arguments have been successfully parsed, autowiring will be used to set
- * various dependencies. The {@JobLauncher} for example, will be loaded this way. If
- * none is contained in the bean factory (it searches by type) then a
- * {@link BeanDefinitionStoreException} will be thrown. The same exception will also
- * be thrown if there is more than one present. Assuming the JobLauncher has been
- * set correctly, the jobName argument will be used to obtain an actual {@link Job}.
- * If a {@link JobLocator} has been set, then it will be used, if not the beanFactory
- * will be asked, using the jobName as the bean id.
- *
- * @author Dave Syer
- * @author Lucas Ward
- * @since 1.0
- */
-public class CommandLineJobRunner {
-
- protected static final Log logger = LogFactory
- .getLog(CommandLineJobRunner.class);
-
- private ExitCodeMapper exitCodeMapper = new SimpleJvmExitCodeMapper();
-
- private ExitCodeExceptionClassifier exceptionClassifier = new SimpleExitCodeExceptionClassifier();
-
- private JobLauncher launcher;
-
- private JobLocator jobLocator;
-
- private SystemExiter systemExiter = new JvmSystemExiter();
-
- private JobParametersFactory jobParametersFactory = new DefaultJobParametersFactory();
-
- /**
- * Injection setter for the {@link JobLauncher}.
- *
- * @param launcher
- * the launcher to set
- */
- public void setLauncher(JobLauncher launcher) {
- this.launcher = launcher;
- }
-
- /**
- * Injection setter for the {@link ExitCodeExceptionClassifier}
- *
- * @param exceptionClassifier
- */
- public void setExceptionClassifier(
- ExitCodeExceptionClassifier exceptionClassifier) {
- this.exceptionClassifier = exceptionClassifier;
- }
-
- /**
- * Injection setter for the {@link JvmExitCodeMapper}.
- *
- * @param exitCodeMapper
- * the exitCodeMapper to set
- */
- public void setExitCodeMapper(ExitCodeMapper exitCodeMapper) {
- this.exitCodeMapper = exitCodeMapper;
- }
-
- /**
- * Injection setter for the {@link SystemExiter}.
- *
- * @param systemExitor
- */
- public void setSystemExiter(SystemExiter systemExitor) {
- this.systemExiter = systemExitor;
- }
-
- /**
- * Delegate to the exiter to (possibly) exit the VM gracefully.
- *
- * @param status
- */
- public void exit(int status) {
- systemExiter.exit(status);
- }
-
- public void setJobLocator(JobLocator jobLocator) {
- this.jobLocator = jobLocator;
- }
-
- /*
- * Start a job by obtaining a combined classpath using the job launcher and
- * job paths. If a JobLocator has been set, then use it to obtain an actual
- * job, if not ask the context for it.
- */
- int start(String jobPath, String jobLauncherPath, String jobName,
- String[] parameters) {
-
- try {
- ApplicationContext context = new ClassPathXmlApplicationContext(
- new String[] { jobPath, jobLauncherPath });
- context.getAutowireCapableBeanFactory().autowireBeanProperties(
- this, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false);
-
- Job job;
- if (jobLocator != null) {
- job = jobLocator.getJob(jobName);
- } else {
- job = (Job) context.getBean(jobName);
- }
-
- JobParameters jobParameters = jobParametersFactory
- .getJobParameters(StringUtils
- .splitArrayElementsIntoProperties(parameters, "="));
-
- JobExecution jobExecution = launcher.run(job, jobParameters);
- return exitCodeMapper.getExitCode(jobExecution.getExitStatus()
- .getExitCode());
- } catch (Throwable e) {
- logger.error("Job Terminated in error:", e);
- return exitCodeMapper.getExitCode(exceptionClassifier
- .classifyForExitCode(e).getExitCode());
- }
- }
-
- /**
- * Launch a batch job using a {@link CommandLineJobRunner}. Creates a
- * new Spring context for the job execution, and uses a common parent for
- * all such contexts. No exception are thrown from this method, rather
- * exceptions are logged and an integer returned through the exit status in
- * a {@link JvmSystemExiter} (which can be overridden by defining one in the
- * Spring context).
- *
- * @param args
- *
- *
- * - jobPath: the xml application context containing a
- * {@link Job}
- *
- jobName: the bean id of the job.
- *
- jobLauncherPath: the xml application context containing a
- * {@link JobLauncher}
- *
- jobParameters: 0 to many parameters that will be used to
- * launch a job.
- *
- *
- */
- public static void main(String[] args) {
-
- CommandLineJobRunner command = new CommandLineJobRunner();
-
- if (args.length < 3) {
- logger
- .error("At least 3 arguments are required: JobPath, JobName, and ExecutionPath.");
- command.exit(1);
- }
-
- String jobPath = args[0];
- String jobName = args[1];
- String jobLauncherPath = args[2];
- String[] parameters = new String[args.length - 3];
- System.arraycopy(args, 2, parameters, 0, args.length - 3);
-
- int result = command.start(jobPath, jobLauncherPath, jobName,
- parameters);
- command.exit(result);
- }
-
-}
diff --git a/spring-batch-execution/src/main/java/org/springframework/batch/execution/bootstrap/support/DefaultJobParametersFactory.java b/spring-batch-execution/src/main/java/org/springframework/batch/execution/bootstrap/support/DefaultJobParametersFactory.java
index 6b76919cd..c866803ef 100644
--- a/spring-batch-execution/src/main/java/org/springframework/batch/execution/bootstrap/support/DefaultJobParametersFactory.java
+++ b/spring-batch-execution/src/main/java/org/springframework/batch/execution/bootstrap/support/DefaultJobParametersFactory.java
@@ -29,7 +29,6 @@ import java.util.Map.Entry;
import org.springframework.batch.core.domain.JobParameters;
import org.springframework.batch.core.domain.JobParametersBuilder;
import org.springframework.batch.core.runtime.JobParametersFactory;
-import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
diff --git a/spring-batch-execution/src/main/java/org/springframework/batch/execution/bootstrap/support/ScheduledJobParametersFactory.java b/spring-batch-execution/src/main/java/org/springframework/batch/execution/bootstrap/support/ScheduledJobParametersFactory.java
index 04748ac17..4711ae0f5 100644
--- a/spring-batch-execution/src/main/java/org/springframework/batch/execution/bootstrap/support/ScheduledJobParametersFactory.java
+++ b/spring-batch-execution/src/main/java/org/springframework/batch/execution/bootstrap/support/ScheduledJobParametersFactory.java
@@ -27,7 +27,6 @@ import java.util.Map.Entry;
import org.springframework.batch.core.domain.JobParameters;
import org.springframework.batch.core.domain.JobParametersBuilder;
import org.springframework.batch.core.runtime.JobParametersFactory;
-import org.springframework.util.Assert;
/**
* @author Lucas Ward
diff --git a/spring-batch-execution/src/main/java/org/springframework/batch/execution/scope/SimpleStepContext.java b/spring-batch-execution/src/main/java/org/springframework/batch/execution/scope/SimpleStepContext.java
index b476f839d..ba8b4b16e 100644
--- a/spring-batch-execution/src/main/java/org/springframework/batch/execution/scope/SimpleStepContext.java
+++ b/spring-batch-execution/src/main/java/org/springframework/batch/execution/scope/SimpleStepContext.java
@@ -21,10 +21,13 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Properties;
import java.util.Set;
import org.springframework.batch.core.domain.StepExecution;
import org.springframework.batch.repeat.context.SynchronizedAttributeAccessor;
+import org.springframework.batch.statistics.StatisticsProvider;
+import org.springframework.batch.statistics.StatisticsService;
/**
* Simple implementation of {@link StepContext}.
@@ -33,25 +36,55 @@ import org.springframework.batch.repeat.context.SynchronizedAttributeAccessor;
*
*/
public class SimpleStepContext extends SynchronizedAttributeAccessor implements
- StepContext {
+ StepContext, StatisticsProvider {
private Map callbacks = new HashMap();
private StepContext parent;
private StepExecution stepExecution;
+ private StatisticsService statisticsService;
/**
* Default constructor.
*/
- public SimpleStepContext() {
- this(null);
+ public SimpleStepContext(StepExecution stepExecution) {
+ this(stepExecution, null, null);
+ }
+
+ /**
+ * Default constructor.
+ */
+ public SimpleStepContext(StepExecution stepExecution, StepContext parent) {
+ this(stepExecution, parent, null);
}
/**
* @param object
*/
- public SimpleStepContext(StepContext parent) {
+ public SimpleStepContext(StepExecution stepExecution, StepContext parent, StatisticsService statisticsService) {
super();
this.parent = parent;
+ this.statisticsService = statisticsService;
+ this.stepExecution = stepExecution;
+ }
+
+ /* (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)) {
+ statisticsService.register(this, (StatisticsProvider) value);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.batch.statistics.StatisticsProvider#getStatistics()
+ */
+ public Properties getStatistics() {
+ if (statisticsService==null) {
+ return new Properties();
+ }
+ return statisticsService.getStatistics(this);
}
/*
@@ -86,10 +119,10 @@ public class SimpleStepContext extends SynchronizedAttributeAccessor implements
}
}
- /*
- * Package access because only needed internally.
+ /* (non-Javadoc)
+ * @see org.springframework.batch.execution.scope.StepContext#close()
*/
- void close() {
+ public void close() {
List errors = new ArrayList();
@@ -133,13 +166,6 @@ public class SimpleStepContext extends SynchronizedAttributeAccessor implements
throw (RuntimeException) errors.get(0);
}
- /**
- * @param stepExecution
- */
- public void setStepExecution(StepExecution stepExecution) {
- this.stepExecution = stepExecution;
- }
-
/*
* (non-Javadoc)
*
diff --git a/spring-batch-execution/src/main/java/org/springframework/batch/execution/scope/StepContext.java b/spring-batch-execution/src/main/java/org/springframework/batch/execution/scope/StepContext.java
index 61cafb629..0df4f248f 100644
--- a/spring-batch-execution/src/main/java/org/springframework/batch/execution/scope/StepContext.java
+++ b/spring-batch-execution/src/main/java/org/springframework/batch/execution/scope/StepContext.java
@@ -16,6 +16,7 @@
package org.springframework.batch.execution.scope;
import org.springframework.batch.core.domain.StepExecution;
+import org.springframework.batch.statistics.StatisticsProvider;
import org.springframework.core.AttributeAccessor;
/**
@@ -24,7 +25,7 @@ import org.springframework.core.AttributeAccessor;
* @author Dave Syer
*
*/
-public interface StepContext extends AttributeAccessor {
+public interface StepContext extends AttributeAccessor, StatisticsProvider {
/**
* Accessor for the {@link StepExecution} associated with the currently
@@ -45,4 +46,9 @@ public interface StepContext extends AttributeAccessor {
* Register a destruction callback for the end of life of the scope.
*/
void registerDestructionCallback(String name, Runnable callback);
+
+ /**
+ * Clean up any resources held during the context of the step.
+ */
+ void close();
}
\ No newline at end of file
diff --git a/spring-batch-execution/src/main/java/org/springframework/batch/execution/scope/StepScope.java b/spring-batch-execution/src/main/java/org/springframework/batch/execution/scope/StepScope.java
index 75781f063..69cb6d976 100644
--- a/spring-batch-execution/src/main/java/org/springframework/batch/execution/scope/StepScope.java
+++ b/spring-batch-execution/src/main/java/org/springframework/batch/execution/scope/StepScope.java
@@ -36,10 +36,17 @@ public class StepScope implements Scope, BeanFactoryPostProcessor, Ordered {
private Object mutex = new Object();
+ /**
+ * @param order the order value to set priority of callback execution for
+ * the {@link BeanFactoryPostProcessor} part of this scope bean.
+ */
public void setOrder(int order) {
this.order = order;
}
+ /* (non-Javadoc)
+ * @see org.springframework.core.Ordered#getOrder()
+ */
public int getOrder() {
return order;
}
@@ -48,16 +55,17 @@ public class StepScope implements Scope, BeanFactoryPostProcessor, Ordered {
* Context key for clients to use for conversation identifier.
*/
public static final String ID_KEY = "JOB_IDENTIFIER";
+
private String name = "step";
/*
* (non-Javadoc)
*
* @see org.springframework.beans.factory.config.Scope#get(java.lang.String,
- * org.springframework.beans.factory.ObjectFactory)
+ * org.springframework.beans.factory.ObjectFactory)
*/
public Object get(String name, ObjectFactory objectFactory) {
- SimpleStepContext context = getContext();
+ StepContext context = getContext();
Object scopedObject = context.getAttribute(name);
if (scopedObject == null) {
synchronized (mutex) {
@@ -80,7 +88,7 @@ public class StepScope implements Scope, BeanFactoryPostProcessor, Ordered {
* @see org.springframework.beans.factory.config.Scope#getConversationId()
*/
public String getConversationId() {
- SimpleStepContext context = getContext();
+ StepContext context = getContext();
Object id = context.getAttribute(ID_KEY);
return "" + id;
}
@@ -89,7 +97,7 @@ public class StepScope implements Scope, BeanFactoryPostProcessor, Ordered {
* (non-Javadoc)
*
* @see org.springframework.beans.factory.config.Scope#registerDestructionCallback(java.lang.String,
- * java.lang.Runnable)
+ * java.lang.Runnable)
*/
public void registerDestructionCallback(String name, Runnable callback) {
StepContext context = getContext();
@@ -102,7 +110,7 @@ public class StepScope implements Scope, BeanFactoryPostProcessor, Ordered {
* @see org.springframework.beans.factory.config.Scope#remove(java.lang.String)
*/
public Object remove(String name) {
- SimpleStepContext context = getContext();
+ StepContext context = getContext();
return context.removeAttribute(name);
}
@@ -111,13 +119,12 @@ public class StepScope implements Scope, BeanFactoryPostProcessor, Ordered {
* can be used to store scoped bean instances.
*
* @return the current step context which we can use as a scope storage
- * medium
+ * medium
*/
- private SimpleStepContext getContext() {
- SimpleStepContext context = StepSynchronizationManager.getContext();
+ private StepContext getContext() {
+ StepContext context = StepSynchronizationManager.getContext();
if (context == null) {
- throw new IllegalStateException(
- "No context holder available for step scope");
+ throw new IllegalStateException("No context holder available for step scope");
}
return context;
}
@@ -125,13 +132,10 @@ public class StepScope implements Scope, BeanFactoryPostProcessor, Ordered {
/**
* Register this scope with the enclosing BeanFactory.
*
- * @param beanFactory
- * the BeanFactory to register with
- * @throws BeansException
- * if there is a problem.
+ * @param beanFactory the BeanFactory to register with
+ * @throws BeansException if there is a problem.
*/
- public void postProcessBeanFactory(
- ConfigurableListableBeanFactory beanFactory) throws BeansException {
+ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
beanFactory.registerScope(name, this);
}
@@ -139,8 +143,7 @@ public class StepScope implements Scope, BeanFactoryPostProcessor, Ordered {
* Public setter for the name property. This can then be used as a bean
* definition attribute, e.g. scope="step". Defaults to "step".
*
- * @param name
- * the name to set for this scope.
+ * @param name the name to set for this scope.
*/
public void setName(String name) {
this.name = name;
diff --git a/spring-batch-execution/src/main/java/org/springframework/batch/execution/scope/StepSynchronizationManager.java b/spring-batch-execution/src/main/java/org/springframework/batch/execution/scope/StepSynchronizationManager.java
deleted file mode 100644
index b7b53380f..000000000
--- a/spring-batch-execution/src/main/java/org/springframework/batch/execution/scope/StepSynchronizationManager.java
+++ /dev/null
@@ -1,80 +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.scope;
-
-/**
- * @author Dave Syer
- *
- */
-public class StepSynchronizationManager {
-
- private static final ThreadLocal contextHolder = new InheritableThreadLocal();
-
- /**
- * Getter for the current context..
- *
- * @return the current {@link SimpleStepContext} or null if there is none
- * (if we are not in a step).
- */
- public static SimpleStepContext getContext() {
- return (SimpleStepContext) contextHolder.get();
- }
-
- /**
- * Method for registering a context - should only be used by
- * {@link StepExecutor} implementations to ensure that {@link #getContext()}
- * always returns the correct value.
- *
- * @return a new context at the start of a batch.
- */
- public static SimpleStepContext open() {
- StepContext oldSession = getContext();
- SimpleStepContext context = new SimpleStepContext(oldSession);
- StepSynchronizationManager.contextHolder.set(context);
- return context;
- }
-
- /**
- * Method for de-registering the current context - should only be used by
- * {@link StepExecutor} implementations to ensure that {@link #getContext()}
- * always returns the correct value.
- *
- * @return the old value if there was one.
- */
- public static StepContext close() {
- SimpleStepContext oldSession = getContext();
- if (oldSession == null) {
- return null;
- }
- oldSession.close();
- StepContext context = oldSession.getParent();
- StepSynchronizationManager.contextHolder.set(context);
- return context;
- }
-
- /**
- * Used internally by {@link StepExecutor} implementations to clear the
- * current context at the end of a batch.
- *
- * @return the old value if there was one.
- */
- public static StepContext clear() {
- StepContext context = getContext();
- StepSynchronizationManager.contextHolder.set(null);
- return context;
- }
-
-}
diff --git a/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/simple/SimpleStepExecutor.java b/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/simple/SimpleStepExecutor.java
index 3c3252b0e..6b17c3751 100644
--- a/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/simple/SimpleStepExecutor.java
+++ b/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/simple/SimpleStepExecutor.java
@@ -30,6 +30,7 @@ import org.springframework.batch.core.executor.StepInterruptedException;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.tasklet.Tasklet;
import org.springframework.batch.execution.scope.SimpleStepContext;
+import org.springframework.batch.execution.scope.StepContext;
import org.springframework.batch.execution.scope.StepScope;
import org.springframework.batch.execution.scope.StepSynchronizationManager;
import org.springframework.batch.execution.step.RepeatOperationsHolder;
@@ -47,7 +48,9 @@ import org.springframework.batch.repeat.support.RepeatTemplate;
import org.springframework.batch.repeat.synch.BatchTransactionSynchronizationManager;
import org.springframework.batch.restart.RestartData;
import org.springframework.batch.restart.Restartable;
+import org.springframework.batch.statistics.SimpleStatisticsService;
import org.springframework.batch.statistics.StatisticsProvider;
+import org.springframework.batch.statistics.StatisticsService;
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
@@ -91,6 +94,26 @@ public class SimpleStepExecutor implements StepExecutor {
// Not for production use...
protected PlatformTransactionManager transactionManager = new ResourcelessTransactionManager();
+ private StatisticsService statisticsService = new SimpleStatisticsService();
+
+ /**
+ * Public setter for the {@link StatisticsService}. 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 statisticsService the {@link StatisticsService} to set. Default is
+ * a {@link SimpleStatisticsService}.
+ */
+ public void setStatisticsService(StatisticsService statisticsService) {
+ this.statisticsService = statisticsService;
+ }
+
+ /**
+ * Injected strategy for transaction management
+ * @param transactionManager
+ */
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
@@ -142,8 +165,8 @@ public class SimpleStepExecutor implements StepExecutor {
* execution
* @see StepExecutor#process(Step, StepExecution)
*/
- public ExitStatus process(final Step step, final StepExecution stepExecution)
- throws BatchCriticalException, StepInterruptedException {
+ public ExitStatus process(final Step step, final StepExecution stepExecution) throws BatchCriticalException,
+ StepInterruptedException {
final StepInstance stepInstance = stepExecution.getStep();
boolean isRestart = stepInstance.getStepExecutionCount() > 0 ? true : false;
@@ -153,8 +176,10 @@ public class SimpleStepExecutor implements StepExecutor {
ExitStatus status = ExitStatus.FAILED;
- final SimpleStepContext stepScopeContext = StepSynchronizationManager.open();
- stepScopeContext.setStepExecution(stepExecution);
+ StepContext parentStepScopeContext = StepSynchronizationManager.getContext();
+ final StepContext stepScopeContext = new SimpleStepContext(stepExecution, parentStepScopeContext,
+ statisticsService);
+ StepSynchronizationManager.register(stepScopeContext);
// Add the job identifier so that it can be used to identify
// the conversation in StepScope
stepScopeContext.setAttribute(StepScope.ID_KEY, stepExecution.getJobExecution().getId());
@@ -198,8 +223,8 @@ public class SimpleStepExecutor implements StepExecutor {
// TODO: Statistics are not thread safe
// - we cannot guarantee that they are
- // up to date. (Maybe we never can?)
- Properties statistics = getStatistics(module);
+ // up to date. (Maybe we never can?)
+ Properties statistics = stepScopeContext.getStatistics();
contribution.setStatistics(statistics);
contribution.incrementCommitCount();
// Apply the contribution to the step
@@ -301,8 +326,8 @@ public class SimpleStepExecutor implements StepExecutor {
* outside this method, so subclasses that override do not need to create a
* transaction.
*
- * @param step the current step containing the {@link Tasklet}
- * with the business logic.
+ * @param step the current step containing the {@link Tasklet} with the
+ * business logic.
* @return true if there is more data to process.
*/
protected final ExitStatus processChunk(final Step step, final StepContribution contribution) {
@@ -378,15 +403,6 @@ public class SimpleStepExecutor implements StepExecutor {
}
}
- private Properties getStatistics(Tasklet tasklet) {
- if (tasklet instanceof StatisticsProvider) {
- return ((StatisticsProvider) tasklet).getStatistics();
- }
- else {
- return null;
- }
- }
-
/**
* Setter for the {@link StepInterruptionPolicy}. The policy is used to
* check whether an external request has been made to interrupt the job
@@ -414,9 +430,9 @@ public class SimpleStepExecutor implements StepExecutor {
*
* - If the configuration is a {@link RepeatOperationsHolder} then we use
* the provided {@link RepeatOperations} instances for chunk and step.
- * - If the configuration is a {@link SimpleStep} then we
- * apply the commit interval at the chunk level and the exception handler at
- * the step level, provided the existing repeat operations are instances of
+ *
- If the configuration is a {@link SimpleStep} then we apply the
+ * commit interval at the chunk level and the exception handler at the step
+ * level, provided the existing repeat operations are instances of
* {@link RepeatTemplate}. In addition if there is a non-zero skip limit
* and no {@link ExceptionHandler} then we inject a
* {@link SimpleLimitExceptionHandler} with that limit.
@@ -431,9 +447,7 @@ public class SimpleStepExecutor implements StepExecutor {
RepeatOperationsHolder holder = (RepeatOperationsHolder) step;
RepeatOperations chunkOperations = holder.getChunkOperations();
RepeatOperations stepOperations = holder.getStepOperations();
- Assert
- .state(chunkOperations != null,
- "Chunk operations obtained from step must be non-null.");
+ Assert.state(chunkOperations != null, "Chunk operations obtained from step must be non-null.");
if (chunkOperations != null) {
setChunkOperations(chunkOperations);
diff --git a/spring-batch-execution/src/main/java/org/springframework/batch/execution/tasklet/ItemOrientedTasklet.java b/spring-batch-execution/src/main/java/org/springframework/batch/execution/tasklet/ItemOrientedTasklet.java
index f5e573258..e3f38f30a 100644
--- a/spring-batch-execution/src/main/java/org/springframework/batch/execution/tasklet/ItemOrientedTasklet.java
+++ b/spring-batch-execution/src/main/java/org/springframework/batch/execution/tasklet/ItemOrientedTasklet.java
@@ -16,12 +16,6 @@
package org.springframework.batch.execution.tasklet;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-
import org.springframework.batch.core.tasklet.Tasklet;
import org.springframework.batch.io.Skippable;
import org.springframework.batch.item.ItemProcessor;
@@ -32,7 +26,6 @@ import org.springframework.batch.retry.RetryPolicy;
import org.springframework.batch.retry.callback.ItemReaderRetryCallback;
import org.springframework.batch.retry.policy.ItemReaderRetryPolicy;
import org.springframework.batch.retry.support.RetryTemplate;
-import org.springframework.batch.statistics.StatisticsProvider;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
@@ -40,13 +33,13 @@ import org.springframework.util.Assert;
* A concrete implementation of the {@link Tasklet} interface that provides
* 'split processing'. This type of processing is characterized by separating
* the reading and processing of batch data into two separate classes:
- * {@link ItemReader} and {@link ItemProcessor}. The {@link ItemReader} class provides a solid means
- * for re-usability and enforces good architecture practices. Because an object
- * must be returned by the {@link ItemReader} to continue
- * processing, (returning null indicates processing should end) a developer is
- * forced to read in all relevant data, place it into a domain object, and
- * return that object. The {@link ItemProcessor} will then use this object for
- * calculations and output.
+ * {@link ItemReader} and {@link ItemProcessor}. The {@link ItemReader} class
+ * provides a solid means for re-usability and enforces good architecture
+ * practices. Because an object must be returned by the
+ * {@link ItemReader} to continue processing, (returning null indicates
+ * processing should end) a developer is forced to read in all relevant data,
+ * place it into a domain object, and return that object. The
+ * {@link ItemProcessor} will then use this object for calculations and output.
*
* If a {@link RetryPolicy} is provided it will be used to construct a stateful
* retry around the {@link ItemProcessor}, delegating identity concerns to the
@@ -75,8 +68,7 @@ import org.springframework.util.Assert;
* @author Robert Kasanicky
*
*/
-public class ItemOrientedTasklet implements Tasklet, Skippable,
- StatisticsProvider, InitializingBean {
+public class ItemOrientedTasklet implements Tasklet, Skippable, InitializingBean {
/**
* Prefix added to statistics keys from processor if needed to avoid
@@ -115,13 +107,11 @@ public class ItemOrientedTasklet implements Tasklet, Skippable,
itemRecoverer = (ItemRecoverer) itemProvider;
}
- ItemReaderRetryPolicy itemProviderRetryPolicy = new ItemReaderRetryPolicy(
- retryPolicy);
+ ItemReaderRetryPolicy itemProviderRetryPolicy = new ItemReaderRetryPolicy(retryPolicy);
template.setRetryPolicy(itemProviderRetryPolicy);
if (retryPolicy != null) {
- retryCallback = new ItemReaderRetryCallback(itemProvider,
- itemProcessor);
+ retryCallback = new ItemReaderRetryCallback(itemProvider, itemProcessor);
retryCallback.setRecoverer(itemRecoverer);
}
@@ -150,7 +140,8 @@ public class ItemOrientedTasklet implements Tasklet, Skippable,
}
try {
itemProcessor.process(item);
- } catch (Exception e) {
+ }
+ catch (Exception e) {
if (itemRecoverer != null) {
itemRecoverer.recover(item, e);
}
@@ -211,77 +202,10 @@ public class ItemOrientedTasklet implements Tasklet, Skippable,
}
}
- /**
- * If the provider and / or processor are {@link StatisticsProvider} then
- * delegate to them in that order. If they both implement
- * {@link StatisticsProvider} then the property keys are prepended with
- * special prefixes to avoid potential ambiguity. The prefixes are only
- * prepended in the case of a duplicate key shared between provider and
- * processor.
- *
- * @see org.springframework.batch.io.Skippable#skip()
- */
- public Properties getStatistics() {
- Properties stats = new Properties();
- if (this.itemProvider instanceof StatisticsProvider) {
- stats = ((StatisticsProvider) this.itemProvider).getStatistics();
- }
- if (this.itemProcessor instanceof StatisticsProvider) {
- Properties props = ((StatisticsProvider) this.itemProcessor)
- .getStatistics();
- if (!stats.isEmpty()) {
- stats = prependKeys(stats, props, PROVIDER_STATISTICS_PREFIX,
- PROCESSOR_STATISTICS_PREFIX);
- } else {
- stats.putAll(props);
- }
- }
- return stats;
- }
-
- /**
- * @param props1
- * @param string
- * @return
- */
- private Properties prependKeys(Properties props1, Properties props2,
- String prefix1, String prefix2) {
- Properties result = new Properties();
- Set duplicates = new HashSet();
- for (Iterator iterator = props1.entrySet().iterator(); iterator
- .hasNext();) {
- Map.Entry entry = (Map.Entry) iterator.next();
- String key = (String) entry.getKey();
- String value = (String) entry.getValue();
- if (props2.containsKey(key)) {
- duplicates.add(key);
- continue;
- }
- result.setProperty(key, value);
- }
- for (Iterator iterator = props2.entrySet().iterator(); iterator
- .hasNext();) {
- Map.Entry entry = (Map.Entry) iterator.next();
- String key = (String) entry.getKey();
- String value = (String) entry.getValue();
- if (duplicates.contains(key)) {
- continue;
- }
- result.setProperty(key, value);
- }
- for (Iterator iterator = duplicates.iterator(); iterator.hasNext();) {
- String key = (String) iterator.next();
- result.setProperty(prefix1 + key, props1.getProperty(key));
- result.setProperty(prefix2 + key, props2.getProperty(key));
- }
- return result;
- }
-
/**
* Public setter for the retryPolicy.
*
- * @param retyPolicy
- * the retryPolicy to set
+ * @param retyPolicy the retryPolicy to set
*/
public void setRetryPolicy(RetryPolicy retryPolicy) {
this.retryPolicy = retryPolicy;
diff --git a/spring-batch-execution/src/test/java/org/springframework/batch/execution/bootstrap/support/CommandLineJobRunnerTests.java b/spring-batch-execution/src/test/java/org/springframework/batch/execution/bootstrap/support/CommandLineJobRunnerTests.java
deleted file mode 100644
index 63df8eb3c..000000000
--- a/spring-batch-execution/src/test/java/org/springframework/batch/execution/bootstrap/support/CommandLineJobRunnerTests.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright 2006-2008 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.bootstrap.support;
-
-import junit.framework.TestCase;
-
-import org.springframework.batch.core.domain.Job;
-import org.springframework.batch.core.domain.JobExecution;
-import org.springframework.batch.core.domain.JobParameters;
-import org.springframework.batch.core.executor.ExitCodeExceptionClassifier;
-import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
-import org.springframework.batch.execution.launch.JobLauncher;
-import org.springframework.batch.repeat.ExitStatus;
-
-/**
- * @author Lucas Ward
- *
- */
-public class CommandLineJobRunnerTests extends TestCase {
-
- private static final String JOB = "org/springframework/batch/execution/bootstrap/support/job.xml";
- private static final String TEST_BATCH_ENVIRONMENT = "org/springframework/batch/execution/bootstrap/support/test-environment.xml";
- private static final String JOB_NAME = "test-job";
-
- private String jobPath = JOB;
- private String environmentPath = TEST_BATCH_ENVIRONMENT;
- private String jobName = JOB_NAME;
- private String jobKey = "job.Key=myKey";
- private String scheduleDate = "schedule.Date=01/23/2008";
- private String vendorId = "vendor.id=33243243";
-
- private String[] args = new String[]{jobPath, jobName, environmentPath, jobKey, scheduleDate, vendorId};
-
- private JobExecution jobExecution;
-
- /* (non-Javadoc)
- * @see junit.framework.TestCase#setUp()
- */
- protected void setUp() throws Exception {
- super.setUp();
-
- jobExecution = new JobExecution(null, new Long(1));
- ExitStatus exitStatus = ExitStatus.FINISHED;
- jobExecution.setExitStatus(exitStatus);
- }
-
- public void testMain(){
-
- StubJobLauncher.jobExecution = jobExecution;
-
- CommandLineJobRunner.main(args);
-
- assertEquals(0, StubSystemExiter.getStatus());
- }
-
- public void testJobAlreadyRunning(){
-
- StubJobLauncher.throwExecutionRunningException = true;
-
- CommandLineJobRunner.main(args);
-
- assertTrue(StubExceptionClassifier.exception instanceof JobExecutionAlreadyRunningException);
- }
-
- //can't test because it will cause the system to exit.
-// public void testInvalidArgs(){
-//
-// String[] args = new String[]{jobPath, jobName};
-// CommandLineJobRunner.main(args);
-// }
-
- public void testWithNoParameters(){
-
- String[] args = new String[]{jobPath, jobName, environmentPath};
- CommandLineJobRunner.main(args);
- assertEquals(new JobParameters(), StubJobLauncher.jobParameters);
- }
-
- protected void tearDown() throws Exception {
- super.tearDown();
-
- StubJobLauncher.tearDown();
- }
-
- public static class StubSystemExiter implements SystemExiter {
-
- public static int status;
-
- public void exit(int status) {
- StubSystemExiter.status = status;
- }
-
- public static int getStatus() {
- return status;
- }
- }
-
- public static class StubJobLauncher implements JobLauncher{
-
- public static JobExecution jobExecution;
- public static boolean throwExecutionRunningException = false;
- public static JobParameters jobParameters;
-
- public JobExecution run(Job job, JobParameters jobParameters)
- throws JobExecutionAlreadyRunningException {
-
- StubJobLauncher.jobParameters = jobParameters;
-
- if(throwExecutionRunningException){
- throw new JobExecutionAlreadyRunningException("");
- }
-
- return jobExecution;
- }
-
- public static void tearDown(){
- jobExecution = null;
- throwExecutionRunningException = false;
- jobParameters = null;
- }
- }
-
- public static class StubExceptionClassifier implements ExitCodeExceptionClassifier{
-
- public static Throwable exception;
-
- public Object classify(Throwable throwable) {
- return null;
- }
-
- public Object getDefault() {
- return null;
- }
-
- public ExitStatus classifyForExitCode(Throwable throwable) {
- exception = throwable;
- return ExitStatus.FAILED;
- }
-
- }
-}
diff --git a/spring-batch-execution/src/test/java/org/springframework/batch/execution/resource/BatchResourceFactoryBeanTests.java b/spring-batch-execution/src/test/java/org/springframework/batch/execution/resource/BatchResourceFactoryBeanTests.java
index bf039d89f..82adaa448 100644
--- a/spring-batch-execution/src/test/java/org/springframework/batch/execution/resource/BatchResourceFactoryBeanTests.java
+++ b/spring-batch-execution/src/test/java/org/springframework/batch/execution/resource/BatchResourceFactoryBeanTests.java
@@ -63,13 +63,12 @@ public class BatchResourceFactoryBeanTests extends TestCase {
resourceFactory.setRootDirectory(rootDir);
- SimpleStepContext context = new SimpleStepContext();
jobInstance = new JobInstance(new Long(0), new JobParameters());
jobInstance.setJob(new Job("testJob"));
JobExecution jobExecution = new JobExecution(jobInstance);
StepInstance step = new StepInstance(jobInstance, "bar");
StepExecution stepExecution = new StepExecution(step, jobExecution, null);
- context.setStepExecution(stepExecution);
+ SimpleStepContext context = new SimpleStepContext(stepExecution);
resourceFactory.setStepContext(context);
resourceFactory.afterPropertiesSet();
diff --git a/spring-batch-execution/src/test/java/org/springframework/batch/execution/scope/SimpleStepContextTests.java b/spring-batch-execution/src/test/java/org/springframework/batch/execution/scope/SimpleStepContextTests.java
index a4200fea0..c8e6eb0e0 100644
--- a/spring-batch-execution/src/test/java/org/springframework/batch/execution/scope/SimpleStepContextTests.java
+++ b/spring-batch-execution/src/test/java/org/springframework/batch/execution/scope/SimpleStepContextTests.java
@@ -16,47 +16,57 @@
package org.springframework.batch.execution.scope;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Properties;
import junit.framework.TestCase;
import org.springframework.batch.core.domain.StepExecution;
+import org.springframework.batch.statistics.StatisticsProvider;
+import org.springframework.batch.statistics.StatisticsService;
+import org.springframework.batch.support.PropertiesConverter;
/**
* @author Dave Syer
- *
+ *
*/
public class SimpleStepContextTests extends TestCase {
- private SimpleStepContext context = new SimpleStepContext(new SimpleStepContext());
-
+ private SimpleStepContext context = new SimpleStepContext(null, new SimpleStepContext(null));
+
/**
- * Test method for {@link org.springframework.batch.execution.scope.SimpleStepContext#StepScopeContext()}.
+ * Test method for
+ * {@link org.springframework.batch.execution.scope.SimpleStepContext#StepScopeContext()}.
*/
public void testStepScopeContext() {
- assertNull(new SimpleStepContext().getParent());
+ assertNull(new SimpleStepContext(null).getParent());
}
/**
- * Test method for {@link org.springframework.batch.execution.scope.SimpleStepContext#getParent()}.
+ * Test method for
+ * {@link org.springframework.batch.execution.scope.SimpleStepContext#getParent()}.
*/
public void testGetParent() {
assertNotNull(context.getParent());
}
/**
- * Test method for {@link org.springframework.batch.execution.scope.SimpleStepContext#getStepExecution()}.
+ * Test method for
+ * {@link org.springframework.batch.execution.scope.SimpleStepContext#getStepExecution()}.
*/
- public void testGetJobIdentifier() {
+ public void testGetStepExecution() {
assertNull(context.getStepExecution());
- context.setStepExecution(new StepExecution(null, null, null));
+ context = new SimpleStepContext(new StepExecution(null, null, null));
assertNotNull(context.getStepExecution());
}
private List list = new ArrayList();
/**
- * Test method for {@link org.springframework.batch.repeat.context.SimpleStepContext#registerDestructionCallback(java.lang.String, java.lang.Runnable)}.
+ * Test method for
+ * {@link org.springframework.batch.repeat.context.SimpleStepContext#registerDestructionCallback(java.lang.String, java.lang.Runnable)}.
*/
public void testDestructionCallbackSunnyDay() throws Exception {
SimpleStepContext context = new SimpleStepContext(null);
@@ -72,7 +82,8 @@ public class SimpleStepContextTests extends TestCase {
}
/**
- * Test method for {@link org.springframework.batch.repeat.context.SimpleStepContext#registerDestructionCallback(java.lang.String, java.lang.Runnable)}.
+ * Test method for
+ * {@link org.springframework.batch.repeat.context.SimpleStepContext#registerDestructionCallback(java.lang.String, java.lang.Runnable)}.
*/
public void testDestructionCallbackMissingAttribute() throws Exception {
SimpleStepContext context = new SimpleStepContext(null);
@@ -82,12 +93,14 @@ public class SimpleStepContextTests extends TestCase {
}
});
context.close();
- // Yes the callback should be called even if the attribute is missing - for inner beans
+ // Yes the callback should be called even if the attribute is missing -
+ // for inner beans
assertEquals(1, list.size());
}
/**
- * Test method for {@link org.springframework.batch.repeat.context.SimpleStepContext#registerDestructionCallback(java.lang.String, java.lang.Runnable)}.
+ * Test method for
+ * {@link org.springframework.batch.repeat.context.SimpleStepContext#registerDestructionCallback(java.lang.String, java.lang.Runnable)}.
*/
public void testDestructionCallbackWithException() throws Exception {
SimpleStepContext context = new SimpleStepContext(null);
@@ -108,7 +121,8 @@ public class SimpleStepContextTests extends TestCase {
try {
context.close();
fail("Expected RuntimeException");
- } catch (RuntimeException e) {
+ }
+ catch (RuntimeException e) {
// We don't care which one was thrown...
assertEquals("fail!", e.getMessage());
}
@@ -117,5 +131,56 @@ 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));
+ 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));
+ StubStatisticsProvider provider = new StubStatisticsProvider();
+ context.setAttribute("foo", provider);
+ assertEquals(1, map.size());
+ assertEquals(context, map.keySet().iterator().next());
+ assertEquals(provider, map.values().iterator().next());
+ }
+
+ /**
+ * @author Dave Syer
+ *
+ */
+ private class StubStatisticsService implements StatisticsService {
+ private final Map map;
+
+ private StubStatisticsService(Map map) {
+ this.map = map;
+ }
+
+ public Properties getStatistics(Object key) {
+ return PropertiesConverter.stringToProperties("foo=bar");
+ }
+
+ public void register(Object key, StatisticsProvider provider) {
+ map.put(key, provider);
+ }
+ }
+
+ /**
+ * @author Dave Syer
+ *
+ */
+ private class StubStatisticsProvider implements StatisticsProvider {
+ public Properties getStatistics() {
+ return null;
+ }
+ }
}
diff --git a/spring-batch-execution/src/test/java/org/springframework/batch/execution/scope/StepContextAwareStepScopeTests.java b/spring-batch-execution/src/test/java/org/springframework/batch/execution/scope/StepContextAwareStepScopeTests.java
index cc841721d..71c335a9a 100644
--- a/spring-batch-execution/src/test/java/org/springframework/batch/execution/scope/StepContextAwareStepScopeTests.java
+++ b/spring-batch-execution/src/test/java/org/springframework/batch/execution/scope/StepContextAwareStepScopeTests.java
@@ -40,7 +40,7 @@ public class StepContextAwareStepScopeTests extends TestCase {
}
public void testScopedBean() throws Exception {
- StepSynchronizationManager.open();
+ StepSynchronizationManager.register(new SimpleStepContext(null));
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("scope-tests.xml", getClass());
TestBean bean = (TestBean) applicationContext.getBean("bean");
assertNotNull(bean);
@@ -49,7 +49,7 @@ public class StepContextAwareStepScopeTests extends TestCase {
public void testScopedBeanWithDestroyCallback() throws Exception {
assertEquals(0, list.size());
- StepSynchronizationManager.open();
+ StepSynchronizationManager.register(new SimpleStepContext(null));
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("scope-tests.xml", getClass());
TestBean bean = (TestBean) applicationContext.getBean("bean");
assertNotNull(bean);
@@ -58,7 +58,8 @@ public class StepContextAwareStepScopeTests extends TestCase {
}
public void testScopedBeanWithAware() throws Exception {
- StepContext context = StepSynchronizationManager.open();
+ StepContext context = new SimpleStepContext(null);
+ StepSynchronizationManager.register(context);
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("scope-tests.xml", getClass());
TestBeanAware bean = (TestBeanAware) applicationContext.getBean("aware");
assertNotNull(bean);
@@ -67,7 +68,7 @@ public class StepContextAwareStepScopeTests extends TestCase {
}
public void testScopedBeanWithInner() throws Exception {
- StepSynchronizationManager.open();
+ StepSynchronizationManager.register(new SimpleStepContext(null));
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(
"scope-tests.xml", getClass());
TestBean bean = ((TestBean) applicationContext.getBean("inner")).child;
@@ -78,7 +79,8 @@ public class StepContextAwareStepScopeTests extends TestCase {
}
public void testScopedBeanWithProxy() throws Exception {
- StepContext context = StepSynchronizationManager.open();
+ StepContext context = new SimpleStepContext(null);
+ StepSynchronizationManager.register(context);
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("scope-tests.xml", getClass());
TestBeanAware bean = (TestBeanAware) applicationContext.getBean("proxy");
assertNotNull(bean);
@@ -89,7 +91,7 @@ public class StepContextAwareStepScopeTests extends TestCase {
}
public void testScopedBeanWithProxyInThread() throws Exception {
- StepSynchronizationManager.open();
+ StepSynchronizationManager.register(new SimpleStepContext(null));
final ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("scope-tests.xml", getClass());
new Thread(new Runnable() {
public void run() {
@@ -109,7 +111,7 @@ public class StepContextAwareStepScopeTests extends TestCase {
}
public void testScopedBeanWithTwoProxiesInThreads() throws Exception {
- StepSynchronizationManager.open();
+ StepSynchronizationManager.register(new SimpleStepContext(null));
final ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("scope-tests.xml", getClass());
new Thread(new Runnable() {
public void run() {
diff --git a/spring-batch-execution/src/test/java/org/springframework/batch/execution/scope/StepScopeTests.java b/spring-batch-execution/src/test/java/org/springframework/batch/execution/scope/StepScopeTests.java
index 9f533dd66..812cb4c4b 100644
--- a/spring-batch-execution/src/test/java/org/springframework/batch/execution/scope/StepScopeTests.java
+++ b/spring-batch-execution/src/test/java/org/springframework/batch/execution/scope/StepScopeTests.java
@@ -41,7 +41,8 @@ public class StepScopeTests extends TestCase {
*/
protected void setUp() throws Exception {
super.setUp();
- context = StepSynchronizationManager.open();
+ context = new SimpleStepContext(null);
+ StepSynchronizationManager.register(context);
}
/* (non-Javadoc)
@@ -104,7 +105,8 @@ public class StepScopeTests extends TestCase {
* {@link org.springframework.batch.execution.scope.StepScope#get(java.lang.String, org.springframework.beans.factory.ObjectFactory)}.
*/
public void testGetWithSomethingAlreadyInParentContext() {
- SimpleStepContext context = StepSynchronizationManager.open();
+ StepContext context = new SimpleStepContext(null);
+ StepSynchronizationManager.register(context);
context.setAttribute("foo", "bar");
Object value = scope.get("foo", new ObjectFactory() {
public Object getObject() throws BeansException {
diff --git a/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/simple/DefaultStepExecutorTests.java b/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/simple/SimpleStepExecutorTests.java
similarity index 90%
rename from spring-batch-execution/src/test/java/org/springframework/batch/execution/step/simple/DefaultStepExecutorTests.java
rename to spring-batch-execution/src/test/java/org/springframework/batch/execution/step/simple/SimpleStepExecutorTests.java
index 5b45c4173..7f0e4af81 100644
--- a/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/simple/DefaultStepExecutorTests.java
+++ b/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/simple/SimpleStepExecutorTests.java
@@ -18,7 +18,10 @@ package org.springframework.batch.execution.step.simple;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Properties;
import junit.framework.TestCase;
@@ -50,8 +53,11 @@ import org.springframework.batch.repeat.policy.SimpleCompletionPolicy;
import org.springframework.batch.repeat.support.RepeatTemplate;
import org.springframework.batch.restart.RestartData;
import org.springframework.batch.restart.Restartable;
+import org.springframework.batch.statistics.StatisticsProvider;
+import org.springframework.batch.statistics.StatisticsService;
+import org.springframework.batch.support.PropertiesConverter;
-public class DefaultStepExecutorTests extends TestCase {
+public class SimpleStepExecutorTests extends TestCase {
ArrayList processed = new ArrayList();
@@ -411,6 +417,43 @@ public class DefaultStepExecutorTests extends TestCase {
assertEquals(1, list.size());
}
+ public void testStatisticsService() throws Exception {
+ StepInstance step = new StepInstance(new Long(1));
+ step.setStepExecutionCount(1);
+ stepConfiguration.setTasklet(new Tasklet() {
+ public ExitStatus execute() throws Exception {
+ return ExitStatus.FINISHED;
+ }
+ });
+ stepConfiguration.setSaveRestartData(true);
+ JobExecution jobExecution = new JobExecution(jobInstance);
+ StepExecution stepExecution = new StepExecution(step, jobExecution);
+
+ assertEquals(null, stepExecution.getStatistics().getProperty("foo"));
+
+ final Map map = new HashMap();
+ stepExecutor.setStatisticsService(new StatisticsService() {
+ public Properties getStatistics(Object key) {
+ return PropertiesConverter.stringToProperties("foo=bar");
+ }
+ public void register(Object key, StatisticsProvider provider) {
+ map.put(key, provider);
+ }
+ });
+
+ try {
+ stepExecutor.process(stepConfiguration, stepExecution);
+ }
+ catch (Throwable t) {
+ fail();
+ }
+
+ // 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());
+ }
+
private class MockRestartableTasklet implements Tasklet, Restartable {
private boolean getRestartDataCalled = false;
diff --git a/spring-batch-execution/src/test/java/org/springframework/batch/execution/tasklet/ItemOrientedTaskletTests.java b/spring-batch-execution/src/test/java/org/springframework/batch/execution/tasklet/ItemOrientedTaskletTests.java
index 089f3199b..1e4d8be15 100644
--- a/spring-batch-execution/src/test/java/org/springframework/batch/execution/tasklet/ItemOrientedTaskletTests.java
+++ b/spring-batch-execution/src/test/java/org/springframework/batch/execution/tasklet/ItemOrientedTaskletTests.java
@@ -159,41 +159,6 @@ public class ItemOrientedTaskletTests extends TestCase {
assertEquals(2, list.size());
}
- public void testStatisticsProvider() throws Exception {
- module.setItemReader(new SkippableItemReader());
- Properties stats = module.getStatistics();
- assertEquals(1, stats.size());
- assertEquals("bar", stats.getProperty("foo"));
- }
-
- public void testStatisticsProcessor() throws Exception {
- module.setItemProcessor(new SkippableItemProcessor());
- Properties stats = module.getStatistics();
- assertEquals(1, stats.size());
- assertEquals("bar", stats.getProperty("foo"));
- }
-
- public void testStatisticsProviderProcessor() throws Exception {
- module.setItemReader(new SkippableItemReader());
- module.setItemProcessor(new SkippableItemProcessor());
- Properties stats = module.getStatistics();
- assertEquals(2, stats.size());
- assertEquals("bar", stats.getProperty("provider.foo"));
- assertEquals("bar", stats.getProperty("processor.foo"));
- }
-
- public void testStatisticsProviderProcessorMergeDuplicates()
- throws Exception {
- module.setItemReader(new SkippableItemReader());
- module.setItemProcessor(new SkippableItemProcessor(
- "foo=bar\nspam=bucket"));
- Properties stats = module.getStatistics();
- assertEquals(3, stats.size());
- assertEquals("bar", stats.getProperty("provider.foo"));
- assertEquals("bar", stats.getProperty("processor.foo"));
- assertEquals("bucket", stats.getProperty("spam"));
- }
-
public void testRecoverable() throws Exception {
// set up and call execute
diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/processor/CompositeItemProcessor.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/processor/CompositeItemProcessor.java
index e7b39d28b..b7829f298 100644
--- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/processor/CompositeItemProcessor.java
+++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/processor/CompositeItemProcessor.java
@@ -11,14 +11,13 @@ import org.springframework.batch.item.ItemReader;
import org.springframework.batch.restart.GenericRestartData;
import org.springframework.batch.restart.RestartData;
import org.springframework.batch.restart.Restartable;
-import org.springframework.batch.statistics.StatisticsProvider;
/**
* Runs a collection of ItemProcessors in fixed-order sequence.
*
* @author Robert Kasanicky
*/
-public class CompositeItemProcessor implements ItemProcessor, Restartable, StatisticsProvider {
+public class CompositeItemProcessor implements ItemProcessor, Restartable {
private static final String SEPARATOR = "#";
@@ -72,23 +71,6 @@ public class CompositeItemProcessor implements ItemProcessor, Restartable, Stati
}
- /**
- * @return Properties containing statistics of all injected ItemProcessors,
- * property keys are prefixed with the list index of the ItemProcessor.
- */
- public Properties getStatistics() {
- return createCompoundProperties(new PropertiesExtractor() {
- public Properties extractProperties(Object o) {
- if (o instanceof StatisticsProvider) {
- return ((StatisticsProvider) o).getStatistics();
- }
- else {
- return null;
- }
- }
- });
- }
-
public void setItemProcessors(List itemProcessors) {
this.itemProcessors = itemProcessors;
}
diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/processor/ItemWriterItemProcessor.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/processor/ItemWriterItemProcessor.java
index 9a736f07f..96223c4cf 100644
--- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/processor/ItemWriterItemProcessor.java
+++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/processor/ItemWriterItemProcessor.java
@@ -8,24 +8,23 @@ import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.restart.GenericRestartData;
import org.springframework.batch.restart.RestartData;
import org.springframework.batch.restart.Restartable;
-import org.springframework.batch.statistics.StatisticsProvider;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
/**
- * Simple wrapper around {@link ItemWriter} providing {@link Restartable} and
- * {@link StatisticsProvider} where the {@link ItemWriter} does.
+ * Simple wrapper around {@link ItemWriter} providing {@link Restartable} where
+ * the {@link ItemWriter} does. To make sure
*
* @author Dave Syer
* @author Robert Kasanicky
*/
-public class ItemWriterItemProcessor implements ItemProcessor, Restartable, Skippable,
- StatisticsProvider, InitializingBean {
+public class ItemWriterItemProcessor implements ItemProcessor, Restartable, Skippable, InitializingBean {
private ItemWriter writer;
/**
- * Calls {@link #doProcess(Object)} and then writes the result to the {@link ItemWriter}.
+ * Calls {@link #doProcess(Object)} and then writes the result to the
+ * {@link ItemWriter}.
*
* @see org.springframework.batch.item.ItemProcessor#process(java.lang.Object)
*/
@@ -35,8 +34,8 @@ public class ItemWriterItemProcessor implements ItemProcessor, Restartable, Skip
}
/**
- * By default returns the argument. This method is an extension point
- * meant to be overridden by subclasses that implement processing logic.
+ * By default returns the argument. This method is an extension point meant
+ * to be overridden by subclasses that implement processing logic.
*/
protected Object doProcess(Object item) throws Exception {
return item;
@@ -53,13 +52,13 @@ public class ItemWriterItemProcessor implements ItemProcessor, Restartable, Skip
* @see Restartable#getRestartData()
*/
public RestartData getRestartData() {
-
+
Assert.state(writer != null, "Source must not be null.");
-
+
if (writer instanceof Restartable) {
return ((Restartable) writer).getRestartData();
}
- else{
+ else {
return new GenericRestartData(new Properties());
}
}
@@ -68,35 +67,21 @@ public class ItemWriterItemProcessor implements ItemProcessor, Restartable, Skip
* @see Restartable#restoreFrom(RestartData)
*/
public void restoreFrom(RestartData data) {
-
+
Assert.state(writer != null, "Source must not be null.");
-
+
if (writer instanceof Restartable) {
((Restartable) writer).restoreFrom(data);
}
-
- }
- /**
- * @return delegates to the parent template of it is a
- * {@link StatisticsProvider}, otherwise returns an empty
- * {@link Properties} instance.
- * @see StatisticsProvider#getStatistics()
- */
- public Properties getStatistics() {
- if (!(writer instanceof StatisticsProvider)) {
- return new Properties();
- }
- return ((StatisticsProvider) writer).getStatistics();
}
public void skip() {
if (writer instanceof Skippable) {
- ((Skippable)writer).skip();
+ ((Skippable) writer).skip();
}
}
-
public void afterPropertiesSet() throws Exception {
Assert.notNull(writer);
}
diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/reader/DelegatingItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/reader/DelegatingItemReader.java
index d592b5d90..6678ea358 100644
--- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/reader/DelegatingItemReader.java
+++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/reader/DelegatingItemReader.java
@@ -16,13 +16,10 @@
package org.springframework.batch.item.reader;
-import java.util.Properties;
-
import org.springframework.batch.io.Skippable;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.restart.RestartData;
import org.springframework.batch.restart.Restartable;
-import org.springframework.batch.statistics.StatisticsProvider;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
@@ -33,7 +30,7 @@ import org.springframework.util.Assert;
*
* @author Dave Syer
*/
-public class DelegatingItemReader extends AbstractItemReader implements Restartable, StatisticsProvider, Skippable, InitializingBean{
+public class DelegatingItemReader extends AbstractItemReader implements Restartable, Skippable, InitializingBean{
private ItemReader inputSource;
@@ -73,19 +70,6 @@ public class DelegatingItemReader extends AbstractItemReader implements Restarta
((Restartable) inputSource).restoreFrom(data);
}
- /**
- * @return delegates to the parent template of it is a
- * {@link StatisticsProvider}, otherwise returns an empty
- * {@link Properties} instance.
- * @see StatisticsProvider#getStatistics()
- */
- public Properties getStatistics() {
- if (!(inputSource instanceof StatisticsProvider)) {
- return new Properties();
- }
- return ((StatisticsProvider) inputSource).getStatistics();
- }
-
/**
* Setter for input source.
* @param source
diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/statistics/SimpleStatisticsService.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/statistics/SimpleStatisticsService.java
new file mode 100644
index 000000000..2bdb29ab9
--- /dev/null
+++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/statistics/SimpleStatisticsService.java
@@ -0,0 +1,89 @@
+/*
+ * 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.statistics;
+
+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;
+
+/**
+ * Simple {@link StatisticsService} 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 SimpleStatisticsService implements StatisticsService {
+
+ 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 Properties getStatistics(Object key) {
+ Set set = new LinkedHashSet();
+ synchronized (registry) {
+ Collection collection = (Collection) registry.get(key);
+ if (collection != null) {
+ set = new LinkedHashSet(collection);
+ }
+ }
+ return 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();) {
+ StatisticsProvider provider = (StatisticsProvider) iterator.next();
+ Properties properties = provider.getStatistics();
+ if (properties != null) {
+ result.putAll(properties);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Register a {@link StatisticsProvider} as one of the interesting providers
+ * under the provided key.
+ *
+ * @see org.springframework.batch.statistics.StatisticsService#register(java.lang.Object,
+ * org.springframework.batch.statistics.StatisticsProvider)
+ */
+ public void register(Object key, StatisticsProvider provider) {
+ synchronized (registry) {
+ Set set = (Set) registry.get(key);
+ if (set == null) {
+ set = new LinkedHashSet();
+ registry.put(key, set);
+ }
+ set.add(provider);
+ }
+ }
+
+}
diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/statistics/StatisticsProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/statistics/StatisticsProvider.java
index db6bbf908..e50bcf194 100644
--- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/statistics/StatisticsProvider.java
+++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/statistics/StatisticsProvider.java
@@ -20,7 +20,7 @@ import java.util.Properties;
/**
* Provides statistics for a given module run. Any class that implements
- * this interface is garunteeing that it will provide Statistics.
+ * this interface is guaranteeing that it will provide Statistics.
*
* @author Lucas Ward
*
diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/statistics/StatisticsService.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/statistics/StatisticsService.java
new file mode 100644
index 000000000..2d93838e9
--- /dev/null
+++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/statistics/StatisticsService.java
@@ -0,0 +1,50 @@
+/*
+ * 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.statistics;
+
+import java.util.Properties;
+
+/**
+ * Generalised statistics aggregation strategy. Clients register
+ * {@link StatisticsProvider} instances under a well-known key, and then when
+ * they ask for statistics by that key, they receive an aggregate of all the
+ * values given by registered providers.
+ *
+ * @author Dave Syer
+ *
+ */
+public interface StatisticsService {
+
+ /**
+ * Register the {@link StatisticsProvider} instance as one of possibly
+ * several that are associated with the given key.
+ *
+ * @param key the key under which to add the provider
+ * @param provider a {@link StatisticsProvider}
+ */
+ void register(Object key, StatisticsProvider provider);
+
+ /**
+ * Extract and aggregate the statistics from all providers under this key.
+ *
+ * @param key the key under which {@link StatisticsProvider} instances might
+ * have been registered.
+ * @return {@link Properties} summarising the statistics of all providers
+ * registered under this key, or empty otherwise.
+ */
+ Properties getStatistics(Object key);
+
+}
diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/processor/CompositeItemProcessorTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/processor/CompositeItemProcessorTests.java
index 6809f10e5..8ba156bce 100644
--- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/processor/CompositeItemProcessorTests.java
+++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/processor/CompositeItemProcessorTests.java
@@ -58,24 +58,6 @@ public class CompositeItemProcessorTests extends TestCase {
}
}
- /**
- * Statistics of injected ItemProcessors should be returned under keys prefixed with their list index.
- */
- public void testStatistics() {
- final ItemProcessor p1 = new ItemProcessorStub();
- final ItemProcessor p2 = new ItemProcessorStub();
-
- List itemProcessors = new ArrayList(){{
- add(p1);
- add(p2);
- }};
-
- itemProcessor.setItemProcessors(itemProcessors);
- Properties stats = itemProcessor.getStatistics();
- assertEquals(String.valueOf(p1.hashCode()), stats.getProperty("0#" + ItemProcessorStub.STATS_KEY));
- assertEquals(String.valueOf(p2.hashCode()), stats.getProperty("1#" + ItemProcessorStub.STATS_KEY));
- }
-
/**
* All Restartable processors should be restarted, not-Restartable processors should be ignored.
*/
diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/processor/ItemWriterItemProcessorTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/processor/ItemWriterItemProcessorTests.java
index 10dd0e0b8..49c11b105 100644
--- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/processor/ItemWriterItemProcessorTests.java
+++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/processor/ItemWriterItemProcessorTests.java
@@ -23,7 +23,6 @@ import junit.framework.TestCase;
import org.springframework.batch.io.Skippable;
import org.springframework.batch.item.ItemWriter;
-import org.springframework.batch.item.processor.ItemWriterItemProcessor;
import org.springframework.batch.restart.GenericRestartData;
import org.springframework.batch.restart.RestartData;
import org.springframework.batch.restart.Restartable;
@@ -56,14 +55,6 @@ public class ItemWriterItemProcessorTests extends TestCase {
assertEquals("test:foo", list.get(0));
}
- /**
- * Gets statistics from the input template
- */
- public void testGetStatistics() {
- Properties props = processor.getStatistics();
- assertEquals("b", props.getProperty("a"));
- }
-
/**
* Gets restart data from the input template
*/
@@ -111,15 +102,6 @@ public class ItemWriterItemProcessorTests extends TestCase {
// expected
}
}
-
- /**
- * Gets statistics from the input template
- */
- public void testGetStatisticsWithoutStatisticsProvider() {
- processor.setItemWriter(null);
- Properties props = processor.getStatistics();
- assertEquals(null, props.getProperty("a"));
- }
public void testSkip() {
processor.skip();
diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/reader/DelegatingItemReaderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/reader/DelegatingItemReaderTests.java
index 3964c4766..3a6fec2ab 100644
--- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/reader/DelegatingItemReaderTests.java
+++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/reader/DelegatingItemReaderTests.java
@@ -72,14 +72,6 @@ public class DelegatingItemReaderTests extends TestCase {
assertSame("domain object is provided by the input template", this, result);
}
- /**
- * Gets statistics from the input template
- */
- public void testGetStatistics() {
- Properties props = itemProvider.getStatistics();
- assertEquals("b", props.getProperty("a"));
- }
-
/**
* Gets restart data from the input template
*/
diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/statistics/SimpleStatisticsServiceTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/statistics/SimpleStatisticsServiceTests.java
new file mode 100644
index 000000000..d1a4b7d67
--- /dev/null
+++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/statistics/SimpleStatisticsServiceTests.java
@@ -0,0 +1,72 @@
+/*
+ * 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.statistics;
+
+import java.util.Properties;
+
+import org.springframework.batch.support.PropertiesConverter;
+
+import junit.framework.TestCase;
+
+/**
+ * @author Dave Syer
+ *
+ */
+public class SimpleStatisticsServiceTests extends TestCase {
+
+ private SimpleStatisticsService service = new SimpleStatisticsService();
+
+ public void testRegistration() throws Exception {
+ service.register("FOO", new StubStatisticsProvider());
+ assertEquals("bar", service.getStatistics("FOO").getProperty("foo"));
+ }
+
+ public void testAggregation() throws Exception {
+ service.register("FOO", new StubStatisticsProvider());
+ service.register("FOO", new StubStatisticsProvider("spam=bucket"));
+ assertEquals("bar", service.getStatistics("FOO").getProperty("foo"));
+ assertEquals("bucket", service.getStatistics("FOO").getProperty("spam"));
+ }
+
+ public void testDoubleAggregationOrder() throws Exception {
+ StubStatisticsProvider provider = new StubStatisticsProvider();
+ service.register("FOO", provider);
+ service.register("FOO", provider);
+ assertEquals(1, service.getStatistics("FOO").size());
+ }
+
+ /**
+ * @author Dave Syer
+ *
+ */
+ private class StubStatisticsProvider implements StatisticsProvider {
+ String values = "foo=bar";
+
+ public StubStatisticsProvider(String values) {
+ super();
+ this.values = values;
+ }
+
+ public StubStatisticsProvider() {
+ super();
+ }
+
+ public Properties getStatistics() {
+ return PropertiesConverter.stringToProperties(values);
+ }
+ }
+
+}
diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/tasklet/SimpleTradeTasklet.java b/spring-batch-samples/src/main/java/org/springframework/batch/sample/tasklet/SimpleTradeTasklet.java
index a7de38394..cebd66fe4 100644
--- a/spring-batch-samples/src/main/java/org/springframework/batch/sample/tasklet/SimpleTradeTasklet.java
+++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/tasklet/SimpleTradeTasklet.java
@@ -86,7 +86,7 @@ public class SimpleTradeTasklet implements Tasklet, StatisticsProvider {
public Properties getStatistics() {
Properties statistics = new Properties();
- statistics.setProperty("Trade.Count", String.valueOf(tradeCount));
+ statistics.setProperty("trade.count", String.valueOf(tradeCount));
statistics.putAll(inputSource.getStatistics());
return statistics;
}
diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/item/processor/StagingItemProcessorTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/item/processor/StagingItemProcessorTests.java
index 3301f746d..eb8677487 100644
--- a/spring-batch-samples/src/test/java/org/springframework/batch/sample/item/processor/StagingItemProcessorTests.java
+++ b/spring-batch-samples/src/test/java/org/springframework/batch/sample/item/processor/StagingItemProcessorTests.java
@@ -11,8 +11,7 @@ import org.springframework.batch.execution.scope.StepSynchronizationManager;
import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;
import org.springframework.util.ClassUtils;
-public class StagingItemProcessorTests extends
- AbstractTransactionalDataSourceSpringContextTests {
+public class StagingItemProcessorTests extends AbstractTransactionalDataSourceSpringContextTests {
private StagingItemProcessor processor;
@@ -21,25 +20,21 @@ public class StagingItemProcessorTests extends
}
protected String[] getConfigLocations() {
- return new String[] { ClassUtils.addResourcePathToPackagePath(
- StagingItemProcessor.class, "staging-test-context.xml") };
+ return new String[] { ClassUtils.addResourcePathToPackagePath(StagingItemProcessor.class,
+ "staging-test-context.xml") };
}
protected void prepareTestInstance() throws Exception {
- SimpleStepContext stepScopeContext = StepSynchronizationManager
- .open();
- stepScopeContext.setStepExecution(new StepExecution(new StepInstance(
- new Long(11)), new JobExecution(new JobInstance(new Long(12),
- new JobParameters(), new Job("job")))));
+ SimpleStepContext stepScopeContext = new SimpleStepContext(new StepExecution(new StepInstance(new Long(11)),
+ new JobExecution(new JobInstance(new Long(12), new JobParameters(), new Job("job")))));
+ StepSynchronizationManager.register(stepScopeContext);
super.prepareTestInstance();
}
public void testProcessInsertsNewItem() throws Exception {
- int before = getJdbcTemplate().queryForInt(
- "SELECT COUNT(*) from BATCH_STAGING");
+ int before = getJdbcTemplate().queryForInt("SELECT COUNT(*) from BATCH_STAGING");
processor.process("FOO");
- int after = getJdbcTemplate().queryForInt(
- "SELECT COUNT(*) from BATCH_STAGING");
+ int after = getJdbcTemplate().queryForInt("SELECT COUNT(*) from BATCH_STAGING");
assertEquals(before + 1, after);
}
diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/item/reader/StagingItemReaderTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/item/reader/StagingItemReaderTests.java
index 427c2413e..b6dbcc406 100644
--- a/spring-batch-samples/src/test/java/org/springframework/batch/sample/item/reader/StagingItemReaderTests.java
+++ b/spring-batch-samples/src/test/java/org/springframework/batch/sample/item/reader/StagingItemReaderTests.java
@@ -6,21 +6,22 @@ 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.execution.scope.SimpleStepContext;
+import org.springframework.batch.execution.scope.StepContext;
import org.springframework.batch.execution.scope.StepSynchronizationManager;
import org.springframework.batch.repeat.context.RepeatContextSupport;
import org.springframework.batch.repeat.synch.BatchTransactionSynchronizationManager;
import org.springframework.batch.repeat.synch.RepeatSynchronizationManager;
import org.springframework.batch.sample.item.processor.StagingItemProcessor;
-import org.springframework.batch.sample.item.reader.StagingItemReader;
import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;
import org.springframework.util.ClassUtils;
-public class StagingItemReaderTests extends
- AbstractTransactionalDataSourceSpringContextTests {
+public class StagingItemReaderTests extends AbstractTransactionalDataSourceSpringContextTests {
private StagingItemProcessor processor;
+
private StagingItemReader provider;
- private Long jobId;
+
+ private Long jobId = new Long(11);
public void setProcessor(StagingItemProcessor processor) {
this.processor = processor;
@@ -31,16 +32,14 @@ public class StagingItemReaderTests extends
}
protected String[] getConfigLocations() {
- return new String[] { ClassUtils.addResourcePathToPackagePath(
- StagingItemProcessor.class, "staging-test-context.xml") };
+ return new String[] { ClassUtils.addResourcePathToPackagePath(StagingItemProcessor.class,
+ "staging-test-context.xml") };
}
protected void prepareTestInstance() throws Exception {
- SimpleStepContext stepScopeContext = StepSynchronizationManager.open();
- jobId = new Long(11);
- stepScopeContext.setStepExecution(new StepExecution(new StepInstance(
- new Long(12)), new JobExecution(new JobInstance(jobId,
- new JobParameters()))));
+ StepContext stepScopeContext = new SimpleStepContext(new StepExecution(new StepInstance(new Long(12)),
+ new JobExecution(new JobInstance(jobId, new JobParameters()))));
+ StepSynchronizationManager.register(stepScopeContext);
RepeatSynchronizationManager.register(new RepeatContextSupport(null));
super.prepareTestInstance();
}
@@ -59,36 +58,31 @@ public class StagingItemReaderTests extends
public void testReaderUpdatesProcessIndicator() throws Exception {
- long id = getJdbcTemplate().queryForLong(
- "SELECT MIN(ID) from BATCH_STAGING where JOB_ID=?",
+ long id = getJdbcTemplate().queryForLong("SELECT MIN(ID) from BATCH_STAGING where JOB_ID=?",
new Object[] { jobId });
- String before = (String) getJdbcTemplate().queryForObject(
- "SELECT PROCESSED from BATCH_STAGING where ID=?",
+ String before = (String) getJdbcTemplate().queryForObject("SELECT PROCESSED from BATCH_STAGING where ID=?",
new Object[] { new Long(id) }, String.class);
assertEquals(StagingItemProcessor.NEW, before);
Object item = provider.read();
assertEquals("FOO", item);
- String after = (String) getJdbcTemplate().queryForObject(
- "SELECT PROCESSED from BATCH_STAGING where ID=?",
+ String after = (String) getJdbcTemplate().queryForObject("SELECT PROCESSED from BATCH_STAGING where ID=?",
new Object[] { new Long(id) }, String.class);
assertEquals(StagingItemProcessor.DONE, after);
}
-
+
public void testUpdateProcessIndicatorAfterCommit() throws Exception {
testReaderUpdatesProcessIndicator();
setComplete();
endTransaction();
startNewTransaction();
- long id = getJdbcTemplate().queryForLong(
- "SELECT MIN(ID) from BATCH_STAGING where JOB_ID=?",
+ long id = getJdbcTemplate().queryForLong("SELECT MIN(ID) from BATCH_STAGING where JOB_ID=?",
new Object[] { jobId });
- String before = (String) getJdbcTemplate().queryForObject(
- "SELECT PROCESSED from BATCH_STAGING where ID=?",
+ String before = (String) getJdbcTemplate().queryForObject("SELECT PROCESSED from BATCH_STAGING where ID=?",
new Object[] { new Long(id) }, String.class);
- assertEquals(StagingItemProcessor.DONE, before);
+ assertEquals(StagingItemProcessor.DONE, before);
}
public void testProviderRollsBackMultipleTimes() throws Exception {
@@ -96,14 +90,14 @@ public class StagingItemReaderTests extends
setComplete();
endTransaction();
startNewTransaction();
- // After a rollback we have to resynchronize the TX to simulate a real batch
+ // After a rollback we have to resynchronize the TX to simulate a real
+ // batch
BatchTransactionSynchronizationManager.resynchronize();
- int count = getJdbcTemplate().queryForInt(
- "SELECT COUNT(*) from BATCH_STAGING where JOB_ID=? AND PROCESSED=?",
+ int count = getJdbcTemplate().queryForInt("SELECT COUNT(*) from BATCH_STAGING where JOB_ID=? AND PROCESSED=?",
new Object[] { jobId, StagingItemProcessor.NEW });
assertEquals(4, count);
-
+
Object item = provider.read();
assertEquals("FOO", item);
item = provider.read();
@@ -112,7 +106,7 @@ public class StagingItemReaderTests extends
endTransaction();
startNewTransaction();
BatchTransactionSynchronizationManager.resynchronize();
-
+
item = provider.read();
assertEquals("FOO", item);
item = provider.read();
@@ -123,25 +117,24 @@ public class StagingItemReaderTests extends
endTransaction();
startNewTransaction();
BatchTransactionSynchronizationManager.resynchronize();
-
+
item = provider.read();
assertEquals("FOO", item);
}
-
+
public void testProviderRollsBackProcessIndicator() throws Exception {
setComplete();
endTransaction();
startNewTransaction();
- // After a rollback we have to resynchronize the TX to simulate a real batch
+ // After a rollback we have to resynchronize the TX to simulate a real
+ // batch
BatchTransactionSynchronizationManager.resynchronize();
- long id = getJdbcTemplate().queryForLong(
- "SELECT MIN(ID) from BATCH_STAGING where JOB_ID=?",
+ long id = getJdbcTemplate().queryForLong("SELECT MIN(ID) from BATCH_STAGING where JOB_ID=?",
new Object[] { jobId });
- String before = (String) getJdbcTemplate().queryForObject(
- "SELECT PROCESSED from BATCH_STAGING where ID=?",
+ String before = (String) getJdbcTemplate().queryForObject("SELECT PROCESSED from BATCH_STAGING where ID=?",
new Object[] { new Long(id) }, String.class);
assertEquals(StagingItemProcessor.NEW, before);
@@ -150,11 +143,11 @@ public class StagingItemReaderTests extends
endTransaction();
startNewTransaction();
- // After a rollback we have to resynchronize the TX to simulate a real batch
+ // After a rollback we have to resynchronize the TX to simulate a real
+ // batch
BatchTransactionSynchronizationManager.resynchronize();
- String after = (String) getJdbcTemplate().queryForObject(
- "SELECT PROCESSED from BATCH_STAGING where ID=?",
+ String after = (String) getJdbcTemplate().queryForObject("SELECT PROCESSED from BATCH_STAGING where ID=?",
new Object[] { new Long(id) }, String.class);
assertEquals(StagingItemProcessor.NEW, after);