RESOLVED - issue BATCH-88: StatisticsProvider is a leaky abstraction
http://jira.springframework.org/browse/BATCH-88
This commit is contained in:
@@ -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;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The arguments to this class are roughly as follows:
|
||||
* </p>
|
||||
*
|
||||
* <code>
|
||||
* java jobPath jobName jobLauncherPath jobParameters...
|
||||
* </code>
|
||||
*
|
||||
* <p>
|
||||
* <ul>
|
||||
* <li>jobPath: the xml application context containing a {@link Job}
|
||||
* <li>jobName: the bean id of the job.
|
||||
* <li>jobLauncherPath: the xml application context containing a
|
||||
* {@link JobLauncher}
|
||||
* <li>jobParameters: 0 to many parameters that will be used to launch a job.
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 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: "
|
||||
*
|
||||
* <p>
|
||||
* <code>
|
||||
* java org.springframework.batch.execution.bootstrap.support.CommandLineJobRunner testJob.xml
|
||||
* testJob standard-job-launcher.xml schedule.date=2008/01/24 vendor.id=3902483920
|
||||
* <code></p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @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
|
||||
* <p>
|
||||
* <ul>
|
||||
* <li>jobPath: the xml application context containing a
|
||||
* {@link Job}
|
||||
* <li>jobName: the bean id of the job.
|
||||
* <li>jobLauncherPath: the xml application context containing a
|
||||
* {@link JobLauncher}
|
||||
* <li>jobParameters: 0 to many parameters that will be used to
|
||||
* launch a job.
|
||||
* </ul>
|
||||
* </p>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
* <ul>
|
||||
* <li> If the configuration is a {@link RepeatOperationsHolder} then we use
|
||||
* the provided {@link RepeatOperations} instances for chunk and step. </li>
|
||||
* <li> 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
|
||||
* <li> 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.</li>
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
* <em>must</em> 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.<br/>
|
||||
* {@link ItemReader} and {@link ItemProcessor}. The {@link ItemReader} class
|
||||
* provides a solid means for re-usability and enforces good architecture
|
||||
* practices. Because an object <em>must</em> 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.<br/>
|
||||
*
|
||||
* 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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user