[BATCH-429] Moved all code out of execution
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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.configuration;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.configuration.JobLocator;
|
||||
import org.springframework.batch.core.configuration.JobRegistry;
|
||||
import org.springframework.batch.core.repository.DuplicateJobException;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.FatalBeanException;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link BeanPostProcessor} that registers {@link Job} beans
|
||||
* with a {@link JobRegistry}. Include a bean of this type along
|
||||
* with your job configuration, and use the same
|
||||
* {@link JobRegistry} as a {@link JobLocator} when
|
||||
* you need to locate a {@link JobLocator} to launch.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class JobRegistryBeanPostProcessor implements BeanPostProcessor, InitializingBean, DisposableBean {
|
||||
|
||||
// It doesn't make sense for this to have a default value...
|
||||
private JobRegistry jobConfigurationRegistry = null;
|
||||
|
||||
private Collection jobNames = new HashSet();
|
||||
|
||||
/**
|
||||
* Injection setter for {@link JobRegistry}.
|
||||
*
|
||||
* @param jobRegistry the jobConfigurationRegistry to set
|
||||
*/
|
||||
public void setJobRegistry(JobRegistry jobRegistry) {
|
||||
this.jobConfigurationRegistry = jobRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the registry is set before use.
|
||||
*
|
||||
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
|
||||
*/
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(jobConfigurationRegistry, "JobConfigurationRegistry must not be null");
|
||||
}
|
||||
|
||||
/**
|
||||
* De-register all the {@link Job} instances that were
|
||||
* regsistered by this post processor.
|
||||
* @see org.springframework.beans.factory.DisposableBean#destroy()
|
||||
*/
|
||||
public void destroy() throws Exception {
|
||||
for (Iterator iter = jobNames.iterator(); iter.hasNext();) {
|
||||
String name = (String) iter.next();
|
||||
jobConfigurationRegistry.unregister(name);
|
||||
}
|
||||
jobNames.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the bean is an instance of {@link Job} then register it.
|
||||
* @throws FatalBeanException if there is a
|
||||
* {@link DuplicateJobException}.
|
||||
*
|
||||
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object,
|
||||
* java.lang.String)
|
||||
*/
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (bean instanceof Job) {
|
||||
Job job = (Job) bean;
|
||||
try {
|
||||
jobConfigurationRegistry.register(new ReferenceJobFactory(job));
|
||||
jobNames.add(job.getName());
|
||||
}
|
||||
catch (DuplicateJobException e) {
|
||||
throw new FatalBeanException("Cannot register job configuration", e);
|
||||
}
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing.
|
||||
*
|
||||
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object,
|
||||
* java.lang.String)
|
||||
*/
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
return bean;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.configuration;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.configuration.JobFactory;
|
||||
import org.springframework.batch.core.configuration.JobRegistry;
|
||||
import org.springframework.batch.core.configuration.ListableJobRegistry;
|
||||
import org.springframework.batch.core.repository.DuplicateJobException;
|
||||
import org.springframework.batch.core.repository.NoSuchJobException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Simple map-based implementation of {@link JobRegistry}. Access to the map is
|
||||
* synchronized, guarded by an internal lock.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class MapJobRegistry implements ListableJobRegistry {
|
||||
|
||||
private Map map = new HashMap();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.container.common.configuration.JobConfigurationRegistry#registerJobConfiguration(org.springframework.batch.container.common.configuration.JobConfiguration)
|
||||
*/
|
||||
public void register(JobFactory jobFactory) throws DuplicateJobException {
|
||||
Assert.notNull(jobFactory);
|
||||
String name = jobFactory.getJobName();
|
||||
Assert.notNull(name, "Job configuration must have a name.");
|
||||
synchronized (map) {
|
||||
if (map.containsKey(name)) {
|
||||
throw new DuplicateJobException("A job configuration with this name [" + name
|
||||
+ "] was already registered");
|
||||
}
|
||||
map.put(name, jobFactory);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.container.common.configuration.JobConfigurationRegistry#unregister(org.springframework.batch.container.common.configuration.JobConfiguration)
|
||||
*/
|
||||
public void unregister(String name) {
|
||||
Assert.notNull(name, "Job configuration must have a name.");
|
||||
synchronized (map) {
|
||||
map.remove(name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.container.common.configuration.JobConfigurationLocator#getJobConfiguration(java.lang.String)
|
||||
*/
|
||||
public Job getJob(String name) throws NoSuchJobException {
|
||||
synchronized (map) {
|
||||
if (!map.containsKey(name)) {
|
||||
throw new NoSuchJobException("No job configuration with the name [" + name + "] was registered");
|
||||
}
|
||||
return (Job) ((JobFactory) map.get(name)).createJob();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.container.common.configuration.ListableJobConfigurationRegistry#getJobConfigurations()
|
||||
*/
|
||||
public Collection getJobNames() {
|
||||
synchronized (map) {
|
||||
return Collections.unmodifiableCollection(new HashSet(map.keySet()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.configuration;
|
||||
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.configuration.JobFactory;
|
||||
|
||||
/**
|
||||
* A {@link JobFactory} that just keeps a reference to a {@link Job}. It never
|
||||
* modifies its {@link Job}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ReferenceJobFactory implements JobFactory {
|
||||
|
||||
private Job job;
|
||||
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* @param job the {@link Job} to return from {@link #createJob()}.
|
||||
*/
|
||||
public ReferenceJobFactory(Job job) {
|
||||
super();
|
||||
this.job = job;
|
||||
this.name = job.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Just return the instance passed in on initialization.
|
||||
*
|
||||
* @see org.springframework.batch.core.configuration.JobFactory#createJob()
|
||||
*/
|
||||
public Job createJob() {
|
||||
return job;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the job name as passed in on initialization.
|
||||
*
|
||||
* @see org.springframework.batch.core.configuration.JobFactory#getJobName()
|
||||
*/
|
||||
public String getJobName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Specific implementations of configuration concerns.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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.job;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Batch domain object representing a job. Job is an explicit abstraction
|
||||
* representing the configuration of a job specified by a developer. It should
|
||||
* be noted that restart policy is applied to the job as a whole and not to a
|
||||
* step.
|
||||
*
|
||||
* @author Lucas Ward
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public abstract class AbstractJob implements BeanNameAware, Job {
|
||||
|
||||
private List steps = new ArrayList();
|
||||
|
||||
private String name;
|
||||
|
||||
private boolean restartable = false;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public AbstractJob() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience constructor to immediately add name (which is mandatory but
|
||||
* not final).
|
||||
*
|
||||
* @param name
|
||||
*/
|
||||
public AbstractJob(String name) {
|
||||
super();
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name property if it is not already set. Because of the order of
|
||||
* the callbacks in a Spring container the name property will be set first
|
||||
* if it is present. Care is needed with bean definition inheritance - if a
|
||||
* parent bean has a name, then its children need an explicit name as well,
|
||||
* otherwise they will not be unique.
|
||||
*
|
||||
* @see org.springframework.beans.factory.BeanNameAware#setBeanName(java.lang.String)
|
||||
*/
|
||||
public void setBeanName(String name) {
|
||||
if (this.name == null) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name property. Always overrides the default value if this object
|
||||
* is a Spring bean.
|
||||
*
|
||||
* @see #setBeanName(java.lang.String)
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.core.domain.IJob#getName()
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.core.domain.IJob#getSteps()
|
||||
*/
|
||||
public List getSteps() {
|
||||
return steps;
|
||||
}
|
||||
|
||||
public void setSteps(List steps) {
|
||||
this.steps.clear();
|
||||
this.steps.addAll(steps);
|
||||
}
|
||||
|
||||
public void addStep(Step step) {
|
||||
this.steps.add(step);
|
||||
}
|
||||
|
||||
public void setRestartable(boolean restartable) {
|
||||
this.restartable = restartable;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.core.domain.IJob#isRestartable()
|
||||
*/
|
||||
public boolean isRestartable() {
|
||||
return restartable;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return ClassUtils.getShortName(getClass()) + ": [name=" + name + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* 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.job;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.UnexpectedJobExecutionException;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobExecutionException;
|
||||
import org.springframework.batch.core.JobInstance;
|
||||
import org.springframework.batch.core.JobInterruptedException;
|
||||
import org.springframework.batch.core.JobListener;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.execution.listener.CompositeJobListener;
|
||||
import org.springframework.batch.repeat.ExitStatus;
|
||||
|
||||
/**
|
||||
* Simple implementation of (@link Job} interface providing the ability to run a {@link JobExecution}. Sequentially
|
||||
* executes a job by iterating through its list of steps.
|
||||
*
|
||||
* @author Lucas Ward
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class SimpleJob extends AbstractJob {
|
||||
|
||||
private JobRepository jobRepository;
|
||||
|
||||
private CompositeJobListener listener = new CompositeJobListener();
|
||||
|
||||
/**
|
||||
* Public setter for injecting {@link JobListener}s. They will all be given the {@link JobListener} callbacks at
|
||||
* the appropriate point in the job.
|
||||
*
|
||||
* @param listeners the listeners to set.
|
||||
*/
|
||||
public void setJobListeners(JobListener[] listeners) {
|
||||
for (int i = 0; i < listeners.length; i++) {
|
||||
this.listener.register(listeners[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a single listener for the {@link JobListener} callbacks.
|
||||
*
|
||||
* @param listener a {@link JobListener}
|
||||
*/
|
||||
public void registerListener(JobListener listener) {
|
||||
this.listener.register(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the specified job by looping through the steps and delegating to the {@link Step}.
|
||||
*
|
||||
* @see org.springframework.batch.core.Job#execute(org.springframework.batch.core.JobExecution)
|
||||
*/
|
||||
public void execute(JobExecution execution) throws JobExecutionException {
|
||||
|
||||
JobInstance jobInstance = execution.getJobInstance();
|
||||
|
||||
StepExecution currentStepExecution = null;
|
||||
int startedCount = 0;
|
||||
List steps = getSteps();
|
||||
|
||||
try {
|
||||
|
||||
// The job was already stopped before we even got this far. Deal
|
||||
// with it in the same way as any other interruption.
|
||||
if (execution.getStatus() == BatchStatus.STOPPING) {
|
||||
throw new JobInterruptedException("JobExecution already stopped before being executed.");
|
||||
}
|
||||
|
||||
execution.setStartTime(new Date());
|
||||
updateStatus(execution, BatchStatus.STARTING);
|
||||
|
||||
listener.beforeJob(execution);
|
||||
|
||||
for (Iterator i = steps.iterator(); i.hasNext();) {
|
||||
|
||||
Step step = (Step) i.next();
|
||||
|
||||
if (shouldStart(jobInstance, step)) {
|
||||
|
||||
startedCount++;
|
||||
updateStatus(execution, BatchStatus.STARTED);
|
||||
currentStepExecution = execution.createStepExecution(step);
|
||||
|
||||
step.execute(currentStepExecution);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
updateStatus(execution, BatchStatus.COMPLETED);
|
||||
|
||||
listener.afterJob(execution);
|
||||
|
||||
} catch (JobInterruptedException e) {
|
||||
execution.setStatus(BatchStatus.STOPPED);
|
||||
rethrow(e);
|
||||
} catch (Throwable t) {
|
||||
execution.setStatus(BatchStatus.FAILED);
|
||||
rethrow(t);
|
||||
} finally {
|
||||
ExitStatus status = ExitStatus.FAILED;
|
||||
if (startedCount == 0) {
|
||||
if (steps.size() > 0) {
|
||||
status = ExitStatus.NOOP
|
||||
.addExitDescription("All steps already completed. No processing was done.");
|
||||
} else {
|
||||
status = ExitStatus.NOOP.addExitDescription("No steps configured for this job.");
|
||||
}
|
||||
} else if (currentStepExecution != null) {
|
||||
status = currentStepExecution.getExitStatus();
|
||||
}
|
||||
|
||||
execution.setEndTime(new Date());
|
||||
execution.setExitStatus(status);
|
||||
jobRepository.saveOrUpdate(execution);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void updateStatus(JobExecution jobExecution, BatchStatus status) {
|
||||
jobExecution.setStatus(status);
|
||||
jobRepository.saveOrUpdate(jobExecution);
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a step and configuration, return true if the step should start, false if it should not, and throw an
|
||||
* exception if the job should finish.
|
||||
*/
|
||||
private boolean shouldStart(JobInstance jobInstance, Step step) throws JobExecutionException {
|
||||
|
||||
BatchStatus stepStatus;
|
||||
// if the last execution is null, the step has never been executed.
|
||||
StepExecution lastStepExecution = jobRepository.getLastStepExecution(jobInstance, step);
|
||||
if (lastStepExecution == null) {
|
||||
stepStatus = BatchStatus.STARTING;
|
||||
} else {
|
||||
stepStatus = lastStepExecution.getStatus();
|
||||
}
|
||||
|
||||
if (stepStatus == BatchStatus.UNKNOWN) {
|
||||
throw new JobExecutionException("Cannot restart step from UNKNOWN status. "
|
||||
+ "The last execution ended with a failure that could not be rolled back, "
|
||||
+ "so it may be dangerous to proceed. " + "Manual intervention is probably necessary.");
|
||||
}
|
||||
|
||||
if (stepStatus == BatchStatus.COMPLETED && step.isAllowStartIfComplete() == false) {
|
||||
// step is complete, false should be returned, indicating that the
|
||||
// step should not be started
|
||||
return false;
|
||||
}
|
||||
|
||||
if (jobRepository.getStepExecutionCount(jobInstance, step) < step.getStartLimit()) {
|
||||
// step start count is less than start max, return true
|
||||
return true;
|
||||
} else {
|
||||
// start max has been exceeded, throw an exception.
|
||||
throw new UnexpectedJobExecutionException("Maximum start limit exceeded for step: " + step.getName() + "StartMax: "
|
||||
+ step.getStartLimit());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param t
|
||||
*/
|
||||
private static void rethrow(Throwable t) throws RuntimeException {
|
||||
if (t instanceof RuntimeException) {
|
||||
throw (RuntimeException) t;
|
||||
} else {
|
||||
throw new UnexpectedJobExecutionException(t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link JobRepository} that is needed to manage the state of the batch meta domain (jobs,
|
||||
* steps, executions) during the life of a job.
|
||||
*
|
||||
* @param jobRepository
|
||||
*/
|
||||
public void setJobRepository(JobRepository jobRepository) {
|
||||
this.jobRepository = jobRepository;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Specific implementations of job concerns.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.launch;
|
||||
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.repository.JobRestartException;
|
||||
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
|
||||
|
||||
/**
|
||||
* Simple interface for controlling jobs, including possible ad-hoc executions,
|
||||
* based on different runtime identifiers. It is extremely important to note
|
||||
* that this interface makes absolutely no guarantees about whether or not calls
|
||||
* to it are executed synchronously or asynchronously. The javadocs for specific
|
||||
* implementations should be checked to ensure callers fully understand how the
|
||||
* job will be run.
|
||||
*
|
||||
* @author Lucas Ward
|
||||
* @author Dave Syer
|
||||
*/
|
||||
|
||||
public interface JobLauncher {
|
||||
|
||||
/**
|
||||
* Start a job execution for the given {@link Job} and {@link JobParameters}.
|
||||
*
|
||||
* @return the exit code from the job if it returns synchronously. If the
|
||||
* implementation is asynchronous, the status might well be unknown.
|
||||
*
|
||||
* @throws JobExecutionAlreadyRunningException if the JobInstance identified
|
||||
* by the properties already has an execution running.
|
||||
* @throws IllegalArgumentException if the job or jobInstanceProperties are
|
||||
* null.
|
||||
* @throws JobRestartException if the job has been run before and
|
||||
* circumstances that preclude a re-start.
|
||||
*/
|
||||
public JobExecution run(Job job, JobParameters jobParameters) throws JobExecutionAlreadyRunningException,
|
||||
JobRestartException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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.launch;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobInstance;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.repository.JobRestartException;
|
||||
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.core.task.SyncTaskExecutor;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Simple implementation of the {@link JobLauncher} interface. The Spring Core
|
||||
* {@link TaskExecutor} interface is used to launch a {@link Job}. This means
|
||||
* that the type of executor set is very important. If a
|
||||
* {@link SyncTaskExecutor} is used, then the job will be processed
|
||||
* <strong>within the same thread that called the launcher.</strong> Care
|
||||
* should be taken to ensure any users of this class understand fully whether or
|
||||
* not the implementation of TaskExecutor used will start tasks synchronously or
|
||||
* asynchronously. The default setting uses a synchronous task executor.
|
||||
*
|
||||
* There is only one required dependency of this Launcher, a
|
||||
* {@link JobRepository}. The JobRepository is used to obtain a valid
|
||||
* JobExecution. The Repository must be used because the provided {@link Job}
|
||||
* could be a restart of an existing {@link JobInstance}, and only the
|
||||
* Repository can reliably recreate it.
|
||||
*
|
||||
* @author Lucas Ward
|
||||
* @since 1.0
|
||||
* @see JobRepository
|
||||
* @see TaskExecutor
|
||||
*/
|
||||
public class SimpleJobLauncher implements JobLauncher, InitializingBean {
|
||||
|
||||
protected static final Log logger = LogFactory.getLog(SimpleJobLauncher.class);
|
||||
|
||||
private JobRepository jobRepository;
|
||||
|
||||
private TaskExecutor taskExecutor = new SyncTaskExecutor();
|
||||
|
||||
/**
|
||||
* Run the provided job with the given {@link JobParameters}. The
|
||||
* {@link JobParameters} will be used to determine if this is an execution
|
||||
* of an existing job instance, or if a new one should be created.
|
||||
*
|
||||
* @param job, the job to be run.
|
||||
* @param jobParameters, the {@link JobParameters} for this particular
|
||||
* execution.
|
||||
* @return JobExecutionAlreadyRunningException if the JobInstance already
|
||||
* exists and has an execution already running.
|
||||
* @throws JobRestartException if the execution would be a re-start, but a
|
||||
* re-start is either not allowed or not needed.
|
||||
*/
|
||||
public JobExecution run(final Job job, final JobParameters jobParameters)
|
||||
throws JobExecutionAlreadyRunningException, JobRestartException {
|
||||
|
||||
Assert.notNull(job, "The Job must not be null.");
|
||||
Assert.notNull(jobParameters, "The JobParameters must not be null.");
|
||||
|
||||
final JobExecution jobExecution = jobRepository.createJobExecution(job, jobParameters);
|
||||
|
||||
taskExecutor.execute(new Runnable() {
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
logger.info("Job: [" + job + "] launched with the following parameters: [" + jobParameters + "]");
|
||||
job.execute(jobExecution);
|
||||
logger.info("Job: [" + job + "] completed successfully with the following parameters: ["
|
||||
+ jobParameters + "]");
|
||||
}
|
||||
catch (Throwable t) {
|
||||
logger.info("Job: [" + job + "] failed with the following parameters: [" + jobParameters + "]", t);
|
||||
rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
private void rethrow(Throwable t) {
|
||||
if (t instanceof RuntimeException) {
|
||||
throw (RuntimeException) t;
|
||||
}
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
});
|
||||
|
||||
return jobExecution;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the JobRepsitory.
|
||||
*
|
||||
* @param jobRepository
|
||||
*/
|
||||
public void setJobRepository(JobRepository jobRepository) {
|
||||
this.jobRepository = jobRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the TaskExecutor. (Optional)
|
||||
*
|
||||
* @param taskExecutor
|
||||
*/
|
||||
public void setTaskExecutor(TaskExecutor taskExecutor) {
|
||||
this.taskExecutor = taskExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the required dependencies of a {@link JobRepository} have been
|
||||
* set.
|
||||
*/
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.state(jobRepository != null, "A JobRepository has not been set.");
|
||||
logger.info("No TaskExecutor has been set, defaulting to synchronous executor.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Specific implementations of facade concerns.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* 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.launch.support;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.configuration.JobLocator;
|
||||
import org.springframework.batch.core.runtime.ExitStatusExceptionClassifier;
|
||||
import org.springframework.batch.core.runtime.JobParametersFactory;
|
||||
import org.springframework.batch.execution.launch.JobLauncher;
|
||||
import org.springframework.batch.execution.step.support.SimpleExitStatusExceptionClassifier;
|
||||
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 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 ExitStatusExceptionClassifier exceptionClassifier = new SimpleExitStatusExceptionClassifier();
|
||||
|
||||
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 ExitStatusExceptionClassifier}
|
||||
*
|
||||
* @param exceptionClassifier
|
||||
*/
|
||||
public void setExceptionClassifier(
|
||||
ExitStatusExceptionClassifier 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 jobName, String[] parameters) {
|
||||
|
||||
try {
|
||||
ApplicationContext context = new ClassPathXmlApplicationContext(jobPath);
|
||||
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 < 2) {
|
||||
logger
|
||||
.error("At least 2 arguments are required: JobPath and JobName.");
|
||||
command.exit(1);
|
||||
}
|
||||
|
||||
String jobPath = args[0];
|
||||
String jobName = args[1];
|
||||
String[] parameters = new String[args.length - 2];
|
||||
System.arraycopy(args, 2, parameters, 0, args.length - 2);
|
||||
|
||||
int result = command.start(jobPath, jobName, parameters);
|
||||
command.exit(result);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* 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.launch.support;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.core.runtime.JobParametersFactory;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Factory for {@link JobParameters} instances using a simple naming convention
|
||||
* for property keys. Key names ending with "(<type>)" where type is one
|
||||
* of string, date, long are converted to the corresponding type. The default
|
||||
* type is string. E.g.
|
||||
*
|
||||
* <pre>
|
||||
* schedule.date(date)=2007/12/11
|
||||
* department.id(long)=2345
|
||||
* </pre>
|
||||
*
|
||||
* The literal values are converted to the correct type using the default Spring
|
||||
* strategies, augmented if necessary by the custom editors provided.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class DefaultJobParametersFactory implements JobParametersFactory {
|
||||
|
||||
public static String DATE_TYPE = "(date)";
|
||||
|
||||
public static String STRING_TYPE = "(string)";
|
||||
|
||||
public static String LONG_TYPE = "(long)";
|
||||
|
||||
private DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");
|
||||
|
||||
private NumberFormat numberFormat = new DecimalFormat("#");
|
||||
|
||||
/**
|
||||
* Check for suffix on keys and use those to decide how to convert the
|
||||
* value.
|
||||
*
|
||||
* @throws IllegalArgumentException if a number or date is passed in that
|
||||
* cannot be parsed, or cast to the correct type.
|
||||
*
|
||||
* @see org.springframework.batch.core.runtime.JobParametersFactory#getJobParameters(java.util.Properties)
|
||||
*/
|
||||
public JobParameters getJobParameters(Properties props) {
|
||||
|
||||
if (props == null || props.isEmpty()) {
|
||||
return new JobParameters();
|
||||
}
|
||||
|
||||
JobParametersBuilder propertiesBuilder = new JobParametersBuilder();
|
||||
|
||||
for (Iterator it = props.entrySet().iterator(); it.hasNext();) {
|
||||
Entry entry = (Entry) it.next();
|
||||
String key = (String) entry.getKey();
|
||||
String value = (String) entry.getValue();
|
||||
if (key.endsWith(DATE_TYPE)) {
|
||||
Date date;
|
||||
try {
|
||||
date = dateFormat.parse(value);
|
||||
}
|
||||
catch (ParseException ex) {
|
||||
String suffix = (dateFormat instanceof SimpleDateFormat) ? ", use "
|
||||
+ ((SimpleDateFormat) dateFormat).toPattern() : "";
|
||||
throw new IllegalArgumentException("Date format is invalid: [" + value + "]" + suffix, ex);
|
||||
}
|
||||
propertiesBuilder.addDate(StringUtils.replace(key, DATE_TYPE, ""), date);
|
||||
}
|
||||
else if (key.endsWith(LONG_TYPE)) {
|
||||
Long result;
|
||||
try {
|
||||
result = (Long) numberFormat.parse(value);
|
||||
}
|
||||
catch (ParseException ex) {
|
||||
String suffix = (numberFormat instanceof DecimalFormat) ? ", use "
|
||||
+ ((DecimalFormat) numberFormat).toPattern() : "";
|
||||
throw new IllegalArgumentException(
|
||||
"Number format is invalid: [" + value + "], use " + suffix, ex);
|
||||
}
|
||||
catch (ClassCastException ex) {
|
||||
throw new IllegalArgumentException("Number format is invalid: [" + value
|
||||
+ "], use a format with no decimal places", ex);
|
||||
}
|
||||
propertiesBuilder.addLong(StringUtils.replace(key, LONG_TYPE, ""), result);
|
||||
}
|
||||
else if (StringUtils.endsWithIgnoreCase(key, STRING_TYPE)) {
|
||||
propertiesBuilder.addString(StringUtils.replace(key, STRING_TYPE, ""), value);
|
||||
}
|
||||
else {
|
||||
propertiesBuilder.addString(key.toString(), value.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return propertiesBuilder.toJobParameters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the same suffixes to create properties (omitting the string suffix
|
||||
* because it is the default).
|
||||
*
|
||||
* @see org.springframework.batch.core.runtime.JobParametersFactory#getProperties(org.springframework.batch.core.JobParameters)
|
||||
*/
|
||||
public Properties getProperties(JobParameters params) {
|
||||
|
||||
if (params == null || params.isEmpty()) {
|
||||
return new Properties();
|
||||
}
|
||||
|
||||
Map parameters = params.getParameters();
|
||||
Properties result = new Properties();
|
||||
for (Iterator iterator = parameters.keySet().iterator(); iterator.hasNext();) {
|
||||
String key = (String) iterator.next();
|
||||
Object value = parameters.get(key);
|
||||
if (value instanceof Date) {
|
||||
result.setProperty(key + DATE_TYPE, dateFormat.format(value));
|
||||
}
|
||||
else if (value instanceof Long) {
|
||||
result.setProperty(key + LONG_TYPE, numberFormat.format(value));
|
||||
}
|
||||
else {
|
||||
result.setProperty(key, "" + value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for injecting a date format.
|
||||
* @param dateFormat a {@link DateFormat}, defaults to "yyyy/MM/dd"
|
||||
*/
|
||||
public void setDateFormat(DateFormat dateFormat) {
|
||||
this.dateFormat = dateFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link NumberFormat}. Used to parse longs, so must
|
||||
* not contain decimal place (e.g. use "#" or "#,###").
|
||||
*
|
||||
* @param numberFormat the {@link NumberFormat} to set
|
||||
*/
|
||||
public void setNumberFormat(NumberFormat numberFormat) {
|
||||
this.numberFormat = numberFormat;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.springframework.batch.execution.launch.support;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* This interface should be implemented when an environment calling the batch famework has specific
|
||||
* requirements regarding the process return codes.
|
||||
*
|
||||
* @param The type of returncode expected by the environment
|
||||
* @author Stijn Maller
|
||||
* @author Lucas Ward
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public interface ExitCodeMapper {
|
||||
|
||||
static int JVM_EXITCODE_COMPLETED = 0;
|
||||
static int JVM_EXITCODE_GENERIC_ERROR = 1;
|
||||
static int JVM_EXITCODE_JOB_ERROR = 2;
|
||||
public static final String NO_SUCH_JOB = "NO_SUCH_JOB";
|
||||
public static final String JOB_NOT_PROVIDED = "JOB_NOT_PROVIDED";
|
||||
|
||||
/**
|
||||
* Transform the exitcode known by the batchframework into an exitcode in the
|
||||
* format of the calling environment.
|
||||
* @param exitCode The exitcode which is used internally by the batch framework.
|
||||
* @return The corresponding exitcode as known by the calling environment.
|
||||
*/
|
||||
public int getExitCode(String exitCode);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.launch.support;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.execution.launch.JobLauncher;
|
||||
|
||||
/**
|
||||
* Interface to expose for remote management of jobs. Similar to
|
||||
* {@link JobLauncher}, but replaces {@link JobExecution} and
|
||||
* {@link JobIdentifier} with Strings in return types and method parameters, so
|
||||
* it can be inspected by remote clients like the jconsole from the JRE without
|
||||
* any links to Spring Batch.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public interface ExportedJobLauncher {
|
||||
|
||||
/**
|
||||
* Launch a job with the given name.
|
||||
*
|
||||
* @param name the name of the job to launch
|
||||
* @return a representation of the {@link JobExecution} returned by a
|
||||
* {@link JobLauncher}.
|
||||
*/
|
||||
String run(String name);
|
||||
|
||||
/**
|
||||
* Launch a job with the given name and parameters.
|
||||
*
|
||||
* @param name the name of the job to launch
|
||||
* @return a representation of the {@link JobExecution} returned by a
|
||||
* {@link JobLauncher}.
|
||||
*/
|
||||
String run(String name, String params);
|
||||
|
||||
/**
|
||||
* Stop all running jobs.
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* Enquire if any jobs launched here are still running.
|
||||
*
|
||||
* @return true if any jobs are running.
|
||||
*/
|
||||
boolean isRunning();
|
||||
|
||||
/**
|
||||
* Query statistics of currently executing jobs.
|
||||
*
|
||||
* @return properties representing last known state of currently executing
|
||||
* jobs
|
||||
*/
|
||||
public Properties getStatistics();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.launch.support;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link SystemExiter} interface that calls the standards
|
||||
* System.exit method. It should be noted that there will be no unit tests for
|
||||
* this class, since there is only one line of actual code, that would only be
|
||||
* testable by mocking System or Runtime.
|
||||
*
|
||||
* @author Lucas Ward
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class JvmSystemExiter implements SystemExiter {
|
||||
|
||||
/**
|
||||
* Delegate call to System.exit() with the argument provided. Do not use
|
||||
* this at home children!
|
||||
*
|
||||
* @see org.springframework.batch.execution.launch.support.SystemExiter#exit(int)
|
||||
*/
|
||||
public void exit(int status) {
|
||||
System.exit(status);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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.launch.support;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.core.runtime.JobParametersFactory;
|
||||
|
||||
/**
|
||||
* @author Lucas Ward
|
||||
*
|
||||
*/
|
||||
public class ScheduledJobParametersFactory implements JobParametersFactory {
|
||||
|
||||
public static String SCHEDULE_DATE_KEY = "schedule.date";
|
||||
|
||||
private DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.core.runtime.JobParametersFactory#getJobParameters(java.util.Properties)
|
||||
*/
|
||||
public JobParameters getJobParameters(Properties props) {
|
||||
|
||||
if(props == null || props.isEmpty()){
|
||||
return new JobParameters();
|
||||
}
|
||||
|
||||
JobParametersBuilder propertiesBuilder = new JobParametersBuilder();
|
||||
|
||||
for (Iterator it = props.entrySet().iterator(); it.hasNext();) {
|
||||
Entry entry = (Entry) it.next();
|
||||
if (entry.getKey().equals(SCHEDULE_DATE_KEY)) {
|
||||
Date scheduleDate;
|
||||
try {
|
||||
scheduleDate = dateFormat.parse(entry.getValue().toString());
|
||||
}
|
||||
catch (ParseException ex) {
|
||||
throw new IllegalArgumentException("Date format is invalid: [" + entry.getValue() + "]",
|
||||
ex);
|
||||
}
|
||||
propertiesBuilder.addDate(entry.getKey().toString(), scheduleDate);
|
||||
}
|
||||
else {
|
||||
propertiesBuilder.addString(entry.getKey().toString(), entry.getValue().toString());
|
||||
}
|
||||
}
|
||||
|
||||
return propertiesBuilder.toJobParameters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert schedule date to Date, and assume all other parameters can be
|
||||
* represented by their default string value.
|
||||
*
|
||||
* @see org.springframework.batch.core.runtime.JobParametersFactory#getProperties(org.springframework.batch.core.JobParameters)
|
||||
*/
|
||||
public Properties getProperties(JobParameters params) {
|
||||
|
||||
if(params == null || params.isEmpty()){
|
||||
return new Properties();
|
||||
}
|
||||
|
||||
Map parameters = params.getParameters();
|
||||
Properties result = new Properties();
|
||||
for (Iterator iterator = parameters.keySet().iterator(); iterator.hasNext();) {
|
||||
String key = (String) iterator.next();
|
||||
Object value = parameters.get(key);
|
||||
if (key.equals(SCHEDULE_DATE_KEY)) {
|
||||
result.setProperty(key, dateFormat.format(value));
|
||||
} else {
|
||||
result.setProperty(key,""+value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for injecting a date format.
|
||||
* @param dateFormat a {@link DateFormat}, defaults to "yyyy/MM/dd"
|
||||
*/
|
||||
public void setDateFormat(DateFormat dateFormat) {
|
||||
this.dateFormat = dateFormat;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* 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.launch.support;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.configuration.JobLocator;
|
||||
import org.springframework.batch.core.repository.JobRestartException;
|
||||
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
|
||||
import org.springframework.batch.core.repository.NoSuchJobException;
|
||||
import org.springframework.batch.core.runtime.JobParametersFactory;
|
||||
import org.springframework.batch.execution.launch.JobLauncher;
|
||||
import org.springframework.batch.support.PropertiesConverter;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class SimpleExportedJobLauncher implements ExportedJobLauncher, InitializingBean {
|
||||
|
||||
private JobLauncher launcher;
|
||||
|
||||
private JobLocator jobLocator;
|
||||
|
||||
private Map registry = new HashMap();
|
||||
|
||||
private JobParametersFactory jobParametersFactory = new DefaultJobParametersFactory();
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
|
||||
*/
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(launcher, "JobLauncher must be provided.");
|
||||
Assert.notNull(jobLocator, "JobLocator must be provided.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link JobLauncher}.
|
||||
* @param launcher the launcher to set
|
||||
*/
|
||||
public void setLauncher(JobLauncher launcher) {
|
||||
this.launcher = launcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the JobLocator.
|
||||
* @param jobLocator the jobLocator to set
|
||||
*/
|
||||
public void setJobLocator(JobLocator jobLocator) {
|
||||
this.jobLocator = jobLocator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the JobParametersFactory.
|
||||
* @param jobParametersFactory the jobParametersFactory to set
|
||||
*/
|
||||
public void setJobParametersFactory(JobParametersFactory jobParametersFactory) {
|
||||
this.jobParametersFactory = jobParametersFactory;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.execution.bootstrap.support.ExportedJobLauncher#getStatistics()
|
||||
*/
|
||||
public Properties getStatistics() {
|
||||
Properties result = new Properties();
|
||||
int i = 0;
|
||||
for (Iterator iterator = registry.keySet().iterator(); iterator.hasNext();) {
|
||||
String key = (String) iterator.next();
|
||||
JobExecution execution = (JobExecution) registry.get(key);
|
||||
addStatistics(result, execution, "job" + i + ".");
|
||||
i++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param result
|
||||
* @param execution
|
||||
*/
|
||||
private void addStatistics(Properties result, JobExecution execution, String prefix) {
|
||||
int i = 0;
|
||||
for (Iterator iterator = execution.getStepExecutions().iterator(); iterator.hasNext();) {
|
||||
StepExecution stepExecution = (StepExecution) iterator.next();
|
||||
Properties statistics = stepExecution.getExecutionContext().getProperties();
|
||||
for (Iterator iter = statistics.keySet().iterator(); iter.hasNext();) {
|
||||
String key = (String) iter.next();
|
||||
result.setProperty(prefix + "step" + i + "." + key, statistics.getProperty(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.execution.bootstrap.support.ExportedJobLauncher#isRunning()
|
||||
*/
|
||||
public boolean isRunning() {
|
||||
for (Iterator iterator = registry.keySet().iterator(); iterator.hasNext();) {
|
||||
String key = (String) iterator.next();
|
||||
JobExecution execution = (JobExecution) registry.get(key);
|
||||
if (execution.isRunning()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.execution.bootstrap.support.ExportedJobLauncher#run(java.lang.String)
|
||||
*/
|
||||
public String run(String name) {
|
||||
return run(name, null);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.execution.bootstrap.support.ExportedJobLauncher#run(java.lang.String,
|
||||
* java.lang.String)
|
||||
*/
|
||||
public String run(String name, String params) {
|
||||
|
||||
Job job;
|
||||
try {
|
||||
job = jobLocator.getJob(name);
|
||||
}
|
||||
catch (NoSuchJobException e) {
|
||||
return e.getClass().getName() + ": " + e.getMessage();
|
||||
}
|
||||
|
||||
JobParameters jobParameters = new JobParameters();
|
||||
if (params != null) {
|
||||
jobParameters = jobParametersFactory.getJobParameters(PropertiesConverter.stringToProperties(params));
|
||||
}
|
||||
|
||||
JobExecution execution;
|
||||
try {
|
||||
execution = launcher.run(job, jobParameters);
|
||||
}
|
||||
catch (JobExecutionAlreadyRunningException e) {
|
||||
return e.getClass().getName() + ": " + e.getMessage();
|
||||
}
|
||||
catch (JobRestartException e) {
|
||||
return e.getClass().getName() + ": " + e.getMessage();
|
||||
}
|
||||
|
||||
registry.put(name + params, execution);
|
||||
|
||||
return execution.toString();
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.execution.bootstrap.support.ExportedJobLauncher#stop()
|
||||
*/
|
||||
public void stop() {
|
||||
for (Iterator iterator = registry.keySet().iterator(); iterator.hasNext();) {
|
||||
String key = (String) iterator.next();
|
||||
JobExecution execution = (JobExecution) registry.get(key);
|
||||
execution.stop();
|
||||
}
|
||||
registry.clear();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.launch.support;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.batch.repeat.ExitStatus;
|
||||
|
||||
|
||||
/**
|
||||
* An implementation of {@link ExitCodeMapper} that can be configured
|
||||
* through a map from batch exit codes (String) to integer results.
|
||||
*
|
||||
* @author Stijn Maller
|
||||
* @author Lucas Ward
|
||||
* @author Dave Syer
|
||||
*/
|
||||
|
||||
public class SimpleJvmExitCodeMapper implements ExitCodeMapper {
|
||||
|
||||
protected Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private Map mapping;
|
||||
|
||||
public SimpleJvmExitCodeMapper(){
|
||||
mapping = new HashMap();
|
||||
mapping.put(ExitStatus.FINISHED.getExitCode(),
|
||||
new Integer(JVM_EXITCODE_COMPLETED));
|
||||
mapping.put(ExitStatus.FAILED.getExitCode(),
|
||||
new Integer(JVM_EXITCODE_GENERIC_ERROR));
|
||||
mapping.put(ExitCodeMapper.JOB_NOT_PROVIDED,
|
||||
new Integer(JVM_EXITCODE_JOB_ERROR));
|
||||
mapping.put(ExitCodeMapper.NO_SUCH_JOB,
|
||||
new Integer(JVM_EXITCODE_JOB_ERROR));
|
||||
}
|
||||
|
||||
public Map getMapping() {
|
||||
return mapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supply the ExitCodeMappings
|
||||
* @param exitCodeMap A set of mappings between environment specific exit codes
|
||||
* and batch framework internal exit codes
|
||||
*/
|
||||
public void setMapping(Map exitCodeMap) {
|
||||
mapping.putAll(exitCodeMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the JVM exitcode that matches a certain Batch Framework Exitcode
|
||||
* @param exitCode The exitcode of the Batch Job as known by the Batch Framework
|
||||
* @return The exitCode of the Batch Job as known by the JVM
|
||||
*/
|
||||
public int getExitCode(String exitCode) {
|
||||
|
||||
Integer statusCode = null;
|
||||
|
||||
try{
|
||||
statusCode = (Integer)mapping.get(exitCode);
|
||||
}
|
||||
catch(RuntimeException ex){
|
||||
//We still need to return an exit code, even if there is an issue with
|
||||
//the mapper.
|
||||
logger.fatal("Error mapping exit code, generic exit code returned.", ex);
|
||||
}
|
||||
|
||||
return (statusCode != null) ? statusCode.intValue() : JVM_EXITCODE_GENERIC_ERROR;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.launch.support;
|
||||
|
||||
/**
|
||||
* Interface for exiting the JVM. This abstraction is only
|
||||
* useful in order to allow classes that make System.exit calls
|
||||
* to be testable, since calling System.exit during a unit
|
||||
* test would cause the entire jvm to finish.
|
||||
*
|
||||
* @author Lucas Ward
|
||||
*
|
||||
*/
|
||||
public interface SystemExiter {
|
||||
|
||||
/**
|
||||
* Terminate the currently running Java Virtual Machine.
|
||||
*
|
||||
* @param status exit status.
|
||||
* @throws SecurityException
|
||||
* if a security manager exists and its <code>checkExit</code>
|
||||
* method doesn't allow exit with the specified status.
|
||||
* @see System.exit
|
||||
*/
|
||||
void exit(int status);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Support classes for use in bootstrap implementations or configurations.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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.execution.listener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.batch.core.ChunkListener;
|
||||
|
||||
/**
|
||||
* @author Lucas Ward
|
||||
*
|
||||
*/
|
||||
public class CompositeChunkListener implements ChunkListener {
|
||||
|
||||
private List listeners = new ArrayList();
|
||||
|
||||
/**
|
||||
* Public setter for the listeners.
|
||||
*
|
||||
* @param listeners
|
||||
*/
|
||||
public void setListeners(ChunkListener[] listeners) {
|
||||
this.listeners = Arrays.asList(listeners);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register additional listener.
|
||||
*
|
||||
* @param stepListener
|
||||
*/
|
||||
public void register(ChunkListener chunkListener) {
|
||||
if (!listeners.contains(chunkListener)) {
|
||||
listeners.add(chunkListener);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.core.domain.ChunkListener#afterChunk()
|
||||
*/
|
||||
public void afterChunk() {
|
||||
for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
|
||||
ChunkListener listener = (ChunkListener) iterator.next();
|
||||
listener.afterChunk();
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.core.domain.ChunkListener#beforeChunk()
|
||||
*/
|
||||
public void beforeChunk() {
|
||||
for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
|
||||
ChunkListener listener = (ChunkListener) iterator.next();
|
||||
listener.beforeChunk();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.listener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.batch.core.ChunkListener;
|
||||
import org.springframework.batch.core.ItemReadListener;
|
||||
|
||||
/**
|
||||
* @author Lucas Ward
|
||||
*
|
||||
*/
|
||||
public class CompositeItemReadListener implements ItemReadListener {
|
||||
|
||||
private List listeners = new ArrayList();
|
||||
|
||||
/**
|
||||
* Public setter for the listeners.
|
||||
*
|
||||
* @param listeners
|
||||
*/
|
||||
public void setListeners(ChunkListener[] listeners) {
|
||||
this.listeners = Arrays.asList(listeners);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register additional listener.
|
||||
*
|
||||
* @param itemReaderListener
|
||||
*/
|
||||
public void register(ItemReadListener itemReaderListener) {
|
||||
if (!listeners.contains(itemReaderListener)) {
|
||||
listeners.add(itemReaderListener);
|
||||
}
|
||||
}
|
||||
|
||||
public void afterRead(Object item) {
|
||||
for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
|
||||
ItemReadListener listener = (ItemReadListener) iterator.next();
|
||||
listener.afterRead(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void beforeRead() {
|
||||
for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
|
||||
ItemReadListener listener = (ItemReadListener) iterator.next();
|
||||
listener.beforeRead();
|
||||
}
|
||||
}
|
||||
|
||||
public void onReadError(Exception ex) {
|
||||
for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
|
||||
ItemReadListener listener = (ItemReadListener) iterator.next();
|
||||
listener.onReadError(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.listener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.batch.core.ChunkListener;
|
||||
import org.springframework.batch.core.ItemWriteListener;
|
||||
|
||||
/**
|
||||
* @author Lucas Ward
|
||||
*
|
||||
*/
|
||||
public class CompositeItemWriteListener implements ItemWriteListener {
|
||||
|
||||
private List listeners = new ArrayList();
|
||||
|
||||
/**
|
||||
* Public setter for the listeners.
|
||||
*
|
||||
* @param listeners
|
||||
*/
|
||||
public void setListeners(ChunkListener[] listeners) {
|
||||
this.listeners = Arrays.asList(listeners);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register additional listener.
|
||||
*
|
||||
* @param itemReaderListener
|
||||
*/
|
||||
public void register(ItemWriteListener itemReaderListener) {
|
||||
if (!listeners.contains(itemReaderListener)) {
|
||||
listeners.add(itemReaderListener);
|
||||
}
|
||||
}
|
||||
|
||||
public void afterWrite() {
|
||||
for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
|
||||
ItemWriteListener listener = (ItemWriteListener) iterator.next();
|
||||
listener.afterWrite();
|
||||
}
|
||||
}
|
||||
|
||||
public void beforeWrite(Object item) {
|
||||
for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
|
||||
ItemWriteListener listener = (ItemWriteListener) iterator.next();
|
||||
listener.beforeWrite(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void onWriteError(Exception ex, Object item) {
|
||||
for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
|
||||
ItemWriteListener listener = (ItemWriteListener) iterator.next();
|
||||
listener.onWriteError(ex, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.listener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobListener;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class CompositeJobListener implements JobListener {
|
||||
|
||||
private List listeners = new ArrayList();
|
||||
|
||||
/**
|
||||
* Public setter for the listeners.
|
||||
*
|
||||
* @param listeners
|
||||
*/
|
||||
public void setListeners(JobListener[] listeners) {
|
||||
this.listeners = Arrays.asList(listeners);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register additional listener.
|
||||
*
|
||||
* @param jobListener
|
||||
*/
|
||||
public void register(JobListener jobListener) {
|
||||
if (!listeners.contains(jobListener)) {
|
||||
listeners.add(jobListener);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.core.domain.StepListener#close()
|
||||
*/
|
||||
public void afterJob(JobExecution jobExecution) {
|
||||
for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
|
||||
JobListener listener = (JobListener) iterator.next();
|
||||
listener.afterJob(jobExecution);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.core.domain.StepListener#open(org.springframework.batch.core.domain.JobParameters)
|
||||
*/
|
||||
public void beforeJob(JobExecution jobExecution) {
|
||||
for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
|
||||
JobListener listener = (JobListener) iterator.next();
|
||||
listener.beforeJob(jobExecution);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.listener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.StepListener;
|
||||
import org.springframework.batch.repeat.ExitStatus;
|
||||
|
||||
/**
|
||||
* @author Lucas Ward
|
||||
*
|
||||
*/
|
||||
public class CompositeStepListener implements StepListener {
|
||||
|
||||
private List listeners = new ArrayList();
|
||||
|
||||
/**
|
||||
* Public setter for the listeners.
|
||||
*
|
||||
* @param listeners
|
||||
*/
|
||||
public void setListeners(StepListener[] listeners) {
|
||||
this.listeners = Arrays.asList(listeners);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register additional listener.
|
||||
*
|
||||
* @param stepListener
|
||||
*/
|
||||
public void register(StepListener stepListener) {
|
||||
if (!listeners.contains(stepListener)) {
|
||||
listeners.add(stepListener);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.core.domain.StepListener#close()
|
||||
*/
|
||||
public ExitStatus afterStep(StepExecution stepExecution) {
|
||||
ExitStatus status = null;
|
||||
for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
|
||||
StepListener listener = (StepListener) iterator.next();
|
||||
ExitStatus close = listener.afterStep(stepExecution);
|
||||
status = status!=null ? status.and(close): close;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.core.domain.StepListener#open(org.springframework.batch.core.domain.JobParameters)
|
||||
*/
|
||||
public void beforeStep(StepExecution stepExecution) {
|
||||
for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
|
||||
StepListener listener = (StepListener) iterator.next();
|
||||
listener.beforeStep(stepExecution);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.core.domain.StepListener#onError(java.lang.Throwable)
|
||||
*/
|
||||
public ExitStatus onErrorInStep(StepExecution stepExecution, Throwable e) {
|
||||
ExitStatus status = null;
|
||||
for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
|
||||
StepListener listener = (StepListener) iterator.next();
|
||||
ExitStatus close = listener.onErrorInStep(stepExecution, e);
|
||||
status = status!=null ? status.and(close): close;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Reference implementation of the Spring Batch Core.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2002-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.batch.execution.repository;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.batch.execution.repository.dao.JdbcJobExecutionDao;
|
||||
import org.springframework.batch.execution.repository.dao.JdbcJobInstanceDao;
|
||||
import org.springframework.batch.execution.repository.dao.JdbcStepExecutionDao;
|
||||
import org.springframework.batch.execution.repository.dao.JobExecutionDao;
|
||||
import org.springframework.batch.execution.repository.dao.JobInstanceDao;
|
||||
import org.springframework.batch.execution.repository.dao.StepExecutionDao;
|
||||
import org.springframework.batch.item.database.support.DataFieldMaxValueIncrementerFactory;
|
||||
import org.springframework.batch.item.database.support.DefaultDataFieldMaxValueIncrementerFactory;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link FactoryBean} that automates the creation of a {@link SimpleJobRepository}. Requires the user
|
||||
* to describe what kind of database they are using.
|
||||
*
|
||||
* @author Ben Hale
|
||||
* @author Lucas Ward
|
||||
*/
|
||||
public class JobRepositoryFactoryBean implements FactoryBean, InitializingBean {
|
||||
|
||||
private DataSource dataSource;
|
||||
|
||||
private String databaseType;
|
||||
|
||||
private DataFieldMaxValueIncrementerFactory incrementerFactory;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
|
||||
public void setDatabaseType(String dbType) {
|
||||
this.databaseType = dbType;
|
||||
}
|
||||
|
||||
public void setIncrementerFactory(
|
||||
DataFieldMaxValueIncrementerFactory incrementerFactory) {
|
||||
this.incrementerFactory = incrementerFactory;
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(dataSource, "Datasource must not be null.");
|
||||
|
||||
if(incrementerFactory == null){
|
||||
incrementerFactory = new DefaultDataFieldMaxValueIncrementerFactory(dataSource);
|
||||
}
|
||||
|
||||
Assert.isTrue(incrementerFactory.isSupportedIncrementerType(databaseType), "Unsupported database type");
|
||||
}
|
||||
|
||||
public Object getObject() throws Exception {
|
||||
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
JobInstanceDao jobInstanceDao = createJobInstanceDao(jdbcTemplate);
|
||||
JobExecutionDao jobExecutionDao = createJobExecutionDao(jdbcTemplate);
|
||||
StepExecutionDao stepExecutionDao = createStepExecutionDao(jdbcTemplate);
|
||||
return new SimpleJobRepository(jobInstanceDao, jobExecutionDao, stepExecutionDao);
|
||||
}
|
||||
|
||||
public Class getObjectType() {
|
||||
return SimpleJobRepository.class;
|
||||
}
|
||||
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private JobInstanceDao createJobInstanceDao(JdbcTemplate jdbcTemplate) throws Exception {
|
||||
JdbcJobInstanceDao dao = new JdbcJobInstanceDao();
|
||||
dao.setJdbcTemplate(jdbcTemplate);
|
||||
dao.setJobIncrementer(incrementerFactory.getIncrementer(databaseType, "BATCH_JOB_SEQ"));
|
||||
dao.afterPropertiesSet();
|
||||
return dao;
|
||||
}
|
||||
|
||||
private JobExecutionDao createJobExecutionDao(JdbcTemplate jdbcTemplate) throws Exception {
|
||||
JdbcJobExecutionDao dao = new JdbcJobExecutionDao();
|
||||
dao.setJdbcTemplate(jdbcTemplate);
|
||||
dao.setJobExecutionIncrementer(incrementerFactory.getIncrementer(databaseType, "BATCH_JOB_EXECUTION_SEQ"));
|
||||
dao.afterPropertiesSet();
|
||||
return dao;
|
||||
}
|
||||
|
||||
private StepExecutionDao createStepExecutionDao(JdbcTemplate jdbcTemplate) throws Exception {
|
||||
JdbcStepExecutionDao dao = new JdbcStepExecutionDao();
|
||||
dao.setJdbcTemplate(jdbcTemplate);
|
||||
dao.setStepExecutionIncrementer(incrementerFactory.getIncrementer(databaseType, "BATCH_STEP_EXECUTION_SEQ"));
|
||||
dao.afterPropertiesSet();
|
||||
return dao;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.batch.execution.repository;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobInstance;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.repository.JobRestartException;
|
||||
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.execution.repository.dao.JobExecutionDao;
|
||||
import org.springframework.batch.execution.repository.dao.JobInstanceDao;
|
||||
import org.springframework.batch.execution.repository.dao.StepExecutionDao;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
*
|
||||
* <p>
|
||||
* Implementation of {@link JobRepository} that stores JobInstances,
|
||||
* JobExecutions, and StepExecutions using the injected DAOs.
|
||||
* <p>
|
||||
*
|
||||
* @author Lucas Ward
|
||||
* @author Dave Syer
|
||||
* @author Robert Kasanicky
|
||||
*
|
||||
* @see JobRepository
|
||||
* @see JobInstanceDao
|
||||
* @see JobExecutionDao
|
||||
* @see StepExecutionDao
|
||||
*
|
||||
*/
|
||||
public class SimpleJobRepository implements JobRepository {
|
||||
|
||||
private JobInstanceDao jobInstanceDao;
|
||||
|
||||
private JobExecutionDao jobExecutionDao;
|
||||
|
||||
private StepExecutionDao stepExecutionDao;
|
||||
|
||||
/**
|
||||
* Provide default constructor with low visibility in case user wants to use
|
||||
* use aop:proxy-target-class="true" for transaction interceptor.
|
||||
*/
|
||||
SimpleJobRepository() {
|
||||
}
|
||||
|
||||
public SimpleJobRepository(JobInstanceDao jobInstanceDao, JobExecutionDao jobExecutionDao,
|
||||
StepExecutionDao stepExecutionDao) {
|
||||
super();
|
||||
this.jobInstanceDao = jobInstanceDao;
|
||||
this.jobExecutionDao = jobExecutionDao;
|
||||
this.stepExecutionDao = stepExecutionDao;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Create a {@link JobExecution} based on the passed in {@link Job} and
|
||||
* {@link JobParameters}. However, unique identification of a job can only
|
||||
* come from the database, and therefore must come from JobDao by either
|
||||
* creating a new job instance or finding an existing one, which will ensure
|
||||
* that the id of the job instance is populated with the correct value.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* There are two ways in which the method determines if a job should be
|
||||
* created or an existing one should be returned. The first is
|
||||
* restartability. The {@link Job} restartable property will be checked
|
||||
* first. If it is false, a new job will be created, regardless of whether
|
||||
* or not one exists. If it is true, the {@link JobInstanceDao} will be
|
||||
* checked to determine if the job already exists, if it does, it's steps
|
||||
* will be populated (there must be at least 1) and a new
|
||||
* {@link JobExecution} will be returned. If no job instance is found, a new
|
||||
* one will be created.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* A check is made to see if any job executions are already running, and an
|
||||
* exception will be thrown if one is detected. To detect a running job
|
||||
* execution we use the {@link JobExecutionDao}:
|
||||
* <ol>
|
||||
* <li>First we find all jobs which match the given {@link JobParameters}
|
||||
* and job name</li>
|
||||
* <li>What happens then depends on how many existing job instances we
|
||||
* find:
|
||||
* <ul>
|
||||
* <li>If there are none, or the {@link Job} is marked restartable, then we
|
||||
* create a new {@link JobInstance}</li>
|
||||
* <li>If there is more than one and the {@link Job} is not marked as
|
||||
* restartable, it is an error. This could be caused by a job whose
|
||||
* restartable flag has changed to be more strict (true not false)
|
||||
* <em>after</em> it has been executed at least once.</li>
|
||||
* <li>If there is precisely one existing {@link JobInstance} then we check
|
||||
* the {@link JobExecution} instances for that job, and if any of them tells
|
||||
* us it is running (see {@link JobExecution#isRunning()}) then it is an
|
||||
* error.</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* </ol>
|
||||
* If this method is run in a transaction (as it normally would be) with
|
||||
* isolation level at {@link Isolation#REPEATABLE_READ} or better, then this
|
||||
* method should block if another transaction is already executing it (for
|
||||
* the same {@link JobParameters} and job name). The first transaction to
|
||||
* complete in this scenario obtains a valid {@link JobExecution}, and
|
||||
* others throw {@link JobExecutionAlreadyRunningException} (or timeout).
|
||||
* There are no such guarantees if the {@link JobDao} does not respect the
|
||||
* transaction isolation levels (e.g. if using a non-relational data-store,
|
||||
* or if the platform does not support the higher isolation levels).
|
||||
* </p>
|
||||
*
|
||||
* @see JobRepository#createJobExecution(Job, JobParameters)
|
||||
*
|
||||
*/
|
||||
public JobExecution createJobExecution(Job job, JobParameters jobParameters)
|
||||
throws JobExecutionAlreadyRunningException, JobRestartException {
|
||||
|
||||
Assert.notNull(job, "Job must not be null.");
|
||||
Assert.notNull(jobParameters, "JobParameters must not be null.");
|
||||
|
||||
/*
|
||||
* Find all jobs matching the runtime information.
|
||||
*
|
||||
* If this method is transactional, and the isolation level is
|
||||
* REPEATABLE_READ or better, another launcher trying to start the same
|
||||
* job in another thread or process will block until this transaction
|
||||
* has finished.
|
||||
*/
|
||||
|
||||
JobInstance jobInstance = jobInstanceDao.getJobInstance(job, jobParameters);
|
||||
|
||||
// existing job instance found
|
||||
if (jobInstance != null) {
|
||||
if (!job.isRestartable()) {
|
||||
throw new JobRestartException("JobInstance already exists and is not restartable");
|
||||
}
|
||||
|
||||
List executions = jobExecutionDao.findJobExecutions(jobInstance);
|
||||
|
||||
// check for running executions and find the last started
|
||||
for (Iterator iterator = executions.iterator(); iterator.hasNext();) {
|
||||
JobExecution execution = (JobExecution) iterator.next();
|
||||
if (execution.isRunning()) {
|
||||
throw new JobExecutionAlreadyRunningException("A job execution for this job is already running: "
|
||||
+ jobInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// no job found, create one
|
||||
jobInstance = jobInstanceDao.createJobInstance(job, jobParameters);
|
||||
}
|
||||
|
||||
JobExecution jobExecution = new JobExecution(jobInstance);
|
||||
|
||||
// Save the JobExecution so that it picks up an ID (useful for clients
|
||||
// monitoring asynchronous executions):
|
||||
saveOrUpdate(jobExecution);
|
||||
|
||||
return jobExecution;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Save or Update a JobExecution. A JobExecution is considered one
|
||||
* 'execution' of a particular job. Therefore, it must have it's jobId field
|
||||
* set before it is passed into this method. It also has it's own unique
|
||||
* identifer, because it must be updatable separately. If an id isn't found,
|
||||
* a new JobExecution is created, if one is found, the current row is
|
||||
* updated.
|
||||
*
|
||||
* @param JobExecution to be stored.
|
||||
* @throws IllegalArgumentException if jobExecution is null.
|
||||
*/
|
||||
public void saveOrUpdate(JobExecution jobExecution) {
|
||||
|
||||
Assert.notNull(jobExecution, "JobExecution cannot be null.");
|
||||
Assert.notNull(jobExecution.getJobId(), "JobExecution must have a Job ID set.");
|
||||
|
||||
if (jobExecution.getId() == null) {
|
||||
// existing instance
|
||||
jobExecutionDao.saveJobExecution(jobExecution);
|
||||
}
|
||||
else {
|
||||
// new execution
|
||||
jobExecutionDao.updateJobExecution(jobExecution);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save or Update the given StepExecution. If it's id is null, it will be
|
||||
* saved and an id will be set, otherwise it will be updated. It should be
|
||||
* noted that assigning an ID randomly will likely cause an exception
|
||||
* depending on the StepDao implementation.
|
||||
*
|
||||
* @param StepExecution to be saved.
|
||||
* @throws IllegalArgumentException if stepExecution is null.
|
||||
*/
|
||||
public void saveOrUpdate(StepExecution stepExecution) {
|
||||
|
||||
Assert.notNull(stepExecution, "StepExecution cannot be null.");
|
||||
Assert.notNull(stepExecution.getStepName(), "StepExecution's step name cannot be null.");
|
||||
Assert.notNull(stepExecution.getJobExecutionId(), "StepExecution must belong to persisted JobExecution");
|
||||
|
||||
if (stepExecution.getId() == null) {
|
||||
stepExecutionDao.saveStepExecution(stepExecution);
|
||||
}
|
||||
else {
|
||||
// existing execution, update
|
||||
stepExecutionDao.updateStepExecution(stepExecution);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.core.repository.JobRepository#saveOrUpdateExecutionContext(org.springframework.batch.core.domain.StepExecution)
|
||||
*/
|
||||
public void saveOrUpdateExecutionContext(StepExecution stepExecution) {
|
||||
saveOrUpdate(stepExecution);
|
||||
stepExecutionDao.saveOrUpdateExecutionContext(stepExecution);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the last execution of the step within given job instance
|
||||
*/
|
||||
public StepExecution getLastStepExecution(JobInstance jobInstance, Step step) {
|
||||
List jobExecutions = jobExecutionDao.findJobExecutions(jobInstance);
|
||||
List stepExecutions = new ArrayList(jobExecutions.size());
|
||||
for (Iterator iterator = jobExecutions.iterator(); iterator.hasNext();) {
|
||||
JobExecution jobExecution = (JobExecution) iterator.next();
|
||||
StepExecution stepExecution = stepExecutionDao.getStepExecution(jobExecution, step);
|
||||
if (stepExecution != null) {
|
||||
stepExecutions.add(stepExecution);
|
||||
}
|
||||
}
|
||||
StepExecution latest = null;
|
||||
for (Iterator iterator = stepExecutions.iterator(); iterator.hasNext();) {
|
||||
StepExecution stepExecution = (StepExecution) iterator.next();
|
||||
if (latest == null) {
|
||||
latest = stepExecution;
|
||||
}
|
||||
if (latest.getStartTime().getTime() < stepExecution.getStartTime().getTime()) {
|
||||
latest = stepExecution;
|
||||
}
|
||||
}
|
||||
return latest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return number of executions of the step within given job instance
|
||||
*/
|
||||
public int getStepExecutionCount(JobInstance jobInstance, Step step) {
|
||||
int count = 0;
|
||||
List jobExecutions = jobExecutionDao.findJobExecutions(jobInstance);
|
||||
for (Iterator iterator = jobExecutions.iterator(); iterator.hasNext();) {
|
||||
JobExecution jobExecution = (JobExecution) iterator.next();
|
||||
if (stepExecutionDao.getStepExecution(jobExecution, step) != null) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.springframework.batch.execution.repository.dao;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.jdbc.core.JdbcOperations;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Encapsulates common functionality needed by JDBC batch metadata DAOs -
|
||||
* provides jdbcTemplate for subclasses and handles table prefixes.
|
||||
*
|
||||
* @author Robert Kasanicky
|
||||
*/
|
||||
public abstract class AbstractJdbcBatchMetadataDao implements InitializingBean {
|
||||
|
||||
/**
|
||||
* Default value for the table prefix property.
|
||||
*/
|
||||
public static final String DEFAULT_TABLE_PREFIX = "BATCH_";
|
||||
|
||||
private String tablePrefix = DEFAULT_TABLE_PREFIX;
|
||||
|
||||
private JdbcOperations jdbcTemplate;
|
||||
|
||||
protected String getQuery(String base) {
|
||||
return StringUtils.replace(base, "%PREFIX%", tablePrefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the table prefix property. This will be prefixed to all
|
||||
* the table names before queries are executed. Defaults to
|
||||
* {@value #DEFAULT_TABLE_PREFIX}.
|
||||
*
|
||||
* @param tablePrefix the tablePrefix to set
|
||||
*/
|
||||
public void setTablePrefix(String tablePrefix) {
|
||||
this.tablePrefix = tablePrefix;
|
||||
}
|
||||
|
||||
public void setJdbcTemplate(JdbcOperations jdbcTemplate) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
protected JdbcOperations getJdbcTemplate() {
|
||||
return jdbcTemplate;
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(jdbcTemplate);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package org.springframework.batch.execution.repository.dao;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobInstance;
|
||||
import org.springframework.batch.repeat.ExitStatus;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Jdbc implementation of {@link JobExecutionDao}. Uses sequences (via Spring's
|
||||
* {@link DataFieldMaxValueIncrementer} abstraction) to create all primary keys
|
||||
* before inserting a new row. Objects are checked to ensure all mandatory
|
||||
* fields to be stored are not null. If any are found to be null, an
|
||||
* IllegalArgumentException will be thrown. This could be left to JdbcTemplate,
|
||||
* however, the exception will be fairly vague, and fails to highlight which
|
||||
* field caused the exception.
|
||||
*
|
||||
* @author Lucas Ward
|
||||
* @author Dave Syer
|
||||
* @author Robert Kasanicky
|
||||
*/
|
||||
public class JdbcJobExecutionDao extends AbstractJdbcBatchMetadataDao implements JobExecutionDao, InitializingBean {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(JdbcJobExecutionDao.class);
|
||||
|
||||
private static final int EXIT_MESSAGE_LENGTH = 250;
|
||||
|
||||
private static final String GET_JOB_EXECUTION_COUNT = "SELECT count(JOB_EXECUTION_ID) from %PREFIX%JOB_EXECUTION "
|
||||
+ "where JOB_INSTANCE_ID = ?";
|
||||
|
||||
private static final String SAVE_JOB_EXECUTION = "INSERT into %PREFIX%JOB_EXECUTION(JOB_EXECUTION_ID, JOB_INSTANCE_ID, START_TIME, "
|
||||
+ "END_TIME, STATUS, CONTINUABLE, EXIT_CODE, EXIT_MESSAGE) values (?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
private static final String CHECK_JOB_EXECUTION_EXISTS = "SELECT COUNT(*) FROM %PREFIX%JOB_EXECUTION WHERE JOB_EXECUTION_ID = ?";
|
||||
|
||||
private static final String UPDATE_JOB_EXECUTION = "UPDATE %PREFIX%JOB_EXECUTION set START_TIME = ?, END_TIME = ?, "
|
||||
+ " STATUS = ?, CONTINUABLE = ?, EXIT_CODE = ?, EXIT_MESSAGE = ? where JOB_EXECUTION_ID = ?";
|
||||
|
||||
private static final String FIND_JOB_EXECUTIONS = "SELECT JOB_EXECUTION_ID, START_TIME, END_TIME, STATUS, CONTINUABLE, EXIT_CODE, EXIT_MESSAGE from %PREFIX%JOB_EXECUTION"
|
||||
+ " where JOB_INSTANCE_ID = ?";
|
||||
|
||||
private static final String GET_LAST_EXECUTION = "SELECT JOB_EXECUTION_ID, START_TIME, END_TIME, STATUS, CONTINUABLE, EXIT_CODE, EXIT_MESSAGE from %PREFIX%JOB_EXECUTION"
|
||||
+ " where JOB_INSTANCE_ID = ? and START_TIME = (SELECT max(START_TIME) from %PREFIX%JOB_EXECUTION where JOB_INSTANCE_ID = ?)";
|
||||
|
||||
private DataFieldMaxValueIncrementer jobExecutionIncrementer;
|
||||
|
||||
public List findJobExecutions(final JobInstance job) {
|
||||
|
||||
Assert.notNull(job, "Job cannot be null.");
|
||||
Assert.notNull(job.getId(), "Job Id cannot be null.");
|
||||
|
||||
return getJdbcTemplate().query(getQuery(FIND_JOB_EXECUTIONS), new Object[] { job.getId() },
|
||||
new JobExecutionRowMapper(job));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see JobDao#getJobExecutionCount(JobInstance)
|
||||
* @throws IllegalArgumentException if jobId is null.
|
||||
*/
|
||||
public int getJobExecutionCount(JobInstance jobInstance) {
|
||||
Long jobId = jobInstance.getId();
|
||||
Assert.notNull(jobId, "JobId cannot be null");
|
||||
|
||||
Object[] parameters = new Object[] { jobId };
|
||||
|
||||
return getJdbcTemplate().queryForInt(getQuery(GET_JOB_EXECUTION_COUNT), parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* SQL implementation using Sequences via the Spring incrementer
|
||||
* abstraction. Once a new id has been obtained, the JobExecution is saved
|
||||
* via a SQL INSERT statement.
|
||||
*
|
||||
* @see JobDao#saveJobExecution(JobExecution)
|
||||
* @throws IllegalArgumentException if jobExecution is null, as well as any
|
||||
* of it's fields to be persisted.
|
||||
*/
|
||||
public void saveJobExecution(JobExecution jobExecution) {
|
||||
|
||||
validateJobExecution(jobExecution);
|
||||
|
||||
jobExecution.setId(new Long(jobExecutionIncrementer.nextLongValue()));
|
||||
Object[] parameters = new Object[] { jobExecution.getId(), jobExecution.getJobId(),
|
||||
jobExecution.getStartTime(), jobExecution.getEndTime(), jobExecution.getStatus().toString(),
|
||||
jobExecution.getExitStatus().isContinuable() ? "Y" : "N", jobExecution.getExitStatus().getExitCode(),
|
||||
jobExecution.getExitStatus().getExitDescription() };
|
||||
getJdbcTemplate().update(
|
||||
getQuery(SAVE_JOB_EXECUTION),
|
||||
parameters,
|
||||
new int[] { Types.INTEGER, Types.INTEGER, Types.TIMESTAMP, Types.TIMESTAMP, Types.VARCHAR, Types.CHAR,
|
||||
Types.VARCHAR, Types.VARCHAR });
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate JobExecution. At a minimum, JobId, StartTime, EndTime, and
|
||||
* Status cannot be null.
|
||||
*
|
||||
* @param jobExecution
|
||||
* @throws IllegalArgumentException
|
||||
*/
|
||||
private void validateJobExecution(JobExecution jobExecution) {
|
||||
|
||||
Assert.notNull(jobExecution);
|
||||
Assert.notNull(jobExecution.getJobId(), "JobExecution Job-Id cannot be null.");
|
||||
Assert.notNull(jobExecution.getStatus(), "JobExecution status cannot be null.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Update given JobExecution using a SQL UPDATE statement. The JobExecution
|
||||
* is first checked to ensure all fields are not null, and that it has an
|
||||
* ID. The database is then queried to ensure that the ID exists, which
|
||||
* ensures that it is valid.
|
||||
*
|
||||
* @see JobDao#updateJobExecution(JobExecution)
|
||||
*/
|
||||
public void updateJobExecution(JobExecution jobExecution) {
|
||||
|
||||
validateJobExecution(jobExecution);
|
||||
|
||||
String exitDescription = jobExecution.getExitStatus().getExitDescription();
|
||||
if (exitDescription != null && exitDescription.length() > EXIT_MESSAGE_LENGTH) {
|
||||
exitDescription = exitDescription.substring(0, EXIT_MESSAGE_LENGTH);
|
||||
logger.debug("Truncating long message before update of JobExecution: " + jobExecution);
|
||||
}
|
||||
Object[] parameters = new Object[] { jobExecution.getStartTime(), jobExecution.getEndTime(),
|
||||
jobExecution.getStatus().toString(), jobExecution.getExitStatus().isContinuable() ? "Y" : "N",
|
||||
jobExecution.getExitStatus().getExitCode(), exitDescription, jobExecution.getId() };
|
||||
|
||||
if (jobExecution.getId() == null) {
|
||||
throw new IllegalArgumentException("JobExecution ID cannot be null. JobExecution must be saved "
|
||||
+ "before it can be updated.");
|
||||
}
|
||||
|
||||
// Check if given JobExecution's Id already exists, if none is found it
|
||||
// is invalid and
|
||||
// an exception should be thrown.
|
||||
if (getJdbcTemplate().queryForInt(getQuery(CHECK_JOB_EXECUTION_EXISTS), new Object[] { jobExecution.getId() }) != 1) {
|
||||
throw new NoSuchObjectException("Invalid JobExecution, ID " + jobExecution.getId()
|
||||
+ " not found.");
|
||||
}
|
||||
|
||||
getJdbcTemplate().update(
|
||||
getQuery(UPDATE_JOB_EXECUTION),
|
||||
parameters,
|
||||
new int[] { Types.TIMESTAMP, Types.TIMESTAMP, Types.VARCHAR, Types.CHAR, Types.VARCHAR, Types.VARCHAR,
|
||||
Types.INTEGER });
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for {@link DataFieldMaxValueIncrementer} to be used when
|
||||
* generating primary keys for {@link JobExecution} instances.
|
||||
*
|
||||
* @param jobExecutionIncrementer the {@link DataFieldMaxValueIncrementer}
|
||||
*/
|
||||
public void setJobExecutionIncrementer(DataFieldMaxValueIncrementer jobExecutionIncrementer) {
|
||||
this.jobExecutionIncrementer = jobExecutionIncrementer;
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
super.afterPropertiesSet();
|
||||
Assert.notNull(jobExecutionIncrementer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-usable mapper for {@link JobExecution} instances.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
private static class JobExecutionRowMapper implements RowMapper {
|
||||
|
||||
private JobInstance job;
|
||||
|
||||
public JobExecutionRowMapper(JobInstance job) {
|
||||
super();
|
||||
this.job = job;
|
||||
}
|
||||
|
||||
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
JobExecution jobExecution = new JobExecution(job);
|
||||
jobExecution.setId(new Long(rs.getLong(1)));
|
||||
jobExecution.setStartTime(rs.getTimestamp(2));
|
||||
jobExecution.setEndTime(rs.getTimestamp(3));
|
||||
jobExecution.setStatus(BatchStatus.getStatus(rs.getString(4)));
|
||||
jobExecution.setExitStatus(new ExitStatus("Y".equals(rs.getString(5)), rs.getString(6), rs.getString(7)));
|
||||
return jobExecution;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public JobExecution getLastJobExecution(JobInstance jobInstance) {
|
||||
|
||||
Long id = jobInstance.getId();
|
||||
|
||||
List executions = getJdbcTemplate().query(getQuery(GET_LAST_EXECUTION), new Object[] { id, id },
|
||||
new JobExecutionRowMapper(jobInstance));
|
||||
|
||||
Assert.state(executions.size() <= 1, "There must be at most one latest job execution");
|
||||
|
||||
if (executions.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return (JobExecution) executions.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
package org.springframework.batch.execution.repository.dao;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
import java.sql.Types;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobInstance;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Jdbc implementation of {@link JobInstanceDao}. Uses sequences (via Spring's
|
||||
* {@link DataFieldMaxValueIncrementer} abstraction) to create all primary keys
|
||||
* before inserting a new row. Objects are checked to ensure all mandatory
|
||||
* fields to be stored are not null. If any are found to be null, an
|
||||
* IllegalArgumentException will be thrown. This could be left to JdbcTemplate,
|
||||
* however, the exception will be fairly vague, and fails to highlight which
|
||||
* field caused the exception.
|
||||
*
|
||||
* @author Lucas Ward
|
||||
* @author Dave Syer
|
||||
* @author Robert Kasanicky
|
||||
*/
|
||||
public class JdbcJobInstanceDao extends AbstractJdbcBatchMetadataDao implements JobInstanceDao, InitializingBean {
|
||||
|
||||
private static final String CREATE_JOB_INSTANCE = "INSERT into %PREFIX%JOB_INSTANCE(JOB_INSTANCE_ID, JOB_NAME, JOB_KEY)"
|
||||
+ " values (?, ?, ?)";
|
||||
|
||||
private static final String CREATE_JOB_PARAMETERS = "INSERT into %PREFIX%JOB_PARAMS(JOB_INSTANCE_ID, KEY_NAME, TYPE_CD, "
|
||||
+ "STRING_VAL, DATE_VAL, LONG_VAL, DOUBLE_VAL) values (?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
private static final String FIND_JOBS = "SELECT JOB_INSTANCE_ID from %PREFIX%JOB_INSTANCE where JOB_NAME = ? and JOB_KEY = ?";
|
||||
|
||||
private DataFieldMaxValueIncrementer jobIncrementer;
|
||||
|
||||
/**
|
||||
* In this jdbc implementation a job id is obtained by asking the
|
||||
* jobIncrementer (which is likely a sequence) for the nextLong, and then
|
||||
* passing the Id and parameter values into an INSERT statement.
|
||||
*
|
||||
* @see JobDao#createJob(JobIdentifier)
|
||||
* @throws IllegalArgumentException if any {@link JobIdentifier} fields are
|
||||
* null.
|
||||
*/
|
||||
public JobInstance createJobInstance(Job job, JobParameters jobParameters) {
|
||||
|
||||
Assert.notNull(job, "Job must not be null.");
|
||||
Assert.hasLength(job.getName(), "Job must have a name");
|
||||
Assert.notNull(jobParameters, "JobParameters must not be null.");
|
||||
|
||||
Assert.state(getJobInstance(job, jobParameters) == null, "JobInstance must not already exist");
|
||||
|
||||
Long jobId = new Long(jobIncrementer.nextLongValue());
|
||||
Object[] parameters = new Object[] { jobId, job.getName(), createJobKey(jobParameters) };
|
||||
getJdbcTemplate().update(getQuery(CREATE_JOB_INSTANCE), parameters,
|
||||
new int[] { Types.INTEGER, Types.VARCHAR, Types.VARCHAR });
|
||||
|
||||
insertJobParameters(jobId, jobParameters);
|
||||
|
||||
JobInstance jobInstance = new JobInstance(jobId, jobParameters, job);
|
||||
return jobInstance;
|
||||
}
|
||||
|
||||
private String createJobKey(JobParameters jobParameters) {
|
||||
|
||||
Map props = jobParameters.getParameters();
|
||||
StringBuffer stringBuffer = new StringBuffer();
|
||||
for (Iterator it = props.entrySet().iterator(); it.hasNext();) {
|
||||
Entry entry = (Entry) it.next();
|
||||
stringBuffer.append(entry.toString() + ";");
|
||||
}
|
||||
|
||||
return stringBuffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method that inserts all parameters from the provided
|
||||
* JobParameters.
|
||||
*
|
||||
*/
|
||||
private void insertJobParameters(Long jobId, JobParameters jobParameters) {
|
||||
|
||||
Map parameters = jobParameters.getStringParameters();
|
||||
|
||||
if (!parameters.isEmpty()) {
|
||||
for (Iterator it = parameters.entrySet().iterator(); it.hasNext();) {
|
||||
Entry entry = (Entry) it.next();
|
||||
insertParameter(jobId, ParameterType.STRING, entry.getKey().toString(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
parameters = jobParameters.getLongParameters();
|
||||
|
||||
if (!parameters.isEmpty()) {
|
||||
for (Iterator it = parameters.entrySet().iterator(); it.hasNext();) {
|
||||
Entry entry = (Entry) it.next();
|
||||
insertParameter(jobId, ParameterType.LONG, entry.getKey().toString(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
parameters = jobParameters.getDoubleParameters();
|
||||
|
||||
if (!parameters.isEmpty()) {
|
||||
for (Iterator it = parameters.entrySet().iterator(); it.hasNext();) {
|
||||
Entry entry = (Entry) it.next();
|
||||
insertParameter(jobId, ParameterType.DOUBLE, entry.getKey().toString(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
parameters = jobParameters.getDateParameters();
|
||||
|
||||
if (!parameters.isEmpty()) {
|
||||
for (Iterator it = parameters.entrySet().iterator(); it.hasNext();) {
|
||||
Entry entry = (Entry) it.next();
|
||||
insertParameter(jobId, ParameterType.DATE, entry.getKey().toString(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method that inserts an individual records into the
|
||||
* JobParameters table.
|
||||
*/
|
||||
private void insertParameter(Long jobId, ParameterType type, String key, Object value) {
|
||||
|
||||
Object[] args = new Object[0];
|
||||
int[] argTypes = new int[] { Types.INTEGER, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.TIMESTAMP,
|
||||
Types.INTEGER, Types.DOUBLE };
|
||||
|
||||
if (type == ParameterType.STRING) {
|
||||
args = new Object[] { jobId, key, type, value, new Timestamp(0L), new Long(0), new Double(0) };
|
||||
}
|
||||
else if (type == ParameterType.LONG) {
|
||||
args = new Object[] { jobId, key, type, "", new Timestamp(0L), value, new Double(0) };
|
||||
}
|
||||
else if (type == ParameterType.DOUBLE) {
|
||||
args = new Object[] { jobId, key, type, "", new Timestamp(0L), new Long(0), value };
|
||||
}
|
||||
else if (type == ParameterType.DATE) {
|
||||
args = new Object[] { jobId, key, type, "", value, new Long(0), new Double(0) };
|
||||
}
|
||||
|
||||
getJdbcTemplate().update(getQuery(CREATE_JOB_PARAMETERS), args, argTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* The job table is queried for <strong>any</strong> jobs that match the
|
||||
* given identifier, adding them to a list via the RowMapper callback.
|
||||
*
|
||||
* @see JobDao#findJobInstances(JobIdentifier)
|
||||
* @throws IllegalArgumentException if any {@link JobIdentifier} fields are
|
||||
* null.
|
||||
*/
|
||||
public JobInstance getJobInstance(final Job job, final JobParameters jobParameters) {
|
||||
|
||||
Assert.notNull(job, "Job must not be null.");
|
||||
Assert.hasLength(job.getName(), "Job must have a name");
|
||||
Assert.notNull(jobParameters, "JobParameters must not be null.");
|
||||
|
||||
Object[] parameters = new Object[] { job.getName(), createJobKey(jobParameters) };
|
||||
|
||||
RowMapper rowMapper = new RowMapper() {
|
||||
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
JobInstance jobInstance = new JobInstance(new Long(rs.getLong(1)), jobParameters, job);
|
||||
return jobInstance;
|
||||
}
|
||||
};
|
||||
|
||||
List instances = getJdbcTemplate().query(getQuery(FIND_JOBS), parameters, rowMapper);
|
||||
|
||||
if (instances.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
Assert.state(instances.size() == 1);
|
||||
return (JobInstance) instances.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for {@link DataFieldMaxValueIncrementer} to be used when
|
||||
* generating primary keys for {@link JobInstance} instances.
|
||||
*
|
||||
* @param jobIncrementer the {@link DataFieldMaxValueIncrementer}
|
||||
*/
|
||||
public void setJobIncrementer(DataFieldMaxValueIncrementer jobIncrementer) {
|
||||
this.jobIncrementer = jobIncrementer;
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
super.afterPropertiesSet();
|
||||
Assert.notNull(jobIncrementer);
|
||||
}
|
||||
|
||||
private static class ParameterType {
|
||||
|
||||
private final String type;
|
||||
|
||||
private ParameterType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public static final ParameterType STRING = new ParameterType("STRING");
|
||||
|
||||
public static final ParameterType DATE = new ParameterType("DATE");
|
||||
|
||||
public static final ParameterType LONG = new ParameterType("LONG");
|
||||
|
||||
public static final ParameterType DOUBLE = new ParameterType("DOUBLE");
|
||||
|
||||
private static final ParameterType[] VALUES = { STRING, DATE, LONG, DOUBLE };
|
||||
|
||||
public static ParameterType getType(String typeAsString) {
|
||||
|
||||
for (int i = 0; i < VALUES.length; i++) {
|
||||
if (VALUES[i].toString().equals(typeAsString)) {
|
||||
return (ParameterType) VALUES[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,425 @@
|
||||
package org.springframework.batch.execution.repository.dao;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.apache.commons.lang.SerializationUtils;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.UnexpectedJobExecutionException;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.item.ExecutionContext;
|
||||
import org.springframework.batch.repeat.ExitStatus;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.jdbc.core.PreparedStatementCallback;
|
||||
import org.springframework.jdbc.core.RowCallbackHandler;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.support.AbstractLobCreatingPreparedStatementCallback;
|
||||
import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;
|
||||
import org.springframework.jdbc.support.lob.DefaultLobHandler;
|
||||
import org.springframework.jdbc.support.lob.LobCreator;
|
||||
import org.springframework.jdbc.support.lob.LobHandler;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Jdbc implementation of {@link StepExecutionDao}.<br/>
|
||||
*
|
||||
* Allows customization of the tables names used by Spring Batch for step meta
|
||||
* data via a prefix property.<br/>
|
||||
*
|
||||
* Uses sequences or tables (via Spring's {@link DataFieldMaxValueIncrementer}
|
||||
* abstraction) to create all primary keys before inserting a new row. All
|
||||
* objects are checked to ensure all fields to be stored are not null. If any
|
||||
* are found to be null, an IllegalArgumentException will be thrown. This could
|
||||
* be left to JdbcTemplate, however, the exception will be fairly vague, and
|
||||
* fails to highlight which field caused the exception.<br/>
|
||||
*
|
||||
* @author Lucas Ward
|
||||
* @author Dave Syer
|
||||
* @author Robert Kasanicky
|
||||
*
|
||||
* @see StepExecutionDao
|
||||
*/
|
||||
public class JdbcStepExecutionDao extends AbstractJdbcBatchMetadataDao implements StepExecutionDao, InitializingBean {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(JdbcStepExecutionDao.class);
|
||||
|
||||
private static final String FIND_STEP_EXECUTION_CONTEXT = "SELECT TYPE_CD, KEY_NAME, STRING_VAL, DOUBLE_VAL, LONG_VAL, OBJECT_VAL "
|
||||
+ "from %PREFIX%STEP_EXECUTION_CONTEXT where STEP_EXECUTION_ID = ?";
|
||||
|
||||
private static final String INSERT_STEP_EXECUTION_CONTEXT = "INSERT into %PREFIX%STEP_EXECUTION_CONTEXT(STEP_EXECUTION_ID, TYPE_CD,"
|
||||
+ " KEY_NAME, STRING_VAL, DOUBLE_VAL, LONG_VAL, OBJECT_VAL) values(?,?,?,?,?,?,?)";
|
||||
|
||||
private static final String SAVE_STEP_EXECUTION = "INSERT into %PREFIX%STEP_EXECUTION(STEP_EXECUTION_ID, VERSION, STEP_NAME, JOB_EXECUTION_ID, START_TIME, "
|
||||
+ "END_TIME, STATUS, COMMIT_COUNT, TASK_COUNT, CONTINUABLE, EXIT_CODE, EXIT_MESSAGE) "
|
||||
+ "values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
private static final String UPDATE_STEP_EXECUTION_CONTEXT = "UPDATE %PREFIX%STEP_EXECUTION_CONTEXT set "
|
||||
+ "TYPE_CD = ?, STRING_VAL = ?, DOUBLE_VAL = ?, LONG_VAL = ?, OBJECT_VAL = ? where STEP_EXECUTION_ID = ? and KEY_NAME = ?";
|
||||
|
||||
private static final String UPDATE_STEP_EXECUTION = "UPDATE %PREFIX%STEP_EXECUTION set START_TIME = ?, END_TIME = ?, "
|
||||
+ "STATUS = ?, COMMIT_COUNT = ?, TASK_COUNT = ?, CONTINUABLE = ? , EXIT_CODE = ?, "
|
||||
+ "EXIT_MESSAGE = ?, VERSION = ? where STEP_EXECUTION_ID = ? and VERSION = ?";
|
||||
|
||||
private static final String GET_STEP_EXECUTION = "SELECT STEP_EXECUTION_ID, STEP_NAME, START_TIME, END_TIME, STATUS, COMMIT_COUNT,"
|
||||
+ " TASK_COUNT, CONTINUABLE, EXIT_CODE, EXIT_MESSAGE from %PREFIX%STEP_EXECUTION where STEP_NAME = ? and JOB_EXECUTION_ID = ?";
|
||||
|
||||
private static final String CURRENT_VERSION_STEP_EXECUTION = "SELECT VERSION FROM %PREFIX%STEP_EXECUTION WHERE STEP_EXECUTION_ID=?";
|
||||
|
||||
private static final int EXIT_MESSAGE_LENGTH = 250;
|
||||
|
||||
private LobHandler lobHandler = new DefaultLobHandler();
|
||||
|
||||
private DataFieldMaxValueIncrementer stepExecutionIncrementer;
|
||||
|
||||
public ExecutionContext findExecutionContext(final StepExecution stepExecution) {
|
||||
|
||||
final Long executionId = stepExecution.getId();
|
||||
Assert.notNull(executionId, "ExecutionId must not be null.");
|
||||
|
||||
final ExecutionContext executionContext = new ExecutionContext();
|
||||
|
||||
RowCallbackHandler callback = new RowCallbackHandler() {
|
||||
|
||||
public void processRow(ResultSet rs) throws SQLException {
|
||||
|
||||
String typeCd = rs.getString("TYPE_CD");
|
||||
AttributeType type = AttributeType.getType(typeCd);
|
||||
String key = rs.getString("KEY_NAME");
|
||||
if (type == AttributeType.STRING) {
|
||||
executionContext.putString(key, rs.getString("STRING_VAL"));
|
||||
}
|
||||
else if (type == AttributeType.LONG) {
|
||||
executionContext.putLong(key, rs.getLong("LONG_VAL"));
|
||||
}
|
||||
else if (type == AttributeType.DOUBLE) {
|
||||
executionContext.putDouble(key, rs.getDouble("DOUBLE_VAL"));
|
||||
}
|
||||
else if (type == AttributeType.OBJECT) {
|
||||
executionContext.put(key, rs.getObject("OBJECT_VAL"));
|
||||
}
|
||||
else {
|
||||
throw new UnexpectedJobExecutionException("Invalid type found: [" + typeCd + "] for execution id: ["
|
||||
+ executionId + "]");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
getJdbcTemplate().query(getQuery(FIND_STEP_EXECUTION_CONTEXT), new Object[] { executionId }, callback);
|
||||
|
||||
return executionContext;
|
||||
}
|
||||
|
||||
private void insertExecutionAttribute(final Long executionId, final String key, final Object value,
|
||||
final AttributeType type) {
|
||||
PreparedStatementCallback callback = new AbstractLobCreatingPreparedStatementCallback(lobHandler) {
|
||||
|
||||
protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException,
|
||||
DataAccessException {
|
||||
|
||||
ps.setLong(1, executionId.longValue());
|
||||
ps.setString(3, key);
|
||||
if (type == AttributeType.STRING) {
|
||||
ps.setString(2, AttributeType.STRING.toString());
|
||||
ps.setString(4, value.toString());
|
||||
ps.setDouble(5, 0.0);
|
||||
ps.setLong(6, 0);
|
||||
lobCreator.setBlobAsBytes(ps, 7, null);
|
||||
}
|
||||
else if (type == AttributeType.DOUBLE) {
|
||||
ps.setString(2, AttributeType.DOUBLE.toString());
|
||||
ps.setString(4, null);
|
||||
ps.setDouble(5, ((Double) value).doubleValue());
|
||||
ps.setLong(6, 0);
|
||||
lobCreator.setBlobAsBytes(ps, 7, null);
|
||||
}
|
||||
else if (type == AttributeType.LONG) {
|
||||
ps.setString(2, AttributeType.LONG.toString());
|
||||
ps.setString(4, null);
|
||||
ps.setDouble(5, 0.0);
|
||||
ps.setLong(6, ((Long) value).longValue());
|
||||
lobCreator.setBlobAsBytes(ps, 7, null);
|
||||
}
|
||||
else {
|
||||
ps.setString(2, AttributeType.OBJECT.toString());
|
||||
ps.setString(4, null);
|
||||
ps.setDouble(5, 0.0);
|
||||
ps.setLong(6, 0);
|
||||
lobCreator.setBlobAsBytes(ps, 7, SerializationUtils.serialize((Serializable) value));
|
||||
}
|
||||
}
|
||||
};
|
||||
getJdbcTemplate().execute(getQuery(INSERT_STEP_EXECUTION_CONTEXT), callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a StepExecution. A unique id will be generated by the
|
||||
* stepExecutionIncrementor, and then set in the StepExecution. All values
|
||||
* will then be stored via an INSERT statement.
|
||||
*
|
||||
* @see StepDao#saveStepExecution(StepExecution)
|
||||
*/
|
||||
public void saveStepExecution(StepExecution stepExecution) {
|
||||
|
||||
validateStepExecution(stepExecution);
|
||||
|
||||
stepExecution.setId(new Long(stepExecutionIncrementer.nextLongValue()));
|
||||
stepExecution.incrementVersion(); // should be 0 now
|
||||
Object[] parameters = new Object[] { stepExecution.getId(), stepExecution.getVersion(),
|
||||
stepExecution.getStepName(), stepExecution.getJobExecutionId(), stepExecution.getStartTime(),
|
||||
stepExecution.getEndTime(), stepExecution.getStatus().toString(), stepExecution.getCommitCount(),
|
||||
stepExecution.getTaskCount(), stepExecution.getExitStatus().isContinuable() ? "Y" : "N",
|
||||
stepExecution.getExitStatus().getExitCode(), stepExecution.getExitStatus().getExitDescription() };
|
||||
getJdbcTemplate().update(
|
||||
getQuery(SAVE_STEP_EXECUTION),
|
||||
parameters,
|
||||
new int[] { Types.INTEGER, Types.INTEGER, Types.VARCHAR, Types.INTEGER, Types.TIMESTAMP,
|
||||
Types.TIMESTAMP, Types.VARCHAR, Types.INTEGER, Types.INTEGER, Types.CHAR, Types.VARCHAR,
|
||||
Types.VARCHAR });
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate StepExecution. At a minimum, JobId, StartTime, and Status cannot
|
||||
* be null. EndTime can be null for an unfinished job.
|
||||
*
|
||||
* @param jobExecution
|
||||
* @throws IllegalArgumentException
|
||||
*/
|
||||
private void validateStepExecution(StepExecution stepExecution) {
|
||||
Assert.notNull(stepExecution);
|
||||
Assert.notNull(stepExecution.getStepName(), "StepExecution step name cannot be null.");
|
||||
Assert.notNull(stepExecution.getStartTime(), "StepExecution start time cannot be null.");
|
||||
Assert.notNull(stepExecution.getStatus(), "StepExecution status cannot be null.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Save or update execution attributes. A lob creator must be used, since
|
||||
* any attributes that don't match a provided type must be serialized into a
|
||||
* blob.
|
||||
*
|
||||
* @see {@link LobCreator}
|
||||
*/
|
||||
public void saveOrUpdateExecutionContext(final StepExecution stepExecution) {
|
||||
|
||||
Long executionId = stepExecution.getId();
|
||||
ExecutionContext executionContext = stepExecution.getExecutionContext();
|
||||
Assert.notNull(executionId, "ExecutionId must not be null.");
|
||||
Assert.notNull(executionContext, "The ExecutionContext must not be null.");
|
||||
|
||||
for (Iterator it = executionContext.entrySet().iterator(); it.hasNext();) {
|
||||
Entry entry = (Entry) it.next();
|
||||
final String key = entry.getKey().toString();
|
||||
final Object value = entry.getValue();
|
||||
|
||||
if (value instanceof String) {
|
||||
updateExecutionAttribute(executionId, key, value, AttributeType.STRING);
|
||||
}
|
||||
else if (value instanceof Double) {
|
||||
updateExecutionAttribute(executionId, key, value, AttributeType.DOUBLE);
|
||||
}
|
||||
else if (value instanceof Long) {
|
||||
updateExecutionAttribute(executionId, key, value, AttributeType.LONG);
|
||||
}
|
||||
else {
|
||||
updateExecutionAttribute(executionId, key, value, AttributeType.OBJECT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateExecutionAttribute(final Long executionId, final String key, final Object value,
|
||||
final AttributeType type) {
|
||||
|
||||
PreparedStatementCallback callback = new AbstractLobCreatingPreparedStatementCallback(lobHandler) {
|
||||
|
||||
protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException,
|
||||
DataAccessException {
|
||||
|
||||
ps.setLong(6, executionId.longValue());
|
||||
ps.setString(7, key);
|
||||
if (type == AttributeType.STRING) {
|
||||
ps.setString(1, AttributeType.STRING.toString());
|
||||
ps.setString(2, value.toString());
|
||||
ps.setDouble(3, 0.0);
|
||||
ps.setLong(4, 0);
|
||||
lobCreator.setBlobAsBytes(ps, 5, null);
|
||||
}
|
||||
else if (type == AttributeType.DOUBLE) {
|
||||
ps.setString(1, AttributeType.DOUBLE.toString());
|
||||
ps.setString(2, null);
|
||||
ps.setDouble(3, ((Double) value).doubleValue());
|
||||
ps.setLong(4, 0);
|
||||
lobCreator.setBlobAsBytes(ps, 5, null);
|
||||
}
|
||||
else if (type == AttributeType.LONG) {
|
||||
ps.setString(1, AttributeType.LONG.toString());
|
||||
ps.setString(2, null);
|
||||
ps.setDouble(3, 0.0);
|
||||
ps.setLong(4, ((Long) value).longValue());
|
||||
lobCreator.setBlobAsBytes(ps, 5, null);
|
||||
}
|
||||
else {
|
||||
ps.setString(1, AttributeType.OBJECT.toString());
|
||||
ps.setString(2, null);
|
||||
ps.setDouble(3, 0.0);
|
||||
ps.setLong(4, 0);
|
||||
lobCreator.setBlobAsBytes(ps, 5, SerializationUtils.serialize((Serializable) value));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// LobCreating callbacks always return the affect row count for SQL DML
|
||||
// statements, if less than 1 row
|
||||
// is affected, then this row is new and should be inserted.
|
||||
Integer affectedRows = (Integer) getJdbcTemplate().execute(getQuery(UPDATE_STEP_EXECUTION_CONTEXT), callback);
|
||||
if (affectedRows.intValue() < 1) {
|
||||
insertExecutionAttribute(executionId, key, value, type);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.execution.repository.dao.StepExecutionDao#updateStepExecution(org.springframework.batch.core.domain.StepExecution)
|
||||
*/
|
||||
public void updateStepExecution(StepExecution stepExecution) {
|
||||
|
||||
validateStepExecution(stepExecution);
|
||||
Assert.notNull(stepExecution.getId(), "StepExecution Id cannot be null. StepExecution must saved"
|
||||
+ " before it can be updated.");
|
||||
|
||||
// Do not check for existence of step execution considering
|
||||
// it is saved at every commit point.
|
||||
|
||||
String exitDescription = stepExecution.getExitStatus().getExitDescription();
|
||||
if (exitDescription != null && exitDescription.length() > EXIT_MESSAGE_LENGTH) {
|
||||
exitDescription = exitDescription.substring(0, EXIT_MESSAGE_LENGTH);
|
||||
logger.debug("Truncating long message before update of StepExecution: " + stepExecution);
|
||||
}
|
||||
|
||||
// Attempt to prevent concurrent modification errors by blocking here if
|
||||
// someone is already trying to do it.
|
||||
synchronized (stepExecution) {
|
||||
|
||||
Integer version = new Integer(stepExecution.getVersion().intValue() + 1);
|
||||
Object[] parameters = new Object[] { stepExecution.getStartTime(), stepExecution.getEndTime(),
|
||||
stepExecution.getStatus().toString(), stepExecution.getCommitCount(), stepExecution.getTaskCount(),
|
||||
stepExecution.getExitStatus().isContinuable() ? "Y" : "N",
|
||||
stepExecution.getExitStatus().getExitCode(), exitDescription, version, stepExecution.getId(),
|
||||
stepExecution.getVersion() };
|
||||
int count = getJdbcTemplate().update(
|
||||
getQuery(UPDATE_STEP_EXECUTION),
|
||||
parameters,
|
||||
new int[] { Types.TIMESTAMP, Types.TIMESTAMP, Types.VARCHAR, Types.INTEGER, Types.INTEGER,
|
||||
Types.CHAR, Types.VARCHAR, Types.VARCHAR, Types.INTEGER, Types.INTEGER, Types.INTEGER });
|
||||
|
||||
// Avoid concurrent modifications...
|
||||
if (count == 0) {
|
||||
int curentVersion = getJdbcTemplate().queryForInt(
|
||||
getQuery(CURRENT_VERSION_STEP_EXECUTION),
|
||||
new Object[] { stepExecution.getId() });
|
||||
throw new OptimisticLockingFailureException("Attempt to update step execution id="
|
||||
+ stepExecution.getId() + " with wrong version (" + stepExecution.getVersion() + "), where current version is "+curentVersion);
|
||||
}
|
||||
|
||||
stepExecution.incrementVersion();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private class StepExecutionRowMapper implements RowMapper {
|
||||
|
||||
private final JobExecution jobExecution;
|
||||
|
||||
private final Step step;
|
||||
|
||||
public StepExecutionRowMapper(JobExecution jobExecution, Step step) {
|
||||
this.jobExecution = jobExecution;
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
|
||||
StepExecution stepExecution = new StepExecution(step, jobExecution, new Long(rs.getLong(1)));
|
||||
stepExecution.setStartTime(rs.getTimestamp(3));
|
||||
stepExecution.setEndTime(rs.getTimestamp(4));
|
||||
stepExecution.setStatus(BatchStatus.getStatus(rs.getString(5)));
|
||||
stepExecution.setCommitCount(rs.getInt(6));
|
||||
stepExecution.setTaskCount(rs.getInt(7));
|
||||
stepExecution.setExitStatus(new ExitStatus("Y".equals(rs.getString(8)), rs.getString(9), rs.getString(10)));
|
||||
stepExecution.setExecutionContext(findExecutionContext(stepExecution));
|
||||
return stepExecution;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void setLobHandler(LobHandler lobHandler) {
|
||||
this.lobHandler = lobHandler;
|
||||
}
|
||||
|
||||
public void setStepExecutionIncrementer(DataFieldMaxValueIncrementer stepExecutionIncrementer) {
|
||||
this.stepExecutionIncrementer = stepExecutionIncrementer;
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(stepExecutionIncrementer, "StepExecutionIncrementer cannot be null.");
|
||||
}
|
||||
|
||||
public static class AttributeType {
|
||||
|
||||
private final String type;
|
||||
|
||||
private AttributeType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public static final AttributeType STRING = new AttributeType("STRING");
|
||||
|
||||
public static final AttributeType LONG = new AttributeType("LONG");
|
||||
|
||||
public static final AttributeType OBJECT = new AttributeType("OBJECT");
|
||||
|
||||
public static final AttributeType DOUBLE = new AttributeType("DOUBLE");
|
||||
|
||||
private static final AttributeType[] VALUES = { STRING, OBJECT, LONG, DOUBLE };
|
||||
|
||||
public static AttributeType getType(String typeAsString) {
|
||||
|
||||
for (int i = 0; i < VALUES.length; i++) {
|
||||
if (VALUES[i].toString().equals(typeAsString)) {
|
||||
return (AttributeType) VALUES[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public StepExecution getStepExecution(JobExecution jobExecution, Step step) {
|
||||
List executions = getJdbcTemplate().query(getQuery(GET_STEP_EXECUTION),
|
||||
new Object[] { step.getName(), jobExecution.getId() }, new StepExecutionRowMapper(jobExecution, step));
|
||||
|
||||
Assert.state(executions.size() <= 1,
|
||||
"There can be at most one step execution with given name for single job execution");
|
||||
if (executions.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return (StepExecution) executions.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package org.springframework.batch.execution.repository.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobInstance;
|
||||
|
||||
/**
|
||||
* Data Access Object for job executions.
|
||||
*
|
||||
* @author Lucas Ward
|
||||
* @author Robert Kasanicky
|
||||
*/
|
||||
public interface JobExecutionDao {
|
||||
|
||||
/**
|
||||
* Save a new JobExecution.
|
||||
*
|
||||
* Preconditions: jobInstance the jobExecution belongs to must have a jobInstanceId.
|
||||
*
|
||||
* @param jobExecution
|
||||
*/
|
||||
void saveJobExecution(JobExecution jobExecution);
|
||||
|
||||
/**
|
||||
* Update and existing JobExecution.
|
||||
*
|
||||
* Preconditions: jobExecution must have an Id (which can be obtained by the
|
||||
* save method) and a jobInstanceId.
|
||||
*
|
||||
* @param jobExecution
|
||||
*/
|
||||
void updateJobExecution(JobExecution jobExecution);
|
||||
|
||||
/**
|
||||
* Return the number of JobExecutions for the given JobInstance
|
||||
*
|
||||
* Preconditions: jobInstance must have an id.
|
||||
*/
|
||||
int getJobExecutionCount(JobInstance jobInstance);
|
||||
|
||||
/**
|
||||
* Return list of JobExecutions for given JobInstance.
|
||||
*
|
||||
* @param jobInstance
|
||||
* @return list of jobExecutions.
|
||||
*/
|
||||
List findJobExecutions(JobInstance jobInstance);
|
||||
|
||||
/**
|
||||
* @return last JobExecution for given JobInstance.
|
||||
*/
|
||||
JobExecution getLastJobExecution(JobInstance jobInstance);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.springframework.batch.execution.repository.dao;
|
||||
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobInstance;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
|
||||
/**
|
||||
* Data Access Object for job instances.
|
||||
*
|
||||
* @author Lucas Ward
|
||||
* @author Robert Kasanicky
|
||||
*
|
||||
*/
|
||||
public interface JobInstanceDao {
|
||||
|
||||
/**
|
||||
* Create a JobInstance with given name and parameters.
|
||||
*
|
||||
* PreConditions: JobInstance for given name and parameters must not already exist
|
||||
*
|
||||
* PostConditions: A valid job instancewill be returned which has been persisted and
|
||||
* contains an unique Id.
|
||||
*
|
||||
* @param jobName
|
||||
* @param jobParameters
|
||||
* @return JobInstance
|
||||
*/
|
||||
JobInstance createJobInstance(Job job, JobParameters jobParameters);
|
||||
|
||||
/**
|
||||
* Find all job instances that match the given name and parameters. If no
|
||||
* matching job instances are found, then a list of size 0 will be
|
||||
* returned.
|
||||
*
|
||||
* @param jobName
|
||||
* @param jobParameters
|
||||
* @return List of {@link JobInstance} objects matching
|
||||
* {@link JobIdentifier}
|
||||
*/
|
||||
JobInstance getJobInstance(Job job, JobParameters jobParameters);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package org.springframework.batch.execution.repository.dao;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobInstance;
|
||||
import org.springframework.batch.support.transaction.TransactionAwareProxyFactory;
|
||||
|
||||
/**
|
||||
* In-memory implementation of {@link JobExecutionDao}.
|
||||
*
|
||||
*/
|
||||
public class MapJobExecutionDao implements JobExecutionDao {
|
||||
|
||||
private static Map executionsByJobInstanceId = TransactionAwareProxyFactory.createTransactionalMap();
|
||||
|
||||
private static long currentId;
|
||||
|
||||
public static void clear() {
|
||||
executionsByJobInstanceId.clear();
|
||||
}
|
||||
|
||||
public int getJobExecutionCount(JobInstance jobInstance) {
|
||||
Set executions = (Set) executionsByJobInstanceId.get(jobInstance.getId());
|
||||
if (executions == null) {
|
||||
return 0;
|
||||
}
|
||||
return executions.size();
|
||||
}
|
||||
|
||||
public void saveJobExecution(JobExecution jobExecution) {
|
||||
Set executions = (Set) executionsByJobInstanceId.get(jobExecution.getJobId());
|
||||
if (executions == null) {
|
||||
executions = TransactionAwareProxyFactory.createTransactionalSet();
|
||||
executionsByJobInstanceId.put(jobExecution.getJobId(), executions);
|
||||
}
|
||||
executions.add(jobExecution);
|
||||
jobExecution.setId(new Long(currentId++));
|
||||
}
|
||||
|
||||
public List findJobExecutions(JobInstance jobInstance) {
|
||||
Set executions = (Set) executionsByJobInstanceId.get(jobInstance.getId());
|
||||
if (executions == null) {
|
||||
return new ArrayList();
|
||||
}
|
||||
else {
|
||||
return new ArrayList(executions);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateJobExecution(JobExecution jobExecution) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
public JobExecution getLastJobExecution(JobInstance jobInstance) {
|
||||
Set executions = (Set) executionsByJobInstanceId.get(jobInstance.getId());
|
||||
if (executions == null) {
|
||||
return null;
|
||||
}
|
||||
JobExecution lastExec = null;
|
||||
for (Iterator iterator = executions.iterator(); iterator.hasNext();) {
|
||||
JobExecution exec = (JobExecution) iterator.next();
|
||||
if (lastExec == null) {
|
||||
lastExec = exec;
|
||||
}
|
||||
if (lastExec.getStartTime().getTime() < exec.getStartTime().getTime()) {
|
||||
lastExec = exec;
|
||||
}
|
||||
}
|
||||
return lastExec;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.springframework.batch.execution.repository.dao;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobInstance;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.support.transaction.TransactionAwareProxyFactory;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
public class MapJobInstanceDao implements JobInstanceDao {
|
||||
|
||||
private static Collection jobInstances = TransactionAwareProxyFactory.createTransactionalList();
|
||||
|
||||
private long currentId = 0;
|
||||
|
||||
public static void clear() {
|
||||
jobInstances.clear();
|
||||
}
|
||||
|
||||
public JobInstance createJobInstance(Job job, JobParameters jobParameters) {
|
||||
|
||||
Assert.state(getJobInstance(job, jobParameters) == null, "JobInstance must not already exist");
|
||||
|
||||
JobInstance jobInstance = new JobInstance(new Long(currentId++), jobParameters, job);
|
||||
jobInstances.add(jobInstance);
|
||||
|
||||
return jobInstance;
|
||||
}
|
||||
|
||||
public JobInstance getJobInstance(Job job, JobParameters jobParameters) {
|
||||
|
||||
for (Iterator iterator = jobInstances.iterator(); iterator.hasNext();) {
|
||||
JobInstance instance = (JobInstance) iterator.next();
|
||||
if (instance.getJobName().equals(job.getName()) && instance.getJobParameters().equals(jobParameters)) {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.batch.execution.repository.dao;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.item.ExecutionContext;
|
||||
import org.springframework.batch.support.transaction.TransactionAwareProxyFactory;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* In-memory implementation of {@link StepExecutionDao}.
|
||||
*/
|
||||
public class MapStepExecutionDao implements StepExecutionDao {
|
||||
|
||||
private static Map executionsByJobExecutionId;
|
||||
|
||||
private static Map contextsByStepExecutionId;
|
||||
|
||||
private static long currentId = 0;
|
||||
|
||||
static {
|
||||
executionsByJobExecutionId = TransactionAwareProxyFactory.createTransactionalMap();
|
||||
contextsByStepExecutionId = TransactionAwareProxyFactory.createTransactionalMap();
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
executionsByJobExecutionId.clear();
|
||||
}
|
||||
|
||||
public ExecutionContext findExecutionContext(StepExecution stepExecution) {
|
||||
return (ExecutionContext) contextsByStepExecutionId.get(stepExecution.getId());
|
||||
}
|
||||
|
||||
public void saveStepExecution(StepExecution stepExecution) {
|
||||
Assert.state(stepExecution.getId() == null);
|
||||
Assert.state(stepExecution.getVersion() == null);
|
||||
Assert.notNull(stepExecution.getJobExecutionId(), "JobExecution must be saved already.");
|
||||
|
||||
Map executions = (Map) executionsByJobExecutionId.get(stepExecution.getJobExecutionId());
|
||||
if (executions == null) {
|
||||
executions = TransactionAwareProxyFactory.createTransactionalMap();
|
||||
executionsByJobExecutionId.put(stepExecution.getJobExecutionId(), executions);
|
||||
}
|
||||
stepExecution.incrementVersion();
|
||||
stepExecution.setId(new Long(currentId++));
|
||||
executions.put(stepExecution.getStepName(), stepExecution);
|
||||
}
|
||||
|
||||
public void updateStepExecution(StepExecution stepExecution) {
|
||||
|
||||
Assert.notNull(stepExecution.getJobExecutionId());
|
||||
|
||||
Map executions = (Map) executionsByJobExecutionId.get(stepExecution.getJobExecutionId());
|
||||
Assert.notNull(executions, "step executions for given job execution are expected to be already saved");
|
||||
|
||||
StepExecution persistedExecution = (StepExecution) executions.get(stepExecution.getStepName());
|
||||
Assert.notNull(persistedExecution, "step execution is expected to be already saved");
|
||||
|
||||
if (!persistedExecution.getVersion().equals(stepExecution.getVersion())) {
|
||||
throw new OptimisticLockingFailureException("Attempt to update step execution id=" + stepExecution.getId()
|
||||
+ " with wrong version (" + stepExecution.getVersion() + "), where current version is "
|
||||
+ persistedExecution.getVersion());
|
||||
}
|
||||
|
||||
stepExecution.incrementVersion();
|
||||
executions.put(stepExecution.getStepName(), stepExecution);
|
||||
}
|
||||
|
||||
public StepExecution getStepExecution(JobExecution jobExecution, Step step) {
|
||||
Map executions = (Map) executionsByJobExecutionId.get(jobExecution.getId());
|
||||
if (executions == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (StepExecution) executions.get(step.getName());
|
||||
}
|
||||
|
||||
public void saveOrUpdateExecutionContext(StepExecution stepExecution) {
|
||||
contextsByStepExecutionId.put(stepExecution.getId(), stepExecution.getExecutionContext());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.batch.execution.repository.dao;
|
||||
|
||||
/**
|
||||
* This exception identifies that a batch domain object is invalid, which
|
||||
* is generally caused by an invalid ID. (An ID which doesn't exist in the database).
|
||||
*
|
||||
* @author Lucas Ward
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class NoSuchObjectException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 4399621765157283111L;
|
||||
|
||||
public NoSuchObjectException(String message){
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.springframework.batch.execution.repository.dao;
|
||||
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.item.ExecutionContext;
|
||||
|
||||
public interface StepExecutionDao {
|
||||
|
||||
/**
|
||||
* Save the given StepExecution.
|
||||
*
|
||||
* Preconditions: Id must be null.
|
||||
*
|
||||
* Postconditions: Id will be set to a unique Long.
|
||||
*
|
||||
* @param stepExecution
|
||||
*/
|
||||
void saveStepExecution(StepExecution stepExecution);
|
||||
|
||||
/**
|
||||
* Update the given StepExecution
|
||||
*
|
||||
* Preconditions: Id must not be null.
|
||||
*
|
||||
* @param stepExecution
|
||||
*/
|
||||
void updateStepExecution(StepExecution stepExecution);
|
||||
|
||||
/**
|
||||
* Find all {@link ExecutionContext} for the given {@link StepExecution}.
|
||||
*
|
||||
* @throws IllegalArgumentException if the id is null.
|
||||
*/
|
||||
ExecutionContext findExecutionContext(StepExecution stepExecution);
|
||||
|
||||
/**
|
||||
* Save the {@link ExecutionContext} of the given {@link StepExecution}.
|
||||
*
|
||||
* @param stepExecution the {@link StepExecution} containing the
|
||||
* {@link ExecutionContext} to be saved.
|
||||
* @throws IllegalArgumentException if the attributes are null.
|
||||
*/
|
||||
void saveOrUpdateExecutionContext(StepExecution stepExecution);
|
||||
|
||||
StepExecution getStepExecution(JobExecution jobExecution, Step step);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Specific implementations of dao concerns.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Specific implementations of repository concerns.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* 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.resource;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.StepListener;
|
||||
import org.springframework.batch.core.listener.StepListenerSupport;
|
||||
import org.springframework.batch.core.runtime.JobParametersFactory;
|
||||
import org.springframework.batch.execution.launch.support.DefaultJobParametersFactory;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
import org.springframework.core.io.FileSystemResourceLoader;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Strategy for locating different resources on the file system. For each unique
|
||||
* step execution, the same file handle will be returned. A unique step is
|
||||
* defined as having the same job instance and step name. An external file mover
|
||||
* (such as an EAI solution) should rename and move any input files to conform
|
||||
* to the pattern defined here.<br/>
|
||||
*
|
||||
* If no pattern is passed in, then following default is used:
|
||||
*
|
||||
* <pre>
|
||||
* data/%JOB_NAME%/%STEP_NAME%.txt
|
||||
* </pre>
|
||||
*
|
||||
* The %% variables are replaced with the corresponding bean property at run
|
||||
* time, when the factory method is executed. To insert {@link JobParameters}
|
||||
* use a pattern with the parameter key surrounded by %%, e.g.
|
||||
*
|
||||
* <pre>
|
||||
* //home/jobs/data/%JOB_NAME%/%STEP_NAME%-%schedule.date%.txt
|
||||
* </pre>
|
||||
*
|
||||
* Note that the default pattern does not start with a separator. Because of the
|
||||
* implementation of the Spring Core Resource abstractions, it would need to
|
||||
* start with a double forward slash "//" to resolve to an absolute directory.<br/>
|
||||
*
|
||||
* To use this resource it must be initialised with a {@link StepExecution}.
|
||||
* The best way to do that is to register it as a listener in the step that is
|
||||
* going to need it. It is to enable this usage that the resource implements
|
||||
* {@link StepListener}.
|
||||
*
|
||||
* @author Tomas Slanina
|
||||
* @author Lucas Ward
|
||||
* @author Dave Syer
|
||||
*
|
||||
* @see Resource
|
||||
*/
|
||||
public class StepExecutionProxyResource extends StepListenerSupport implements Resource, ResourceLoaderAware,
|
||||
StepListener {
|
||||
|
||||
private static final String JOB_NAME_PATTERN = "%JOB_NAME%";
|
||||
|
||||
private static final String STEP_NAME_PATTERN = "%STEP_NAME%";
|
||||
|
||||
private static final String DEFAULT_PATTERN = "data/%JOB_NAME%/" + "%STEP_NAME%.txt";
|
||||
|
||||
private String filePattern = DEFAULT_PATTERN;
|
||||
|
||||
private JobParametersFactory jobParametersFactory = new DefaultJobParametersFactory();
|
||||
|
||||
private ResourceLoader resourceLoader = new FileSystemResourceLoader();
|
||||
|
||||
private Resource delegate;
|
||||
|
||||
/**
|
||||
* @param relativePath
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @see org.springframework.core.io.Resource#createRelative(java.lang.String)
|
||||
*/
|
||||
public Resource createRelative(String relativePath) throws IOException {
|
||||
Assert.state(delegate != null, "The delegate resource has not been initialised. "
|
||||
+ "Remember to register this object as a StepListener.");
|
||||
return delegate.createRelative(relativePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @see org.springframework.core.io.Resource#exists()
|
||||
*/
|
||||
public boolean exists() {
|
||||
Assert.state(delegate != null, "The delegate resource has not been initialised. "
|
||||
+ "Remember to register this object as a StepListener.");
|
||||
return delegate.exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @see org.springframework.core.io.Resource#getDescription()
|
||||
*/
|
||||
public String getDescription() {
|
||||
Assert.state(delegate != null, "The delegate resource has not been initialised. "
|
||||
+ "Remember to register this object as a StepListener.");
|
||||
return delegate.getDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @see org.springframework.core.io.Resource#getFile()
|
||||
*/
|
||||
public File getFile() throws IOException {
|
||||
Assert.state(delegate != null, "The delegate resource has not been initialised. "
|
||||
+ "Remember to register this object as a StepListener.");
|
||||
return delegate.getFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @see org.springframework.core.io.Resource#getFilename()
|
||||
*/
|
||||
public String getFilename() {
|
||||
Assert.state(delegate != null, "The delegate resource has not been initialised. "
|
||||
+ "Remember to register this object as a StepListener.");
|
||||
return delegate.getFilename();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @see org.springframework.core.io.InputStreamSource#getInputStream()
|
||||
*/
|
||||
public InputStream getInputStream() throws IOException {
|
||||
Assert.state(delegate != null, "The delegate resource has not been initialised. "
|
||||
+ "Remember to register this object as a StepListener.");
|
||||
return delegate.getInputStream();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @see org.springframework.core.io.Resource#getURI()
|
||||
*/
|
||||
public URI getURI() throws IOException {
|
||||
Assert.state(delegate != null, "The delegate resource has not been initialised. "
|
||||
+ "Remember to register this object as a StepListener.");
|
||||
return delegate.getURI();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @see org.springframework.core.io.Resource#getURL()
|
||||
*/
|
||||
public URL getURL() throws IOException {
|
||||
Assert.state(delegate != null, "The delegate resource has not been initialised. "
|
||||
+ "Remember to register this object as a StepListener.");
|
||||
return delegate.getURL();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @see org.springframework.core.io.Resource#isOpen()
|
||||
*/
|
||||
public boolean isOpen() {
|
||||
Assert.state(delegate != null, "The delegate resource has not been initialised. "
|
||||
+ "Remember to register this object as a StepListener.");
|
||||
return delegate.isOpen();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.springframework.core.io.Resource#isReadable()
|
||||
*/
|
||||
public boolean isReadable() {
|
||||
Assert.state(delegate != null, "The delegate resource has not been initialised. "
|
||||
+ "Remember to register this object as a StepListener.");
|
||||
return delegate.isReadable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link JobParametersFactory} used to translate
|
||||
* {@link JobParameters} into {@link Properties}. Defaults to a
|
||||
* {@link DefaultJobParametersFactory}.
|
||||
* @param jobParametersFactory the {@link JobParametersFactory} to set
|
||||
*/
|
||||
public void setJobParametersFactory(JobParametersFactory jobParametersFactory) {
|
||||
this.jobParametersFactory = jobParametersFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always false because we are expecting to be step scoped.
|
||||
*
|
||||
* @see org.springframework.beans.factory.config.AbstractFactoryBean#isSingleton()
|
||||
*/
|
||||
public boolean isSingleton() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.context.ResourceLoaderAware#setResourceLoader(org.springframework.core.io.ResourceLoader)
|
||||
*/
|
||||
public void setResourceLoader(ResourceLoader resourceLoader) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* helper method for <code>createFileName()</code>
|
||||
*/
|
||||
private String replacePattern(String string, String pattern, String replacement) {
|
||||
|
||||
if (string == null)
|
||||
return null;
|
||||
|
||||
// check to ensure pattern exists in string.
|
||||
if (string.indexOf(pattern) != -1) {
|
||||
return StringUtils.replace(string, pattern, replacement);
|
||||
}
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a filename given a pattern and step context information.
|
||||
*
|
||||
* Deliberate package access, so that the method can be accessed by unit
|
||||
* tests
|
||||
* @param jobName
|
||||
* @param stepName
|
||||
* @param properties
|
||||
*/
|
||||
private String createFileName(String jobName, String stepName, Properties properties) {
|
||||
Assert.notNull(filePattern, "filename pattern is null");
|
||||
|
||||
String fileName = filePattern;
|
||||
|
||||
fileName = replacePattern(fileName, JOB_NAME_PATTERN, jobName == null ? "job" : jobName);
|
||||
fileName = replacePattern(fileName, STEP_NAME_PATTERN, stepName);
|
||||
|
||||
if (properties != null) {
|
||||
for (Iterator iterator = properties.entrySet().iterator(); iterator.hasNext();) {
|
||||
Entry entry = (Entry) iterator.next();
|
||||
String key = (String) entry.getKey();
|
||||
fileName = replacePattern(fileName, "%" + key + "%", (String) entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void setFilePattern(String filePattern) {
|
||||
this.filePattern = replacePattern(filePattern, "\\", File.separator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the properties of the enclosing {@link StepExecution} that will
|
||||
* be needed to create a file name.
|
||||
*
|
||||
* @see org.springframework.batch.core.StepListener#beforeStep(org.springframework.batch.core.StepExecution)
|
||||
*/
|
||||
public void beforeStep(StepExecution execution) {
|
||||
String stepName = execution.getStepName();
|
||||
String jobName = execution.getJobExecution().getJobInstance().getJobName();
|
||||
Properties properties = jobParametersFactory.getProperties(execution.getJobExecution().getJobInstance()
|
||||
.getJobParameters());
|
||||
delegate = resourceLoader.getResource(createFileName(jobName, stepName, properties));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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.step;
|
||||
|
||||
import org.springframework.batch.core.UnexpectedJobExecutionException;
|
||||
import org.springframework.batch.core.JobInterruptedException;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
|
||||
/**
|
||||
* A {@link Step} implementation that provides common behaviour to subclasses.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Ben Hale
|
||||
*/
|
||||
public abstract class AbstractStep implements Step {
|
||||
|
||||
protected String name;
|
||||
|
||||
protected int startLimit = Integer.MAX_VALUE;
|
||||
|
||||
protected boolean allowStartIfComplete;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public AbstractStep() {
|
||||
super();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name property. Always overrides the default value if this object
|
||||
* is a Spring bean.
|
||||
*
|
||||
* @see #setBeanName(java.lang.String)
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getStartLimit() {
|
||||
return this.startLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the startLimit.
|
||||
*
|
||||
* @param startLimit the startLimit to set
|
||||
*/
|
||||
public void setStartLimit(int startLimit) {
|
||||
this.startLimit = startLimit;
|
||||
}
|
||||
|
||||
public boolean isAllowStartIfComplete() {
|
||||
return this.allowStartIfComplete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the shouldAllowStartIfComplete.
|
||||
*
|
||||
* @param allowStartIfComplete the shouldAllowStartIfComplete to set
|
||||
*/
|
||||
public void setAllowStartIfComplete(boolean allowStartIfComplete) {
|
||||
this.allowStartIfComplete = allowStartIfComplete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient constructor for setting only the name property.
|
||||
*
|
||||
* @param name
|
||||
*/
|
||||
public AbstractStep(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public abstract void execute(StepExecution stepExecution) throws JobInterruptedException, UnexpectedJobExecutionException;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.step;
|
||||
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.item.ClearFailedException;
|
||||
import org.springframework.batch.item.FlushFailedException;
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.batch.item.MarkFailedException;
|
||||
import org.springframework.batch.item.ResetFailedException;
|
||||
import org.springframework.batch.repeat.ExitStatus;
|
||||
|
||||
/**
|
||||
* Strategy for processing a single item in an item-oriented step. Extends
|
||||
* {@link ItemReader} and {@link ItemWriter} because part of the contract of the
|
||||
* processor is that it should delegate calls to those interfaces.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public interface ItemHandler {
|
||||
|
||||
/**
|
||||
* Given the current context in the form of a step contribution, do whatever
|
||||
* is necessary to process this unit inside a chunk. Implementations obtain
|
||||
* the item and return {@link ExitStatus#FINISHED} if it is null. If it is
|
||||
* not null process the item and return {@link ExitStatus#CONTINUABLE}. On
|
||||
* failure throws an exception.
|
||||
*
|
||||
* @param contribution the current step context
|
||||
* @return an {@link ExitStatus} indicating whether processing is
|
||||
* continuable.
|
||||
*/
|
||||
ExitStatus handle(StepContribution contribution) throws Exception;
|
||||
|
||||
/**
|
||||
* Implementations should delegate to an {@link ItemReader}.
|
||||
*
|
||||
* @see org.springframework.batch.item.ItemReader#mark()
|
||||
*/
|
||||
void mark() throws MarkFailedException;
|
||||
|
||||
/**
|
||||
* Implementations should delegate to an {@link ItemReader}.
|
||||
*
|
||||
* @see org.springframework.batch.item.ItemReader#reset()
|
||||
*/
|
||||
void reset() throws ResetFailedException;
|
||||
|
||||
/**
|
||||
* Implementations should delegate to an {@link ItemWriter}.
|
||||
*
|
||||
* @see org.springframework.batch.item.ItemWriter#flush()
|
||||
*/
|
||||
public void flush() throws FlushFailedException;
|
||||
|
||||
/**
|
||||
* Implementations should delegate to an {@link ItemWriter}.
|
||||
*
|
||||
* @see org.springframework.batch.item.ItemWriter#clear()
|
||||
*/
|
||||
public void clear() throws ClearFailedException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
/*
|
||||
* 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.step;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.UnexpectedJobExecutionException;
|
||||
import org.springframework.batch.core.JobInstance;
|
||||
import org.springframework.batch.core.JobInterruptedException;
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.StepListener;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.core.runtime.ExitStatusExceptionClassifier;
|
||||
import org.springframework.batch.core.tasklet.Tasklet;
|
||||
import org.springframework.batch.execution.listener.CompositeStepListener;
|
||||
import org.springframework.batch.execution.step.support.DefaultStepExecutionSynchronizer;
|
||||
import org.springframework.batch.execution.step.support.SimpleExitStatusExceptionClassifier;
|
||||
import org.springframework.batch.execution.step.support.StepExecutionSynchronizer;
|
||||
import org.springframework.batch.execution.step.support.StepInterruptionPolicy;
|
||||
import org.springframework.batch.execution.step.support.ThreadStepInterruptionPolicy;
|
||||
import org.springframework.batch.item.ExecutionContext;
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ItemStream;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.batch.item.stream.CompositeItemStream;
|
||||
import org.springframework.batch.repeat.ExitStatus;
|
||||
import org.springframework.batch.repeat.RepeatCallback;
|
||||
import org.springframework.batch.repeat.RepeatContext;
|
||||
import org.springframework.batch.repeat.RepeatOperations;
|
||||
import org.springframework.batch.repeat.support.RepeatTemplate;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
||||
|
||||
/**
|
||||
* Simple implementation of executing the step as a set of chunks, each chunk surrounded by a transaction. The structure
|
||||
* is therefore that of two nested loops, with transaction boundary around the whole inner loop. The outer loop is
|
||||
* controlled by the step operations ({@link #setStepOperations(RepeatOperations)}), and the inner loop by the chunk
|
||||
* operations ({@link #setChunkOperations(RepeatOperations)}). The inner loop should always be executed in a single
|
||||
* thread, so the chunk operations should not do any concurrent execution. N.B. usually that means that the chunk
|
||||
* operations should be a {@link RepeatTemplate} (which is the default).<br/>
|
||||
*
|
||||
* Clients can use interceptors in the step operations to intercept or listen to the iteration on a step-wide basis, for
|
||||
* instance to get a callback when the step is complete. Those that want callbacks at the level of an individual tasks,
|
||||
* can specify interceptors for the chunk operations.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Lucas Ward
|
||||
* @author Ben Hale
|
||||
*/
|
||||
public class ItemOrientedStep extends AbstractStep {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(ItemOrientedStep.class);
|
||||
|
||||
private RepeatOperations chunkOperations = new RepeatTemplate();
|
||||
|
||||
private RepeatOperations stepOperations = new RepeatTemplate();
|
||||
|
||||
private ExitStatusExceptionClassifier exceptionClassifier = new SimpleExitStatusExceptionClassifier();
|
||||
|
||||
// default to checking current thread for interruption.
|
||||
private StepInterruptionPolicy interruptionPolicy = new ThreadStepInterruptionPolicy();
|
||||
|
||||
private CompositeItemStream stream = new CompositeItemStream();
|
||||
|
||||
private CompositeStepListener listener = new CompositeStepListener();
|
||||
|
||||
private JobRepository jobRepository;
|
||||
|
||||
private PlatformTransactionManager transactionManager;
|
||||
|
||||
private ItemHandler itemHandler;
|
||||
|
||||
private StepExecutionSynchronizer synchronizer = new DefaultStepExecutionSynchronizer();
|
||||
|
||||
/**
|
||||
* @param name
|
||||
*/
|
||||
public ItemOrientedStep(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for {@link JobRepository}.
|
||||
*
|
||||
* @param jobRepository is a mandatory dependence (no default).
|
||||
*/
|
||||
public void setJobRepository(JobRepository jobRepository) {
|
||||
this.jobRepository = jobRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link PlatformTransactionManager}.
|
||||
*
|
||||
* @param transactionManager the transaction manager to set
|
||||
*/
|
||||
public void setTransactionManager(PlatformTransactionManager transactionManager) {
|
||||
this.transactionManager = transactionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link ItemHandler}.
|
||||
*
|
||||
* @param itemHandler the {@link ItemHandler} to set
|
||||
*/
|
||||
public void setItemHandler(ItemHandler itemHandler) {
|
||||
this.itemHandler = itemHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register each of the streams for callbacks at the appropriate time in the step. The {@link ItemReader} and
|
||||
* {@link ItemWriter} are automatically registered, but it doesn't hurt to also register them here. Injected
|
||||
* dependencies of the reader and writer are not automatically registered, so if you implement {@link ItemWriter}
|
||||
* using delegation to another object which itself is a {@link ItemStream}, you need to register the delegate here.
|
||||
*
|
||||
* @param streams an array of {@link ItemStream} objects.
|
||||
*/
|
||||
public void setStreams(ItemStream[] streams) {
|
||||
for (int i = 0; i < streams.length; i++) {
|
||||
registerStream(streams[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a single {@link ItemStream} for callbacks to the stream interface.
|
||||
*
|
||||
* @param stream
|
||||
*/
|
||||
public void registerStream(ItemStream stream) {
|
||||
this.stream.register(stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register each of the objects as listeners. If the {@link ItemReader} or {@link ItemWriter} themselves implements
|
||||
* this interface they will be registered automatically, but their injected dependencies will not be. This is a good
|
||||
* way to get access to job parameters and execution context if the tasklet is parameterised.
|
||||
*
|
||||
* @param listeners an array of listener objects of known types.
|
||||
*/
|
||||
public void setStepListeners(StepListener[] listeners) {
|
||||
for (int i = 0; i < listeners.length; i++) {
|
||||
registerStepListener(listeners[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a step listener for callbacks at the appropriate stages in a step execution.
|
||||
*
|
||||
* @param listener a {@link StepListener}
|
||||
*/
|
||||
public void registerStepListener(StepListener listener) {
|
||||
this.listener.register(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link RepeatOperations} to use for the outer loop of the batch processing. Should be set up by the caller
|
||||
* through a factory. Defaults to a plain {@link RepeatTemplate}.
|
||||
*
|
||||
* @param stepOperations a {@link RepeatOperations} instance.
|
||||
*/
|
||||
public void setStepOperations(RepeatOperations stepOperations) {
|
||||
this.stepOperations = stepOperations;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link RepeatOperations} to use for the inner loop of the batch processing. should be set up by the caller
|
||||
* through a factory. defaults to a plain {@link RepeatTemplate}.
|
||||
*
|
||||
* @param chunkOperations a {@link RepeatOperations} instance.
|
||||
*/
|
||||
public void setChunkOperations(RepeatOperations chunkOperations) {
|
||||
this.chunkOperations = chunkOperations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the {@link StepInterruptionPolicy}. The policy is used to check whether an external request has been
|
||||
* made to interrupt the job execution.
|
||||
*
|
||||
* @param interruptionPolicy a {@link StepInterruptionPolicy}
|
||||
*/
|
||||
public void setInterruptionPolicy(StepInterruptionPolicy interruptionPolicy) {
|
||||
this.interruptionPolicy = interruptionPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the {@link ExitStatusExceptionClassifier} that will be used to classify any exception that causes a
|
||||
* job to fail.
|
||||
*
|
||||
* @param exceptionClassifier
|
||||
*/
|
||||
public void setExceptionClassifier(ExitStatusExceptionClassifier exceptionClassifier) {
|
||||
this.exceptionClassifier = exceptionClassifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mostly useful for testing, but could be used to remove dependence on backport concurrency utilities. Public
|
||||
* setter for the {@link StepExecutionSynchronizer}.
|
||||
*
|
||||
* @param synchronizer the {@link StepExecutionSynchronizer} to set
|
||||
*/
|
||||
public void setSynchronizer(StepExecutionSynchronizer synchronizer) {
|
||||
this.synchronizer = synchronizer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the step and update its context so that progress can be monitored by the caller. The step is broken down
|
||||
* into chunks, each one executing in a transaction. The step and its execution and execution context are all given
|
||||
* an up to date {@link BatchStatus}, and the {@link JobRepository} is used to store the result. Various reporting
|
||||
* information are also added to the current context (the {@link RepeatContext} governing the step execution, which
|
||||
* would normally be available to the caller somehow through the step's {@link JobExecutionContext}.<br/>
|
||||
*
|
||||
* @throws JobInterruptedException if the step or a chunk is interrupted
|
||||
* @throws RuntimeException if there is an exception during a chunk execution
|
||||
* @see StepExecutor#execute(StepExecution)
|
||||
*/
|
||||
public void execute(final StepExecution stepExecution) throws UnexpectedJobExecutionException, JobInterruptedException {
|
||||
|
||||
JobInstance jobInstance = stepExecution.getJobExecution().getJobInstance();
|
||||
StepExecution lastStepExecution = jobRepository.getLastStepExecution(jobInstance, this);
|
||||
|
||||
boolean isRestart = jobRepository.getStepExecutionCount(jobInstance, this) > 0 ? true : false;
|
||||
|
||||
ExitStatus status = ExitStatus.FAILED;
|
||||
final ExceptionHolder fatalException = new ExceptionHolder();
|
||||
|
||||
try {
|
||||
|
||||
stepExecution.setStartTime(new Date(System.currentTimeMillis()));
|
||||
// We need to save the step execution right away, before we start
|
||||
// using its ID. It would be better to make the creation atomic in
|
||||
// the caller.
|
||||
fatalException.setException(updateStatus(stepExecution, BatchStatus.STARTED));
|
||||
|
||||
if (isRestart && lastStepExecution != null) {
|
||||
stepExecution.setExecutionContext(lastStepExecution.getExecutionContext());
|
||||
} else {
|
||||
stepExecution.setExecutionContext(new ExecutionContext());
|
||||
}
|
||||
|
||||
// Execute step level listeners *after* the execution context is
|
||||
// fixed in the step. E.g. ItemStream instances need the the same
|
||||
// reference to the ExecutionContext as the step execution.
|
||||
listener.beforeStep(stepExecution);
|
||||
stream.open(stepExecution.getExecutionContext());
|
||||
|
||||
status = stepOperations.iterate(new RepeatCallback() {
|
||||
|
||||
public ExitStatus doInIteration(final RepeatContext context) throws Exception {
|
||||
|
||||
final StepContribution contribution = stepExecution.createStepContribution();
|
||||
contribution.setExecutionContext(stepExecution.getExecutionContext());
|
||||
// Before starting a new transaction, check for
|
||||
// interruption.
|
||||
interruptionPolicy.checkInterrupted(context);
|
||||
|
||||
ExitStatus result = ExitStatus.CONTINUABLE;
|
||||
|
||||
TransactionStatus transaction = transactionManager
|
||||
.getTransaction(new DefaultTransactionDefinition());
|
||||
|
||||
try {
|
||||
|
||||
itemHandler.mark();
|
||||
result = processChunk(contribution);
|
||||
contribution.incrementCommitCount();
|
||||
|
||||
// If the step operations are asynchronous then we need
|
||||
// to synchronize changes to the step execution (at a
|
||||
// minimum).
|
||||
try {
|
||||
synchronizer.lock(stepExecution);
|
||||
} catch (InterruptedException e) {
|
||||
stepExecution.setStatus(BatchStatus.STOPPED);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
// Apply the contribution to the step
|
||||
// only if chunk was successful
|
||||
stepExecution.apply(contribution);
|
||||
|
||||
stream.update(stepExecution.getExecutionContext());
|
||||
try {
|
||||
jobRepository.saveOrUpdateExecutionContext(stepExecution);
|
||||
} catch (Exception e) {
|
||||
fatalException.setException(e);
|
||||
stepExecution.setStatus(BatchStatus.UNKNOWN);
|
||||
throw new CommitFailedException(
|
||||
"Fatal error detected during save of step execution context", e);
|
||||
}
|
||||
|
||||
try {
|
||||
itemHandler.mark();
|
||||
itemHandler.flush();
|
||||
transactionManager.commit(transaction);
|
||||
} catch (Exception e) {
|
||||
fatalException.setException(e);
|
||||
stepExecution.setStatus(BatchStatus.UNKNOWN);
|
||||
throw new CommitFailedException("Fatal error detected during commit", e);
|
||||
}
|
||||
|
||||
} catch (CommitFailedException e) {
|
||||
throw e;
|
||||
} catch (Throwable t) {
|
||||
/*
|
||||
* Any exception thrown within the transaction template will automatically cause the transaction
|
||||
* to rollback. We need to include exceptions during an attempted commit (e.g. Hibernate flush)
|
||||
* so this catch block comes outside the transaction.
|
||||
*/
|
||||
stepExecution.rollback();
|
||||
|
||||
try {
|
||||
itemHandler.reset();
|
||||
itemHandler.clear();
|
||||
transactionManager.rollback(transaction);
|
||||
} catch (Exception e) {
|
||||
fatalException.setException(e);
|
||||
stepExecution.setStatus(BatchStatus.UNKNOWN);
|
||||
}
|
||||
|
||||
if (t instanceof RuntimeException) {
|
||||
throw (RuntimeException) t;
|
||||
} else {
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
|
||||
} finally {
|
||||
synchronizer.release(stepExecution);
|
||||
}
|
||||
|
||||
// Check for interruption after transaction as well, so that
|
||||
// the interrupted exception is correctly propagated up to
|
||||
// caller
|
||||
interruptionPolicy.checkInterrupted(context);
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
fatalException.setException(updateStatus(stepExecution, BatchStatus.COMPLETED));
|
||||
} catch (CommitFailedException e) {
|
||||
logger.error("Fatal error detected during commit.");
|
||||
throw e;
|
||||
} catch (RuntimeException e) {
|
||||
|
||||
// classify exception so an exit code can be stored.
|
||||
status = exceptionClassifier.classifyForExitCode(e);
|
||||
|
||||
if (e.getCause() instanceof JobInterruptedException) {
|
||||
updateStatus(stepExecution, BatchStatus.STOPPED);
|
||||
throw (JobInterruptedException) e.getCause();
|
||||
} else if (!fatalException.hasException()) {
|
||||
try {
|
||||
status = status.and(listener.onErrorInStep(stepExecution, e));
|
||||
} catch (RuntimeException ex) {
|
||||
logger.error("Unexpected error in listener on error in step.", ex);
|
||||
}
|
||||
updateStatus(stepExecution, BatchStatus.FAILED);
|
||||
throw e;
|
||||
} else {
|
||||
logger.error("Fatal error detected during rollback caused by underlying exception: ", e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
} finally {
|
||||
|
||||
try {
|
||||
status = status.and(listener.afterStep(stepExecution));
|
||||
} catch (RuntimeException e) {
|
||||
logger.error("Unexpected error in listener after step.", e);
|
||||
}
|
||||
|
||||
stepExecution.setExitStatus(status);
|
||||
stepExecution.setEndTime(new Date(System.currentTimeMillis()));
|
||||
|
||||
try {
|
||||
jobRepository.saveOrUpdate(stepExecution);
|
||||
} catch (RuntimeException e) {
|
||||
String msg = "Fatal error detected during final save of meta data";
|
||||
logger.error(msg, e);
|
||||
if (!fatalException.hasException()) {
|
||||
fatalException.setException(e);
|
||||
}
|
||||
throw new UnexpectedJobExecutionException(msg, fatalException.getException());
|
||||
}
|
||||
|
||||
try {
|
||||
stream.close(stepExecution.getExecutionContext());
|
||||
} catch (RuntimeException e) {
|
||||
String msg = "Fatal error detected during close of streams. "
|
||||
+ "The job execution completed (possibly unsuccessfully but with consistent meta-data).";
|
||||
logger.error(msg, e);
|
||||
if (!fatalException.hasException()) {
|
||||
fatalException.setException(e);
|
||||
}
|
||||
throw new UnexpectedJobExecutionException(msg, fatalException.getException());
|
||||
}
|
||||
|
||||
if (fatalException.hasException()) {
|
||||
throw new UnexpectedJobExecutionException("Encountered an error saving batch meta data.", fatalException
|
||||
.getException());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a bunch of identical business logic operations all within a transaction. The transaction is
|
||||
* programmatically started and stopped 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.
|
||||
* @return true if there is more data to process.
|
||||
*/
|
||||
protected ExitStatus processChunk(final StepContribution contribution) {
|
||||
ExitStatus result = chunkOperations.iterate(new RepeatCallback() {
|
||||
public ExitStatus doInIteration(final RepeatContext context) throws Exception {
|
||||
if (contribution.isTerminateOnly()) {
|
||||
context.setTerminateOnly();
|
||||
}
|
||||
// check for interruption before each item as well
|
||||
interruptionPolicy.checkInterrupted(context);
|
||||
ExitStatus exitStatus = itemHandler.handle(contribution);
|
||||
contribution.incrementTaskCount();
|
||||
// check for interruption after each item as well
|
||||
interruptionPolicy.checkInterrupted(context);
|
||||
return exitStatus;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to update the status in all relevant places.
|
||||
*
|
||||
* @param stepInstance the current step
|
||||
* @param stepExecution the current stepExecution
|
||||
* @param status the status to set
|
||||
*/
|
||||
private Exception updateStatus(StepExecution stepExecution, BatchStatus status) {
|
||||
stepExecution.setStatus(status);
|
||||
try {
|
||||
jobRepository.saveOrUpdate(stepExecution);
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
return e;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
private static class ExceptionHolder {
|
||||
|
||||
private Exception exception;
|
||||
|
||||
public boolean hasException() {
|
||||
return exception != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param exception
|
||||
*/
|
||||
public void setException(Exception exception) {
|
||||
this.exception = exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public Exception getException() {
|
||||
return this.exception;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class CommitFailedException extends RuntimeException {
|
||||
|
||||
public CommitFailedException(String string, Exception e) {
|
||||
super(string, e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* 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.step;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.UnexpectedJobExecutionException;
|
||||
import org.springframework.batch.core.JobInterruptedException;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.StepListener;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.core.tasklet.Tasklet;
|
||||
import org.springframework.batch.execution.listener.CompositeStepListener;
|
||||
import org.springframework.batch.repeat.ExitStatus;
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link Step} that executes a {@link Tasklet} directly. This step does not
|
||||
* manage transactions or any looping functionality. The tasklet should do this
|
||||
* on its own.
|
||||
*
|
||||
* @author Ben Hale
|
||||
*/
|
||||
public class TaskletStep extends AbstractStep implements Step, InitializingBean, BeanNameAware {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(TaskletStep.class);
|
||||
|
||||
private Tasklet tasklet;
|
||||
|
||||
private JobRepository jobRepository;
|
||||
|
||||
private CompositeStepListener listener = new CompositeStepListener();
|
||||
|
||||
/**
|
||||
* Set the name property if it is not already set. Because of the order of
|
||||
* the callbacks in a Spring container the name property will be set first
|
||||
* if it is present. Care is needed with bean definition inheritance - if a
|
||||
* parent bean has a name, then its children need an explicit name as well,
|
||||
* otherwise they will not be unique.
|
||||
*
|
||||
* @see org.springframework.beans.factory.BeanNameAware#setBeanName(java.lang.String)
|
||||
*/
|
||||
public void setBeanName(String name) {
|
||||
if (this.name == null) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register each of the objects as listeners. If the {@link Tasklet} itself
|
||||
* implements this interface it will be registered automatically, but its
|
||||
* injected dependencies will not be. This is a good way to get access to
|
||||
* job parameters and execution context if the tasklet is parameterised.
|
||||
*
|
||||
* @param listeners an array of listener objects of known types.
|
||||
*/
|
||||
public void setStepListeners(StepListener[] listeners) {
|
||||
for (int i = 0; i < listeners.length; i++) {
|
||||
this.listener.register(listeners[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check mandatory properties.
|
||||
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
|
||||
*/
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(jobRepository, "JobRepository is mandatory for TaskletStep");
|
||||
Assert.notNull(tasklet, "Tasklet is mandatory for TaskletStep");
|
||||
if (tasklet instanceof StepListener) {
|
||||
listener.register((StepListener) tasklet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor is useful for XML configuration.
|
||||
*/
|
||||
public TaskletStep() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new <code>Step</code> for executing a <code>Tasklet</code>
|
||||
*
|
||||
* @param tasklet The <code>Tasklet</code> to execute
|
||||
* @param jobRepository The <code>JobRepository</code> to use for
|
||||
* persistence of incremental state
|
||||
*/
|
||||
public TaskletStep(Tasklet tasklet, JobRepository jobRepository) {
|
||||
this();
|
||||
this.tasklet = tasklet;
|
||||
this.jobRepository = jobRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link Tasklet}.
|
||||
* @param tasklet the {@link Tasklet} to set
|
||||
*/
|
||||
public void setTasklet(Tasklet tasklet) {
|
||||
this.tasklet = tasklet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link JobRepository}.
|
||||
* @param jobRepository the {@link JobRepository} to set
|
||||
*/
|
||||
public void setJobRepository(JobRepository jobRepository) {
|
||||
this.jobRepository = jobRepository;
|
||||
}
|
||||
|
||||
public void execute(StepExecution stepExecution) throws JobInterruptedException, UnexpectedJobExecutionException {
|
||||
stepExecution.setStartTime(new Date());
|
||||
updateStatus(stepExecution, BatchStatus.STARTED);
|
||||
|
||||
ExitStatus exitStatus = ExitStatus.FAILED;
|
||||
Exception fatalException = null;
|
||||
try {
|
||||
|
||||
listener.beforeStep(stepExecution);
|
||||
exitStatus = tasklet.execute();
|
||||
try {
|
||||
exitStatus = exitStatus.and(listener.afterStep(stepExecution));
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.error("Encountered an error on listener close.", e);
|
||||
}
|
||||
|
||||
try {
|
||||
jobRepository.saveOrUpdateExecutionContext(stepExecution);
|
||||
updateStatus(stepExecution, BatchStatus.COMPLETED);
|
||||
}
|
||||
catch (Exception e) {
|
||||
fatalException = e;
|
||||
updateStatus(stepExecution, BatchStatus.UNKNOWN);
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.error("Encountered an error running the tasklet");
|
||||
updateStatus(stepExecution, BatchStatus.FAILED);
|
||||
try {
|
||||
exitStatus = exitStatus.and(listener.onErrorInStep(stepExecution, e));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
logger.error("Encountered an error on listener close.", ex);
|
||||
}
|
||||
if (e instanceof RuntimeException) {
|
||||
throw (RuntimeException) e;
|
||||
}
|
||||
throw new UnexpectedJobExecutionException(e);
|
||||
}
|
||||
finally {
|
||||
stepExecution.setExitStatus(exitStatus);
|
||||
stepExecution.setEndTime(new Date());
|
||||
try {
|
||||
jobRepository.saveOrUpdate(stepExecution);
|
||||
}
|
||||
catch (Exception e) {
|
||||
fatalException = e;
|
||||
}
|
||||
if (fatalException != null) {
|
||||
logger.error("Encountered an error saving batch meta data."
|
||||
+ "This job is now in an unknown state and should not be restarted.", fatalException);
|
||||
throw new UnexpectedJobExecutionException("Encountered an error saving batch meta data.", fatalException);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void updateStatus(StepExecution stepExecution, BatchStatus status) {
|
||||
stepExecution.setStatus(status);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Specific implementations of step concerns.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* 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.step.support;
|
||||
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.execution.step.ItemOrientedStep;
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Base class for factory beans for {@link ItemOrientedStep}. Ensures that all
|
||||
* the mandatory properties are set, and provides basic support for the
|
||||
* {@link Step} interface responsibilities like start limit.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractStepFactoryBean implements FactoryBean, BeanNameAware {
|
||||
|
||||
private String name;
|
||||
|
||||
private int startLimit = Integer.MAX_VALUE;
|
||||
|
||||
private boolean allowStartIfComplete;
|
||||
|
||||
private ItemReader itemReader;
|
||||
|
||||
private ItemWriter itemWriter;
|
||||
|
||||
private PlatformTransactionManager transactionManager;
|
||||
|
||||
private JobRepository jobRepository;
|
||||
|
||||
private boolean singleton = true;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public AbstractStepFactoryBean() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bean name property, which will become the name of the
|
||||
* {@link Step} when it is created.
|
||||
*
|
||||
* @see org.springframework.beans.factory.BeanNameAware#setBeanName(java.lang.String)
|
||||
*/
|
||||
public void setBeanName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public getter for the String.
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the startLimit.
|
||||
*
|
||||
* @param startLimit the startLimit to set
|
||||
*/
|
||||
public void setStartLimit(int startLimit) {
|
||||
this.startLimit = startLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the shouldAllowStartIfComplete.
|
||||
*
|
||||
* @param allowStartIfComplete the shouldAllowStartIfComplete to set
|
||||
*/
|
||||
public void setAllowStartIfComplete(boolean allowStartIfComplete) {
|
||||
this.allowStartIfComplete = allowStartIfComplete;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param itemReader the itemReader to set
|
||||
*/
|
||||
public void setItemReader(ItemReader itemReader) {
|
||||
this.itemReader = itemReader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param itemWriter the itemWriter to set
|
||||
*/
|
||||
public void setItemWriter(ItemWriter itemWriter) {
|
||||
this.itemWriter = itemWriter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Protected getter for the {@link ItemReader} for subclasses to use.
|
||||
* @return the itemReader
|
||||
*/
|
||||
protected ItemReader getItemReader() {
|
||||
return itemReader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Protected getter for the {@link ItemWriter} for subclasses to use
|
||||
* @return the itemWriter
|
||||
*/
|
||||
protected ItemWriter getItemWriter() {
|
||||
return itemWriter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for {@link JobRepository}.
|
||||
*
|
||||
* @param jobRepository is a mandatory dependence (no default).
|
||||
*/
|
||||
public void setJobRepository(JobRepository jobRepository) {
|
||||
this.jobRepository = jobRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link PlatformTransactionManager}.
|
||||
*
|
||||
* @param transactionManager the transaction manager to set
|
||||
*/
|
||||
public void setTransactionManager(PlatformTransactionManager transactionManager) {
|
||||
this.transactionManager = transactionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link Step} from the configuration provided.
|
||||
*
|
||||
* @see org.springframework.beans.factory.FactoryBean#getObject()
|
||||
*/
|
||||
public final Object getObject() throws Exception {
|
||||
ItemOrientedStep step = new ItemOrientedStep(getName());
|
||||
applyConfiguration(step);
|
||||
return step;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param step
|
||||
*
|
||||
*/
|
||||
protected void applyConfiguration(ItemOrientedStep step) {
|
||||
|
||||
Assert.notNull(getItemReader(), "ItemReader must be provided");
|
||||
Assert.notNull(getItemWriter(), "ItemWriter must be provided");
|
||||
Assert.notNull(jobRepository, "JobRepository must be provided");
|
||||
Assert.notNull(transactionManager, "TransactionManager must be provided");
|
||||
|
||||
step.setItemHandler(new SimpleItemHandler(itemReader, itemWriter));
|
||||
step.setTransactionManager(transactionManager);
|
||||
step.setJobRepository(jobRepository);
|
||||
step.setStartLimit(startLimit);
|
||||
step.setAllowStartIfComplete(allowStartIfComplete);
|
||||
|
||||
}
|
||||
|
||||
public Class getObjectType() {
|
||||
return Step.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true by default, but in most cases a {@link Step} should not be
|
||||
* treated as thread safe. Clients are recommended to create a new step for
|
||||
* each job execution.
|
||||
*
|
||||
* @see org.springframework.beans.factory.FactoryBean#isSingleton()
|
||||
*/
|
||||
public boolean isSingleton() {
|
||||
return this.singleton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the singleton flag.
|
||||
* @param singleton the value to set. Defaults to true.
|
||||
*/
|
||||
public void setSingleton(boolean singleton) {
|
||||
this.singleton = singleton;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.step.support;
|
||||
|
||||
import org.springframework.batch.core.ItemSkipPolicy;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link ItemSkipPolicy} interface that
|
||||
* will always return that an item should be skipped.
|
||||
*
|
||||
* @author Ben Hale
|
||||
* @author Lucas Ward
|
||||
*/
|
||||
public class AlwaysSkipItemSkipPolicy implements ItemSkipPolicy {
|
||||
|
||||
public boolean shouldSkip(Throwable t, int skipCount) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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.step.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.batch.core.BatchListener;
|
||||
import org.springframework.batch.core.ChunkListener;
|
||||
import org.springframework.batch.core.ItemReadListener;
|
||||
import org.springframework.batch.core.ItemWriteListener;
|
||||
import org.springframework.batch.core.StepListener;
|
||||
import org.springframework.batch.execution.listener.CompositeChunkListener;
|
||||
import org.springframework.batch.execution.listener.CompositeItemReadListener;
|
||||
import org.springframework.batch.execution.listener.CompositeItemWriteListener;
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.batch.item.reader.DelegatingItemReader;
|
||||
import org.springframework.batch.item.writer.DelegatingItemWriter;
|
||||
import org.springframework.batch.repeat.ExitStatus;
|
||||
import org.springframework.batch.repeat.RepeatContext;
|
||||
import org.springframework.batch.repeat.RepeatOperations;
|
||||
import org.springframework.batch.repeat.listener.RepeatListenerSupport;
|
||||
import org.springframework.batch.repeat.support.RepeatTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Package private helper for step factory beans.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
class BatchListenerFactoryHelper {
|
||||
|
||||
/**
|
||||
* @param itemReader2
|
||||
* @param listeners
|
||||
* @return
|
||||
*/
|
||||
public ItemReader getItemReader(ItemReader itemReader, BatchListener[] listeners) {
|
||||
|
||||
final CompositeItemReadListener multicaster = new CompositeItemReadListener();
|
||||
|
||||
for (int i = 0; i < listeners.length; i++) {
|
||||
BatchListener listener = listeners[i];
|
||||
if (listener instanceof ItemReadListener) {
|
||||
multicaster.register((ItemReadListener) listener);
|
||||
}
|
||||
}
|
||||
|
||||
itemReader = new DelegatingItemReader(itemReader) {
|
||||
public Object read() throws Exception {
|
||||
try {
|
||||
multicaster.beforeRead();
|
||||
Object item = super.read();
|
||||
multicaster.afterRead(item);
|
||||
return item;
|
||||
}
|
||||
catch (Exception e) {
|
||||
multicaster.onReadError(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return itemReader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param itemWriter2
|
||||
* @param listeners
|
||||
* @return
|
||||
*/
|
||||
public ItemWriter getItemWriter(ItemWriter itemWriter, BatchListener[] listeners) {
|
||||
final CompositeItemWriteListener multicaster = new CompositeItemWriteListener();
|
||||
|
||||
for (int i = 0; i < listeners.length; i++) {
|
||||
BatchListener listener = listeners[i];
|
||||
if (listener instanceof ItemWriteListener) {
|
||||
multicaster.register((ItemWriteListener) listener);
|
||||
}
|
||||
}
|
||||
|
||||
itemWriter = new DelegatingItemWriter(itemWriter) {
|
||||
public void write(Object item) throws Exception {
|
||||
try {
|
||||
multicaster.beforeWrite(item);
|
||||
super.write(item);
|
||||
multicaster.afterWrite();
|
||||
}
|
||||
catch (Exception e) {
|
||||
multicaster.onWriteError(e, item);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return itemWriter;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param stepOperations
|
||||
* @param listeners
|
||||
* @return
|
||||
*/
|
||||
public RepeatOperations addChunkListeners(RepeatOperations stepOperations, BatchListener[] listeners) {
|
||||
|
||||
final CompositeChunkListener multicaster = new CompositeChunkListener();
|
||||
|
||||
boolean hasChunkListener = false;
|
||||
|
||||
for (int i = 0; i < listeners.length; i++) {
|
||||
BatchListener listener = listeners[i];
|
||||
if (listener instanceof ChunkListener) {
|
||||
hasChunkListener = true;
|
||||
}
|
||||
if (listener instanceof ChunkListener) {
|
||||
multicaster.register((ChunkListener) listener);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasChunkListener) {
|
||||
|
||||
Assert.state(stepOperations instanceof RepeatTemplate,
|
||||
"Step operations is injected but not a RepeatTemplate, so chunk listeners cannot also be registered. "
|
||||
+ "Either inject a RepeatTemplate, or remove the ChunkListener.");
|
||||
|
||||
RepeatTemplate stepTemplate = (RepeatTemplate) stepOperations;
|
||||
stepTemplate.registerListener(new RepeatListenerSupport() {
|
||||
public void before(RepeatContext context) {
|
||||
multicaster.beforeChunk();
|
||||
}
|
||||
public void after(RepeatContext context, ExitStatus result) {
|
||||
multicaster.afterChunk();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return stepOperations;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param listeners
|
||||
* @return
|
||||
*/
|
||||
public StepListener[] getStepListeners(BatchListener[] listeners) {
|
||||
List list = new ArrayList();
|
||||
for (int i = 0; i < listeners.length; i++) {
|
||||
BatchListener listener = listeners[i];
|
||||
if (listener instanceof StepListener) {
|
||||
list.add(listener);
|
||||
}
|
||||
}
|
||||
return (StepListener[]) list.toArray(new StepListener[list.size()]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.step.support;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.batch.core.listener.ItemListenerSupport;
|
||||
|
||||
/**
|
||||
* Default implementation of the {@link ItemListenerSupport} class that
|
||||
* writes all exceptions via commons logging. Since generics can't be used to
|
||||
* ensure the list contains exceptions, any non exceptions will be logged out by
|
||||
* calling toString on the object.
|
||||
*
|
||||
* @author Lucas Ward
|
||||
*
|
||||
*/
|
||||
public class DefaultItemFailureHandler extends ItemListenerSupport {
|
||||
|
||||
protected static final Log logger = LogFactory
|
||||
.getLog(DefaultItemFailureHandler.class);
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.batch.core.domain.ItemFailureLog#log(java.util.List)
|
||||
*/
|
||||
public void onReadError(Exception ex) {
|
||||
try {
|
||||
logger.error("Error encountered while reading", ex);
|
||||
} catch (Exception exception) {
|
||||
logger.error("Invalid type for logging: [" + exception.toString()
|
||||
+ "]");
|
||||
}
|
||||
}
|
||||
|
||||
public void onWriteError(Exception ex, Object item) {
|
||||
try {
|
||||
logger.error("Error encountered while writing item: [ " + item + "]", ex);
|
||||
} catch (Exception exception) {
|
||||
logger.error("Invalid type for logging: [" + exception.toString()
|
||||
+ "]");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.step.support;
|
||||
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
|
||||
import edu.emory.mathcs.backport.java.util.concurrent.Semaphore;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class DefaultStepExecutionSynchronizer implements StepExecutionSynchronizer {
|
||||
|
||||
private Semaphore semaphore = new Semaphore(1);
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.execution.step.support.StepExecutionSynchronizer#lock(org.springframework.batch.core.domain.StepExecution)
|
||||
*/
|
||||
public void lock(StepExecution stepExecution) throws InterruptedException {
|
||||
semaphore.acquire();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.execution.step.support.StepExecutionSynchronizer#release(org.springframework.batch.core.domain.StepExecution)
|
||||
*/
|
||||
public void release(StepExecution stepExecution) {
|
||||
semaphore.release();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* 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.step.support;
|
||||
|
||||
import org.springframework.batch.core.BatchListener;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.StepListener;
|
||||
import org.springframework.batch.execution.step.ItemHandler;
|
||||
import org.springframework.batch.execution.step.ItemOrientedStep;
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ItemStream;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.batch.repeat.exception.ExceptionHandler;
|
||||
import org.springframework.batch.repeat.exception.SimpleLimitExceptionHandler;
|
||||
import org.springframework.batch.repeat.policy.SimpleCompletionPolicy;
|
||||
import org.springframework.batch.repeat.support.RepeatTemplate;
|
||||
import org.springframework.batch.repeat.support.TaskExecutorRepeatTemplate;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
|
||||
/**
|
||||
* Most common configuration options for simple steps should be found here. Use
|
||||
* this factory bean instead of creating a {@link Step} implementation manually.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class DefaultStepFactoryBean extends AbstractStepFactoryBean {
|
||||
|
||||
private int skipLimit = 0;
|
||||
|
||||
private int commitInterval = 0;
|
||||
|
||||
private ItemStream[] streams = new ItemStream[0];
|
||||
|
||||
private BatchListener[] listeners = new BatchListener[0];
|
||||
|
||||
private ListenerMulticaster listener = new ListenerMulticaster();
|
||||
|
||||
private TaskExecutor taskExecutor;
|
||||
|
||||
private ItemHandler itemHandler;
|
||||
|
||||
private RepeatTemplate stepOperations;
|
||||
|
||||
private SimpleLimitExceptionHandler exceptionHandler;
|
||||
|
||||
/**
|
||||
* Set the commit interval.
|
||||
*
|
||||
* @param commitInterval
|
||||
*/
|
||||
public void setCommitInterval(int commitInterval) {
|
||||
this.commitInterval = commitInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* The streams to inject into the {@link Step}. Any instance of
|
||||
* {@link ItemStream} can be used, and will then receive callbacks at the
|
||||
* appropriate stage in the step.
|
||||
*
|
||||
* @param streams an array of listeners
|
||||
*/
|
||||
public void setStreams(ItemStream[] streams) {
|
||||
this.streams = streams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for a limit that determines skip policy. If this value is
|
||||
* positive then an exception in chunk processing will cause the item to be
|
||||
* skipped and no exception propagated until the limit is reached. If it is
|
||||
* zero then all exceptions will be propagated from the chunk and cause the
|
||||
* step to abort.
|
||||
*
|
||||
* @param skipLimit the value to set. Default is 0 (never skip).
|
||||
*/
|
||||
public void setSkipLimit(int skipLimit) {
|
||||
this.skipLimit = skipLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* The listeners to inject into the {@link Step}. Any instance of
|
||||
* {@link BatchListener} can be used, and will then receive callbacks at the
|
||||
* appropriate stage in the step.
|
||||
*
|
||||
* @param listeners an array of listeners
|
||||
*/
|
||||
public void setListeners(BatchListener[] listeners) {
|
||||
this.listeners = listeners;
|
||||
}
|
||||
|
||||
/**
|
||||
* Protected getter for the step operations to make them available in
|
||||
* subclasses.
|
||||
* @return the step operations
|
||||
*/
|
||||
protected RepeatTemplate getStepOperations() {
|
||||
return stepOperations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the SimpleLimitExceptionHandler.
|
||||
* @param exceptionHandler the exceptionHandler to set
|
||||
*/
|
||||
public void setExceptionHandler(SimpleLimitExceptionHandler exceptionHandler) {
|
||||
this.exceptionHandler = exceptionHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Protected getter for the {@link ExceptionHandler}.
|
||||
* @return the {@link ExceptionHandler}
|
||||
*/
|
||||
protected ExceptionHandler getExceptionHandler() {
|
||||
return exceptionHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link TaskExecutor}. If this is set, then it will
|
||||
* be used to execute the chunk processing inside the {@link Step}.
|
||||
*
|
||||
* @param taskExecutor the taskExecutor to set
|
||||
*/
|
||||
public void setTaskExecutor(TaskExecutor taskExecutor) {
|
||||
this.taskExecutor = taskExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public getter for the ItemProcessor.
|
||||
* @return the itemProcessor
|
||||
*/
|
||||
protected ItemHandler getItemHandler() {
|
||||
return itemHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the ItemProcessor.
|
||||
* @param itemHandler the itemProcessor to set
|
||||
*/
|
||||
protected void setItemHandler(ItemHandler itemHandler) {
|
||||
this.itemHandler = itemHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param step
|
||||
*
|
||||
*/
|
||||
protected void applyConfiguration(ItemOrientedStep step) {
|
||||
|
||||
super.applyConfiguration(step);
|
||||
|
||||
step.setStreams(streams);
|
||||
|
||||
for (int i = 0; i < listeners.length; i++) {
|
||||
BatchListener listener = listeners[i];
|
||||
if (listener instanceof StepListener) {
|
||||
step.registerStepListener((StepListener) listener);
|
||||
}
|
||||
else {
|
||||
this.listener.register(listener);
|
||||
}
|
||||
}
|
||||
|
||||
ItemReader itemReader = getItemReader();
|
||||
ItemWriter itemWriter = getItemWriter();
|
||||
|
||||
// Since we are going to wrap these things with listener callbacks we
|
||||
// need to register them here because the step will not know we did
|
||||
// that.
|
||||
if (itemReader instanceof ItemStream) {
|
||||
step.registerStream((ItemStream) itemReader);
|
||||
}
|
||||
if (itemReader instanceof StepListener) {
|
||||
step.registerStepListener((StepListener) itemReader);
|
||||
}
|
||||
if (itemWriter instanceof ItemStream) {
|
||||
step.registerStream((ItemStream) itemWriter);
|
||||
}
|
||||
if (itemWriter instanceof StepListener) {
|
||||
step.registerStepListener((StepListener) itemWriter);
|
||||
}
|
||||
|
||||
BatchListenerFactoryHelper helper = new BatchListenerFactoryHelper();
|
||||
|
||||
if (commitInterval > 0) {
|
||||
RepeatTemplate chunkOperations = new RepeatTemplate();
|
||||
chunkOperations.setCompletionPolicy(new SimpleCompletionPolicy(commitInterval));
|
||||
helper.addChunkListeners(chunkOperations, listeners);
|
||||
step.setChunkOperations(chunkOperations);
|
||||
}
|
||||
|
||||
StepListener[] stepListeners = helper.getStepListeners(listeners);
|
||||
itemReader = helper.getItemReader(itemReader, listeners);
|
||||
itemWriter = helper.getItemWriter(itemWriter, listeners);
|
||||
stepOperations = new RepeatTemplate();
|
||||
|
||||
// In case they are used by subclasses:
|
||||
setItemReader(itemReader);
|
||||
setItemWriter(itemWriter);
|
||||
|
||||
step.setStepListeners(stepListeners);
|
||||
|
||||
if (taskExecutor != null) {
|
||||
TaskExecutorRepeatTemplate repeatTemplate = new TaskExecutorRepeatTemplate();
|
||||
repeatTemplate.setTaskExecutor(taskExecutor);
|
||||
stepOperations = repeatTemplate;
|
||||
}
|
||||
|
||||
step.setStepOperations(stepOperations);
|
||||
|
||||
ItemSkipPolicyItemHandler itemProcessor = new ItemSkipPolicyItemHandler(itemReader, itemWriter);
|
||||
|
||||
if (skipLimit > 0) {
|
||||
/*
|
||||
* If there is a skip limit (not the default) then we are prepared
|
||||
* to absorb exceptions at the step level because the failed items
|
||||
* will never re-appear after a rollback.
|
||||
*/
|
||||
itemProcessor.setItemSkipPolicy(new LimitCheckingItemSkipPolicy(skipLimit));
|
||||
setExceptionHandler(new SimpleLimitExceptionHandler(skipLimit));
|
||||
stepOperations.setExceptionHandler(getExceptionHandler());
|
||||
step.setStepOperations(stepOperations);
|
||||
}
|
||||
else {
|
||||
// This is the default in ItemOrientedStep anyway...
|
||||
itemProcessor.setItemSkipPolicy(new NeverSkipItemSkipPolicy());
|
||||
}
|
||||
|
||||
setItemHandler(itemProcessor);
|
||||
step.setItemHandler(itemProcessor);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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.step.support;
|
||||
|
||||
import org.springframework.batch.core.ItemSkipPolicy;
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.batch.item.Skippable;
|
||||
import org.springframework.batch.repeat.ExitStatus;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ItemSkipPolicyItemHandler extends SimpleItemHandler {
|
||||
|
||||
private ItemSkipPolicy itemSkipPolicy = new NeverSkipItemSkipPolicy();
|
||||
|
||||
/**
|
||||
* @param itemReader
|
||||
* @param itemWriter
|
||||
*/
|
||||
public ItemSkipPolicyItemHandler(ItemReader itemReader, ItemWriter itemWriter) {
|
||||
super(itemReader, itemWriter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param itemSkipPolicy
|
||||
*/
|
||||
public void setItemSkipPolicy(ItemSkipPolicy itemSkipPolicy) {
|
||||
this.itemSkipPolicy = itemSkipPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the business logic, delegating to the reader and writer.
|
||||
* Subclasses could extend the behaviour as long as they always return the
|
||||
* value of this method call in their superclass.<br/>
|
||||
*
|
||||
* Read from the {@link ItemReader} and process (if not null) with the
|
||||
* {@link ItemWriter}.<br/>
|
||||
*
|
||||
* If there is an exception and the reader or writer implements
|
||||
* {@link Skippable} then the skip method is called.
|
||||
*
|
||||
* @param contribution the current step
|
||||
* @return {@link ExitStatus#CONTINUABLE} if there is more processing to do
|
||||
* @throws Exception if there is an error
|
||||
*/
|
||||
public ExitStatus handle(StepContribution contribution) throws Exception {
|
||||
ExitStatus exitStatus = ExitStatus.CONTINUABLE;
|
||||
|
||||
try {
|
||||
|
||||
exitStatus = super.handle(contribution);
|
||||
|
||||
}
|
||||
catch (Exception e) {
|
||||
|
||||
if (itemSkipPolicy.shouldSkip(e, contribution.getSkipCount())) {
|
||||
contribution.incrementSkipCount();
|
||||
skip();
|
||||
}
|
||||
else {
|
||||
// Rethrow so that outer transaction is rolled back properly
|
||||
throw e;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return exitStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the current item as skipped if possible. If the reader and / or
|
||||
* writer are {@link Skippable} then delegate to them in that order.
|
||||
*
|
||||
* @see org.springframework.batch.item.Skippable#skip()
|
||||
*/
|
||||
private void skip() {
|
||||
if (getItemReader() instanceof Skippable) {
|
||||
((Skippable) getItemReader()).skip();
|
||||
}
|
||||
if (getItemWriter() instanceof Skippable) {
|
||||
((Skippable) getItemWriter()).skip();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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.step.support;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.batch.core.ItemSkipPolicy;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.item.file.FlatFileParseException;
|
||||
import org.springframework.batch.support.ExceptionClassifier;
|
||||
import org.springframework.batch.support.SubclassExceptionClassifier;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* {@link ItemSkipPolicy} that determines whether or not reading should continue
|
||||
* based upon how many items have been skipped. This is extremely useful
|
||||
* behavior, as it allows you to skip records, but will throw a
|
||||
* {@link SkipLimitExceededException} if a set limit has been exceeded. For
|
||||
* example, it is generally advisable to skip {@link FlatFileParseException}s,
|
||||
* however, if the vast majority of records are causing exceptions, the file is
|
||||
* likely bad.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Furthermore, it is also likely that you only want to skip certain exceptions.
|
||||
* {@link FlatFileParseException} is a good example of an exception you will
|
||||
* likely want to skip, but a {@link FileNotFoundException} should cause
|
||||
* immediate termination of the {@link Step}. Because it would be impossible
|
||||
* for a general purpose policy to determine all the types of exceptions that
|
||||
* should be skipped from those that shouldn't, a list must be passed in, with
|
||||
* all of the exceptions that are 'skippable'
|
||||
* </p>
|
||||
*
|
||||
* @author Ben Hale
|
||||
* @author Lucas Ward
|
||||
*/
|
||||
public class LimitCheckingItemSkipPolicy implements ItemSkipPolicy {
|
||||
|
||||
/**
|
||||
* Label for classifying skippable exceptions.
|
||||
*/
|
||||
private static final String SKIP = "skip";
|
||||
|
||||
private final int skipLimit;
|
||||
|
||||
private ExceptionClassifier exceptionClassifier;
|
||||
|
||||
public LimitCheckingItemSkipPolicy(int skipLimit) {
|
||||
this(skipLimit, new ArrayList(0));
|
||||
}
|
||||
|
||||
public LimitCheckingItemSkipPolicy(int skipLimit, List skippableExceptions) {
|
||||
this.skipLimit = skipLimit;
|
||||
SubclassExceptionClassifier exceptionClassifier = new SubclassExceptionClassifier();
|
||||
Map typeMap = new HashMap();
|
||||
for (Iterator iterator = skippableExceptions.iterator(); iterator.hasNext();) {
|
||||
Class throwable = (Class) iterator.next();
|
||||
typeMap.put(throwable, SKIP);
|
||||
}
|
||||
exceptionClassifier.setTypeMap(typeMap);
|
||||
this.exceptionClassifier = exceptionClassifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the provided exception and skip count, determine whether or not
|
||||
* processing should continue for the given exception. If the exception is
|
||||
* not within the list of 'skippable exceptions', false will be returned. If
|
||||
* the exception is within the list, and {@link StepExecution} skipCount is
|
||||
* greater than the skipLimit, then a {@link SkipLimitExceededException}
|
||||
* will be thrown.
|
||||
*/
|
||||
public boolean shouldSkip(Throwable t, int skipCount) {
|
||||
if (exceptionClassifier.classify(t).equals(SKIP)) {
|
||||
if (skipCount < skipLimit) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
throw new SkipLimitExceededException(skipLimit, t);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* 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.step.support;
|
||||
|
||||
import org.springframework.batch.core.BatchListener;
|
||||
import org.springframework.batch.core.ChunkListener;
|
||||
import org.springframework.batch.core.ItemReadListener;
|
||||
import org.springframework.batch.core.ItemWriteListener;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.StepListener;
|
||||
import org.springframework.batch.execution.listener.CompositeChunkListener;
|
||||
import org.springframework.batch.execution.listener.CompositeItemReadListener;
|
||||
import org.springframework.batch.execution.listener.CompositeItemWriteListener;
|
||||
import org.springframework.batch.execution.listener.CompositeStepListener;
|
||||
import org.springframework.batch.item.ItemStream;
|
||||
import org.springframework.batch.repeat.ExitStatus;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ListenerMulticaster implements StepListener, ChunkListener, ItemReadListener,
|
||||
ItemWriteListener {
|
||||
|
||||
private CompositeStepListener stepListener = new CompositeStepListener();
|
||||
|
||||
private CompositeChunkListener chunkListener = new CompositeChunkListener();
|
||||
|
||||
private CompositeItemReadListener itemReadListener = new CompositeItemReadListener();
|
||||
|
||||
private CompositeItemWriteListener itemWriteListener = new CompositeItemWriteListener();
|
||||
|
||||
/**
|
||||
* Initialise the listener instance.
|
||||
*/
|
||||
public ListenerMulticaster() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register each of the objects as listeners. Once registered, calls to the
|
||||
* {@link ListenerMulticaster} broadcast to the individual listeners.
|
||||
*
|
||||
* @param listeners an array of listener objects of types known to the
|
||||
* multicaster.
|
||||
*/
|
||||
public void setListeners(BatchListener[] listeners) {
|
||||
for (int i = 0; i < listeners.length; i++) {
|
||||
register(listeners[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the listener for callbacks on the appropriate interfaces
|
||||
* implemented. Any {@link BatchListener} can be provided, or an
|
||||
* {@link ItemStream}. Other types will be ignored.
|
||||
*/
|
||||
public void register(BatchListener listener) {
|
||||
if (listener instanceof StepListener) {
|
||||
this.stepListener.register((StepListener) listener);
|
||||
}
|
||||
if (listener instanceof ChunkListener) {
|
||||
this.chunkListener.register((ChunkListener) listener);
|
||||
}
|
||||
if (listener instanceof ItemReadListener) {
|
||||
this.itemReadListener.register((ItemReadListener) listener);
|
||||
}
|
||||
if (listener instanceof ItemWriteListener) {
|
||||
this.itemWriteListener.register((ItemWriteListener) listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @see org.springframework.batch.execution.listener.CompositeStepListener#afterStep()
|
||||
*/
|
||||
public ExitStatus afterStep(StepExecution stepExecution) {
|
||||
return stepListener.afterStep(stepExecution);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param stepExecution
|
||||
* @see org.springframework.batch.execution.listener.CompositeStepListener#beforeStep(org.springframework.batch.core.StepExecution)
|
||||
*/
|
||||
public void beforeStep(StepExecution stepExecution) {
|
||||
stepListener.beforeStep(stepExecution);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param e
|
||||
* @return
|
||||
* @see org.springframework.batch.execution.listener.CompositeStepListener#onErrorInStep(java.lang.Throwable)
|
||||
*/
|
||||
public ExitStatus onErrorInStep(StepExecution stepExecution, Throwable e) {
|
||||
return stepListener.onErrorInStep(stepExecution, e);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see org.springframework.batch.execution.listener.CompositeChunkListener#afterChunk()
|
||||
*/
|
||||
public void afterChunk() {
|
||||
chunkListener.afterChunk();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see org.springframework.batch.execution.listener.CompositeChunkListener#beforeChunk()
|
||||
*/
|
||||
public void beforeChunk() {
|
||||
chunkListener.beforeChunk();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param item
|
||||
* @see org.springframework.batch.execution.listener.CompositeItemReadListener#afterRead(java.lang.Object)
|
||||
*/
|
||||
public void afterRead(Object item) {
|
||||
itemReadListener.afterRead(item);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see org.springframework.batch.execution.listener.CompositeItemReadListener#beforeRead()
|
||||
*/
|
||||
public void beforeRead() {
|
||||
itemReadListener.beforeRead();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ex
|
||||
* @see org.springframework.batch.execution.listener.CompositeItemReadListener#onReadError(java.lang.Exception)
|
||||
*/
|
||||
public void onReadError(Exception ex) {
|
||||
itemReadListener.onReadError(ex);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see org.springframework.batch.execution.listener.CompositeItemWriteListener#afterWrite()
|
||||
*/
|
||||
public void afterWrite() {
|
||||
itemWriteListener.afterWrite();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param item
|
||||
* @see org.springframework.batch.execution.listener.CompositeItemWriteListener#beforeWrite(java.lang.Object)
|
||||
*/
|
||||
public void beforeWrite(Object item) {
|
||||
itemWriteListener.beforeWrite(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ex
|
||||
* @param item
|
||||
* @see org.springframework.batch.execution.listener.CompositeItemWriteListener#onWriteError(java.lang.Exception,
|
||||
* java.lang.Object)
|
||||
*/
|
||||
public void onWriteError(Exception ex, Object item) {
|
||||
itemWriteListener.onWriteError(ex, item);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.step.support;
|
||||
|
||||
import org.springframework.batch.core.ItemSkipPolicy;
|
||||
|
||||
/**
|
||||
* {@link ItemSkipPolicy} implementation that always returns false,
|
||||
* indicating that an item should not be skipped.
|
||||
*
|
||||
* @author Lucas Ward
|
||||
*/
|
||||
public class NeverSkipItemSkipPolicy implements ItemSkipPolicy{
|
||||
|
||||
public boolean shouldSkip(Throwable t, int skipCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* 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.step.support;
|
||||
|
||||
import org.springframework.batch.core.BatchListener;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.StepListener;
|
||||
import org.springframework.batch.execution.step.ItemOrientedStep;
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ItemStream;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.batch.repeat.RepeatOperations;
|
||||
import org.springframework.batch.repeat.support.RepeatTemplate;
|
||||
|
||||
/**
|
||||
* Factory bean for {@link Step} implementations allowing registration of
|
||||
* listeners and also direct injection of the {@link RepeatOperations} needed at
|
||||
* step and chunk level.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class RepeatOperationsStepFactoryBean extends AbstractStepFactoryBean {
|
||||
|
||||
private ItemStream[] streams = new ItemStream[0];
|
||||
|
||||
private BatchListener[] listeners = new BatchListener[0];
|
||||
|
||||
private RepeatOperations chunkOperations = new RepeatTemplate();
|
||||
|
||||
private RepeatOperations stepOperations = new RepeatTemplate();
|
||||
|
||||
/**
|
||||
* The streams to inject into the {@link Step}. Any instance of
|
||||
* {@link ItemStream} can be used, and will then receive callbacks at the
|
||||
* appropriate stage in the step.
|
||||
*
|
||||
* @param streams an array of listeners
|
||||
*/
|
||||
public void setStreams(ItemStream[] streams) {
|
||||
this.streams = streams;
|
||||
}
|
||||
|
||||
/**
|
||||
* The listeners to inject into the {@link Step}. Any instance of
|
||||
* {@link BatchListener} can be used, and will then receive callbacks at the
|
||||
* appropriate stage in the step.
|
||||
*
|
||||
* @param listeners an array of listeners
|
||||
*/
|
||||
public void setListeners(BatchListener[] listeners) {
|
||||
this.listeners = listeners;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link RepeatOperations} to use for the outer loop of the batch
|
||||
* processing. Should be set up by the caller through a factory. Defaults to
|
||||
* a plain {@link RepeatTemplate}.
|
||||
*
|
||||
* @param stepOperations a {@link RepeatOperations} instance.
|
||||
*/
|
||||
public void setStepOperations(RepeatOperations stepOperations) {
|
||||
this.stepOperations = stepOperations;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link RepeatOperations} to use for the inner loop of the batch
|
||||
* processing. should be set up by the caller through a factory. defaults to
|
||||
* a plain {@link RepeatTemplate}.
|
||||
*
|
||||
* @param chunkOperations a {@link RepeatOperations} instance.
|
||||
*/
|
||||
public void setChunkOperations(RepeatOperations chunkOperations) {
|
||||
this.chunkOperations = chunkOperations;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param step
|
||||
*
|
||||
*/
|
||||
protected void applyConfiguration(ItemOrientedStep step) {
|
||||
|
||||
super.applyConfiguration(step);
|
||||
|
||||
step.setStreams(streams);
|
||||
|
||||
ItemReader itemReader = getItemReader();
|
||||
ItemWriter itemWriter = getItemWriter();
|
||||
|
||||
/*
|
||||
* Since we are going to wrap these things with listener callbacks we
|
||||
* need to register them here because the step will not know we did
|
||||
* that.
|
||||
*/
|
||||
if (itemReader instanceof ItemStream) {
|
||||
step.registerStream((ItemStream) itemReader);
|
||||
}
|
||||
if (itemReader instanceof StepListener) {
|
||||
step.registerStepListener((StepListener) itemReader);
|
||||
}
|
||||
if (itemWriter instanceof ItemStream) {
|
||||
step.registerStream((ItemStream) itemWriter);
|
||||
}
|
||||
if (itemWriter instanceof StepListener) {
|
||||
step.registerStepListener((StepListener) itemWriter);
|
||||
}
|
||||
|
||||
BatchListenerFactoryHelper helper = new BatchListenerFactoryHelper();
|
||||
|
||||
StepListener[] stepListeners = helper.getStepListeners(listeners);
|
||||
itemReader = helper.getItemReader(itemReader, listeners);
|
||||
itemWriter = helper.getItemWriter(itemWriter, listeners);
|
||||
RepeatOperations stepOperations = helper.addChunkListeners(this.stepOperations, listeners);
|
||||
|
||||
// In case they are used by subclasses:
|
||||
setItemReader(itemReader);
|
||||
setItemWriter(itemWriter);
|
||||
|
||||
step.setStepListeners(stepListeners);
|
||||
step.setItemHandler(new SimpleItemHandler(itemReader, itemWriter));
|
||||
|
||||
step.setChunkOperations(chunkOperations);
|
||||
step.setStepOperations(stepOperations);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.step.support;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import org.springframework.batch.core.JobInterruptedException;
|
||||
import org.springframework.batch.core.repository.NoSuchJobException;
|
||||
import org.springframework.batch.core.runtime.ExitStatusExceptionClassifier;
|
||||
import org.springframework.batch.execution.launch.support.ExitCodeMapper;
|
||||
import org.springframework.batch.repeat.ExitStatus;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Simple implementation of {@link ExitStatusExceptionClassifier} that returns
|
||||
* basic String exit codes, and defaults to the class name of the throwable for
|
||||
* the message. Most users will want to write their own implementation that
|
||||
* creates more specific exit codes for different exception types.
|
||||
* </p>
|
||||
*
|
||||
* @author Lucas Ward
|
||||
*
|
||||
*/
|
||||
public class SimpleExitStatusExceptionClassifier implements
|
||||
ExitStatusExceptionClassifier {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.core.executor.ExitCodeExceptionClassifier#classifyForExitCode(java.lang.Throwable)
|
||||
*/
|
||||
public ExitStatus classifyForExitCode(Throwable throwable) {
|
||||
return (ExitStatus) classify(throwable);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.common.ExceptionClassifier#classify(java.lang.Throwable)
|
||||
*/
|
||||
public Object classify(Throwable throwable) {
|
||||
|
||||
ExitStatus exitStatus = ExitStatus.FAILED;
|
||||
|
||||
if (throwable instanceof JobInterruptedException) {
|
||||
exitStatus = new ExitStatus(false, JOB_INTERRUPTED,
|
||||
JobInterruptedException.class.getName());
|
||||
} else if( throwable instanceof NoSuchJobException ) {
|
||||
exitStatus = new ExitStatus(false, ExitCodeMapper.NO_SUCH_JOB);
|
||||
} else {
|
||||
String message = "";
|
||||
if (throwable!=null) {
|
||||
StringWriter writer = new StringWriter();
|
||||
throwable.printStackTrace(new PrintWriter(writer));
|
||||
message = writer.toString();
|
||||
}
|
||||
exitStatus = new ExitStatus(false, FATAL_EXCEPTION, message);
|
||||
}
|
||||
|
||||
return exitStatus;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.batch.common.ExceptionClassifier#getDefault()
|
||||
*/
|
||||
public Object getDefault() {
|
||||
// return without message since we don't know what the exception is
|
||||
return new ExitStatus(false, FATAL_EXCEPTION);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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.step.support;
|
||||
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.execution.step.ItemHandler;
|
||||
import org.springframework.batch.item.ClearFailedException;
|
||||
import org.springframework.batch.item.FlushFailedException;
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.batch.item.MarkFailedException;
|
||||
import org.springframework.batch.item.ResetFailedException;
|
||||
import org.springframework.batch.repeat.ExitStatus;
|
||||
|
||||
/**
|
||||
* Simplest possible implementation of {@link ItemHandler} with no skipping or
|
||||
* recovering. Just delegates all calls to the provided {@link ItemReader} and
|
||||
* {@link ItemWriter}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class SimpleItemHandler implements ItemHandler {
|
||||
|
||||
private ItemReader itemReader;
|
||||
|
||||
private ItemWriter itemWriter;
|
||||
|
||||
/**
|
||||
* @param itemReader
|
||||
* @param itemWriter
|
||||
*/
|
||||
public SimpleItemHandler(ItemReader itemReader, ItemWriter itemWriter) {
|
||||
super();
|
||||
this.itemReader = itemReader;
|
||||
this.itemWriter = itemWriter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public getter for the ItemReader.
|
||||
* @return the itemReader
|
||||
*/
|
||||
public ItemReader getItemReader() {
|
||||
return itemReader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public getter for the ItemWriter.
|
||||
* @return the itemWriter
|
||||
*/
|
||||
public ItemWriter getItemWriter() {
|
||||
return itemWriter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read from the {@link ItemReader} and process (if not null) with the
|
||||
* {@link ItemWriter}.
|
||||
*
|
||||
* @see org.springframework.batch.execution.step.ItemHandler#handle(org.springframework.batch.core.StepContribution)
|
||||
*/
|
||||
public ExitStatus handle(StepContribution contribution) throws Exception {
|
||||
Object item = itemReader.read();
|
||||
if (item == null) {
|
||||
return ExitStatus.FINISHED;
|
||||
}
|
||||
itemWriter.write(item);
|
||||
return ExitStatus.CONTINUABLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws MarkFailedException
|
||||
* @see org.springframework.batch.item.ItemReader#mark()
|
||||
*/
|
||||
public void mark() throws MarkFailedException {
|
||||
itemReader.mark();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ResetFailedException
|
||||
* @see org.springframework.batch.item.ItemReader#reset()
|
||||
*/
|
||||
public void reset() throws ResetFailedException {
|
||||
itemReader.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ClearFailedException
|
||||
* @see org.springframework.batch.item.ItemWriter#clear()
|
||||
*/
|
||||
public void clear() throws ClearFailedException {
|
||||
itemWriter.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FlushFailedException
|
||||
* @see org.springframework.batch.item.ItemWriter#flush()
|
||||
*/
|
||||
public void flush() throws FlushFailedException {
|
||||
itemWriter.flush();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.step.support;
|
||||
|
||||
import org.springframework.batch.repeat.RepeatContext;
|
||||
import org.springframework.batch.repeat.exception.ExceptionHandler;
|
||||
import org.springframework.batch.repeat.synch.RepeatSynchronizationManager;
|
||||
import org.springframework.batch.retry.RetryCallback;
|
||||
import org.springframework.batch.retry.RetryContext;
|
||||
import org.springframework.batch.retry.RetryPolicy;
|
||||
import org.springframework.batch.retry.listener.RetryListenerSupport;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class SimpleRetryExceptionHandler extends RetryListenerSupport implements ExceptionHandler {
|
||||
|
||||
/**
|
||||
* Attribute key, whose existence signals an exhausted retry.
|
||||
*/
|
||||
private static final String EXHAUSTED = SimpleRetryExceptionHandler.class.getName() + ".RETRY_EXHAUSTED";
|
||||
|
||||
private RetryPolicy retryPolicy;
|
||||
|
||||
private ExceptionHandler exceptionHandler;
|
||||
|
||||
/**
|
||||
* @param retryPolicy
|
||||
* @param exceptionHandler
|
||||
*/
|
||||
public SimpleRetryExceptionHandler(RetryPolicy retryPolicy, ExceptionHandler exceptionHandler) {
|
||||
this.retryPolicy = retryPolicy;
|
||||
this.exceptionHandler = exceptionHandler;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.repeat.exception.handler.ExceptionHandler#handleException(org.springframework.batch.repeat.RepeatContext,
|
||||
* java.lang.Throwable)
|
||||
*/
|
||||
public void handleException(RepeatContext context, Throwable throwable) throws RuntimeException {
|
||||
// Only bother to check the delegate exception handler if we know that
|
||||
// retry is exhausted
|
||||
if (context.hasAttribute(EXHAUSTED)) {
|
||||
exceptionHandler.handleException(context, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.retry.RetryListener#close(org.springframework.batch.retry.RetryContext,
|
||||
* org.springframework.batch.retry.RetryCallback, java.lang.Throwable)
|
||||
*/
|
||||
public void close(RetryContext context, RetryCallback callback, Throwable throwable) {
|
||||
if (!retryPolicy.canRetry(context)) {
|
||||
getRepeatContext().setAttribute(EXHAUSTED, "true");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent context (the retry is in an inner "chunk" loop and we want
|
||||
* the exception to be handled at the outer "step" level).
|
||||
* @return the {@link RepeatContext} that should hold the exhausted flag.
|
||||
*/
|
||||
private RepeatContext getRepeatContext() {
|
||||
RepeatContext context = RepeatSynchronizationManager.getContext();
|
||||
if (context.getParent() != null) {
|
||||
return context.getParent();
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.step.support;
|
||||
|
||||
import org.springframework.batch.core.UnexpectedJobExecutionException;
|
||||
|
||||
/**
|
||||
* Exception indicating that the skip limit for a particular {@Step} has
|
||||
* been exceeded.
|
||||
*
|
||||
* @author Ben Hale
|
||||
* @author Lucas Ward
|
||||
*/
|
||||
public class SkipLimitExceededException extends UnexpectedJobExecutionException {
|
||||
|
||||
private final int skipLimit;
|
||||
|
||||
public SkipLimitExceededException(int skipLimit, Throwable t) {
|
||||
super("Skip limit of '" + skipLimit + "' exceeded", t);
|
||||
this.skipLimit = skipLimit;
|
||||
}
|
||||
|
||||
public int getSkipLimit() {
|
||||
return skipLimit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* 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.step.support;
|
||||
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.execution.step.ItemOrientedStep;
|
||||
import org.springframework.batch.item.ItemKeyGenerator;
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ItemRecoverer;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.batch.repeat.ExitStatus;
|
||||
import org.springframework.batch.retry.RetryOperations;
|
||||
import org.springframework.batch.retry.RetryPolicy;
|
||||
import org.springframework.batch.retry.backoff.BackOffPolicy;
|
||||
import org.springframework.batch.retry.callback.ItemReaderRetryCallback;
|
||||
import org.springframework.batch.retry.policy.ItemReaderRetryPolicy;
|
||||
import org.springframework.batch.retry.policy.SimpleRetryPolicy;
|
||||
import org.springframework.batch.retry.support.RetryTemplate;
|
||||
|
||||
/**
|
||||
* Factory bean for step that executes its item processing with a stateful
|
||||
* retry. Failed items are never skipped, but always cause a rollback. Before a
|
||||
* rollback, the {@link Step} makes a record of the failed item, caching it
|
||||
* under a key given by the {@link ItemKeyGenerator}. Then when it is
|
||||
* re-presented by the {@link ItemReader} it is recognised and retried up to a
|
||||
* limit given by the {@link RetryPolicy}. When the retry is exhausted instead
|
||||
* of the item being skipped it is handled by an {@link ItemRecoverer}.<br/>
|
||||
*
|
||||
* The skipLimit property is still used to control the overall exception
|
||||
* handling policy. Only exhausted retries count against the exception handler,
|
||||
* instead of counting all exceptions.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class StatefulRetryStepFactoryBean extends DefaultStepFactoryBean {
|
||||
|
||||
private ItemKeyGenerator itemKeyGenerator;
|
||||
|
||||
private ItemRecoverer itemRecoverer;
|
||||
|
||||
private int retryLimit;
|
||||
|
||||
private Class[] retryableExceptionClasses;
|
||||
|
||||
private BackOffPolicy backOffPolicy;
|
||||
|
||||
/**
|
||||
* Public setter for the retry limit. Each item can be retried up to this
|
||||
* limit.
|
||||
* @param retryLimit the retry limit to set
|
||||
*/
|
||||
public void setRetryLimit(int retryLimit) {
|
||||
this.retryLimit = retryLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the Class[].
|
||||
* @param retryableExceptionClasses the retryableExceptionClasses to set
|
||||
*/
|
||||
public void setRetryableExceptionClasses(Class[] retryableExceptionClasses) {
|
||||
this.retryableExceptionClasses = retryableExceptionClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link BackOffPolicy}.
|
||||
* @param backOffPolicy the {@link BackOffPolicy} to set
|
||||
*/
|
||||
public void setBackOffPolicy(BackOffPolicy backOffPolicy) {
|
||||
this.backOffPolicy = backOffPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link ItemKeyGenerator} which will be used to
|
||||
* cache failed items between transactions. If it is not injected but the
|
||||
* reader or writer implement {@link ItemKeyGenerator}, one of those will
|
||||
* be used instead (preferring the reader to the writer if both would be
|
||||
* appropriate). If neither can be used, then the default will be to just
|
||||
* use the item itself as a cache key.
|
||||
*
|
||||
* @param itemKeyGenerator the {@link ItemKeyGenerator} to set
|
||||
*/
|
||||
public void setItemKeyGenerator(ItemKeyGenerator itemKeyGenerator) {
|
||||
this.itemKeyGenerator = itemKeyGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link ItemRecoverer}. If this is set the
|
||||
* {@link ItemRecoverer#recover(Object, Throwable)} will be called when
|
||||
* retry is exhausted, and within the business transaction (which will not
|
||||
* roll back because of any other item-related errors).
|
||||
*
|
||||
* @param itemRecoverer the {@link ItemRecoverer} to set
|
||||
*/
|
||||
public void setItemRecoverer(ItemRecoverer itemRecoverer) {
|
||||
this.itemRecoverer = itemRecoverer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param step
|
||||
*
|
||||
*/
|
||||
protected void applyConfiguration(ItemOrientedStep step) {
|
||||
|
||||
super.applyConfiguration(step);
|
||||
|
||||
if (retryLimit > 0) {
|
||||
|
||||
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(retryLimit);
|
||||
if (retryableExceptionClasses != null) {
|
||||
retryPolicy.setRetryableExceptionClasses(retryableExceptionClasses);
|
||||
}
|
||||
|
||||
// Co-ordinate the retry policy with the exception handler:
|
||||
getStepOperations()
|
||||
.setExceptionHandler(new SimpleRetryExceptionHandler(retryPolicy, getExceptionHandler()));
|
||||
|
||||
ItemReaderRetryCallback retryCallback = new ItemReaderRetryCallback(getItemReader(), itemKeyGenerator,
|
||||
getItemWriter());
|
||||
retryCallback.setRecoverer(itemRecoverer);
|
||||
ItemReaderRetryPolicy itemProviderRetryPolicy = new ItemReaderRetryPolicy(retryPolicy);
|
||||
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
retryTemplate.setRetryPolicy(itemProviderRetryPolicy);
|
||||
if (backOffPolicy != null) {
|
||||
retryTemplate.setBackOffPolicy(backOffPolicy);
|
||||
}
|
||||
|
||||
StatefulRetryItemHandler itemProcessor = new StatefulRetryItemHandler(getItemReader(), getItemWriter(),
|
||||
retryTemplate, retryCallback);
|
||||
|
||||
step.setItemHandler(itemProcessor);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class StatefulRetryItemHandler extends SimpleItemHandler {
|
||||
|
||||
final private RetryOperations retryOperations;
|
||||
|
||||
final private ItemReaderRetryCallback retryCallback;
|
||||
|
||||
/**
|
||||
* @param itemReader
|
||||
* @param itemWriter
|
||||
* @param retryCallback
|
||||
* @param retryTemplate
|
||||
*/
|
||||
public StatefulRetryItemHandler(ItemReader itemReader, ItemWriter itemWriter, RetryOperations retryTemplate,
|
||||
ItemReaderRetryCallback retryCallback) {
|
||||
super(itemReader, itemWriter);
|
||||
this.retryOperations = retryTemplate;
|
||||
this.retryCallback = retryCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the business logic, delegating to the reader and writer.
|
||||
* Subclasses could extend the behaviour as long as they always return
|
||||
* the value of this method call in their superclass.<br/>
|
||||
*
|
||||
* Read from the {@link ItemReader} and process (if not null) with the
|
||||
* {@link ItemWriter}. The call to {@link ItemWriter} is wrapped in a
|
||||
* stateful retry. In that case the {@link ItemRecoverer} is used (if
|
||||
* provided) in the case of an exception to apply alternate processing
|
||||
* to the item. If the stateful retry is in place then the recovery will
|
||||
* happen in the next transaction automatically, otherwise it might be
|
||||
* necessary for clients to make the recover method transactional with
|
||||
* appropriate propagation behaviour (probably REQUIRES_NEW because the
|
||||
* call will happen in the context of a transaction that is about to
|
||||
* rollback).<br/>
|
||||
*
|
||||
* @param contribution the current step
|
||||
* @return {@link ExitStatus#CONTINUABLE} if there is more processing to
|
||||
* do
|
||||
* @throws Exception if there is an error
|
||||
*/
|
||||
public ExitStatus handle(StepContribution contribution) throws Exception {
|
||||
return new ExitStatus(retryOperations.execute(retryCallback) != null);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.step.support;
|
||||
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
|
||||
/**
|
||||
* Strategy for blocking while a step execution is being updated.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public interface StepExecutionSynchronizer {
|
||||
|
||||
/**
|
||||
* Lock the step execution, blocking if it has been locked by another thread.
|
||||
*
|
||||
* @param stepExecution the {@link StepExecution} that is in progress
|
||||
* @throws InterruptedException if the thread is interrupted while waiting
|
||||
*/
|
||||
void lock(StepExecution stepExecution) throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Release the lock. Use this in a finally block.
|
||||
*
|
||||
* @param stepExecution
|
||||
*/
|
||||
void release(StepExecution stepExecution);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.step.support;
|
||||
|
||||
import org.springframework.batch.core.JobInterruptedException;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.repeat.RepeatContext;
|
||||
|
||||
/**
|
||||
* Strategy interface for an interruption policy. This policy allows
|
||||
* {@link Step} implementations to check if a job has been interrupted.
|
||||
*
|
||||
* @author Lucas Ward
|
||||
*
|
||||
*/
|
||||
public interface StepInterruptionPolicy {
|
||||
|
||||
/**
|
||||
* Has the job been interrupted? If so then throw a
|
||||
* {@link JobInterruptedException}.
|
||||
* @param context the current context of the running step.
|
||||
*
|
||||
* @throws JobInterruptedException when the job has been interrupted.
|
||||
*/
|
||||
void checkInterrupted(RepeatContext context) throws JobInterruptedException;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.step.support;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.batch.core.JobInterruptedException;
|
||||
import org.springframework.batch.repeat.RepeatContext;
|
||||
|
||||
/**
|
||||
* Policy that checks the current thread to see if it has been interrupted.
|
||||
*
|
||||
* @author Lucas Ward
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ThreadStepInterruptionPolicy implements StepInterruptionPolicy {
|
||||
|
||||
protected static final Log logger = LogFactory
|
||||
.getLog(ThreadStepInterruptionPolicy.class);
|
||||
|
||||
|
||||
/**
|
||||
* Returns if the current job lifecycle has been interrupted by checking if
|
||||
* the current thread is interrupted.
|
||||
*/
|
||||
public void checkInterrupted(RepeatContext context) throws JobInterruptedException {
|
||||
|
||||
if (isInterrupted(context)) {
|
||||
throw new JobInterruptedException("Job interrupted status detected.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context the current context
|
||||
* @return true if the job has been interrupted
|
||||
*/
|
||||
private boolean isInterrupted(RepeatContext context) {
|
||||
boolean interrupted = (Thread.currentThread().isInterrupted() || context.isTerminateOnly());
|
||||
if(interrupted){
|
||||
logger.error("Step interrupted");
|
||||
}
|
||||
return interrupted;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Specific implementations of simple concerns.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.batch.execution.tasklet;
|
||||
|
||||
import org.springframework.batch.core.tasklet.Tasklet;
|
||||
import org.springframework.batch.item.adapter.AbstractMethodInvokingDelegator;
|
||||
import org.springframework.batch.repeat.ExitStatus;
|
||||
|
||||
/**
|
||||
* A {@link Tasklet} that wraps a method in a POJO. By default the
|
||||
* {@link ExitStatus} is determined by comparing the return value from the POJO
|
||||
* with null. The POJO method is usually going to have no arguments, but a
|
||||
* static argument or array of arguments can be used by setting the arguments
|
||||
* property.
|
||||
*
|
||||
* @see AbstractMethodInvokingDelegator
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class TaskletAdapter extends AbstractMethodInvokingDelegator implements Tasklet {
|
||||
|
||||
/**
|
||||
* Delegate execution to the target object and translate the return value to
|
||||
* an {@link ExitStatus} by invoking a method in the delegate POJO. N.B. the
|
||||
* delegate method should not be void, otherwise there is no way to
|
||||
* determine when the result indicates a finished job.
|
||||
*
|
||||
* @see org.springframework.batch.core.tasklet.Tasklet#execute()
|
||||
*/
|
||||
public ExitStatus execute() throws Exception {
|
||||
return mapResult(invokeDelegateMethod());
|
||||
}
|
||||
|
||||
/**
|
||||
* If the result is an {@link ExitStatus} already just return that,
|
||||
* otherwise return {@link ExitStatus#FINISHED} if the result is null and
|
||||
* {@link ExitStatus#CONTINUABLE} if not.
|
||||
* @param result the value returned by the delegate method
|
||||
* @return an {@link ExitStatus} consistent with the result
|
||||
*/
|
||||
protected ExitStatus mapResult(Object result) {
|
||||
if (result instanceof ExitStatus) {
|
||||
return (ExitStatus) result;
|
||||
}
|
||||
if (result == null) {
|
||||
return ExitStatus.FINISHED;
|
||||
}
|
||||
return ExitStatus.CONTINUABLE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Specific implementations of tasklet concerns.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user