Initial move over from i21 repo.
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.springframework.batch.core.configuration.NoSuchJobConfigurationException;
|
||||
import org.springframework.batch.core.runtime.JobIdentifier;
|
||||
|
||||
/**
|
||||
* Interface which defines a facade for running jobs. The interface is
|
||||
* intentionally minimal, and depends only on simple java types, so that the
|
||||
* facade can be used to launch a job from basic environments like a command
|
||||
* line or a JMX console. TODO: remove dependency on
|
||||
* {@link JobIdentifier}?
|
||||
*
|
||||
* @author Lucas Ward
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public interface JobExecutorFacade {
|
||||
|
||||
/**
|
||||
* Start a job execution identifiable by the {@link JobIdentifier}.
|
||||
* Implementations normally require a job configuration to be locatable
|
||||
* corresponding to the {@link JobIdentifier}, preferably matching
|
||||
* them at least by name.
|
||||
* @param runtimeInformation
|
||||
*
|
||||
* @throws NoSuchJobConfigurationException
|
||||
*/
|
||||
void start(JobIdentifier runtimeInformation) throws NoSuchJobConfigurationException;
|
||||
|
||||
/**
|
||||
* Stop the job execution that was started with this runtime information.
|
||||
* @param runtimeInformation the {@link JobIdentifier}.
|
||||
* @throws NoSuchJobExecutionException if a job with this runtime
|
||||
* information is not running
|
||||
*/
|
||||
void stop(JobIdentifier runtimeInformation) throws NoSuchJobExecutionException;
|
||||
|
||||
/**
|
||||
* Simple check for whether or not there are jobs in progress. Can be used
|
||||
* by clients to wait for all jobs to finish. Finer grained monitoring and
|
||||
* reporting can be implemented using the persistent execution details
|
||||
* (normally in a database), provided they are maintained by the
|
||||
* implementation.
|
||||
*
|
||||
* @return true if any jobs are active.
|
||||
*/
|
||||
boolean isRunning();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.batch.execution;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class NoSuchJobExecutionException extends Exception {
|
||||
|
||||
/**
|
||||
* @param message
|
||||
*/
|
||||
public NoSuchJobExecutionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
/*
|
||||
* 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.bootstrap;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.batch.core.configuration.JobConfiguration;
|
||||
import org.springframework.batch.core.configuration.NoSuchJobConfigurationException;
|
||||
import org.springframework.batch.core.runtime.JobIdentifier;
|
||||
import org.springframework.batch.core.runtime.JobIdentifierFactory;
|
||||
import org.springframework.batch.execution.JobExecutorFacade;
|
||||
import org.springframework.batch.execution.NoSuchJobExecutionException;
|
||||
import org.springframework.batch.execution.runtime.ScheduledJobIdentifierFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Base class for {@link JobLauncher} implementations making no
|
||||
* choices about concurrent processing of jobs.
|
||||
*
|
||||
* @see JobLauncher
|
||||
* @author Lucas Ward
|
||||
*/
|
||||
public abstract class AbstractJobLauncher implements JobLauncher,
|
||||
InitializingBean, ApplicationListener {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(AbstractJobLauncher.class);
|
||||
|
||||
protected JobExecutorFacade batchContainer;
|
||||
|
||||
private String jobConfigurationName;
|
||||
|
||||
private final Object monitor = new Object();
|
||||
|
||||
// Do not autostart by default - allow user to set job configuration
|
||||
// later and then manually start:
|
||||
private volatile boolean autoStart = false;
|
||||
|
||||
private JobIdentifierFactory jobRuntimeInformationFactory = new ScheduledJobIdentifierFactory();
|
||||
|
||||
// A private registry for keeping track of running jobs.
|
||||
private volatile Map registry = new HashMap();
|
||||
|
||||
/**
|
||||
* Setter for {@link JobIdentifier}.
|
||||
*
|
||||
* @param jobRuntimeInformationFactory the jobRuntimeInformationFactory to
|
||||
* set
|
||||
*/
|
||||
public void setJobRuntimeInformationFactory(JobIdentifierFactory jobRuntimeInformationFactory) {
|
||||
this.jobRuntimeInformationFactory = jobRuntimeInformationFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the {@link JobConfiguration} that this launcher will run.
|
||||
*
|
||||
* @param jobConfiguration the jobConfiguration to set
|
||||
*/
|
||||
public void setJobConfigurationName(String jobConfiguration) {
|
||||
this.jobConfigurationName = jobConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for autostart flag. If this is true then the container will be
|
||||
* started when the Spring context is refreshed. Defaults to false.
|
||||
*
|
||||
* @param autoStart
|
||||
*/
|
||||
public void setAutoStart(boolean autoStart) {
|
||||
this.autoStart = autoStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for {@link JobExecutorFacade}. Mandatory property.
|
||||
*
|
||||
* @param batchContainer
|
||||
*/
|
||||
public void setBatchContainer(JobExecutorFacade batchContainer) {
|
||||
this.batchContainer = batchContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that mandatory properties are set.
|
||||
*
|
||||
* @see #setBatchContainer(JobExecutorFacade)
|
||||
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
|
||||
*/
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(batchContainer);
|
||||
}
|
||||
|
||||
/**
|
||||
* If autostart flag is on, initialise on context start-up.
|
||||
*
|
||||
* @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent)
|
||||
*/
|
||||
public void onApplicationEvent(ApplicationEvent event) {
|
||||
if ((event instanceof ContextRefreshedEvent) && this.autoStart && !isRunning()) {
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension point for subclasses. Implementations might choose to start the
|
||||
* job in a new thread or in the current thread.<br/>
|
||||
* @param runtimeInformation the {@link JobIdentifier} to start the
|
||||
* launcher with.
|
||||
* @throws NoSuchJobConfigurationException
|
||||
*/
|
||||
protected abstract void doStart(JobIdentifier jobIdentifier) throws NoSuchJobConfigurationException;
|
||||
|
||||
/**
|
||||
* Start the provided container. The current thread will first be saved.
|
||||
* This may seem odd at first, however, this simple bootstrap requires that
|
||||
* only one thread can kick off a container, and that the first thread that
|
||||
* calls start is the 'processing thread'. If the container has already been
|
||||
* started, no exception will be thrown.
|
||||
* @throws NoSuchJobConfigurationException if the container cannot locate a job configuration
|
||||
* @throws IllegalStateException if JobConfiguration is null.
|
||||
* @see Lifecycle#start().
|
||||
*/
|
||||
public void start(JobIdentifier jobIdentifier) throws NoSuchJobConfigurationException {
|
||||
|
||||
synchronized (monitor) {
|
||||
if (isRunning(jobIdentifier)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
register(jobIdentifier);
|
||||
doStart(jobIdentifier);
|
||||
|
||||
/*
|
||||
* Subclasses have to take care of unregistering the runtimeInformation -
|
||||
* if we do it here and doStart() is implemented to return immediately
|
||||
* without waiting for the job to finish, then we will have a job
|
||||
* running that is not in the registry.
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a job execution with the given name. If a job is already running
|
||||
* has no effect.
|
||||
*
|
||||
* @param name the name to assign to the job
|
||||
* @throws NoSuchJobConfigurationException
|
||||
*/
|
||||
public void start(String name) throws NoSuchJobConfigurationException {
|
||||
JobIdentifier runtimeInformation = jobRuntimeInformationFactory.getJobIdentifier(name);
|
||||
this.start(runtimeInformation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a job execution with default name and other runtime information
|
||||
* provided by the factory. If a job is already running has no effect. The
|
||||
* default name is taken from the enclosed {@link JobConfiguration}.
|
||||
* @throws NoSuchJobConfigurationException if the job configuration cannot be located
|
||||
*
|
||||
* @see #setJobRuntimeInformationFactory(JobIdentifierFactory)
|
||||
* @see org.springframework.context.Lifecycle#start()
|
||||
*/
|
||||
public void start() {
|
||||
if (jobConfigurationName==null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.start(jobConfigurationName);
|
||||
}
|
||||
catch (NoSuchJobConfigurationException e) {
|
||||
logger.error("Could not start", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension point for subclasses to stop a specific job.
|
||||
* @throws NoSuchJobExecutionException
|
||||
*
|
||||
* @see org.springframework.batch.container.bootstrap.BatchContainerLauncher#stop(JobRuntimeInformation))
|
||||
*/
|
||||
protected abstract void doStop(JobIdentifier runtimeInformation) throws NoSuchJobExecutionException;
|
||||
|
||||
/**
|
||||
* Stop all jobs if any are running. If not, no action will be taken.
|
||||
* Delegates to the {@link #doStop()} method.
|
||||
* @throws NoSuchJobExecutionException
|
||||
* @see org.springframework.context.Lifecycle#stop()
|
||||
* @see org.springframework.batch.execution.bootstrap.JobLauncher#stop()
|
||||
*/
|
||||
final public void stop() {
|
||||
for (Iterator iter = new HashSet(registry.keySet()).iterator(); iter.hasNext();) {
|
||||
JobIdentifier context = (JobIdentifier) iter.next();
|
||||
try {
|
||||
stop(context);
|
||||
}
|
||||
catch (NoSuchJobExecutionException e) {
|
||||
logger.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop a job with this {@link JobIdentifier}. Delegates to the
|
||||
* {@link #doStop(JobIdentifier)} method.
|
||||
* @throws NoSuchJobExecutionException
|
||||
*
|
||||
* @see org.springframework.batch.execution.bootstrap.JobLauncher#stop(org.springframework.batch.core.runtime.JobIdentifier)
|
||||
* @see BatchContainer#stop(JobRuntimeInformation))
|
||||
*/
|
||||
final public void stop(JobIdentifier runtimeInformation) throws NoSuchJobExecutionException {
|
||||
synchronized (monitor) {
|
||||
doStop(runtimeInformation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop all jobs with {@link JobIdentifier} having this name.
|
||||
* Delegates to the {@link #stop(JobIdentifier)}.
|
||||
* @throws NoSuchJobExecutionException
|
||||
*
|
||||
* @see org.springframework.batch.execution.bootstrap.JobLauncher#stop(java.lang.String)
|
||||
*/
|
||||
final public void stop(String name) throws NoSuchJobExecutionException {
|
||||
this.stop(jobRuntimeInformationFactory.getJobIdentifier(name));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.container.bootstrap.BatchContainerLauncher#isRunning()
|
||||
*/
|
||||
public boolean isRunning() {
|
||||
Collection jobs = new HashSet(registry.keySet());
|
||||
for (Iterator iter = jobs.iterator(); iter.hasNext();) {
|
||||
JobIdentifier context = (JobIdentifier) iter.next();
|
||||
if (!isRunning(context)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return !jobs.isEmpty();
|
||||
}
|
||||
|
||||
protected boolean isRunning(JobIdentifier runtimeInformation) {
|
||||
synchronized (registry) {
|
||||
return registry.get(runtimeInformation) != null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient synchronized accessor for the registry. Can be used by
|
||||
* subclasses if necessary (but it isn't likely).
|
||||
* @param runtimeInformation
|
||||
*/
|
||||
protected void register(JobIdentifier runtimeInformation) {
|
||||
synchronized (registry) {
|
||||
registry.put(runtimeInformation, runtimeInformation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient synchronized accessor for the registry. Must be used by
|
||||
* subclasses to release the {@link JobIdentifier} when a job is
|
||||
* finished (or stopped).
|
||||
*
|
||||
* @param runtimeInformation
|
||||
*/
|
||||
protected void unregister(JobIdentifier runtimeInformation) {
|
||||
synchronized (registry) {
|
||||
registry.remove(runtimeInformation);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.bootstrap;
|
||||
|
||||
import org.springframework.batch.core.configuration.NoSuchJobConfigurationException;
|
||||
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.access.ContextSingletonBeanFactoryLocator;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
* @since 2.1
|
||||
*/
|
||||
public class BatchCommandLineLauncher {
|
||||
|
||||
/**
|
||||
* The key for the parent context.
|
||||
*/
|
||||
public static final String PARENT_KEY = "simple-container";
|
||||
|
||||
private ConfigurableApplicationContext parent;
|
||||
|
||||
private JobLauncher launcher;
|
||||
|
||||
/**
|
||||
* Default constructor for the launcher. Sets up the parent context to use
|
||||
* for all job executions using a context key {@link #PARENT_KEY}.
|
||||
*/
|
||||
public BatchCommandLineLauncher() {
|
||||
parent = (ConfigurableApplicationContext) ContextSingletonBeanFactoryLocator.getInstance().useBeanFactory(
|
||||
PARENT_KEY).getFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection setter for the {@link JobLauncher}.
|
||||
*
|
||||
* @param launcher the launcher to set
|
||||
*/
|
||||
public void setLauncher(JobLauncher launcher) {
|
||||
this.launcher = launcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param path the path to a Spring context configuration for this job
|
||||
* @param jobName the name of the job execution to use
|
||||
* @throws NoSuchJobConfigurationException
|
||||
*/
|
||||
private void start(String path, String jobName) throws NoSuchJobConfigurationException {
|
||||
if (!path.endsWith(".xml")) {
|
||||
path = path + ".xml";
|
||||
}
|
||||
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] { path }, parent);
|
||||
context.getAutowireCapableBeanFactory().autowireBeanProperties(this,
|
||||
AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);
|
||||
try {
|
||||
if (!launcher.isRunning()) {
|
||||
if (jobName == null) {
|
||||
launcher.start();
|
||||
}
|
||||
else {
|
||||
launcher.start(jobName);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
context.stop();
|
||||
}
|
||||
finally {
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch a batch job using a {@link BatchCommandLineLauncher}. Creates a
|
||||
* new Spring context for the job execution, and uses a common parent for
|
||||
* all such contexts.
|
||||
*
|
||||
* @param args 0 - path to resource to load job configuration context
|
||||
* (default "job-configuration.xml"); 1 - runtime name for job execution
|
||||
* (default "job-execution-id").
|
||||
* @throws NoSuchJobConfigurationException
|
||||
*/
|
||||
public static void main(String[] args) throws NoSuchJobConfigurationException {
|
||||
String path = "job-configuration.xml";
|
||||
String name = null;
|
||||
if (args.length > 0) {
|
||||
path = args[0];
|
||||
}
|
||||
if (args.length > 1) {
|
||||
name = args[1];
|
||||
}
|
||||
BatchCommandLineLauncher command = new BatchCommandLineLauncher();
|
||||
command.start(path, name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.bootstrap;
|
||||
|
||||
import org.springframework.batch.execution.JobExecutorFacade;
|
||||
import org.springframework.batch.repeat.interceptor.RepeatOperationsApplicationEvent;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* {@link ApplicationEvent} that encodes a request from the execution layer to a
|
||||
* running {@link JobExecutorFacade}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class BatchExecutionRequestEvent extends ApplicationEvent {
|
||||
|
||||
/**
|
||||
* Constructor for {@link BatchExecutionRequestEvent}. The source is the
|
||||
* execution layer service implementation that is sending the signal.<br/>
|
||||
*
|
||||
* TODO: the source sould be Serializable so really it should be just a
|
||||
* message about the request?
|
||||
*
|
||||
* Currently encodes a request to publish back a
|
||||
* {@link RepeatOperationsApplicationEvent}. Could be extended in the
|
||||
* future to narrow the request to ask for specific information to be
|
||||
* published back.
|
||||
*/
|
||||
public BatchExecutionRequestEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.bootstrap;
|
||||
|
||||
import org.springframework.batch.core.configuration.NoSuchJobConfigurationException;
|
||||
import org.springframework.batch.core.runtime.JobIdentifier;
|
||||
import org.springframework.batch.execution.JobExecutorFacade;
|
||||
import org.springframework.batch.execution.NoSuchJobExecutionException;
|
||||
import org.springframework.context.Lifecycle;
|
||||
|
||||
/**
|
||||
* Simple interface for controlling a {@link JobExecutorFacade} for a single job
|
||||
* configuration, and also possibly ad-hoc executions, based on different
|
||||
* runtime information. Implementations should concentrate on launching and
|
||||
* controlling a single job, as configured in a {@link JobExecutorFacade} instance.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @since 2.1
|
||||
*/
|
||||
public interface JobLauncher extends Lifecycle {
|
||||
|
||||
/**
|
||||
* Return whether or not a job execution is currently running.
|
||||
*/
|
||||
boolean isRunning();
|
||||
|
||||
/**
|
||||
* Start a job execution with the given runtime information.
|
||||
* @throws NoSuchJobConfigurationException
|
||||
*/
|
||||
void start(JobIdentifier runtimeInformation) throws NoSuchJobConfigurationException;
|
||||
|
||||
/**
|
||||
* Start a job execution with the given name and other runtime information
|
||||
* generated on the fly.
|
||||
*
|
||||
* @param name the name to assign to the job
|
||||
* @throws NoSuchJobConfigurationException
|
||||
*/
|
||||
void start(String name) throws NoSuchJobConfigurationException;
|
||||
|
||||
/**
|
||||
* Start a job execution with default name and other runtime information
|
||||
* generated on the fly.<br/>
|
||||
*
|
||||
* Because {@link Lifecycle#start()} does not throw checked exceptions this
|
||||
* also does not, so an error message and stack trace will be logged if the
|
||||
* required job(s) cannot be started.
|
||||
*
|
||||
* @see org.springframework.context.Lifecycle#start()
|
||||
*/
|
||||
public void start();
|
||||
|
||||
/**
|
||||
* Stop the job execution that was started with this runtime information.
|
||||
* @param runtimeInformation the {@link JobIdentifier}.
|
||||
* @throws NoSuchJobExecutionException
|
||||
*/
|
||||
void stop(JobIdentifier runtimeInformation) throws NoSuchJobExecutionException;
|
||||
|
||||
/**
|
||||
* Stop all currently executing jobs matching the given name. All jobs
|
||||
* started with {@link JobIdentifier} having this name will be
|
||||
* stopped.
|
||||
* @throws NoSuchJobExecutionException
|
||||
*/
|
||||
void stop(String name) throws NoSuchJobExecutionException;
|
||||
|
||||
/**
|
||||
* Stop the current job executions if there are any. If not, no action will
|
||||
* be taken.
|
||||
* @throws NoSuchJobExecutionException
|
||||
*
|
||||
* @see org.springframework.context.Lifecycle#stop()
|
||||
*/
|
||||
public void stop();
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.bootstrap;
|
||||
|
||||
import org.springframework.batch.core.configuration.NoSuchJobConfigurationException;
|
||||
import org.springframework.batch.core.runtime.JobIdentifier;
|
||||
import org.springframework.batch.execution.JobExecutorFacade;
|
||||
import org.springframework.context.Lifecycle;
|
||||
|
||||
/**
|
||||
* Simple bootstrapping mechanism for running a single job execution in a
|
||||
* {@link JobExecutorFacade}.
|
||||
*
|
||||
* <p>
|
||||
* This simple implementation does not run the job asynchronously, so the start
|
||||
* method will not return before the job ends. However, the job execution to be
|
||||
* interrupted via the stop method in another thread.
|
||||
* </p>
|
||||
*
|
||||
* @see Lifecycle
|
||||
* @author Lucas Ward
|
||||
* @author Dave Syer
|
||||
* @since 2.1
|
||||
*/
|
||||
public class SimpleJobLauncher extends AbstractJobLauncher {
|
||||
|
||||
private volatile Thread processingThread;
|
||||
|
||||
private volatile boolean running = false;
|
||||
|
||||
/**
|
||||
* Return whether or not the container is currently running. This is done by
|
||||
* checking the thread to see if it is still alive.
|
||||
*/
|
||||
public boolean isRunning() {
|
||||
return running && processingThread != null && processingThread.isAlive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the provided container. The current thread will first be saved.
|
||||
* This may seem odd at first, however, this simple bootstrap requires that
|
||||
* only one thread can kick off a container, and that the first thread that
|
||||
* calls start is the 'processing thread'. If the container has already been
|
||||
* started, no exception will be thrown.
|
||||
* @throws NoSuchJobConfigurationException
|
||||
* @see Lifecycle#start().
|
||||
*
|
||||
* @throws IllegalStateException if JobConfiguration is null.
|
||||
*/
|
||||
protected void doStart(JobIdentifier jobIdentifier) throws NoSuchJobConfigurationException {
|
||||
|
||||
/*
|
||||
* There is no reason to kick off a new thread, since only one thread
|
||||
* should be processing at once. However, a handle to the thread should
|
||||
* be maintained to allow for interrupt
|
||||
*/
|
||||
processingThread = Thread.currentThread();
|
||||
// TODO: push this out to a method call in parent inside synchronized
|
||||
// block?
|
||||
running = true;
|
||||
try {
|
||||
batchContainer.start(jobIdentifier);
|
||||
}
|
||||
finally {
|
||||
running = false;
|
||||
unregister(jobIdentifier);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the job if it is running by interrupting its thread. If no job is
|
||||
* running, no action will be taken.
|
||||
*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.context.Lifecycle#stop()
|
||||
*/
|
||||
protected void doStop() {
|
||||
|
||||
if (isRunning()) {
|
||||
processingThread.interrupt();
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates to {@link #doStop()}. Since there is only one job running in
|
||||
* this launcher this is OK.
|
||||
*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.context.Lifecycle#stop()
|
||||
*/
|
||||
protected void doStop(JobIdentifier runtimeInformation) {
|
||||
doStop();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* 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.bootstrap;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.management.Notification;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.batch.core.configuration.NoSuchJobConfigurationException;
|
||||
import org.springframework.batch.core.runtime.JobExecutionContext;
|
||||
import org.springframework.batch.core.runtime.JobIdentifier;
|
||||
import org.springframework.batch.execution.JobExecutorFacade;
|
||||
import org.springframework.batch.execution.NoSuchJobExecutionException;
|
||||
import org.springframework.batch.repeat.interceptor.RepeatOperationsApplicationEvent;
|
||||
import org.springframework.batch.statistics.StatisticsProvider;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.core.task.SyncTaskExecutor;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.jmx.export.notification.NotificationPublisher;
|
||||
import org.springframework.jmx.export.notification.NotificationPublisherAware;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Bootstrapping mechanism for running job executions concurrently with a
|
||||
* {@link JobExecutorFacade}.
|
||||
*
|
||||
* <p>
|
||||
* This implementation can run jobs asynchronously. Jobs are stopped by calling
|
||||
* the container stop methods, which is a graceful shutdown.
|
||||
* </p>
|
||||
*
|
||||
* @see JobExecutorFacade
|
||||
* @author Dave Syer
|
||||
* @since 2.1
|
||||
*/
|
||||
public class TaskExecutorJobLauncher extends AbstractJobLauncher implements ApplicationListener,
|
||||
NotificationPublisherAware, ApplicationEventPublisherAware {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(TaskExecutorJobLauncher.class);
|
||||
|
||||
private TaskExecutor taskExecutor = new SyncTaskExecutor();
|
||||
|
||||
private NotificationPublisher notificationPublisher;
|
||||
|
||||
private int notificationCount = 0;
|
||||
|
||||
private ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.context.ApplicationEventPublisherAware#setApplicationEventPublisher(org.springframework.context.ApplicationEventPublisher)
|
||||
*/
|
||||
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
|
||||
this.applicationEventPublisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the {@link TaskExecutor}. Defaults to a
|
||||
* {@link SyncTaskExecutor}.
|
||||
*
|
||||
* @param taskExecutor the taskExecutor to set
|
||||
*/
|
||||
public void setTaskExecutor(TaskExecutor taskExecutor) {
|
||||
this.taskExecutor = taskExecutor;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.jmx.export.notification.NotificationPublisherAware#setNotificationPublisher(org.springframework.jmx.export.notification.NotificationPublisher)
|
||||
*/
|
||||
public void setNotificationPublisher(NotificationPublisher notificationPublisher) {
|
||||
this.notificationPublisher = notificationPublisher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the provided container using the task executor provided.
|
||||
*
|
||||
* @throws IllegalStateException if JobConfiguration is null.
|
||||
*/
|
||||
protected void doStart(final JobIdentifier runtimeInformation) {
|
||||
|
||||
Assert.state(taskExecutor != null, "TaskExecutor must be provided");
|
||||
|
||||
taskExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
batchContainer.start(runtimeInformation);
|
||||
}
|
||||
catch (NoSuchJobConfigurationException e) {
|
||||
applicationEventPublisher.publishEvent(new RepeatOperationsApplicationEvent(runtimeInformation,
|
||||
"No such job", RepeatOperationsApplicationEvent.ERROR));
|
||||
logger.error("JobConfiguration could not be located inside Runnable for runtime information: ["
|
||||
+ runtimeInformation + "]", e);
|
||||
}
|
||||
finally {
|
||||
unregister(runtimeInformation);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates to the underlying {@link JobExecutorFacade}. Does not wait for
|
||||
* the jobs to stop (probably therefore returns immediately).
|
||||
* @throws NoSuchJobExecutionException
|
||||
*
|
||||
* @see org.springframework.context.Lifecycle#stop()
|
||||
*/
|
||||
protected void doStop(JobIdentifier runtimeInformation) throws NoSuchJobExecutionException {
|
||||
batchContainer.stop(runtimeInformation);
|
||||
// TODO: wait for the jobs to stop?
|
||||
}
|
||||
|
||||
/**
|
||||
* If the event is a {@link RepeatOperationsApplicationEvent} for open and
|
||||
* close we log the event at INFO level and send a JMX notification if we
|
||||
* are also an MBean.
|
||||
*
|
||||
* @see org.springframework.batch.execution.bootstrap.AbstractJobLauncher#onApplicationEvent(org.springframework.context.ApplicationEvent)
|
||||
*/
|
||||
public void onApplicationEvent(ApplicationEvent applicationEvent) {
|
||||
super.onApplicationEvent(applicationEvent);
|
||||
if (applicationEvent instanceof RepeatOperationsApplicationEvent) {
|
||||
RepeatOperationsApplicationEvent event = (RepeatOperationsApplicationEvent) applicationEvent;
|
||||
int type = event.getType();
|
||||
if (type == RepeatOperationsApplicationEvent.OPEN || type == RepeatOperationsApplicationEvent.CLOSE
|
||||
|| type == RepeatOperationsApplicationEvent.ERROR) {
|
||||
String message = event.getMessage() + "; source=" + event.getSource();
|
||||
logger.info(message);
|
||||
publish(message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor for the job executions passed back in response to a call to
|
||||
* {@link #requestContextNotification()}. Because the request is
|
||||
* potentially fulfilled asynchronously, and only on demand, the data might
|
||||
* be out of date by the time this method is called, so it should be used
|
||||
* for information purposes only.
|
||||
*
|
||||
* @return Properties representing the last {@link JobExecutionContext}
|
||||
* objects passed up from the underlying execution. If there are no jobs
|
||||
* running it will be empty.
|
||||
*/
|
||||
public Properties getStatistics() {
|
||||
if (batchContainer instanceof StatisticsProvider) {
|
||||
return ((StatisticsProvider) batchContainer).getStatistics();
|
||||
} else {
|
||||
return new Properties();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param event
|
||||
*/
|
||||
private void publish(String message) {
|
||||
if (notificationPublisher != null) {
|
||||
notificationPublisher.sendNotification(new Notification("RepeatOperationsApplicationEvent", this,
|
||||
notificationCount++, message));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Specific implementations of bootstrap concerns.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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.configuration.DuplicateJobConfigurationException;
|
||||
import org.springframework.batch.core.configuration.JobConfiguration;
|
||||
import org.springframework.batch.core.configuration.JobConfigurationLocator;
|
||||
import org.springframework.batch.core.configuration.JobConfigurationRegistry;
|
||||
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 JobConfiguration} beans
|
||||
* with a {@link JobConfigurationRegistry}. Include a bean of this type along
|
||||
* with your job configuration, and use the same
|
||||
* {@link JobConfigurationRegistry} as a {@link JobConfigurationLocator} when
|
||||
* you need to locate a {@link JobConfigurationLocator} to launch.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class JobConfigurationRegistryBeanPostProcessor implements BeanPostProcessor, InitializingBean, DisposableBean {
|
||||
|
||||
// It doesn't make sense for this to have a default value...
|
||||
private JobConfigurationRegistry jobConfigurationRegistry = null;
|
||||
|
||||
private Collection jobConfigurations = new HashSet();
|
||||
|
||||
/**
|
||||
* Injection setter for {@link JobConfigurationRegistry}.
|
||||
*
|
||||
* @param jobConfigurationRegistry the jobConfigurationRegistry to set
|
||||
*/
|
||||
public void setJobConfigurationRegistry(JobConfigurationRegistry jobConfigurationRegistry) {
|
||||
this.jobConfigurationRegistry = jobConfigurationRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 JobConfiguration} instances that were
|
||||
* regsistered by this post processor.
|
||||
* @see org.springframework.beans.factory.DisposableBean#destroy()
|
||||
*/
|
||||
public void destroy() throws Exception {
|
||||
for (Iterator iter = jobConfigurations.iterator(); iter.hasNext();) {
|
||||
JobConfiguration jobConfiguration = (JobConfiguration) iter.next();
|
||||
jobConfigurationRegistry.unregister(jobConfiguration);
|
||||
}
|
||||
jobConfigurations.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the bean is an instance of {@link JobConfiguration} then register it.
|
||||
* @throws FatalBeanException if there is a
|
||||
* {@link DuplicateJobConfigurationException}.
|
||||
*
|
||||
* @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 JobConfiguration) {
|
||||
JobConfiguration jobConfiguration = (JobConfiguration) bean;
|
||||
try {
|
||||
jobConfigurationRegistry.register(jobConfiguration);
|
||||
jobConfigurations.add(jobConfiguration);
|
||||
}
|
||||
catch (DuplicateJobConfigurationException 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,97 @@
|
||||
/*
|
||||
* 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.configuration.DuplicateJobConfigurationException;
|
||||
import org.springframework.batch.core.configuration.JobConfiguration;
|
||||
import org.springframework.batch.core.configuration.JobConfigurationRegistry;
|
||||
import org.springframework.batch.core.configuration.ListableJobConfigurationRegistry;
|
||||
import org.springframework.batch.core.configuration.NoSuchJobConfigurationException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Simple map-based implementation of {@link JobConfigurationRegistry}. Access
|
||||
* to the map is synchronized, guarded by an internal lock.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class MapJobConfigurationRegistry implements ListableJobConfigurationRegistry {
|
||||
|
||||
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(JobConfiguration jobConfiguration) throws DuplicateJobConfigurationException {
|
||||
Assert.notNull(jobConfiguration);
|
||||
String name = jobConfiguration.getName();
|
||||
Assert.notNull(name, "Job configuration must have a name.");
|
||||
synchronized (map) {
|
||||
if (map.containsKey(name) && jobConfiguration.equals(map.get(name))) {
|
||||
throw new DuplicateJobConfigurationException("A job configuration with this name [" + name
|
||||
+ "] was already registered");
|
||||
}
|
||||
// allow replacing job configuration with new instance
|
||||
map.put(name, jobConfiguration);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.container.common.configuration.JobConfigurationRegistry#unregister(org.springframework.batch.container.common.configuration.JobConfiguration)
|
||||
*/
|
||||
public void unregister(JobConfiguration jobConfiguration) {
|
||||
String name = jobConfiguration.getName();
|
||||
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 JobConfiguration getJobConfiguration(String name) throws NoSuchJobConfigurationException {
|
||||
synchronized (map) {
|
||||
if (!map.containsKey(name)) {
|
||||
throw new NoSuchJobConfigurationException("No job configuration with the name [" + name
|
||||
+ "] was registered");
|
||||
}
|
||||
return (JobConfiguration) map.get(name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.container.common.configuration.ListableJobConfigurationRegistry#getJobConfigurations()
|
||||
*/
|
||||
public Collection getJobConfigurations() {
|
||||
synchronized (map) {
|
||||
return Collections.unmodifiableCollection(new HashSet(map.keySet()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Specific implementations of configuration concerns.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* 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.facade;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.config.AbstractFactoryBean;
|
||||
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;
|
||||
|
||||
/**
|
||||
* *******This class is currently undergoing heavy refactoring*****************
|
||||
*
|
||||
* Strategy for locating different resources on the file system. For each unique
|
||||
* step, the same file handle will be returned. A unique step is defined as
|
||||
* having the same job name, job run, schedule date, stream name, and step name.
|
||||
* An external file mover (such as an EAI solution) should rename and move any
|
||||
* input files to conform to the patter defined by the file pattern.<br/>
|
||||
*
|
||||
* If no pattern is passed in, then following default is used:
|
||||
*
|
||||
* <pre>
|
||||
* %BATCH_ROOT%/job_data/%JOB_NAME%/%SCHEDULE_DATE%-%STREAM_NAME%-%STEP_NAME%.txt
|
||||
* </pre>
|
||||
*
|
||||
* The %% variables are replaced with the corresponding bean property at run
|
||||
* time, when the factory method is executed.
|
||||
*
|
||||
* @author Tomas Slanina
|
||||
* @author Lucas Ward
|
||||
* @author Dave Syer
|
||||
*
|
||||
* @see FactoryBean
|
||||
*/
|
||||
public class BatchResourceFactoryBean extends AbstractFactoryBean implements ResourceLoaderAware {
|
||||
|
||||
private static final String BATCH_ROOT_PATTERN = "%BATCH_ROOT%";
|
||||
|
||||
private static final String JOB_NAME_PATTERN = "%JOB_NAME%";
|
||||
|
||||
private static final String JOB_RUN_PATTERN = "%JOB_RUN%";
|
||||
|
||||
private static final String STEP_NAME_PATTERN = "%STEP_NAME%";
|
||||
|
||||
private static final String STREAM_PATTERN = "%STREAM_NAME%";
|
||||
|
||||
private static final String SCHEDULE_DATE_PATTERN = "%SCHEDULE_DATE%";
|
||||
|
||||
private static final String DEFAULT_PATTERN = "%BATCH_ROOT%/job_data/%JOB_NAME%/"
|
||||
+ "%SCHEDULE_DATE%-%STREAM_NAME%-%STEP_NAME%.txt";
|
||||
|
||||
private String filePattern = DEFAULT_PATTERN;
|
||||
|
||||
private String jobName = "";
|
||||
|
||||
private String jobStream = "";
|
||||
|
||||
private int jobRun = 0;
|
||||
|
||||
private String scheduleDate = "";
|
||||
|
||||
private String rootDirectory = "";
|
||||
|
||||
private String stepName = "";
|
||||
|
||||
private ResourceLoader resourceLoader;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.context.ResourceLoaderAware#setResourceLoader(org.springframework.core.io.ResourceLoader)
|
||||
*/
|
||||
public void setResourceLoader(ResourceLoader resourceLoader) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Resource representing the file defined by the file pattern.
|
||||
*
|
||||
* @see FactoryBean#getObject()
|
||||
* @return a resource representing the file on the file system.
|
||||
*/
|
||||
protected Object createInstance() {
|
||||
|
||||
if (resourceLoader == null) {
|
||||
resourceLoader = new FileSystemResourceLoader();
|
||||
}
|
||||
|
||||
return resourceLoader.getResource(createFileName());
|
||||
}
|
||||
|
||||
public Class getObjectType() {
|
||||
return Resource.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* helper method for <code>createFileName()</code>
|
||||
*/
|
||||
private String replacePattern(String string, String pattern, String replacement) {
|
||||
|
||||
// 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
|
||||
*/
|
||||
private String createFileName() {
|
||||
Assert.notNull(filePattern, "filename pattern is null");
|
||||
|
||||
String fileName = filePattern;
|
||||
|
||||
// TODO consider refactoring to void replacePattern() method and
|
||||
// collecting variable fileName
|
||||
fileName = replacePattern(fileName, BATCH_ROOT_PATTERN, rootDirectory);
|
||||
fileName = replacePattern(fileName, JOB_NAME_PATTERN, jobName);
|
||||
fileName = replacePattern(fileName, STEP_NAME_PATTERN, stepName);
|
||||
fileName = replacePattern(fileName, STREAM_PATTERN, jobStream);
|
||||
fileName = replacePattern(fileName, JOB_RUN_PATTERN, String.valueOf(jobRun));
|
||||
fileName = replacePattern(fileName, SCHEDULE_DATE_PATTERN, scheduleDate);
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void setFilePattern(String filePattern) {
|
||||
this.filePattern = filePattern;
|
||||
}
|
||||
|
||||
public void setRootDirectory(String rootDirectory) {
|
||||
this.rootDirectory = rootDirectory;
|
||||
}
|
||||
|
||||
public void setStepName(String stepName) {
|
||||
this.stepName = stepName;
|
||||
}
|
||||
|
||||
public void setJobName(String jobName) {
|
||||
this.jobName = jobName;
|
||||
}
|
||||
|
||||
public void setJobRun(int jobRun) {
|
||||
this.jobRun = jobRun;
|
||||
}
|
||||
|
||||
public void setJobStream(String jobStream) {
|
||||
this.jobStream = jobStream;
|
||||
}
|
||||
|
||||
public void setScheduleDate(String scheduleDate) {
|
||||
this.scheduleDate = scheduleDate;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* 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.facade;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.batch.core.configuration.JobConfiguration;
|
||||
import org.springframework.batch.core.configuration.JobConfigurationLocator;
|
||||
import org.springframework.batch.core.configuration.NoSuchJobConfigurationException;
|
||||
import org.springframework.batch.core.domain.JobInstance;
|
||||
import org.springframework.batch.core.executor.JobExecutor;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.core.runtime.JobExecutionContext;
|
||||
import org.springframework.batch.core.runtime.JobExecutionRegistry;
|
||||
import org.springframework.batch.core.runtime.JobIdentifier;
|
||||
import org.springframework.batch.execution.JobExecutorFacade;
|
||||
import org.springframework.batch.execution.NoSuchJobExecutionException;
|
||||
import org.springframework.batch.execution.job.DefaultJobExecutor;
|
||||
import org.springframework.batch.repeat.RepeatContext;
|
||||
import org.springframework.batch.statistics.StatisticsProvider;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Simple implementation of (@link {@link JobExecutorFacade}).
|
||||
*
|
||||
* <p>
|
||||
* A {@link JobIdentifier} will be used to uniquely identify the job by the
|
||||
* repository. Once the job is obtained, the {@link JobExecutor} will be used to
|
||||
* run the job.
|
||||
* </p>
|
||||
*
|
||||
* @author Lucas Ward
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class SimpleJobExecutorFacade implements JobExecutorFacade, StatisticsProvider {
|
||||
|
||||
private JobExecutor jobExecutor;
|
||||
|
||||
private JobRepository jobRepository;
|
||||
|
||||
private JobExecutionRegistry jobExecutionRegistry = new VolatileJobExecutionRegistry();
|
||||
|
||||
// there is no sensible default for this
|
||||
private JobConfigurationLocator jobConfigurationLocator;
|
||||
|
||||
private int running = 0;
|
||||
|
||||
private Object mutex = new Object();
|
||||
|
||||
/**
|
||||
* Public accessor for the running property.
|
||||
*
|
||||
* @return the running
|
||||
*/
|
||||
public boolean isRunning() {
|
||||
synchronized (mutex) {
|
||||
return running > 0;
|
||||
}
|
||||
}
|
||||
|
||||
public SimpleJobExecutorFacade() {
|
||||
jobExecutor = new DefaultJobExecutor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the job execution registry. The default should be adequate so
|
||||
* this setter method is mainly used for testing.
|
||||
* @param jobExecutionRegistry the jobExecutionRegistry to set
|
||||
*/
|
||||
public void setJobExecutionRegistry(JobExecutionRegistry jobExecutionRegistry) {
|
||||
this.jobExecutionRegistry = jobExecutionRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for injection of {@link JobConfigurationLocator}.
|
||||
*
|
||||
* @param jobConfigurationLocator the jobConfigurationLocator to set
|
||||
*/
|
||||
public void setJobConfigurationLocator(JobConfigurationLocator jobConfigurationLocator) {
|
||||
this.jobConfigurationLocator = jobConfigurationLocator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locates a {@link JobConfiguration} by using the name of the provided
|
||||
* {@link JobIdentifier} and the {@link JobConfigurationLocator}.
|
||||
*
|
||||
* @see org.springframework.batch.execution.JobExecutorFacade#start(org.springframework.batch.execution.common.domain.JobConfiguration,
|
||||
* org.springframework.batch.core.runtime.JobIdentifier)
|
||||
*
|
||||
* @throws IllegalArgumentException if the runtime information is null or
|
||||
* its name is null
|
||||
* @throws IllegalStateException if the {@link JobConfigurationLocator} does
|
||||
* not contain a {@link JobConfiguration} with the name provided.
|
||||
* @throws IllegalStateException if the {@link JobExecutor} is null
|
||||
* @throws IllegalStateException if the {@link JobConfigurationLocator} is
|
||||
* null
|
||||
*
|
||||
*/
|
||||
public void start(JobIdentifier jobRuntimeInformation) throws NoSuchJobConfigurationException {
|
||||
|
||||
Assert.notNull(jobRuntimeInformation, "JobRuntimeInformation must not be null.");
|
||||
Assert.notNull(jobRuntimeInformation.getName(), "JobRuntimeInformation name must not be null.");
|
||||
|
||||
Assert.state(!jobExecutionRegistry.isRegistered(jobRuntimeInformation),
|
||||
"A job with this JobRuntimeInformation is already executing in this container");
|
||||
|
||||
Assert.state(jobExecutor != null, "JobExecutor must be provided.");
|
||||
Assert.state(jobConfigurationLocator != null, "JobConfigurationLocator must be provided.");
|
||||
|
||||
JobConfiguration jobConfiguration = jobConfigurationLocator
|
||||
.getJobConfiguration(jobRuntimeInformation.getName());
|
||||
|
||||
final JobInstance job = jobRepository.findOrCreateJob(jobConfiguration, jobRuntimeInformation);
|
||||
JobExecutionContext jobExecutionContext = jobExecutionRegistry.register(jobRuntimeInformation, job);
|
||||
try {
|
||||
synchronized (mutex) {
|
||||
running++;
|
||||
}
|
||||
jobExecutor.run(jobConfiguration, jobExecutionContext);
|
||||
}
|
||||
finally {
|
||||
synchronized (mutex) {
|
||||
// assume execution is synchronous so when we get to here we are
|
||||
// not running any more
|
||||
running--;
|
||||
}
|
||||
jobExecutionRegistry.unregister(jobRuntimeInformation);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.container.BatchContainer#stop(org.springframework.batch.container.common.runtime.JobRuntimeInformation)
|
||||
*/
|
||||
public void stop(JobIdentifier runtimeInformation) throws NoSuchJobExecutionException {
|
||||
JobExecutionContext jobExecutionContext = (JobExecutionContext) jobExecutionRegistry.get(runtimeInformation);
|
||||
if (jobExecutionContext == null) {
|
||||
throw new NoSuchJobExecutionException("No such Job is executing: [" + runtimeInformation + "]");
|
||||
}
|
||||
for (Iterator iter = jobExecutionContext.getStepContexts().iterator(); iter.hasNext();) {
|
||||
RepeatContext context = (RepeatContext) iter.next();
|
||||
context.setTerminateOnly();
|
||||
}
|
||||
;
|
||||
for (Iterator iter = jobExecutionContext.getChunkContexts().iterator(); iter.hasNext();) {
|
||||
RepeatContext context = (RepeatContext) iter.next();
|
||||
context.setTerminateOnly();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for {@link JobExecutor}.
|
||||
*
|
||||
* @param jobExecutor
|
||||
*/
|
||||
public void setJobExecutor(JobExecutor jobExecutor) {
|
||||
this.jobExecutor = jobExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for {@link JobRepository}.
|
||||
*
|
||||
* @param jobRepository
|
||||
*/
|
||||
public void setJobRepository(JobRepository jobRepository) {
|
||||
this.jobRepository = jobRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a read-only view of the state of the running jobs.
|
||||
*/
|
||||
public Properties getStatistics() {
|
||||
int i = 0;
|
||||
Properties props = new Properties();
|
||||
for (Iterator iter = jobExecutionRegistry.findAll().iterator(); iter.hasNext();) {
|
||||
JobExecutionContext element = (JobExecutionContext) iter.next();
|
||||
i++;
|
||||
String runtime = "job" + i;
|
||||
props.setProperty(runtime, "" + element.getJobIdentifier());
|
||||
int j = 0;
|
||||
for (Iterator iterator = element.getStepContexts().iterator(); iterator.hasNext();) {
|
||||
RepeatContext context = (RepeatContext) iterator.next();
|
||||
j++;
|
||||
props.setProperty(runtime + ".step" + j, "" + context);
|
||||
|
||||
}
|
||||
j = 0;
|
||||
for (Iterator iterator = element.getChunkContexts().iterator(); iterator.hasNext();) {
|
||||
RepeatContext context = (RepeatContext) iterator.next();
|
||||
j++;
|
||||
props.setProperty(runtime + ".chunk" + j, "" + context);
|
||||
|
||||
}
|
||||
}
|
||||
return props;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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.facade;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.batch.core.domain.JobInstance;
|
||||
import org.springframework.batch.core.runtime.JobExecutionContext;
|
||||
import org.springframework.batch.core.runtime.JobExecutionRegistry;
|
||||
import org.springframework.batch.core.runtime.JobIdentifier;
|
||||
|
||||
/**
|
||||
* Simple in-memory implementation of {@link JobExecutionRegistry}.
|
||||
* Synchronizes all access to the underlying storage. Good for most purposes.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class VolatileJobExecutionRegistry implements JobExecutionRegistry {
|
||||
|
||||
private Map contexts = new HashMap();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.container.common.executor.JobExecutionRegistry#findByName(java.lang.String)
|
||||
*/
|
||||
public Collection findByName(String name) {
|
||||
Set values = new HashSet();
|
||||
HashMap contexts;
|
||||
synchronized (this.contexts) {
|
||||
contexts = new HashMap(this.contexts);
|
||||
}
|
||||
for (Iterator iter = contexts.entrySet().iterator(); iter.hasNext();) {
|
||||
Map.Entry entry = (Map.Entry) iter.next();
|
||||
String runtimeName = ((JobIdentifier) entry.getKey()).getName();
|
||||
if ((name == null && runtimeName == null) || name.equals(runtimeName)) {
|
||||
values.add(entry.getValue());
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.container.common.executor.JobExecutionRegistry#findAll()
|
||||
*/
|
||||
public Collection findAll() {
|
||||
|
||||
synchronized (this.contexts) {
|
||||
return new HashSet(contexts.values());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.container.common.executor.JobExecutionRegistry#findByRuntimeInformation(org.springframework.batch.container.common.runtime.JobRuntimeInformation)
|
||||
*/
|
||||
public JobExecutionContext get(JobIdentifier runtimeInformation) {
|
||||
|
||||
synchronized (this.contexts) {
|
||||
return (JobExecutionContext) contexts.get(runtimeInformation);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.container.common.executor.JobExecutionRegistry#isRegistered(org.springframework.batch.container.common.runtime.JobRuntimeInformation)
|
||||
*/
|
||||
public boolean isRegistered(JobIdentifier runtimeInformation) {
|
||||
|
||||
synchronized (this.contexts) {
|
||||
return contexts.containsKey(runtimeInformation);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.container.common.executor.JobExecutionRegistry#register(org.springframework.batch.container.common.runtime.JobRuntimeInformation,
|
||||
* org.springframework.batch.container.common.domain.JobExecution)
|
||||
*/
|
||||
public JobExecutionContext register(JobIdentifier jobIdentifier, JobInstance job) {
|
||||
if (isRegistered(jobIdentifier)) {
|
||||
return get(jobIdentifier);
|
||||
}
|
||||
JobExecutionContext context = new JobExecutionContext(jobIdentifier, job);
|
||||
|
||||
synchronized (this.contexts) {
|
||||
contexts.put(jobIdentifier, context);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.container.common.executor.JobExecutionRegistry#unregister(org.springframework.batch.container.common.runtime.JobRuntimeInformation)
|
||||
*/
|
||||
public void unregister(JobIdentifier runtimeInformation) {
|
||||
|
||||
synchronized (this.contexts) {
|
||||
contexts.remove(runtimeInformation);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Specific implementations of facade concerns.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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.sql.Timestamp;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.batch.core.configuration.JobConfiguration;
|
||||
import org.springframework.batch.core.configuration.StepConfiguration;
|
||||
import org.springframework.batch.core.domain.BatchStatus;
|
||||
import org.springframework.batch.core.domain.JobExecution;
|
||||
import org.springframework.batch.core.domain.JobInstance;
|
||||
import org.springframework.batch.core.domain.StepInstance;
|
||||
import org.springframework.batch.core.executor.JobExecutor;
|
||||
import org.springframework.batch.core.executor.StepExecutor;
|
||||
import org.springframework.batch.core.executor.StepExecutorFactory;
|
||||
import org.springframework.batch.core.executor.StepInterruptedException;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.core.runtime.JobExecutionContext;
|
||||
import org.springframework.batch.core.runtime.StepExecutionContext;
|
||||
import org.springframework.batch.execution.step.DefaultStepExecutorFactory;
|
||||
import org.springframework.batch.io.exception.BatchCriticalException;
|
||||
import org.springframework.batch.repeat.ExitStatus;
|
||||
import org.springframework.batch.repeat.RepeatContext;
|
||||
|
||||
/**
|
||||
* Default implementation of (@JobLifecycle) interface. Sequentially executes a
|
||||
* job by iterating it's life of steps. Interruption of a job run is pluggable
|
||||
* by passing in various interruption policies.
|
||||
*
|
||||
* @author Lucas Ward
|
||||
*/
|
||||
public class DefaultJobExecutor implements JobExecutor {
|
||||
|
||||
private JobRepository jobRepository;
|
||||
|
||||
private StepExecutorFactory stepExecutorResolver = new DefaultStepExecutorFactory();
|
||||
|
||||
public void run(JobConfiguration configuration, JobExecutionContext jobExecutionContext)
|
||||
throws BatchCriticalException {
|
||||
|
||||
JobInstance job = jobExecutionContext.getJob();
|
||||
JobExecution jobExecution = jobExecutionContext.getJobExecution();
|
||||
updateStatus(jobExecutionContext, BatchStatus.STARTING);
|
||||
|
||||
List steps = job.getSteps();
|
||||
|
||||
ExitStatus status = ExitStatus.FAILED;
|
||||
|
||||
try {
|
||||
for (Iterator i = steps.iterator(), j = configuration.getStepConfigurations().iterator(); i.hasNext()
|
||||
&& j.hasNext();) {
|
||||
StepInstance step = (StepInstance) i.next();
|
||||
StepConfiguration stepConfiguration = (StepConfiguration) j.next();
|
||||
if (shouldStart(step, stepConfiguration)) {
|
||||
updateStatus(jobExecutionContext, BatchStatus.STARTED);
|
||||
StepExecutor stepExecutor = stepExecutorResolver.getExecutor(stepConfiguration);
|
||||
StepExecutionContext stepExecutionContext = new StepExecutionContext(jobExecutionContext, step);
|
||||
status = stepExecutor.process(stepConfiguration, stepExecutionContext);
|
||||
}
|
||||
}
|
||||
|
||||
updateStatus(jobExecutionContext, BatchStatus.COMPLETED);
|
||||
}
|
||||
catch (StepInterruptedException e) {
|
||||
updateStatus(jobExecutionContext, BatchStatus.STOPPED);
|
||||
rethrow(e);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
updateStatus(jobExecutionContext, BatchStatus.FAILED);
|
||||
rethrow(t);
|
||||
}
|
||||
finally {
|
||||
jobExecution.setEndTime(new Timestamp(System.currentTimeMillis()));
|
||||
jobExecution.setExitCode(status.getExitCode());
|
||||
jobRepository.saveOrUpdate(jobExecution);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateStatus(JobExecutionContext jobExecutionContext, BatchStatus status) {
|
||||
JobInstance job = jobExecutionContext.getJob();
|
||||
JobExecution jobExecution = jobExecutionContext.getJobExecution();
|
||||
jobExecution.setStatus(status);
|
||||
job.setStatus(status);
|
||||
jobRepository.update(job);
|
||||
jobRepository.saveOrUpdate(jobExecution);
|
||||
for (Iterator iter = jobExecutionContext.getStepContexts().iterator(); iter.hasNext();) {
|
||||
RepeatContext context = (RepeatContext) iter.next();
|
||||
context.setAttribute("JOB_STATUS", status);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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(StepInstance step, StepConfiguration stepConfiguration) {
|
||||
|
||||
if (step.getStatus() == BatchStatus.COMPLETED && stepConfiguration.isAllowStartIfComplete() == false) {
|
||||
// step is complete, false should be returned, indicated that the
|
||||
// step should
|
||||
// not be started
|
||||
return false;
|
||||
}
|
||||
|
||||
if (step.getStepExecutionCount() < stepConfiguration.getStartLimit()) {
|
||||
// step start count is less than start max, return true
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
// start max has been exceeded, throw an exception.
|
||||
throw new BatchCriticalException("Maximum start limit exceeded for step: " + step.getName() + "StartMax: "
|
||||
+ stepConfiguration.getStartLimit());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param t
|
||||
*/
|
||||
private static void rethrow(Throwable t) throws RuntimeException {
|
||||
if (t instanceof RuntimeException) {
|
||||
throw (RuntimeException) t;
|
||||
}
|
||||
else {
|
||||
throw new BatchCriticalException(t);
|
||||
}
|
||||
}
|
||||
|
||||
public void setJobRepository(JobRepository jobRepository) {
|
||||
this.jobRepository = jobRepository;
|
||||
}
|
||||
|
||||
public void setStepExecutorResolver(StepExecutorFactory stepExecutorResolver) {
|
||||
this.stepExecutorResolver = stepExecutorResolver;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Specific implementations of job concerns.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Reference implementation of the Spring Batch Core.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* 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.configuration.JobConfiguration;
|
||||
import org.springframework.batch.core.configuration.StepConfiguration;
|
||||
import org.springframework.batch.core.domain.JobExecution;
|
||||
import org.springframework.batch.core.domain.JobInstance;
|
||||
import org.springframework.batch.core.domain.StepExecution;
|
||||
import org.springframework.batch.core.domain.StepInstance;
|
||||
import org.springframework.batch.core.repository.BatchRestartException;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.core.repository.NoSuchBatchDomainObjectException;
|
||||
import org.springframework.batch.core.runtime.JobIdentifier;
|
||||
import org.springframework.batch.execution.repository.dao.JobDao;
|
||||
import org.springframework.batch.execution.repository.dao.StepDao;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
*
|
||||
* <p>
|
||||
* Simple Job Repository that stores Jobs, JobExecutions, Steps, and
|
||||
* StepExecutions using the provided JobDao and StepDao.
|
||||
* <p>
|
||||
*
|
||||
* @author Lucas Ward
|
||||
* @author Dave Syer
|
||||
* @see JobRepository
|
||||
* @see StepDao
|
||||
* @see JobDao
|
||||
*
|
||||
*/
|
||||
public class SimpleJobRepository implements JobRepository {
|
||||
|
||||
private JobDao jobDao;
|
||||
|
||||
private StepDao stepDao;
|
||||
|
||||
public SimpleJobRepository(JobDao jobDao, StepDao stepDao) {
|
||||
super();
|
||||
this.jobDao = jobDao;
|
||||
this.stepDao = stepDao;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Find or Create a Job(@link Job) based on the passed in RuntimeInformation
|
||||
* and Configuration. JobRuntimeInformation contains the following fields
|
||||
* which logically identify a job: JobName, JobStream, JobRun, and Schedule
|
||||
* Date. However, unique identification of a job can only come from the
|
||||
* database, and therefore must come from JobDao by either creating a new
|
||||
* job or finding an existing one, which will ensure that the id field of
|
||||
* the job 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 Job's restartPolicy will be checked first. If it is
|
||||
* not restartable, a new job will be created, regardless of whether or not
|
||||
* one exists. If it is restartable, the JobDao 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 it will be returned. If no job is found, a
|
||||
* new one will be created based on the configuration.
|
||||
* </p>
|
||||
*
|
||||
* @see JobRepository#findOrCreateJob(JobConfiguration,
|
||||
* JobIdentifier)
|
||||
*/
|
||||
public JobInstance findOrCreateJob(JobConfiguration jobConfiguration, JobIdentifier runtimeInformation) {
|
||||
|
||||
List jobs;
|
||||
|
||||
// Check if a job is restartable, if not, create and return a new job
|
||||
if (jobConfiguration.isRestartable() == false) {
|
||||
return createJob(jobConfiguration, runtimeInformation);
|
||||
}
|
||||
else {
|
||||
// find all jobs matching the runtime information.
|
||||
jobs = jobDao.findJobs(runtimeInformation);
|
||||
}
|
||||
|
||||
if (jobs.size() == 1) {
|
||||
// One job was found
|
||||
JobInstance job = (JobInstance) jobs.get(0);
|
||||
job.setSteps(findSteps(jobConfiguration.getStepConfigurations(), job));
|
||||
job.setJobExecutionCount(jobDao.getJobExecutionCount(job.getId()));
|
||||
if (job.getJobExecutionCount() > jobConfiguration.getStartLimit()) {
|
||||
throw new BatchRestartException("Restart Max exceeded for Job: " + job.toString());
|
||||
}
|
||||
return job;
|
||||
}
|
||||
else if (jobs.size() == 0) {
|
||||
// no job found, create one
|
||||
return createJob(jobConfiguration, runtimeInformation);
|
||||
}
|
||||
else {
|
||||
// More than one job found, throw exception
|
||||
throw new NoSuchBatchDomainObjectException("Error obtaining" + "previous job run: "
|
||||
+ jobConfiguration.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
jobDao.save(jobExecution);
|
||||
}
|
||||
else {
|
||||
// new execution
|
||||
jobDao.update(jobExecution);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing job. A job must have been obtained from the
|
||||
* findOrCreateJob method, otherwise it is likely that the id is incorrect
|
||||
* or non-existant.
|
||||
*
|
||||
* @param job to be updated.
|
||||
* @throws IllegalArgumentException if Job or it's Id is null.
|
||||
*/
|
||||
public void update(JobInstance job) {
|
||||
|
||||
Assert.notNull(job, "Job cannot be null.");
|
||||
Assert.notNull(job.getId(), "Job cannot be updated if it's ID is null. It must be obtained"
|
||||
+ "from SimpleJobRepository.findOrCreateJob to be considered valid.");
|
||||
|
||||
jobDao.update(job);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.getStepId(), "StepExecution's Step Id cannot be null.");
|
||||
|
||||
if (stepExecution.getId() == null) {
|
||||
// new execution, obtain id and insert
|
||||
stepDao.save(stepExecution);
|
||||
}
|
||||
else {
|
||||
// existing execution, update
|
||||
stepDao.update(stepExecution);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the given step.
|
||||
*
|
||||
* @param StepInstance to be updated.
|
||||
* @throws IllegalArgumentException if step or it's id is null.
|
||||
*/
|
||||
public void update(StepInstance step) {
|
||||
|
||||
Assert.notNull(step, "Step cannot be null.");
|
||||
Assert.notNull(step.getId(), "Step cannot be updated if it's ID is null. It must be obtained"
|
||||
+ "from SimpleJobRepository.findOrCreateJob to be considered valid.");
|
||||
|
||||
stepDao.update(step);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Convenience method for creating a new job. A new job is created by
|
||||
* calling {@link JobDao#createJob(JobRuntimeInformation)} and then it's
|
||||
* list of StepConfigurations is passed to the createSteps method.
|
||||
*/
|
||||
private JobInstance createJob(JobConfiguration jobConfiguration, JobIdentifier runtimeInformation) {
|
||||
|
||||
JobInstance job = jobDao.createJob(runtimeInformation);
|
||||
job.setSteps(createSteps(job, jobConfiguration.getStepConfigurations()));
|
||||
return job;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create steps based on the given Job and list of StepConfigurations.
|
||||
*/
|
||||
private List createSteps(JobInstance job, List stepConfigurations) {
|
||||
|
||||
List steps = new ArrayList();
|
||||
Iterator i = stepConfigurations.iterator();
|
||||
while (i.hasNext()) {
|
||||
StepConfiguration stepConfiguration = (StepConfiguration) i.next();
|
||||
StepInstance step = stepDao.createStep(job, stepConfiguration.getName());
|
||||
steps.add(step);
|
||||
}
|
||||
|
||||
return steps;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find Steps for the given list of StepConfiguration's with a given JobId
|
||||
*/
|
||||
protected List findSteps(List stepConfigurations, JobInstance job) {
|
||||
List steps = new ArrayList();
|
||||
Iterator i = stepConfigurations.iterator();
|
||||
while (i.hasNext()) {
|
||||
|
||||
StepConfiguration stepConfiguration = (StepConfiguration) i.next();
|
||||
StepInstance step = stepDao.findStep(job, stepConfiguration.getName());
|
||||
if (step != null) {
|
||||
|
||||
step.setStepExecutionCount(stepDao.getStepExecutionCount(step.getId()));
|
||||
|
||||
steps.add(step);
|
||||
}
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.repository.dao;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.springframework.batch.core.domain.BatchStatus;
|
||||
import org.springframework.jdbc.support.lob.LobCreator;
|
||||
import org.springframework.jdbc.support.lob.LobHandler;
|
||||
import org.springframework.orm.hibernate3.support.ClobStringType;
|
||||
|
||||
/**
|
||||
* User type object to help Hibernate to persist {@link BatchStatus} objects
|
||||
* (just plonking it a Clob).
|
||||
*
|
||||
* @author tomas.slanina
|
||||
*
|
||||
*/
|
||||
public class BatchStatusUserType extends ClobStringType {
|
||||
|
||||
/**
|
||||
* Get a {@link BatchStatus} from a Clob.
|
||||
*
|
||||
* @return a {@link BatchStatus} object whose string representation is the
|
||||
* same as the database value.
|
||||
*
|
||||
* @see org.springframework.orm.hibernate3.support.ClobStringType#nullSafeGetInternal(java.sql.ResultSet,
|
||||
* java.lang.String[], java.lang.Object,
|
||||
* org.springframework.jdbc.support.lob.LobHandler)
|
||||
*/
|
||||
protected Object nullSafeGetInternal(ResultSet rs, String[] names, Object owner, LobHandler lobHandler)
|
||||
throws SQLException {
|
||||
String status = (String) super.nullSafeGetInternal(rs, names, owner, lobHandler);
|
||||
return BatchStatus.getStatus(status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an object to a string and then pop it in a Clob.
|
||||
*
|
||||
* @see org.springframework.orm.hibernate3.support.ClobStringType#nullSafeSetInternal(java.sql.PreparedStatement,
|
||||
* int, java.lang.Object, org.springframework.jdbc.support.lob.LobCreator)
|
||||
*/
|
||||
protected void nullSafeSetInternal(PreparedStatement ps, int index, Object value, LobCreator lobCreator)
|
||||
throws SQLException {
|
||||
String status = (value == null) ? "" : value.toString();
|
||||
super.nullSafeSetInternal(ps, index, status, lobCreator);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* 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.List;
|
||||
|
||||
import org.hibernate.Criteria;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.criterion.Expression;
|
||||
import org.springframework.batch.core.domain.JobExecution;
|
||||
import org.springframework.batch.core.domain.JobInstance;
|
||||
import org.springframework.batch.core.repository.NoSuchBatchDomainObjectException;
|
||||
import org.springframework.batch.core.runtime.JobIdentifier;
|
||||
import org.springframework.batch.execution.runtime.ScheduledJobIdentifier;
|
||||
import org.springframework.orm.hibernate3.HibernateCallback;
|
||||
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Implementation of {@link JobDao} functionality based on the Hibernate ORM
|
||||
* framework. Its advantage is the independence of implementation on the
|
||||
* underlying database.
|
||||
*
|
||||
* @author tomas.slanina
|
||||
* @author Dave Syer
|
||||
*/
|
||||
|
||||
public class HibernateJobDao extends HibernateDaoSupport implements JobDao {
|
||||
|
||||
/**
|
||||
* @see JobDao#createJob(JobIdentifier)
|
||||
*
|
||||
* In this Hibernate implementation a job is stored into the database. Id is
|
||||
* obtained from Hibernate.
|
||||
*/
|
||||
public JobInstance createJob(JobIdentifier jobIdentifier) {
|
||||
|
||||
ScheduledJobIdentifier jobRuntimeInformation = (ScheduledJobIdentifier) jobIdentifier;
|
||||
|
||||
validateJobIdentifier(jobRuntimeInformation);
|
||||
|
||||
JobInstance job = new JobInstance();
|
||||
job.setIdentifier(jobIdentifier);
|
||||
|
||||
Long jobId = (Long) getHibernateTemplate().save(job);
|
||||
|
||||
job.setId(jobId);
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see JobDao#findJobs(JobIdentifier)
|
||||
*
|
||||
* Hibernate is asked to get all jobs that matches criteria. Afterwards,
|
||||
* result is mapped into domain objects.
|
||||
*/
|
||||
public List findJobs(JobIdentifier jobIdentifier) {
|
||||
|
||||
final ScheduledJobIdentifier jobRuntimeInformation = (ScheduledJobIdentifier) jobIdentifier;
|
||||
|
||||
validateJobIdentifier(jobRuntimeInformation);
|
||||
|
||||
List list = this.getHibernateTemplate().executeFind(new HibernateCallback() {
|
||||
public Object doInHibernate(Session session) {
|
||||
Criteria criteria = session.createCriteria(JobInstance.class);
|
||||
criteria.add(Expression.eq("identifier", jobRuntimeInformation));
|
||||
return criteria.list();
|
||||
}
|
||||
});
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see JobDao#getJobExecutionCount(Long)
|
||||
*/
|
||||
public int getJobExecutionCount(final Long jobId) {
|
||||
|
||||
Assert.notNull(jobId, "JobId cannot be null");
|
||||
|
||||
Long result = (Long) this.getHibernateTemplate().execute(new HibernateCallback() {
|
||||
public Object doInHibernate(Session session) {
|
||||
return session.createQuery("select count(id) from JobExecution where jobId = :jobId").setLong("jobId",
|
||||
jobId.longValue()).uniqueResult();
|
||||
}
|
||||
});
|
||||
|
||||
return (result == null) ? 0 : result.intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see JobDao#save(JobExecution)
|
||||
*
|
||||
* Hibernate implementation persists JobExecution instance. Id is obtained
|
||||
* from Hibernate.
|
||||
*/
|
||||
public void save(JobExecution jobExecution) {
|
||||
|
||||
validateJobExecution(jobExecution);
|
||||
|
||||
Long id = (Long) getHibernateTemplate().save(jobExecution);
|
||||
jobExecution.setId(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see JobDao#update(JobInstance)
|
||||
*/
|
||||
public void update(JobInstance job) {
|
||||
|
||||
Assert.notNull(job, "Job Cannot be Null");
|
||||
Assert.notNull(job.getStatus(), "Job Status cannot be Null");
|
||||
Assert.notNull(job.getId(), "Job ID cannot be null");
|
||||
|
||||
getHibernateTemplate().update(job);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see JobDao#update(JobExecution)
|
||||
*/
|
||||
public void update(final JobExecution jobExecution) {
|
||||
|
||||
validateJobExecution(jobExecution);
|
||||
|
||||
if (jobExecution.getId() == null) {
|
||||
throw new IllegalArgumentException("JobExecution ID cannot be null. JobExecution must be saved "
|
||||
+ "before it can be updated.");
|
||||
}
|
||||
|
||||
if (getHibernateTemplate().get(JobExecution.class, jobExecution.getId()) == null) {
|
||||
throw new NoSuchBatchDomainObjectException("Invalid JobExecution, ID " + jobExecution.getId()
|
||||
+ " not found.");
|
||||
}
|
||||
|
||||
getHibernateTemplate().update(jobExecution);
|
||||
}
|
||||
|
||||
public List findJobExecutions(JobInstance job) {
|
||||
|
||||
Assert.notNull(job, "Job cannot be null.");
|
||||
Assert.notNull(job.getId(), "Job ID cannot be null.");
|
||||
|
||||
final Long jobId = job.getId();
|
||||
|
||||
List list = this.getHibernateTemplate().executeFind(new HibernateCallback() {
|
||||
public Object doInHibernate(Session session) {
|
||||
Criteria criteria = session.createCriteria(JobExecution.class);
|
||||
criteria.add(Expression.eq("jobId", jobId));
|
||||
return criteria.list();
|
||||
}
|
||||
});
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.getStartTime(), "JobExecution start time cannot be null.");
|
||||
Assert.notNull(jobExecution.getStatus(), "JobExecution status cannot be null.");
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate JobRuntimeInformation. Due to differing requirements, it is
|
||||
* acceptable for any field to be blank, however null fields may cause odd
|
||||
* and vague exception reports from the database driver.
|
||||
*/
|
||||
private void validateJobIdentifier(ScheduledJobIdentifier jobRuntimeInformation) {
|
||||
|
||||
Assert.notNull(jobRuntimeInformation, "JobRuntimeInformation cannot be null.");
|
||||
Assert.notNull(jobRuntimeInformation.getName(), "JobRuntimeInformation name cannot be null.");
|
||||
Assert.notNull(jobRuntimeInformation.getJobStream(), "JobRuntimeInformation JobStream cannot be null.");
|
||||
Assert.notNull(jobRuntimeInformation.getScheduleDate(), "JobRuntimeInformation ScheduleDate cannot be null.");
|
||||
}
|
||||
}
|
||||
@@ -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.repository.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.Criteria;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.criterion.Expression;
|
||||
import org.springframework.batch.core.domain.JobInstance;
|
||||
import org.springframework.batch.core.domain.StepExecution;
|
||||
import org.springframework.batch.core.domain.StepInstance;
|
||||
import org.springframework.orm.hibernate3.HibernateCallback;
|
||||
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* It represents an implementation of {@link StepDao} functionality based
|
||||
* on the Hibernate ORM framework. Its advantage is the independency of implementation
|
||||
* on the underlying database.
|
||||
*
|
||||
* @author tomas.slanina
|
||||
*/
|
||||
public class HibernateStepDao extends HibernateDaoSupport implements StepDao {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.container.repository.dao.StepDao#createStep(String, java.lang.Long)
|
||||
*/
|
||||
public StepInstance createStep(JobInstance job, String stepName) {
|
||||
|
||||
Assert.notNull(job, "Job cannot be null.");
|
||||
Assert.notNull(stepName, "StepName cannot be null.");
|
||||
|
||||
StepInstance step = new StepInstance();
|
||||
step.setName(stepName);
|
||||
step.setJob(job);
|
||||
|
||||
Long stepId = (Long)getHibernateTemplate().save(step);
|
||||
|
||||
step.setId(stepId);
|
||||
|
||||
return step;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @see StepDao#findStep(Long, String)
|
||||
*/
|
||||
public StepInstance findStep(final JobInstance job, final String stepName) {
|
||||
|
||||
Assert.notNull(job, "Job cannot be null.");
|
||||
Assert.notNull(job.getId(), "Job ID cannot be null");
|
||||
Assert.notNull(stepName, "StepName cannot be null");
|
||||
|
||||
return (StepInstance) this.getHibernateTemplate().execute(new HibernateCallback() {
|
||||
public Object doInHibernate(Session session) {
|
||||
Criteria criteria = session.createCriteria(StepInstance.class);
|
||||
criteria.add(Expression.eq("name", stepName));
|
||||
criteria.add(Expression.eq("job.id", job.getId()));
|
||||
return criteria.uniqueResult();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @see StepDao#findSteps(Long)
|
||||
*
|
||||
* Hibernate is asked to get all jobs that matches criteria. Afterwards, result is mapped into domain objects.
|
||||
* It should be noted that restart data must be requested separately.
|
||||
*
|
||||
*/
|
||||
public List findSteps(final Long jobId) {
|
||||
|
||||
Assert.notNull(jobId, "JobId cannot be null.");
|
||||
|
||||
List list = this.getHibernateTemplate().executeFind(new HibernateCallback() {
|
||||
public Object doInHibernate(Session session) {
|
||||
Criteria criteria = session.createCriteria(StepInstance.class);
|
||||
criteria.add(Expression.eq("job.id", jobId));
|
||||
return criteria.list();
|
||||
}
|
||||
});
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see StepDao#getStepExecutionCount(Long)
|
||||
*/
|
||||
public int getStepExecutionCount(final Long stepId) {
|
||||
Long result = (Long) this.getHibernateTemplate().execute(new HibernateCallback() {
|
||||
public Object doInHibernate(Session session) {
|
||||
return session.createQuery("select count(id) from StepExecution s where s.stepId = :stepId")
|
||||
.setLong("stepId", stepId.longValue())
|
||||
.uniqueResult();
|
||||
}
|
||||
});
|
||||
|
||||
return (result==null) ? 0 :result.intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see StepDao#save(StepExecution)
|
||||
*
|
||||
* Hibernate implementation persists StepExecution instance. Id is obtained from Hibernate.
|
||||
*/
|
||||
public void save(StepExecution stepExecution) {
|
||||
|
||||
validateStepExecution(stepExecution);
|
||||
|
||||
Long id = (Long)getHibernateTemplate().save(stepExecution);
|
||||
stepExecution.setId(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see StepDao#update(StepInstance)
|
||||
*/
|
||||
public void update(StepInstance step) {
|
||||
|
||||
Assert.notNull(step, "Step cannot be null.");
|
||||
Assert.notNull(step.getStatus(), "Step status cannot be null.");
|
||||
Assert.notNull(step.getId(), "Step Id cannot be null.");
|
||||
|
||||
getHibernateTemplate().update(step);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see StepDao#update(StepExecution)
|
||||
*/
|
||||
public void update(StepExecution stepExecution) {
|
||||
|
||||
validateStepExecution(stepExecution);
|
||||
Assert.notNull(stepExecution.getId(), "StepExecution Id cannot be null. StepExecution must saved" +
|
||||
" before it can be updated.");
|
||||
|
||||
getHibernateTemplate().update(stepExecution);
|
||||
}
|
||||
|
||||
public List findStepExecutions(StepInstance step) {
|
||||
|
||||
Assert.notNull(step, "Step cannot be null.");
|
||||
Assert.notNull(step.getId(), "Step id cannot be null.");
|
||||
|
||||
final Long stepId = step.getId();
|
||||
|
||||
List results = this.getHibernateTemplate().executeFind(new HibernateCallback() {
|
||||
public Object doInHibernate(Session session) {
|
||||
Criteria criteria = session.createCriteria(StepExecution.class);
|
||||
criteria.add(Expression.eq("stepId", stepId));
|
||||
return criteria.list();
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate StepExecution. At a minimum, JobId, StartTime, EndTime, 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.getStepId(), "StepExecution Step-Id cannot be null.");
|
||||
Assert.notNull(stepExecution.getStartTime(), "StepExecution start time cannot be null.");
|
||||
Assert.notNull(stepExecution.getStatus(), "StepExecution status cannot be null.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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.List;
|
||||
|
||||
import org.springframework.batch.core.domain.JobExecution;
|
||||
import org.springframework.batch.core.domain.JobInstance;
|
||||
import org.springframework.batch.core.runtime.JobIdentifier;
|
||||
|
||||
/**
|
||||
* Data Access Object for jobs.
|
||||
*
|
||||
* @author Lucas Ward
|
||||
*
|
||||
*/
|
||||
public interface JobDao {
|
||||
|
||||
/**
|
||||
* Create a job using the provided JobIdentifier as the natural key.
|
||||
*
|
||||
* PostConditions: A valid job will be returned which contains an unique Id.
|
||||
*
|
||||
* @param jobIdentifier
|
||||
* @return Job
|
||||
*/
|
||||
public JobInstance createJob(JobIdentifier jobIdentifier);
|
||||
|
||||
/**
|
||||
* Find all jobs that match the given JobIdentifier. If no jobs matching the
|
||||
* Identifier are found, then a list of size 0 will be returned.
|
||||
*
|
||||
* @param jobIdentifier
|
||||
* @return List of jobs matching JobIdentifier
|
||||
*/
|
||||
public List findJobs(JobIdentifier jobIdentifier);
|
||||
|
||||
/**
|
||||
* Update an existing Job.
|
||||
*
|
||||
* Preconditions: Job must have an ID.
|
||||
*
|
||||
* @param job
|
||||
*/
|
||||
public void update(JobInstance job);
|
||||
|
||||
/**
|
||||
* Save a new JobExecution.
|
||||
*
|
||||
* Preconditions: JobExecution must have a JobId.
|
||||
*
|
||||
* @param jobExecution
|
||||
*/
|
||||
public void save(JobExecution jobExecution);
|
||||
|
||||
/**
|
||||
* Update and existing JobExecution.
|
||||
*
|
||||
* Preconditions: JobExecution must have an Id (which can be obtained by the
|
||||
* save method) and a JobId.
|
||||
*
|
||||
* @param jobExecution
|
||||
*/
|
||||
public void update(JobExecution jobExecution);
|
||||
|
||||
/**
|
||||
* Return the number of JobExecutions with the given Job Id
|
||||
*
|
||||
* Preconditions: Job must have an id.
|
||||
*
|
||||
* @param job
|
||||
*/
|
||||
public int getJobExecutionCount(Long jobId);
|
||||
|
||||
/**
|
||||
* Return list of JobExecutions for given job.
|
||||
*
|
||||
* @param job
|
||||
* @return list of jobExecutions.
|
||||
*/
|
||||
public List findJobExecutions(JobInstance job);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.batch.core.domain.JobExecution;
|
||||
import org.springframework.batch.core.domain.JobInstance;
|
||||
import org.springframework.batch.core.runtime.JobIdentifier;
|
||||
import org.springframework.batch.support.transaction.TransactionAwareProxyFactory;
|
||||
|
||||
public class MapJobDao implements JobDao {
|
||||
|
||||
private static Map jobsById;
|
||||
private static Map executionsById;
|
||||
|
||||
private long currentId = 0;
|
||||
|
||||
static {
|
||||
jobsById = TransactionAwareProxyFactory.createTransactionalMap();
|
||||
executionsById = TransactionAwareProxyFactory.createTransactionalMap();
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
jobsById.clear();
|
||||
executionsById.clear();
|
||||
}
|
||||
|
||||
public JobInstance createJob(JobIdentifier jobIdentifier) {
|
||||
JobInstance job = new JobInstance(new Long(currentId++));
|
||||
job.setIdentifier(jobIdentifier);
|
||||
|
||||
jobsById.put(job.getId(), job);
|
||||
return job;
|
||||
}
|
||||
|
||||
public List findJobs(JobIdentifier jobRuntimeInformation) {
|
||||
List list = new ArrayList();
|
||||
for (Iterator iter = jobsById.values().iterator(); iter.hasNext();) {
|
||||
JobInstance job = (JobInstance) iter.next();
|
||||
if (job.getName().equals(jobRuntimeInformation.getName())) {
|
||||
list.add(job);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public int getJobExecutionCount(Long jobId) {
|
||||
Set executions = (Set) executionsById.get(jobId);
|
||||
if (executions==null) return 0;
|
||||
return executions.size(); }
|
||||
|
||||
public void save(JobExecution jobExecution) {
|
||||
Set executions = (Set) executionsById.get(jobExecution.getJobId());
|
||||
if (executions==null) {
|
||||
executions = TransactionAwareProxyFactory.createTransactionalSet();
|
||||
executionsById.put(jobExecution.getJobId(), executions);
|
||||
}
|
||||
executions.add(jobExecution);
|
||||
jobExecution.setId(new Long(currentId++));
|
||||
}
|
||||
|
||||
public List findJobExecutions(JobInstance job) {
|
||||
Set executions = (Set) executionsById.get(job.getId());
|
||||
if( executions == null ){
|
||||
return new ArrayList();
|
||||
}
|
||||
else{
|
||||
return new ArrayList(executions);
|
||||
}
|
||||
}
|
||||
|
||||
public void update(JobInstance job) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
public void update(JobExecution jobExecution) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.batch.core.domain.JobInstance;
|
||||
import org.springframework.batch.core.domain.StepExecution;
|
||||
import org.springframework.batch.core.domain.StepInstance;
|
||||
import org.springframework.batch.restart.RestartData;
|
||||
import org.springframework.batch.support.transaction.TransactionAwareProxyFactory;
|
||||
|
||||
public class MapStepDao implements StepDao {
|
||||
|
||||
private static Map stepsByJobId;
|
||||
private static Map executionsById;
|
||||
private static Map restartsById;
|
||||
private static long currentId = 0;
|
||||
|
||||
static {
|
||||
stepsByJobId = TransactionAwareProxyFactory.createTransactionalMap();
|
||||
executionsById = TransactionAwareProxyFactory.createTransactionalMap();
|
||||
restartsById = TransactionAwareProxyFactory.createTransactionalMap();
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
stepsByJobId.clear();
|
||||
executionsById.clear();
|
||||
restartsById.clear();
|
||||
}
|
||||
|
||||
public StepInstance createStep(JobInstance job, String stepName) {
|
||||
StepInstance step = new StepInstance(new Long(currentId++));
|
||||
step.setName(stepName);
|
||||
step.setJob(job);
|
||||
Set steps = (Set) stepsByJobId.get(job.getId());
|
||||
if (steps==null) {
|
||||
steps = TransactionAwareProxyFactory.createTransactionalSet();
|
||||
stepsByJobId.put(job.getId(), steps);
|
||||
}
|
||||
steps.add(step);
|
||||
//System.err.println(steps);
|
||||
return step;
|
||||
}
|
||||
|
||||
public StepInstance findStep(JobInstance job, String stepName) {
|
||||
for (Iterator iter = stepsByJobId.values().iterator(); iter.hasNext();) {
|
||||
Set steps = (Set) iter.next();
|
||||
for (Iterator iterator = steps.iterator(); iterator.hasNext();) {
|
||||
StepInstance step = (StepInstance) iterator.next();
|
||||
if (step.getName().equals(stepName)) {
|
||||
return step;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List findSteps(Long jobId) {
|
||||
Set steps = (Set) stepsByJobId.get(jobId);
|
||||
if (steps==null) {
|
||||
return new ArrayList();
|
||||
}
|
||||
return new ArrayList(steps);
|
||||
}
|
||||
|
||||
public RestartData getRestartData(Long stepId) {
|
||||
return (RestartData) restartsById.get(stepId);
|
||||
}
|
||||
|
||||
public int getStepExecutionCount(Long jobId) {
|
||||
Set executions = (Set) executionsById.get(jobId);
|
||||
if (executions==null) return 0;
|
||||
return executions.size(); }
|
||||
|
||||
public void save(StepExecution stepExecution) {
|
||||
Set executions = (Set) executionsById.get(stepExecution.getStepId());
|
||||
if (executions==null) {
|
||||
executions = TransactionAwareProxyFactory.createTransactionalSet();
|
||||
executionsById.put(stepExecution.getStepId(), executions);
|
||||
}
|
||||
stepExecution.setId(new Long(currentId++));
|
||||
executions.add(stepExecution);
|
||||
}
|
||||
|
||||
public void saveRestartData(Long stepId, RestartData restartData) {
|
||||
restartsById.put(stepId, restartData);
|
||||
}
|
||||
|
||||
public List findStepExecutions(StepInstance step) {
|
||||
Set executions = (Set) executionsById.get(step.getId());
|
||||
|
||||
if(executions == null){
|
||||
//no step executions, return empty array list.
|
||||
return new ArrayList();
|
||||
}
|
||||
else{
|
||||
return new ArrayList(executions);
|
||||
}
|
||||
}
|
||||
|
||||
public void update(StepInstance step) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
public void update(StepExecution stepExecution) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.repository.dao;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.batch.support.PropertiesConverter;
|
||||
import org.springframework.jdbc.support.lob.LobCreator;
|
||||
import org.springframework.jdbc.support.lob.LobHandler;
|
||||
import org.springframework.orm.hibernate3.support.ClobStringType;
|
||||
|
||||
/**
|
||||
* User type object to help Hibernate to persist Poperties objects
|
||||
* (just plonking it a Clob).
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class PropertiesUserType extends ClobStringType {
|
||||
|
||||
/**
|
||||
* Get a {@link Properties} from a Clob.
|
||||
*
|
||||
* @return a {@link Properties} object whose string representation is the
|
||||
* same as the database value.
|
||||
*
|
||||
* @see org.springframework.orm.hibernate3.support.ClobStringType#nullSafeGetInternal(java.sql.ResultSet,
|
||||
* java.lang.String[], java.lang.Object,
|
||||
* org.springframework.jdbc.support.lob.LobHandler)
|
||||
*/
|
||||
protected Object nullSafeGetInternal(ResultSet rs, String[] names, Object owner, LobHandler lobHandler)
|
||||
throws SQLException {
|
||||
final String value = (String) super.nullSafeGetInternal(rs, names, owner, lobHandler);
|
||||
return PropertiesConverter.stringToProperties(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a {@link Properties} object to a string and then pop it in a Clob.
|
||||
*
|
||||
* @see org.springframework.orm.hibernate3.support.ClobStringType#nullSafeSetInternal(java.sql.PreparedStatement, int, java.lang.Object, org.springframework.jdbc.support.lob.LobCreator)
|
||||
*/
|
||||
protected void nullSafeSetInternal(PreparedStatement ps, int index, Object value, LobCreator lobCreator)
|
||||
throws SQLException {
|
||||
String string = PropertiesConverter.propertiesToString((Properties)value);
|
||||
super.nullSafeSetInternal(ps, index, string, lobCreator);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.batch.restart.GenericRestartData;
|
||||
import org.springframework.batch.restart.RestartData;
|
||||
import org.springframework.batch.support.PropertiesConverter;
|
||||
import org.springframework.jdbc.support.lob.LobCreator;
|
||||
import org.springframework.jdbc.support.lob.LobHandler;
|
||||
import org.springframework.orm.hibernate3.support.ClobStringType;
|
||||
|
||||
/**
|
||||
* User type object to help Hibernate persist (@link RestartData) objects by setting
|
||||
* a string in a clob.
|
||||
*
|
||||
* @author Lucas Ward
|
||||
*
|
||||
*/
|
||||
public class RestartDataUserType extends ClobStringType {
|
||||
|
||||
/**
|
||||
* Get a {@link Properties} from a Clob.
|
||||
*
|
||||
* @return a {@link GenericRestartData} object whose internal properties string representation is the
|
||||
* same as the database value.
|
||||
*
|
||||
* @see org.springframework.orm.hibernate3.support.ClobStringType#nullSafeGetInternal(java.sql.ResultSet,
|
||||
* java.lang.String[], java.lang.Object,
|
||||
* org.springframework.jdbc.support.lob.LobHandler)
|
||||
*/
|
||||
protected Object nullSafeGetInternal(ResultSet rs, String[] names, Object owner, LobHandler lobHandler)
|
||||
throws SQLException {
|
||||
final String value = (String) super.nullSafeGetInternal(rs, names, owner, lobHandler);
|
||||
return new GenericRestartData(PropertiesConverter.stringToProperties(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a {@link RestartData} object to a string and then pop it in a Clob.
|
||||
*
|
||||
* @see org.springframework.orm.hibernate3.support.ClobStringType#nullSafeSetInternal(java.sql.PreparedStatement, int, java.lang.Object, org.springframework.jdbc.support.lob.LobCreator)
|
||||
*/
|
||||
protected void nullSafeSetInternal(PreparedStatement ps, int index, Object value, LobCreator lobCreator)
|
||||
throws SQLException {
|
||||
final RestartData restartData = (RestartData)value;
|
||||
String string = (restartData == null) ? ""
|
||||
:PropertiesConverter.propertiesToString(restartData.getProperties());
|
||||
super.nullSafeSetInternal(ps, index, string, lobCreator);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
* 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.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.batch.core.domain.BatchStatus;
|
||||
import org.springframework.batch.core.domain.JobExecution;
|
||||
import org.springframework.batch.core.domain.JobInstance;
|
||||
import org.springframework.batch.core.repository.NoSuchBatchDomainObjectException;
|
||||
import org.springframework.batch.core.runtime.JobIdentifier;
|
||||
import org.springframework.batch.execution.runtime.ScheduledJobIdentifier;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* SQL implementation of {@link JobDao}. 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
|
||||
*/
|
||||
public class SqlJobDao implements JobDao, InitializingBean {
|
||||
|
||||
// Job SQL statements
|
||||
private static final String CREATE_JOB = "INSERT into BATCH_JOB(ID, JOB_NAME, JOB_STREAM, SCHEDULE_DATE, JOB_RUN)"
|
||||
+ " values (?, ?, ?, ?, ?)";
|
||||
|
||||
private static final String FIND_JOBS = "SELECT ID, STATUS from BATCH_JOB where JOB_NAME = ? and "
|
||||
+ "JOB_STREAM = ? and SCHEDULE_DATE = ? and JOB_RUN = ?";
|
||||
|
||||
private static final String UPDATE_JOB = "UPDATE BATCH_JOB set STATUS = ? where ID = ?";
|
||||
|
||||
private static final String GET_JOB_EXECUTION_COUNT = "SELECT count(ID) from BATCH_JOB_EXECUTION "
|
||||
+ "where JOB_ID = ?";
|
||||
|
||||
// Job Execution SqlStatements
|
||||
private static final String UPDATE_JOB_EXECUTION = "UPDATE BATCH_JOB_EXECUTION set START_TIME = ?, END_TIME = ?, "
|
||||
+ " STATUS = ? where ID = ?";
|
||||
|
||||
private static final String SAVE_JOB_EXECUTION = "INSERT into BATCH_JOB_EXECUTION(ID, JOB_ID, START_TIME, END_TIME, STATUS)"
|
||||
+ " values (?, ?, ?, ?, ?)";
|
||||
|
||||
private static final String CHECK_JOB_EXECUTION_EXISTS = "SELECT COUNT(*) FROM BATCH_JOB_EXECUTION WHERE ID=?";
|
||||
|
||||
private static final String FIND_JOB_EXECUTIONS = "SELECT ID, START_TIME, END_TIME, STATUS from BATCH_JOB_EXECUTION"
|
||||
+ " where JOB_ID = ?";
|
||||
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
private DataFieldMaxValueIncrementer jobIncrementer;
|
||||
|
||||
private DataFieldMaxValueIncrementer jobExecutionIncrementer;
|
||||
|
||||
/**
|
||||
* In this sql implementation a job id is obtained by asking the
|
||||
* jobIncrementer (which is likely a sequence) for the nextLong, and then
|
||||
* passing the Id and identifier values (job name, stream, run, schedule
|
||||
* date) into an INSERT statement.
|
||||
*
|
||||
* @see JobDao#createJob(JobIdentifier)
|
||||
* @throws IllegalArgumentException if any JobRuntimeInformation fields are
|
||||
* null.
|
||||
*/
|
||||
public JobInstance createJob(JobIdentifier jobIdentifier) {
|
||||
|
||||
ScheduledJobIdentifier jobRuntimeInformation = (ScheduledJobIdentifier) jobIdentifier;
|
||||
validateJobRuntimeInformation(jobRuntimeInformation);
|
||||
|
||||
Long jobId = new Long(jobIncrementer.nextLongValue());
|
||||
Object[] parameters = new Object[] { jobId, jobRuntimeInformation.getName(),
|
||||
jobRuntimeInformation.getJobStream(), jobRuntimeInformation.getScheduleDate(),
|
||||
new Long(jobRuntimeInformation.getJobRun()) };
|
||||
jdbcTemplate.update(CREATE_JOB, parameters);
|
||||
|
||||
JobInstance job = new JobInstance(jobId);
|
||||
return job;
|
||||
}
|
||||
|
||||
/**
|
||||
* The BATCH_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#findJobs(JobIdentifier)
|
||||
* @throws IllegalArgumentException if any JobRuntimeInformation fields are
|
||||
* null.
|
||||
*/
|
||||
public List findJobs(JobIdentifier jobIdentifier) {
|
||||
|
||||
ScheduledJobIdentifier defaultJobId = (ScheduledJobIdentifier) jobIdentifier;
|
||||
validateJobRuntimeInformation(defaultJobId);
|
||||
|
||||
Object[] parameters = new Object[] { defaultJobId.getName(), defaultJobId.getJobStream(),
|
||||
defaultJobId.getScheduleDate(), new Integer(defaultJobId.getJobRun()) };
|
||||
|
||||
RowMapper rowMapper = new RowMapper() {
|
||||
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
|
||||
JobInstance job = new JobInstance(new Long(rs.getLong(1)));
|
||||
job.setStatus(BatchStatus.getStatus(rs.getString(2)));
|
||||
|
||||
return job;
|
||||
}
|
||||
};
|
||||
|
||||
return jdbcTemplate.query(FIND_JOBS, parameters, rowMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see JobDao#update(JobInstance)
|
||||
* @throws IllegalArgumentException if Job, Job.status, or job.id is null
|
||||
*/
|
||||
public void update(JobInstance job) {
|
||||
|
||||
Assert.notNull(job, "Job Cannot be Null");
|
||||
Assert.notNull(job.getStatus(), "Job Status cannot be Null");
|
||||
Assert.notNull(job.getId(), "Job ID cannot be null");
|
||||
|
||||
Object[] parameters = new Object[] { job.getStatus().toString(), job.getId() };
|
||||
jdbcTemplate.update(UPDATE_JOB, 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#save(JobExecution)
|
||||
* @throws IllegalArgumentException if jobExecution is null, as well as any
|
||||
* of it's fields to be persisted.
|
||||
*/
|
||||
public void save(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() };
|
||||
jdbcTemplate.update(SAVE_JOB_EXECUTION, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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#update(JobExecution)
|
||||
*/
|
||||
public void update(JobExecution jobExecution) {
|
||||
|
||||
validateJobExecution(jobExecution);
|
||||
|
||||
Object[] parameters = new Object[] { jobExecution.getStartTime(), jobExecution.getEndTime(),
|
||||
jobExecution.getStatus().toString(), 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 (jdbcTemplate.queryForInt(CHECK_JOB_EXECUTION_EXISTS, new Object[] { jobExecution.getId() }) != 1) {
|
||||
throw new NoSuchBatchDomainObjectException("Invalid JobExecution, ID " + jobExecution.getId()
|
||||
+ " not found.");
|
||||
}
|
||||
|
||||
jdbcTemplate.update(UPDATE_JOB_EXECUTION, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see JobDao#getJobExecutionCount(JobInstance)
|
||||
* @throws IllegalArgumentException if jobId is null.
|
||||
*/
|
||||
public int getJobExecutionCount(Long jobId) {
|
||||
|
||||
Assert.notNull(jobId, "JobId cannot be null");
|
||||
|
||||
Object[] parameters = new Object[] { jobId };
|
||||
|
||||
return jdbcTemplate.queryForInt(GET_JOB_EXECUTION_COUNT, parameters);
|
||||
}
|
||||
|
||||
public List findJobExecutions(JobInstance job) {
|
||||
|
||||
Assert.notNull(job, "Job cannot be null.");
|
||||
Assert.notNull(job.getId(), "Job Id cannot be null.");
|
||||
|
||||
final Long jobId = job.getId();
|
||||
|
||||
RowMapper rowMapper = new RowMapper() {
|
||||
|
||||
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
|
||||
JobExecution jobExecution = new JobExecution(jobId);
|
||||
jobExecution.setId(new Long(rs.getLong(1)));
|
||||
jobExecution.setStartTime(rs.getTimestamp(2));
|
||||
jobExecution.setEndTime(rs.getTimestamp(3));
|
||||
jobExecution.setStatus(BatchStatus.getStatus(rs.getString(4)));
|
||||
|
||||
return jobExecution;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return jdbcTemplate.query(FIND_JOB_EXECUTIONS, new Object[] { jobId }, rowMapper);
|
||||
}
|
||||
|
||||
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
public void setJobIncrementer(DataFieldMaxValueIncrementer jobIncrementer) {
|
||||
this.jobIncrementer = jobIncrementer;
|
||||
}
|
||||
|
||||
public void setJobExecutionIncrementer(DataFieldMaxValueIncrementer jobExecutionIncrementer) {
|
||||
this.jobExecutionIncrementer = jobExecutionIncrementer;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
|
||||
*
|
||||
* Ensure jdbcTemplate and incrementers have been provided.
|
||||
*/
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
|
||||
Assert.notNull(jdbcTemplate, "JdbcTemplate cannot be null");
|
||||
Assert.notNull(jobIncrementer, "JobIncrementor cannot be null");
|
||||
Assert.notNull(jobExecutionIncrementer, "JobExecutionIncrementer cannot be null");
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.getStartTime(), "JobExecution start time cannot be null.");
|
||||
Assert.notNull(jobExecution.getStatus(), "JobExecution status cannot be null.");
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate JobRuntimeInformation. Due to differing requirements, it is
|
||||
* acceptable for any field to be blank, however null fields may cause odd
|
||||
* and vague exception reports from the database driver.
|
||||
*
|
||||
* TODO: remove dependency on ScheduledJobIdentifier
|
||||
*/
|
||||
private void validateJobRuntimeInformation(ScheduledJobIdentifier jobRuntimeInformation) {
|
||||
|
||||
Assert.notNull(jobRuntimeInformation, "JobRuntimeInformation cannot be null.");
|
||||
Assert.notNull(jobRuntimeInformation.getName(), "JobRuntimeInformation name cannot be null.");
|
||||
Assert.notNull(jobRuntimeInformation.getJobStream(), "JobRuntimeInformation JobStream cannot be null.");
|
||||
Assert.notNull(jobRuntimeInformation.getScheduleDate(), "JobRuntimeInformation ScheduleDate cannot be null.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
* 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.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.batch.core.domain.BatchStatus;
|
||||
import org.springframework.batch.core.domain.JobInstance;
|
||||
import org.springframework.batch.core.domain.StepExecution;
|
||||
import org.springframework.batch.core.domain.StepInstance;
|
||||
import org.springframework.batch.core.repository.NoSuchBatchDomainObjectException;
|
||||
import org.springframework.batch.restart.GenericRestartData;
|
||||
import org.springframework.batch.restart.RestartData;
|
||||
import org.springframework.batch.support.PropertiesConverter;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Sql implementation of StepDao. Uses Sequences (via Spring's
|
||||
* @link DataFieldMaxValueIncrementer abstraction) to create all Step and
|
||||
* StepExecution 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.
|
||||
*
|
||||
* TODO: JavaDoc should be geared more towards usability, the comments
|
||||
* above are useful information, and should be there, but needs usability
|
||||
* stuff. Depends on the step dao java docs as well.
|
||||
*
|
||||
* @author Lucas Ward
|
||||
* @see StepDao
|
||||
*/
|
||||
public class SqlStepDao implements StepDao, InitializingBean {
|
||||
|
||||
// Step SQL statements
|
||||
private static final String FIND_STEPS = "SELECT ID, STEP_NAME, STATUS, RESTART_DATA from BATCH_STEP where JOB_ID = ?";
|
||||
|
||||
private static final String FIND_STEP = "SELECT ID, STATUS, RESTART_DATA from BATCH_STEP where JOB_ID = ? "
|
||||
+ "and STEP_NAME = ?";
|
||||
|
||||
private static final String CREATE_STEP = "INSERT into BATCH_STEP(ID, JOB_ID, STEP_NAME) values (?, ?, ?)";
|
||||
|
||||
private static final String UPDATE_STEP = "UPDATE BATCH_STEP set STATUS = ?, RESTART_DATA = ? where ID = ?";
|
||||
|
||||
// StepExecution statements
|
||||
private static final String SAVE_STEP_EXECUTION = "INSERT into BATCH_STEP_EXECUTION(ID, VERSION, STEP_ID, JOB_EXECUTION_ID, START_TIME, "
|
||||
+ "END_TIME, STATUS, COMMIT_COUNT, TASK_COUNT, TASK_STATISTICS, EXIT_CODE) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
private static final String UPDATE_STEP_EXECUTION = "UPDATE BATCH_STEP_EXECUTION set START_TIME = ?, END_TIME = ?, "
|
||||
+ "STATUS = ?, COMMIT_COUNT = ?, TASK_COUNT = ?, TASK_STATISTICS = ?, EXIT_CODE = ? where ID = ?";
|
||||
|
||||
private static final String GET_STEP_EXECUTION_COUNT = "SELECT count(ID) from BATCH_STEP_EXECUTION where "
|
||||
+ "STEP_ID = ?";
|
||||
|
||||
private static final String FIND_STEP_EXECUTIONS = "SELECT ID, JOB_EXECUTION_ID, START_TIME, END_TIME, STATUS, COMMIT_COUNT,"
|
||||
+ " TASK_COUNT, TASK_STATISTICS, EXIT_CODE from BATCH_STEP_EXECUTION where STEP_ID = ?";
|
||||
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
private DataFieldMaxValueIncrementer stepIncrementer;
|
||||
|
||||
private DataFieldMaxValueIncrementer stepExecutionIncrementer;
|
||||
|
||||
/**
|
||||
* Find one step for given job and stepName. A RowMapper is used to map each
|
||||
* row returned to a step object. If none are found, the list will be empty
|
||||
* and null will be returned. If one step is found, it will be returned. If
|
||||
* anymore than one step is found, an exception is thrown.
|
||||
*
|
||||
* @see StepDao#findStep(Long, String)
|
||||
* @throws IllegalArgumentException if job, stepName, or job.id is null.
|
||||
* @throws NoSuchBatchDomainObjectException if more than one step is found.
|
||||
*/
|
||||
public StepInstance findStep(JobInstance job, String stepName) {
|
||||
|
||||
Assert.notNull(job, "Job cannot be null.");
|
||||
Assert.notNull(job.getId(), "Job ID cannot be null");
|
||||
Assert.notNull(stepName, "StepName cannot be null");
|
||||
|
||||
Object[] parameters = new Object[] { job.getId(), stepName };
|
||||
|
||||
RowMapper rowMapper = new RowMapper() {
|
||||
|
||||
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
|
||||
StepInstance step = new StepInstance(new Long(rs.getLong(1)));
|
||||
step.setStatus(BatchStatus.getStatus(rs.getString(2)));
|
||||
step.setRestartData(
|
||||
new GenericRestartData(PropertiesConverter.stringToProperties(rs.getString(3))));
|
||||
return step;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
List steps = jdbcTemplate.query(FIND_STEP, parameters, rowMapper);
|
||||
|
||||
if (steps.size() == 0) {
|
||||
// No step found
|
||||
return null;
|
||||
}
|
||||
else if (steps.size() == 1) {
|
||||
StepInstance step = (StepInstance) steps.get(0);
|
||||
step.setName(stepName);
|
||||
return step;
|
||||
}
|
||||
else {
|
||||
// This error will likely never be thrown, because there should
|
||||
// never be two steps with the same name and Job_ID due to database
|
||||
// constraints.
|
||||
throw new NoSuchBatchDomainObjectException("Step Invalid, multiple steps found for StepName:" + stepName
|
||||
+ " and JobId:" + job.getId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @see StepDao#findSteps(Long)
|
||||
*
|
||||
* Sql implementation which uses a RowMapper to populate a list of all rows
|
||||
* in the BATCH_STEP table with the same JOB_ID.
|
||||
*
|
||||
* @throws IllegalArgumentException if jobId is null.
|
||||
*/
|
||||
public List findSteps(Long jobId) {
|
||||
|
||||
Assert.notNull(jobId, "JobId cannot be null.");
|
||||
|
||||
Object[] parameters = new Object[] { jobId };
|
||||
|
||||
RowMapper rowMapper = new RowMapper() {
|
||||
|
||||
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
|
||||
StepInstance step = new StepInstance(new Long(rs.getLong(1)));
|
||||
step.setName(rs.getString(2));
|
||||
String status = rs.getString(3);
|
||||
step.setStatus(BatchStatus.getStatus(status));
|
||||
step.setRestartData(
|
||||
new GenericRestartData(PropertiesConverter.stringToProperties(rs.getString(3))));
|
||||
return step;
|
||||
}
|
||||
};
|
||||
|
||||
return jdbcTemplate.query(FIND_STEPS, parameters, rowMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a step with the given job's id, and the provided step name. A
|
||||
* unique id is created for the step using an incrementer. (@link
|
||||
* DataFieldMaxValueIncrementer)
|
||||
*
|
||||
* @see StepDao#createStep(JobInstance, String)
|
||||
* @throws IllegalArgumentException if job or stepName is null.
|
||||
*/
|
||||
public StepInstance createStep(JobInstance job, String stepName) {
|
||||
|
||||
Assert.notNull(job, "Job cannot be null.");
|
||||
Assert.notNull(stepName, "StepName cannot be null.");
|
||||
|
||||
Long stepId = new Long(stepIncrementer.nextLongValue());
|
||||
Object[] parameters = new Object[] { stepId, job.getId(), stepName };
|
||||
jdbcTemplate.update(CREATE_STEP, parameters);
|
||||
|
||||
StepInstance step = new StepInstance(stepId);
|
||||
step.setJob(job);
|
||||
step.setName(stepName);
|
||||
return step;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see StepDao#update(StepInstance)
|
||||
* @throws IllegalArgumentException if step, or it's status and id is null.
|
||||
*/
|
||||
public void update(final StepInstance step) {
|
||||
|
||||
Assert.notNull(step, "Step cannot be null.");
|
||||
Assert.notNull(step.getStatus(), "Step status cannot be null.");
|
||||
Assert.notNull(step.getId(), "Step Id cannot be null.");
|
||||
|
||||
Properties restartProps = null;
|
||||
RestartData restartData = step.getRestartData();
|
||||
if (restartData != null) {
|
||||
restartProps = restartData.getProperties();
|
||||
}
|
||||
|
||||
Object[] parameters = new Object[]{ step.getStatus().toString(),
|
||||
PropertiesConverter.propertiesToString(restartProps),
|
||||
step.getId()
|
||||
};
|
||||
|
||||
jdbcTemplate.update(UPDATE_STEP, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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#save(StepExecution)
|
||||
*/
|
||||
public void save(StepExecution stepExecution) {
|
||||
|
||||
validateStepExecution(stepExecution);
|
||||
|
||||
stepExecution.setId(new Long(stepExecutionIncrementer.nextLongValue()));
|
||||
Object[] parameters = new Object[] { stepExecution.getId(), new Long(0), stepExecution.getStepId(), stepExecution.getJobExecutionId(),
|
||||
stepExecution.getStartTime(), stepExecution.getEndTime(), stepExecution.getStatus().toString(),
|
||||
stepExecution.getCommitCount(), stepExecution.getTaskCount(),
|
||||
PropertiesConverter.propertiesToString(stepExecution.getStatistics()), new Integer(stepExecution.getExitCode()) };
|
||||
jdbcTemplate.update(SAVE_STEP_EXECUTION, parameters);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @see StepDao#update(StepExecution)
|
||||
*/
|
||||
public void update(StepExecution stepExecution) {
|
||||
|
||||
validateStepExecution(stepExecution);
|
||||
Assert.notNull(stepExecution.getId(), "StepExecution Id cannot be null. StepExecution must saved"
|
||||
+ " before it can be updated.");
|
||||
|
||||
// TODO: Not sure if this is a good idea on step execution considering
|
||||
// it is saved at every commit
|
||||
// point.
|
||||
// if (jdbcTemplate.queryForInt(CHECK_STEP_EXECUTION_EXISTS, new
|
||||
// Object[] { stepExecution.getId() }) != 1) {
|
||||
// return; // throw exception?
|
||||
// }
|
||||
|
||||
Object[] parameters = new Object[] { stepExecution.getStartTime(), stepExecution.getEndTime(),
|
||||
stepExecution.getStatus().toString(), stepExecution.getCommitCount(),
|
||||
stepExecution.getTaskCount(), PropertiesConverter.propertiesToString(stepExecution.getStatistics()),
|
||||
new Integer(stepExecution.getExitCode()),
|
||||
stepExecution.getId() };
|
||||
jdbcTemplate.update(UPDATE_STEP_EXECUTION, parameters);
|
||||
|
||||
}
|
||||
|
||||
public int getStepExecutionCount(Long stepId) {
|
||||
|
||||
Object[] parameters = new Object[] { stepId };
|
||||
|
||||
return jdbcTemplate.queryForInt(GET_STEP_EXECUTION_COUNT, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get StepExecution for the given step. Due to the nature of statistics,
|
||||
* they will not be returned with reconstituted object.
|
||||
*
|
||||
* @see StepDao#getStepExecution(Long)
|
||||
* @throws IllegalArgumentException if id is null.
|
||||
* @throws NoSuchBatchDomainObjectException if more than one step execution is
|
||||
* returned.
|
||||
*/
|
||||
public List findStepExecutions(StepInstance step) {
|
||||
|
||||
Assert.notNull(step, "Step cannot be null.");
|
||||
Assert.notNull(step.getId(), "Step id cannot be null.");
|
||||
|
||||
final Long stepId = step.getId();
|
||||
|
||||
RowMapper rowMapper = new RowMapper() {
|
||||
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
|
||||
StepExecution stepExecution = new StepExecution(stepId, new Long(rs.getLong(2)));
|
||||
stepExecution.setId(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.setStatistics(PropertiesConverter.stringToProperties(rs.getString(8)));
|
||||
stepExecution.setExitCode(rs.getInt(9));
|
||||
return stepExecution;
|
||||
}
|
||||
};
|
||||
|
||||
return jdbcTemplate.query(FIND_STEP_EXECUTIONS, new Object[] { stepId }, rowMapper);
|
||||
|
||||
}
|
||||
|
||||
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
public void setStepIncrementer(DataFieldMaxValueIncrementer stepIncrementer) {
|
||||
this.stepIncrementer = stepIncrementer;
|
||||
}
|
||||
|
||||
public void setStepExecutionIncrementer(DataFieldMaxValueIncrementer stepExecutionIncrementer) {
|
||||
this.stepExecutionIncrementer = stepExecutionIncrementer;
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(jdbcTemplate, "JdbcTemplate cannot be null.");
|
||||
Assert.notNull(stepIncrementer, "StepIncrementer cannot be null.");
|
||||
Assert.notNull(stepExecutionIncrementer, "StepExecutionIncrementer canot be null.");
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.getStepId(), "StepExecution Step-Id cannot be null.");
|
||||
Assert.notNull(stepExecution.getStartTime(), "StepExecution start time cannot be null.");
|
||||
Assert.notNull(stepExecution.getStatus(), "StepExecution status cannot be null.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.repository.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.batch.core.domain.JobInstance;
|
||||
import org.springframework.batch.core.domain.StepExecution;
|
||||
import org.springframework.batch.core.domain.StepInstance;
|
||||
|
||||
/**
|
||||
* Data access object for steps.
|
||||
*
|
||||
* TODO: Add java doc.
|
||||
*
|
||||
* @author Lucas Ward
|
||||
*
|
||||
*/
|
||||
public interface StepDao {
|
||||
|
||||
/**
|
||||
* Find a step with the given JobId and Step Name. Return null if none
|
||||
* are found.
|
||||
*
|
||||
* @param jobId
|
||||
* @param stepName
|
||||
* @return Step
|
||||
*/
|
||||
public StepInstance findStep(JobInstance job, String stepName);
|
||||
|
||||
/**
|
||||
* Find all steps with the given Job ID.
|
||||
*
|
||||
* @param jobId
|
||||
* @return
|
||||
*/
|
||||
public List findSteps(Long jobId);
|
||||
|
||||
/**
|
||||
* Create a step for the given Step Name and Job Id.
|
||||
* @param job
|
||||
* @param stepName
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public StepInstance createStep(JobInstance job, String stepName);
|
||||
|
||||
/**
|
||||
* Update an existing Step.
|
||||
*
|
||||
* Preconditions: Step must have an ID.
|
||||
*
|
||||
* @param job
|
||||
*/
|
||||
public void update(StepInstance step);
|
||||
|
||||
/**
|
||||
* Save the given StepExecution.
|
||||
*
|
||||
* Preconditions: Id must be null. Postconditions: Id will be set to a
|
||||
* Unique Long.
|
||||
*
|
||||
* @param stepExecution
|
||||
*/
|
||||
public void save(StepExecution stepExecution);
|
||||
|
||||
/**
|
||||
* Update the given StepExecution
|
||||
*
|
||||
* Preconditions: Id must not be null.
|
||||
*
|
||||
* @param stepExecution
|
||||
*/
|
||||
public void update(StepExecution stepExecution);
|
||||
|
||||
/**
|
||||
* Return the count of StepExecutions with the given StepId.
|
||||
*
|
||||
* @param stepId
|
||||
* @return
|
||||
*/
|
||||
public int getStepExecutionCount(Long stepId);
|
||||
|
||||
/**
|
||||
* Return all StepExecutions for the given step.
|
||||
*
|
||||
* @param id
|
||||
* @return list of stepExecutions
|
||||
*/
|
||||
public List findStepExecutions(StepInstance 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,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.runtime;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.commons.lang.builder.EqualsBuilder;
|
||||
import org.apache.commons.lang.builder.HashCodeBuilder;
|
||||
import org.springframework.batch.core.runtime.JobIdentifier;
|
||||
import org.springframework.batch.core.runtime.SimpleJobIdentifier;
|
||||
|
||||
public class ScheduledJobIdentifier extends SimpleJobIdentifier implements JobIdentifier {
|
||||
|
||||
private Date scheduleDate = new Date(0);
|
||||
|
||||
private int jobRun = 0;
|
||||
|
||||
private String jobStream = "";
|
||||
|
||||
ScheduledJobIdentifier() {}
|
||||
|
||||
public ScheduledJobIdentifier(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
public int getJobRun() {
|
||||
return jobRun;
|
||||
}
|
||||
|
||||
public void setJobRun(int jobRun) {
|
||||
this.jobRun = jobRun;
|
||||
}
|
||||
|
||||
public String getJobStream() {
|
||||
return jobStream;
|
||||
}
|
||||
|
||||
public void setJobStream(String jobStream) {
|
||||
this.jobStream = jobStream;
|
||||
}
|
||||
|
||||
public Date getScheduleDate() {
|
||||
return scheduleDate;
|
||||
}
|
||||
|
||||
public void setScheduleDate(Date scheduleDate) {
|
||||
this.scheduleDate = scheduleDate;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
|
||||
return super.toString() + ",stream=" + jobStream + ",run=" + jobRun + ",scheduleDate="
|
||||
+ scheduleDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the provided JobIdentifier equals this JobIdentifier. Two
|
||||
* Identifiers are considered to be equal if they have the same name,
|
||||
* stream, run, and schedule date.
|
||||
*/
|
||||
public boolean equals(Object other) {
|
||||
return EqualsBuilder.reflectionEquals(this, other);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return HashCodeBuilder.reflectionHashCode(this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.runtime;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.springframework.batch.core.runtime.JobIdentifier;
|
||||
import org.springframework.batch.core.runtime.JobIdentifierFactory;
|
||||
|
||||
/**
|
||||
* {@link JobIdentifierFactory} for creating {@link ScheduledJobIdentifier}
|
||||
* instances.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ScheduledJobIdentifierFactory implements JobIdentifierFactory {
|
||||
|
||||
private String jobStream = "stream";
|
||||
|
||||
private int jobRun = 0;
|
||||
|
||||
private Date scheduleDate = new Date(0L);
|
||||
|
||||
public JobIdentifier getJobIdentifier(String name) {
|
||||
|
||||
ScheduledJobIdentifier runtimeInformation = new ScheduledJobIdentifier(name);
|
||||
runtimeInformation.setJobStream(jobStream);
|
||||
runtimeInformation.setJobRun(jobRun);
|
||||
runtimeInformation.setScheduleDate(scheduleDate);
|
||||
return runtimeInformation;
|
||||
}
|
||||
|
||||
public void setJobRun(int jobRun) {
|
||||
this.jobRun = jobRun;
|
||||
}
|
||||
|
||||
public void setJobStream(String jobStream) {
|
||||
this.jobStream = jobStream;
|
||||
}
|
||||
|
||||
public void setScheduleDate(Date scheduleDate) {
|
||||
this.scheduleDate = scheduleDate;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Specific implementations of runtime concerns.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.batch.execution.scope;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.batch.core.runtime.JobIdentifier;
|
||||
import org.springframework.batch.repeat.context.SynchronizedAttributeAccessor;
|
||||
|
||||
/**
|
||||
* Simple implementation of {@link StepContext}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class SimpleStepContext extends SynchronizedAttributeAccessor implements StepContext {
|
||||
|
||||
private Map callbacks = new HashMap();
|
||||
private StepContext parent;
|
||||
private JobIdentifier jobIdentifier;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public SimpleStepContext() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object
|
||||
*/
|
||||
public SimpleStepContext(StepContext parent) {
|
||||
super();
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.execution.scope.StepContext#getParent()
|
||||
*/
|
||||
public StepContext getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.repeat.RepeatContext#registerDestructionCallback(java.lang.String,
|
||||
* java.lang.Runnable)
|
||||
*/
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.execution.scope.StepContext#registerDestructionCallback(java.lang.String, java.lang.Runnable)
|
||||
*/
|
||||
public void registerDestructionCallback(String name, Runnable callback) {
|
||||
synchronized (callbacks) {
|
||||
Set set = (Set) callbacks.get(name);
|
||||
if (set == null) {
|
||||
set = new HashSet();
|
||||
callbacks.put(name, set);
|
||||
}
|
||||
set.add(callback);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Package access because only needed internally.
|
||||
*/
|
||||
void close() {
|
||||
|
||||
List errors = new ArrayList();
|
||||
|
||||
Set copy;
|
||||
|
||||
synchronized (callbacks) {
|
||||
copy = new HashSet(callbacks.entrySet());
|
||||
}
|
||||
|
||||
for (Iterator iter = copy.iterator(); iter.hasNext();) {
|
||||
Map.Entry entry = (Map.Entry) iter.next();
|
||||
String name = (String) entry.getKey();
|
||||
Set set = (Set) entry.getValue();
|
||||
for (Iterator iterator = set.iterator(); iterator.hasNext();) {
|
||||
Runnable callback = (Runnable) iterator.next();
|
||||
if (hasAttribute(name) && callback != null) {
|
||||
/*
|
||||
* The documentation of the interface says that these
|
||||
* callbacks must not throw exceptions, but we don't trust
|
||||
* them necessarily...
|
||||
*/
|
||||
try {
|
||||
callback.run();
|
||||
}
|
||||
catch (RuntimeException t) {
|
||||
errors.add(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw (RuntimeException) errors.get(0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param jobIdentifier
|
||||
*/
|
||||
public void setJobIdentifier(JobIdentifier jobIdentifier) {
|
||||
this.jobIdentifier = jobIdentifier;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.execution.scope.StepContext#getJobIdentifier()
|
||||
*/
|
||||
public JobIdentifier getJobIdentifier() {
|
||||
return jobIdentifier;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.batch.execution.scope;
|
||||
|
||||
import org.springframework.batch.core.runtime.JobIdentifier;
|
||||
import org.springframework.core.AttributeAccessor;
|
||||
|
||||
/**
|
||||
* Interface for step-scoped context object and step-scoped services.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public interface StepContext extends AttributeAccessor {
|
||||
|
||||
/**
|
||||
* Accessor for the {@link JobIdentifier} associated with the currently
|
||||
* executing step.
|
||||
*
|
||||
* @return the {@link JobIdentifier} associated with the current step
|
||||
*/
|
||||
JobIdentifier getJobIdentifier();
|
||||
|
||||
/**
|
||||
* Accessor for the parent context.
|
||||
*
|
||||
* @return the parent of this context (or null if there isn't one)
|
||||
*/
|
||||
StepContext getParent();
|
||||
|
||||
/**
|
||||
* Register a destruction callback for the end of life of the scope.
|
||||
*/
|
||||
void registerDestructionCallback(String name, Runnable callback);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.batch.execution.scope;
|
||||
|
||||
import org.springframework.batch.repeat.RepeatContext;
|
||||
import org.springframework.core.AttributeAccessor;
|
||||
|
||||
/**
|
||||
* Marker interface for beans to be injected with a {@link RepeatContext}.
|
||||
* Useful for business logic implementations that want to store some state in
|
||||
* the context, to communicate between iterations, or with an enclosing
|
||||
* interceptor.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public interface StepContextAware {
|
||||
|
||||
/**
|
||||
* Callback for injection of {@link RepeatContext}.
|
||||
*
|
||||
* @param context the current context supplied by framework.
|
||||
*/
|
||||
void setStepScopeContext(AttributeAccessor context);
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.batch.execution.scope;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.beans.factory.config.Scope;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
|
||||
/**
|
||||
* Scope for step context. Objects in this scope with <aop:scoped-proxy/>
|
||||
* use the Spring container as an object factory, so there is only one instance
|
||||
* of such a bean per executing step.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class StepScope implements Scope, BeanFactoryAware, BeanPostProcessor {
|
||||
|
||||
/**
|
||||
* Context key for clients to use for conversation identifier.
|
||||
*/
|
||||
public static final String ID_KEY = "JOB_IDENTIFIER";
|
||||
|
||||
/**
|
||||
* Injection callback for BeanFactory. Ensures that the bean factory
|
||||
* contains a BeanPostProcessor of this type (so if this bean is an inner
|
||||
* bean it will still be applied as a post processor).
|
||||
*
|
||||
* @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory)
|
||||
*/
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
if (beanFactory instanceof DefaultListableBeanFactory) {
|
||||
DefaultListableBeanFactory listable = (DefaultListableBeanFactory) beanFactory;
|
||||
if (listable.getBeanNamesForType(getClass()).length == 0) {
|
||||
listable.addBeanPostProcessor(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.config.Scope#get(java.lang.String,
|
||||
* org.springframework.beans.factory.ObjectFactory)
|
||||
*/
|
||||
public Object get(String name, ObjectFactory objectFactory) {
|
||||
SimpleStepContext context = getContext();
|
||||
Object scopedObject = context.getAttribute(name);
|
||||
if (scopedObject == null) {
|
||||
scopedObject = objectFactory.getObject();
|
||||
context.setAttribute(name, scopedObject);
|
||||
}
|
||||
return scopedObject;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.config.Scope#getConversationId()
|
||||
*/
|
||||
public String getConversationId() {
|
||||
SimpleStepContext context = getContext();
|
||||
Object id = context.getAttribute(ID_KEY);
|
||||
return "" + id;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.config.Scope#registerDestructionCallback(java.lang.String,
|
||||
* java.lang.Runnable)
|
||||
*/
|
||||
public void registerDestructionCallback(String name, Runnable callback) {
|
||||
StepContext context = getContext();
|
||||
context.registerDestructionCallback(name, callback);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.config.Scope#remove(java.lang.String)
|
||||
*/
|
||||
public Object remove(String name) {
|
||||
SimpleStepContext context = getContext();
|
||||
return context.removeAttribute(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an attribute accessor in the form of a {@link SimpleStepContext} that
|
||||
* can be used to store scoped bean instances.
|
||||
*
|
||||
* @return the current step context which we can use as a scope storage
|
||||
* medium
|
||||
*/
|
||||
private SimpleStepContext getContext() {
|
||||
SimpleStepContext context = StepSynchronizationManager.getContext();
|
||||
if (context == null) {
|
||||
throw new IllegalStateException("No context holder available for step scope");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* No-op.
|
||||
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object,
|
||||
* java.lang.String)
|
||||
*/
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for {@link StepContextAware} and set context.
|
||||
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object,
|
||||
* java.lang.String)
|
||||
*/
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (bean instanceof StepContextAware) {
|
||||
SimpleStepContext context = getContext();
|
||||
((StepContextAware) bean).setStepScopeContext(context);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.batch.execution.scope;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class StepSynchronizationManager {
|
||||
|
||||
private static final ThreadLocal contextHolder = new ThreadLocal();
|
||||
|
||||
/**
|
||||
* Getter for the current context..
|
||||
*
|
||||
* @return the current {@link SimpleStepContext} or null if there is none (if
|
||||
* we are not in a step).
|
||||
*/
|
||||
public static SimpleStepContext getContext() {
|
||||
return (SimpleStepContext) contextHolder.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for registering a context - should only be used by
|
||||
* {@link StepExecutor} implementations to ensure that {@link #getContext()}
|
||||
* always returns the correct value.
|
||||
*
|
||||
* @return a new context at the start of a batch.
|
||||
*/
|
||||
public static SimpleStepContext open() {
|
||||
StepContext oldSession = getContext();
|
||||
SimpleStepContext context = new SimpleStepContext(oldSession);
|
||||
StepSynchronizationManager.contextHolder.set(context);
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for de-registering the current context - should only be used by
|
||||
* {@link StepExecutor} implementations to ensure that {@link #getContext()}
|
||||
* always returns the correct value.
|
||||
*
|
||||
* @return the old value if there was one.
|
||||
*/
|
||||
public static StepContext close() {
|
||||
SimpleStepContext oldSession = getContext();
|
||||
if (oldSession==null) {
|
||||
return null;
|
||||
}
|
||||
oldSession.close();
|
||||
StepContext context = oldSession.getParent();
|
||||
StepSynchronizationManager.contextHolder.set(context);
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used internally by {@link StepExecutor} implementations to clear the
|
||||
* current context at the end of a batch.
|
||||
*
|
||||
* @return the old value if there was one.
|
||||
*/
|
||||
public static StepContext clear() {
|
||||
StepContext context = getContext();
|
||||
StepSynchronizationManager.contextHolder.set(null);
|
||||
return context;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Specific implementations of scope concerns.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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.configuration.StepConfiguration;
|
||||
import org.springframework.batch.core.executor.StepExecutor;
|
||||
import org.springframework.batch.core.executor.StepExecutorFactory;
|
||||
import org.springframework.batch.execution.step.simple.SimpleStepConfiguration;
|
||||
import org.springframework.batch.execution.step.simple.SimpleStepExecutor;
|
||||
import org.springframework.batch.repeat.RepeatOperations;
|
||||
import org.springframework.batch.repeat.policy.SimpleCompletionPolicy;
|
||||
import org.springframework.batch.repeat.support.RepeatTemplate;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link StepExecutorFactory} that uses a prototype bean in the application
|
||||
* context to satisfy the factory contract. If the prototype bean and
|
||||
* {@link StepConfiguration} are of known (simple) type, they can be combined to
|
||||
* add the commit interval information from the configuration.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class DefaultStepExecutorFactory implements StepExecutorFactory, BeanFactoryAware, InitializingBean {
|
||||
|
||||
private String stepExecutorName = null;
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
/**
|
||||
* Setter for injected {@link BeanFactory}.
|
||||
* @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory)
|
||||
*/
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that if the step executor name is provided, then it is valid and
|
||||
* is of prototype scope.
|
||||
*
|
||||
* @see InitializingBean#afterPropertiesSet()
|
||||
*/
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
// Make an assertion that the bean exists and is of the correct type
|
||||
Assert.notNull(beanFactory.getBean(stepExecutorName, StepExecutor.class),
|
||||
"Step executor name must correspond to a StepExecutor instance.");
|
||||
Assert.state(beanFactory.isPrototype(stepExecutorName),
|
||||
"StepExecutor must be a prototype. Change the scope of the bean named '" + stepExecutorName
|
||||
+ "' to prototype.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate a {@link StepExecutor} for this configuration, allowing different
|
||||
* strategies for configuring the inner loop (chunk operations). Try the
|
||||
* following in this order, until one succeeds. In each case first obtain
|
||||
* the {@link StepExecutor} referred to by the {@link #stepExecutorName},
|
||||
* then:
|
||||
* <ul>
|
||||
*
|
||||
* <li>If the {@link StepExecutor} refers to a {@link SimpleStepExecutor},
|
||||
* and {@link StepConfiguration} is an instance of
|
||||
* {@link RepeatOperationsHolder}, then the {@link RepeatOperations} for
|
||||
* the chunk will be pulled from there directly. This gives maximum
|
||||
* flexibility for clients to control the properties of the iteration. For
|
||||
* simple use cases where clients only need to control a few aspects of the
|
||||
* execution, like the commit interval, this is not necessary.</li>
|
||||
*
|
||||
* <li>If the {@link StepExecutor} is a {@link SimpleStepExecutor} and the
|
||||
* configuration is a {@link SimpleStepConfiguration} then this
|
||||
* implementation modifies the state of the {@link StepExecutor} to set the
|
||||
* completion policy of the chunk operations. In this case the chunk
|
||||
* operations cannot be set by the client of this factory.</li>
|
||||
*
|
||||
* <li> Use the {@link StepExecutor} directly. </li>
|
||||
*
|
||||
* </ul>
|
||||
* <br/>
|
||||
*
|
||||
* @throws IllegalStateException if no {@link StepExecutor} can be located.
|
||||
*
|
||||
* @see StepExecutorFactory#getExecutor(StepConfiguration)
|
||||
*/
|
||||
public StepExecutor getExecutor(StepConfiguration configuration) {
|
||||
|
||||
StepExecutor executor = getStepExecutor();
|
||||
|
||||
if (executor instanceof SimpleStepExecutor) {
|
||||
RepeatTemplate template = new RepeatTemplate();
|
||||
RepeatOperations repeatOperations = template;
|
||||
if (configuration instanceof RepeatOperationsHolder) {
|
||||
repeatOperations = ((RepeatOperationsHolder) configuration).getChunkOperations();
|
||||
Assert.state(repeatOperations != null,
|
||||
"Chunk operations obtained from step configuration must be non-null.");
|
||||
}
|
||||
else if (configuration instanceof SimpleStepConfiguration) {
|
||||
template.setCompletionPolicy(new SimpleCompletionPolicy(((SimpleStepConfiguration) configuration)
|
||||
.getCommitInterval()));
|
||||
}
|
||||
((SimpleStepExecutor) executor).setChunkOperations(repeatOperations);
|
||||
}
|
||||
|
||||
return executor;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the bean name of the {@link StepExecutor} to use. The
|
||||
* corresponding bean must be prototype scoped, so that its properties can
|
||||
* be overridden per execution by the {@link StepConfiguration}.
|
||||
*
|
||||
* @param stepExecutor the stepExecutor to set
|
||||
*/
|
||||
public void setStepExecutorName(String stepExecutorName) {
|
||||
this.stepExecutorName = stepExecutorName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal convenience method to get a step executor instance.
|
||||
*
|
||||
* @return the step executor instance to use.
|
||||
*/
|
||||
private StepExecutor getStepExecutor() {
|
||||
return (StepExecutor) beanFactory.getBean(stepExecutorName, StepExecutor.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.configuration.StepConfiguration;
|
||||
import org.springframework.batch.core.executor.StepExecutor;
|
||||
import org.springframework.batch.repeat.RepeatOperations;
|
||||
|
||||
/**
|
||||
* Marker interface for indicating that a {@link RepeatOperations} instance is
|
||||
* available for the inner loop (chunk operations) in a {@link StepExecutor}.
|
||||
* The inner loop is normally going to be in-process and thread-bound so it
|
||||
* makes sense for {@link StepConfiguration} implementations to be able to
|
||||
* override the strategies that control that loop.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public interface RepeatOperationsHolder {
|
||||
|
||||
/**
|
||||
* Principal method in the {@link RepeatOperationsHolder} interface.
|
||||
*
|
||||
* @return a {@link RepeatOperations} which can be used to iterate over an
|
||||
* inner loop (chunk).
|
||||
*/
|
||||
RepeatOperations getChunkOperations();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Specific implementations of step concerns.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.simple;
|
||||
|
||||
import org.springframework.batch.core.configuration.StepConfiguration;
|
||||
import org.springframework.batch.core.configuration.StepConfigurationSupport;
|
||||
import org.springframework.batch.repeat.exception.handler.ExceptionHandler;
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
|
||||
/**
|
||||
* A {@link StepConfiguration} implementation that provides common behaviour to
|
||||
* subclasses. Implements {@link BeanNameAware} so that if no name is provided
|
||||
* explicitly it will be inferred from the bean definition in Spring
|
||||
* configuration.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class AbstractStepConfiguration extends StepConfigurationSupport implements BeanNameAware {
|
||||
|
||||
private int skipLimit = 0;
|
||||
|
||||
private boolean saveRestartData = false;
|
||||
|
||||
private ExceptionHandler exceptionHandler;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public AbstractStepConfiguration() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenent constructor for setting only the name property.
|
||||
* @param name
|
||||
*/
|
||||
public AbstractStepConfiguration(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name property if it has not already been set explicitly (and is
|
||||
* therefore not null).
|
||||
*
|
||||
* @see org.springframework.beans.factory.BeanNameAware#setBeanName(java.lang.String)
|
||||
*/
|
||||
public void setBeanName(String name) {
|
||||
if (getName() == null) {
|
||||
setName(name);
|
||||
}
|
||||
}
|
||||
|
||||
public ExceptionHandler getExceptionHandler() {
|
||||
return exceptionHandler;
|
||||
}
|
||||
|
||||
public void setExceptionHandler(ExceptionHandler exceptionHandler) {
|
||||
this.exceptionHandler = exceptionHandler;
|
||||
}
|
||||
|
||||
public void setSkipLimit(int skipLimit) {
|
||||
this.skipLimit = skipLimit;
|
||||
}
|
||||
|
||||
public int getSkipLimit() {
|
||||
return skipLimit;
|
||||
}
|
||||
|
||||
public void setSaveRestartData(boolean saveRestartData) {
|
||||
this.saveRestartData = saveRestartData;
|
||||
}
|
||||
|
||||
public boolean isSaveRestartData() {
|
||||
return saveRestartData;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.simple;
|
||||
|
||||
import org.springframework.batch.core.configuration.StepConfiguration;
|
||||
import org.springframework.batch.core.tasklet.Tasklet;
|
||||
import org.springframework.batch.execution.step.RepeatOperationsHolder;
|
||||
import org.springframework.batch.repeat.RepeatOperations;
|
||||
|
||||
/**
|
||||
* {@link StepConfiguration} implementation that allows full configuration of
|
||||
* the {@link RepeatOperations} that will be used in the chunk (inner loop).
|
||||
*
|
||||
* @author Lucas Ward
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ChunkOperationsStepConfiguration extends AbstractStepConfiguration implements RepeatOperationsHolder {
|
||||
|
||||
// default StepExecutor is null
|
||||
private RepeatOperations chunkOperations;
|
||||
|
||||
public ChunkOperationsStepConfiguration() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ChunkOperationsStepConfiguration(RepeatOperations repeatOperations) {
|
||||
this();
|
||||
this.chunkOperations = repeatOperations;
|
||||
}
|
||||
|
||||
public ChunkOperationsStepConfiguration(Tasklet module) {
|
||||
this();
|
||||
setTasklet(module);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public accessor for the chunkOperations property.
|
||||
*
|
||||
* @return the executor
|
||||
*/
|
||||
public RepeatOperations getChunkOperations() {
|
||||
return chunkOperations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the chunkOperations.
|
||||
*
|
||||
* @param chunkOperations the repeatOperations to set
|
||||
*/
|
||||
public void setChunkOperations(RepeatOperations chunkOperations) {
|
||||
this.chunkOperations = chunkOperations;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.simple;
|
||||
|
||||
import org.springframework.batch.core.domain.StepInstance;
|
||||
import org.springframework.batch.core.tasklet.Tasklet;
|
||||
import org.springframework.batch.core.tasklet.Recoverable;
|
||||
import org.springframework.batch.io.Skippable;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
||||
import org.springframework.transaction.support.TransactionCallback;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
/**
|
||||
* Adds some recovery behaviour to {@link SimpleStepExecutor}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class DefaultStepExecutor extends SimpleStepExecutor {
|
||||
|
||||
/**
|
||||
* Extends {@link SimpleStepExecutor#doTaskletProcessing(Tasklet, StepInstance)} to
|
||||
* add some basic recovery behaviour. If the {@link Tasklet} implements
|
||||
* {@link Recoverable} <em>and</em> {@link Skippable} then the recovery
|
||||
* and skip methods are called. The recovery is done in a new transaction,
|
||||
* started with propagation
|
||||
* {@link TransactionDefinition#PROPAGATION_REQUIRES_NEW} so that the
|
||||
* inevitable rollback on the main processing loop does not cause the
|
||||
* recovery to roll back as well.
|
||||
*
|
||||
* @throws Exception whenever {@link SimpleStepExecutor} would, but takes
|
||||
* the recovery path first.
|
||||
*
|
||||
* @see org.springframework.batch.execution.step.simple.SimpleStepExecutor#doTaskletProcessing(org.springframework.batch.core.tasklet.Tasklet,
|
||||
* org.springframework.batch.core.domain.StepInstance)
|
||||
*/
|
||||
protected boolean doTaskletProcessing(Tasklet module, final StepInstance step) throws Exception {
|
||||
|
||||
boolean result = true;
|
||||
|
||||
try {
|
||||
|
||||
result = super.doTaskletProcessing(module, step);
|
||||
|
||||
}
|
||||
catch (final Exception e) {
|
||||
|
||||
if (module instanceof Recoverable && module instanceof Skippable) {
|
||||
final Recoverable recoverable = (Recoverable) module;
|
||||
new TransactionTemplate(transactionManager, new DefaultTransactionDefinition(
|
||||
TransactionDefinition.PROPAGATION_REQUIRES_NEW)).execute(new TransactionCallback() {
|
||||
public Object doInTransaction(TransactionStatus status) {
|
||||
recoverable.recover(e);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (module instanceof Skippable) {
|
||||
((Skippable) module).skip();
|
||||
}
|
||||
|
||||
// Rethrow so that outer transaction is rolled back properly
|
||||
throw e;
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.simple;
|
||||
|
||||
import org.springframework.batch.core.configuration.StepConfiguration;
|
||||
import org.springframework.batch.core.tasklet.Tasklet;
|
||||
|
||||
/**
|
||||
* Simple {@link StepConfiguration} good enough for most purposes and easy to
|
||||
* configure simple properties, principally the commit interval.
|
||||
*
|
||||
* @author Lucas Ward
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class SimpleStepConfiguration extends AbstractStepConfiguration {
|
||||
|
||||
// default commit interval is one
|
||||
private int commitInterval = 1;
|
||||
|
||||
public SimpleStepConfiguration() {
|
||||
super();
|
||||
}
|
||||
|
||||
public SimpleStepConfiguration(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
public SimpleStepConfiguration(Tasklet module) {
|
||||
this();
|
||||
setTasklet(module);
|
||||
}
|
||||
|
||||
public void setCommitInterval(int commitInterval) {
|
||||
this.commitInterval = commitInterval;
|
||||
}
|
||||
|
||||
public int getCommitInterval() {
|
||||
return commitInterval;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
/*
|
||||
* 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.simple;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.batch.core.configuration.StepConfiguration;
|
||||
import org.springframework.batch.core.domain.BatchStatus;
|
||||
import org.springframework.batch.core.domain.StepExecution;
|
||||
import org.springframework.batch.core.domain.StepInstance;
|
||||
import org.springframework.batch.core.executor.StepExecutor;
|
||||
import org.springframework.batch.core.executor.StepInterruptedException;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.core.runtime.JobExecutionContext;
|
||||
import org.springframework.batch.core.runtime.StepExecutionContext;
|
||||
import org.springframework.batch.core.tasklet.Tasklet;
|
||||
import org.springframework.batch.execution.scope.StepScope;
|
||||
import org.springframework.batch.execution.scope.SimpleStepContext;
|
||||
import org.springframework.batch.execution.scope.StepSynchronizationManager;
|
||||
import org.springframework.batch.io.exception.BatchCriticalException;
|
||||
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.batch.repeat.synch.BatchTransactionSynchronizationManager;
|
||||
import org.springframework.batch.restart.RestartData;
|
||||
import org.springframework.batch.restart.Restartable;
|
||||
import org.springframework.batch.statistics.StatisticsProvider;
|
||||
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.TransactionCallback;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Simple implementation of {@link StepExecutor} 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
|
||||
*
|
||||
*/
|
||||
public class SimpleStepExecutor implements StepExecutor {
|
||||
|
||||
/**
|
||||
* Key placed in step scope context to identify the
|
||||
* {@link StepExecutionContext}.
|
||||
*/
|
||||
public static final String STEP_KEY = "STEP";
|
||||
|
||||
/**
|
||||
* Context attribute key for step execution. Used by monitoring and managing
|
||||
* clients to inspect current step execution.
|
||||
*/
|
||||
private static final String STEP_EXECUTION_KEY = "STEP_EXECUTION";
|
||||
|
||||
/**
|
||||
* Attribute key for statistics instance in step context.
|
||||
*/
|
||||
public static final String STATISTICS_KEY = "STATISTICS";
|
||||
|
||||
private RepeatOperations chunkOperations = new RepeatTemplate();
|
||||
|
||||
private RepeatOperations stepOperations = new RepeatTemplate();
|
||||
|
||||
private JobRepository jobRepository;
|
||||
|
||||
// default to checking current thread for interruption.
|
||||
private StepInterruptionPolicy interruptionPolicy = new ThreadStepInterruptionPolicy();
|
||||
|
||||
// Not for production use...
|
||||
protected PlatformTransactionManager transactionManager = new ResourcelessTransactionManager();
|
||||
|
||||
public void setTransactionManager(PlatformTransactionManager transactionManager) {
|
||||
this.transactionManager = transactionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injected strategy for storage and retrieval of persistent step
|
||||
* information. Mandatory property.
|
||||
* @param jobRepository
|
||||
*/
|
||||
public void setRepository(JobRepository jobRepository) {
|
||||
this.jobRepository = jobRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
* @throws StepInterruptedException if the step or a chunk is interrupted
|
||||
* @throws RuntimeException if there is an exception during a chunk
|
||||
* execution
|
||||
* @see StepExecutor#process(StepConfiguration, StepExecutionContext)
|
||||
*/
|
||||
public ExitStatus process(final StepConfiguration configuration, final StepExecutionContext stepExecutionContext)
|
||||
throws BatchCriticalException, StepInterruptedException {
|
||||
|
||||
final StepInstance step = stepExecutionContext.getStep();
|
||||
Assert.notNull(step);
|
||||
|
||||
final StepExecution stepExecution = stepExecutionContext.getStepExecution();
|
||||
final Tasklet module = configuration.getTasklet();
|
||||
step.setStepExecution(stepExecution);
|
||||
|
||||
ExitStatus status = ExitStatus.FAILED;
|
||||
|
||||
final SimpleStepContext stepScopeContext = StepSynchronizationManager.open();
|
||||
|
||||
try {
|
||||
stepExecution.setStartTime(new Timestamp(System.currentTimeMillis()));
|
||||
updateStatus(stepExecutionContext, BatchStatus.STARTED);
|
||||
|
||||
final boolean shouldPersistRestartData = ((AbstractStepConfiguration) configuration).isSaveRestartData();
|
||||
|
||||
if (shouldPersistRestartData) {
|
||||
restoreFromRestartData(module, step.getRestartData());
|
||||
}
|
||||
|
||||
status = stepOperations.iterate(new RepeatCallback() {
|
||||
|
||||
public ExitStatus doInIteration(final RepeatContext context) throws Exception {
|
||||
|
||||
stepExecutionContext.getJobExecutionContext().registerStepContext(context);
|
||||
context.registerDestructionCallback("STEP_EXECUTION_CONTEXT_CALLBACK", new Runnable() {
|
||||
public void run() {
|
||||
stepExecutionContext.getJobExecutionContext().unregisterStepContext(context);
|
||||
}
|
||||
});
|
||||
stepScopeContext.setJobIdentifier(stepExecutionContext.getJobExecutionContext().getJobIdentifier());
|
||||
context.setAttribute(StepScope.ID_KEY, stepExecutionContext.getJobExecutionContext()
|
||||
.getJobIdentifier());
|
||||
// Mark the context as a step context as a hint to scope
|
||||
// implementations.
|
||||
context.setAttribute(STEP_KEY, stepExecutionContext);
|
||||
// Add the step execution as an attribute so monitoring
|
||||
// clients can see it.
|
||||
context.setAttribute(STEP_EXECUTION_KEY, stepExecution);
|
||||
// Before starting a new transaction, check for
|
||||
// interruption.
|
||||
interruptionPolicy.checkInterrupted(context);
|
||||
|
||||
ExitStatus result = (ExitStatus) new TransactionTemplate(transactionManager)
|
||||
.execute(new TransactionCallback() {
|
||||
public Object doInTransaction(TransactionStatus status) {
|
||||
// New transaction obtained, resynchronize
|
||||
// TransactionSyncrhonization objects
|
||||
BatchTransactionSynchronizationManager.resynchronize();
|
||||
ExitStatus result;
|
||||
|
||||
try {
|
||||
result = processChunk(configuration, stepExecutionContext);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
/*
|
||||
* any exception thrown within the
|
||||
* transaction template will
|
||||
* automatically cause the transaction
|
||||
* to rollback
|
||||
*/
|
||||
stepExecution.incrementRollbackCount();
|
||||
if (t instanceof RuntimeException) {
|
||||
throw (RuntimeException) t;
|
||||
}
|
||||
else {
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldPersistRestartData) {
|
||||
step.setRestartData(getRestartData(module));
|
||||
jobRepository.update(step);
|
||||
}
|
||||
Properties statistics = getStatistics(module);
|
||||
stepExecution.setStatistics(statistics);
|
||||
context.setAttribute(STATISTICS_KEY, statistics);
|
||||
stepExecution.incrementCommitCount();
|
||||
jobRepository.saveOrUpdate(stepExecution);
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
// Check for interruption after transaction as well, so that
|
||||
// the interrupted exception is correctly propagated up to
|
||||
// caller
|
||||
interruptionPolicy.checkInterrupted(context);
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
stepExecution.setExitCode(status.getExitCode());
|
||||
updateStatus(stepExecutionContext, BatchStatus.COMPLETED);
|
||||
return status;
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
if (e.getCause() instanceof StepInterruptedException) {
|
||||
updateStatus(stepExecutionContext, BatchStatus.STOPPED);
|
||||
throw (StepInterruptedException) e.getCause();
|
||||
}
|
||||
else {
|
||||
updateStatus(stepExecutionContext, BatchStatus.FAILED);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
stepExecution.setEndTime(new Timestamp(System.currentTimeMillis()));
|
||||
try {
|
||||
jobRepository.saveOrUpdate(stepExecution);
|
||||
}
|
||||
finally {
|
||||
// clear any registered synchronizations
|
||||
try {
|
||||
StepSynchronizationManager.close();
|
||||
}
|
||||
finally {
|
||||
BatchTransactionSynchronizationManager.clearSynchronizations();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to update the status in all relevant places.
|
||||
* @param step the current step
|
||||
* @param stepExecution the current stepExecution
|
||||
* @param status the status to set
|
||||
*/
|
||||
private void updateStatus(StepExecutionContext stepExecutionContext, BatchStatus status) {
|
||||
StepInstance step = stepExecutionContext.getStep();
|
||||
StepExecution stepExecution = stepExecutionContext.getStepExecution();
|
||||
stepExecution.setStatus(status);
|
||||
step.setStatus(status);
|
||||
jobRepository.update(step);
|
||||
jobRepository.saveOrUpdate(stepExecution);
|
||||
for (Iterator iter = stepExecutionContext.getJobExecutionContext().getStepContexts().iterator(); iter.hasNext();) {
|
||||
RepeatContext context = (RepeatContext) iter.next();
|
||||
context.setAttribute("JOB_STATUS", status);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 configuration the current step configuration
|
||||
* @param stepExecutionContext the current step, containing the
|
||||
* {@link Tasklet} with the business logic.
|
||||
* @return true if there is more data to process.
|
||||
*/
|
||||
protected final ExitStatus processChunk(final StepConfiguration configuration,
|
||||
final StepExecutionContext stepExecutionContext) {
|
||||
return chunkOperations.iterate(new RepeatCallback() {
|
||||
public ExitStatus doInIteration(final RepeatContext context) throws Exception {
|
||||
stepExecutionContext.getJobExecutionContext().registerChunkContext(context);
|
||||
context.registerDestructionCallback("CHUNK_EXECUTION_CONTEXT_CALLBACK", new Runnable() {
|
||||
public void run() {
|
||||
stepExecutionContext.getJobExecutionContext().unregisterStepContext(context);
|
||||
}
|
||||
});
|
||||
// check for interruption before each item as well
|
||||
interruptionPolicy.checkInterrupted(context);
|
||||
boolean result = doTaskletProcessing(configuration.getTasklet(), stepExecutionContext.getStep());
|
||||
stepExecutionContext.getStepExecution().incrementTaskCount();
|
||||
// check for interruption after each item as well
|
||||
interruptionPolicy.checkInterrupted(context);
|
||||
return new ExitStatus(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the business logic, delegating to the given {@link Tasklet}.
|
||||
* Subclasses could extend the behaviour as long as they always return the
|
||||
* value of this method call in their superclass.
|
||||
* @param tasklet the unit of business logic to execute
|
||||
* @param step the current step
|
||||
* @return boolean if there is more processing to do
|
||||
* @throws Exception if there is an error
|
||||
*/
|
||||
protected boolean doTaskletProcessing(Tasklet tasklet, StepInstance step) throws Exception {
|
||||
return tasklet.execute();
|
||||
}
|
||||
|
||||
private RestartData getRestartData(Tasklet module) {
|
||||
if (module instanceof Restartable) {
|
||||
return ((Restartable) module).getRestartData();
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void restoreFromRestartData(Tasklet tasklet, RestartData restartData) {
|
||||
if (tasklet instanceof Restartable && restartData != null) {
|
||||
((Restartable) tasklet).restoreFrom(restartData);
|
||||
}
|
||||
}
|
||||
|
||||
private Properties getStatistics(Tasklet tasklet) {
|
||||
if (tasklet instanceof StatisticsProvider) {
|
||||
return ((StatisticsProvider) tasklet).getStatistics();
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the {@link StepInterruptionPolicy}. The policy is used to
|
||||
* check whether an external request has been made to interrupt the job
|
||||
* execution.
|
||||
* @param interruptionPolicy a {@link StepInterruptionPolicy}
|
||||
*/
|
||||
public void setInterruptionPolicy(StepInterruptionPolicy interruptionPolicy) {
|
||||
this.interruptionPolicy = interruptionPolicy;
|
||||
}
|
||||
}
|
||||
@@ -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.simple;
|
||||
|
||||
import org.springframework.batch.core.executor.StepExecutor;
|
||||
import org.springframework.batch.core.executor.StepInterruptedException;
|
||||
import org.springframework.batch.repeat.RepeatContext;
|
||||
|
||||
/**
|
||||
* Strategy interface for an interruption policy. This policy allows
|
||||
* {@link StepExecutor} 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 StepInterruptedException}.
|
||||
* @param context the current context of the running step.
|
||||
*
|
||||
* @throws StepInterruptedException when the job has been interrupted.
|
||||
*/
|
||||
void checkInterrupted(RepeatContext context) throws StepInterruptedException;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.simple;
|
||||
|
||||
import org.springframework.batch.core.executor.StepInterruptedException;
|
||||
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 {
|
||||
|
||||
/**
|
||||
* Returns if the current job lifecycle has been interrupted by checking if
|
||||
* the current thread is interrupted.
|
||||
*/
|
||||
public void checkInterrupted(RepeatContext context) throws StepInterruptedException {
|
||||
|
||||
if (isInterrupted(context)) {
|
||||
throw new StepInterruptedException("Job interrupted status detected.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: add more interruption policies: they should all check context.isTerminateOnly()
|
||||
* @param context the current context
|
||||
* @return true if the job has been interrupted
|
||||
*/
|
||||
private boolean isInterrupted(RepeatContext context) {
|
||||
return Thread.currentThread().isInterrupted() || context.isTerminateOnly();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Specific implementations of simple concerns.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.batch.execution.tasklet;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.batch.core.tasklet.Recoverable;
|
||||
import org.springframework.batch.core.tasklet.Tasklet;
|
||||
import org.springframework.batch.io.Skippable;
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.batch.item.ItemProvider;
|
||||
import org.springframework.batch.repeat.RepeatContext;
|
||||
import org.springframework.batch.repeat.synch.RepeatSynchronizationManager;
|
||||
import org.springframework.batch.retry.RetryOperations;
|
||||
import org.springframework.batch.retry.RetryPolicy;
|
||||
import org.springframework.batch.retry.callback.ItemProviderRetryCallback;
|
||||
import org.springframework.batch.retry.policy.ItemProviderRetryPolicy;
|
||||
import org.springframework.batch.retry.support.RetryTemplate;
|
||||
import org.springframework.batch.statistics.StatisticsProvider;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A concrete implementation of the {@link Tasklet} interface that provides
|
||||
* functionality for 'split processing'. This type of processing is
|
||||
* characterised by separating the reading and processing of batch data into two
|
||||
* separate classes: ItemProvider and DataProcessor. The ItemProvider class
|
||||
* provides a solid means for re-usability and enforces good architecture
|
||||
* practices. Because an object *must* be returned by the {@link ItemProvider}
|
||||
* to continue processing, (returning null indicates processing should end) a
|
||||
* developer is forced to read in all relevant data, place it into a domain
|
||||
* object, and return that object. The {@link ItemProcessor} will then use this
|
||||
* object for calculations and output.<br/>
|
||||
*
|
||||
* If a {@link RetryPolicy} is provided it will be used to construct a stateful
|
||||
* retry around the {@link ItemProcessor}, delegating recover and identity
|
||||
* concerns to the {@link ItemProvider}. In this case clients of this class do
|
||||
* not need to take any additional action at runtime to take advantage of the
|
||||
* retry and recovery, provided the {@link #execute()} method is called again
|
||||
* with the {@link ItemProvider} in the same state (normally this would be the
|
||||
* case because a transaction would have rolled back and the item would be
|
||||
* represented).<br/>
|
||||
*
|
||||
* If neither a {@link RetryPolicy} nor a {@link RetryOperations} is provided
|
||||
* then the {@link Recoverable} interface can be used to attempt to recover
|
||||
* immediately (with no retry) from a processing error. Clients of this class
|
||||
* must call {@link Recoverable#recover(Throwable)} directly, which is simply
|
||||
* delegated to {@link ItemProvider#recover(Object, Throwable)}.
|
||||
*
|
||||
* @see ItemProvider
|
||||
* @see ItemProcessor
|
||||
* @see RetryPolicy
|
||||
* @see Recoverable
|
||||
*
|
||||
* @author Lucas Ward
|
||||
* @author Dave Syer
|
||||
* @author Robert Kasanicky
|
||||
*
|
||||
*/
|
||||
public class ItemProviderProcessTasklet implements Tasklet, Recoverable, Skippable, StatisticsProvider,
|
||||
InitializingBean {
|
||||
|
||||
/**
|
||||
* Prefix added to statistics keys from processor if needed to avoid
|
||||
* ambiguity between provider and processor.
|
||||
*/
|
||||
public static final String PROCESSOR_STATISTICS_PREFIX = "processor.";
|
||||
|
||||
/**
|
||||
* Prefix added to statistics keys from provider if needed to avoid
|
||||
* ambiguity between provider and processor.
|
||||
*/
|
||||
public static final String PROVIDER_STATISTICS_PREFIX = "provider.";
|
||||
|
||||
/**
|
||||
* Attribute key in the surrounding {@link RepeatContext} for the current
|
||||
* item being processed. Needed to provide recoverable behaviour if
|
||||
* {@link RetryOperations} are not provided.
|
||||
*/
|
||||
private static final String ITEM_KEY = ItemProviderProcessTasklet.class + ".ITEM";
|
||||
|
||||
private RetryPolicy retryPolicy = null;
|
||||
|
||||
private RetryOperations retryOperations = null;
|
||||
|
||||
protected ItemProvider itemProvider;
|
||||
|
||||
protected ItemProcessor itemProcessor;
|
||||
|
||||
/**
|
||||
* Check mandatory properties (provider and processor), and ensure that only
|
||||
* one (or neither) of {@link RetryPolicy} or {@link RetryOperations} is
|
||||
* provided.
|
||||
*
|
||||
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
|
||||
*/
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(itemProvider, "ItemProvider must be provided");
|
||||
Assert.notNull(itemProcessor, "ItemProcessor must be provided");
|
||||
Assert.state(!(retryPolicy != null && retryOperations != null),
|
||||
"Either RetryOperations or RetryPolicy can be provided, but not both.");
|
||||
if (retryPolicy != null) {
|
||||
RetryTemplate template = new RetryTemplate();
|
||||
template.setRetryPolicy(new ItemProviderRetryPolicy(retryPolicy));
|
||||
retryOperations = template;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read from the {@link ItemProvider} and process (if not null) with the
|
||||
* {@link ItemProcessor}. The call to {@link ItemProcessor} is wrapped in a
|
||||
* retry, if either a {@link RetryPolicy} or a {@link RetryOperations} is
|
||||
* provided.
|
||||
*
|
||||
* @see org.springframework.batch.core.tasklet.Tasklet#execute()
|
||||
*/
|
||||
public boolean execute() throws Exception {
|
||||
if (retryOperations != null) {
|
||||
return retryOperations.execute(new ItemProviderRetryCallback(itemProvider, itemProcessor)) != null;
|
||||
}
|
||||
else {
|
||||
Object data = itemProvider.next();
|
||||
if (data == null) {
|
||||
return false;
|
||||
}
|
||||
RepeatContext context = RepeatSynchronizationManager.getContext();
|
||||
Assert.state(context != null,
|
||||
"No context available: you probably need to use this class inside a batch operation.");
|
||||
context.setAttribute(ITEM_KEY, data);
|
||||
itemProcessor.process(data);
|
||||
// No exception so clear context (we can't recover directly because
|
||||
// the current transaction is going to roll back)
|
||||
context.removeAttribute(ITEM_KEY);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call out to the provider for recovery step.
|
||||
*
|
||||
* @see org.springframework.batch.core.tasklet.Recoverable#recover(java.lang.Throwable)
|
||||
*/
|
||||
public void recover(Throwable cause) {
|
||||
RepeatContext context = RepeatSynchronizationManager.getContext();
|
||||
Assert.state(context != null,
|
||||
"No context available: you probably need to use this class inside a batch operation.");
|
||||
|
||||
try {
|
||||
Object data = context.getAttribute(ITEM_KEY);
|
||||
itemProvider.recover(data, cause);
|
||||
}
|
||||
finally {
|
||||
context.removeAttribute(ITEM_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param itemProvider
|
||||
*/
|
||||
public void setItemProvider(ItemProvider itemProvider) {
|
||||
this.itemProvider = itemProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param moduleProcessor
|
||||
*/
|
||||
public void setItemProcessor(ItemProcessor moduleProcessor) {
|
||||
this.itemProcessor = moduleProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the provider and / or processor are {@link Skippable} then delegate to
|
||||
* them in that order.
|
||||
*
|
||||
* @see org.springframework.batch.io.Skippable#skip()
|
||||
*/
|
||||
public void skip() {
|
||||
if (this.itemProvider instanceof Skippable) {
|
||||
((Skippable) this.itemProvider).skip();
|
||||
}
|
||||
if (this.itemProcessor instanceof Skippable) {
|
||||
((Skippable) this.itemProcessor).skip();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the provider and / or processor are {@link StatisticsProvider} then
|
||||
* delegate to them in that order. If they both implement
|
||||
* {@link StatisticsProvider} then the property keys are prepended with
|
||||
* special prefixes to avoid potential ambiguity. The prefixes are only
|
||||
* prepended in the case of a duplicate key shared between provider and
|
||||
* processor.
|
||||
*
|
||||
* @see org.springframework.batch.io.Skippable#skip()
|
||||
*/
|
||||
public Properties getStatistics() {
|
||||
Properties stats = new Properties();
|
||||
if (this.itemProvider instanceof StatisticsProvider) {
|
||||
stats = ((StatisticsProvider) this.itemProvider).getStatistics();
|
||||
}
|
||||
if (this.itemProcessor instanceof StatisticsProvider) {
|
||||
Properties props = ((StatisticsProvider) this.itemProcessor).getStatistics();
|
||||
if (!stats.isEmpty()) {
|
||||
stats = prependKeys(stats, props, PROVIDER_STATISTICS_PREFIX, PROCESSOR_STATISTICS_PREFIX);
|
||||
} else {
|
||||
stats.putAll(props);
|
||||
}
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param props1
|
||||
* @param string
|
||||
* @return
|
||||
*/
|
||||
private Properties prependKeys(Properties props1, Properties props2, String prefix1, String prefix2) {
|
||||
Properties result = new Properties();
|
||||
Set duplicates = new HashSet();
|
||||
for (Iterator iterator = props1.entrySet().iterator(); iterator.hasNext();) {
|
||||
Map.Entry entry = (Map.Entry) iterator.next();
|
||||
String key = (String) entry.getKey();
|
||||
String value = (String) entry.getValue();
|
||||
if (props2.containsKey(key)) {
|
||||
duplicates.add(key);
|
||||
continue;
|
||||
}
|
||||
result.setProperty(key, value);
|
||||
}
|
||||
for (Iterator iterator = props2.entrySet().iterator(); iterator.hasNext();) {
|
||||
Map.Entry entry = (Map.Entry) iterator.next();
|
||||
String key = (String) entry.getKey();
|
||||
String value = (String) entry.getValue();
|
||||
if (duplicates.contains(key)) {
|
||||
continue;
|
||||
}
|
||||
result.setProperty(key, value);
|
||||
}
|
||||
for (Iterator iterator = duplicates.iterator(); iterator.hasNext();) {
|
||||
String key = (String) iterator.next();
|
||||
result.setProperty(prefix1+key, props1.getProperty(key));
|
||||
result.setProperty(prefix2+key, props2.getProperty(key));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the retryPolicy.
|
||||
*
|
||||
* @param retyPolicy the retryPolicy to set
|
||||
*/
|
||||
public void setRetryPolicy(RetryPolicy retryPolicy) {
|
||||
this.retryPolicy = retryPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the retryOperations.
|
||||
*
|
||||
* @param retryOperations the retryOperations to set
|
||||
*/
|
||||
public void setRetryOperations(RetryOperations retryOperations) {
|
||||
this.retryOperations = retryOperations;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Provides the basic batch module for reading and processing data.
|
||||
* Implementations of this class will be handling both the input and output of
|
||||
* data within one class. Developers should ensure that all reading is done
|
||||
* before returning from the read() method. This is to ensure all data has been
|
||||
* read first, before beginning to process. It is possibly detrimental to
|
||||
* performance if processing begins when records still need to be read, because
|
||||
* any writing of output will put the transaction in a volatile state, since
|
||||
* errors with any additional input will need to cause a rollback, rather than
|
||||
* simply skipping that record.
|
||||
*
|
||||
* @author Lucas Ward
|
||||
*
|
||||
*/
|
||||
public abstract class ReadProcessTasklet implements Tasklet {
|
||||
|
||||
/**
|
||||
* Required for implementation of the {@link Tasklet} interface. The boolean returned
|
||||
* from the abstract read method will be returned to the {@link Tasklet}, to indicate
|
||||
* whether or not processing should continue.
|
||||
*/
|
||||
public final boolean execute() throws Exception {
|
||||
if (!read()) {
|
||||
return false;
|
||||
}
|
||||
process();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract read method to be implemented by batch developers. All data
|
||||
* should be read from within this method and a boolean indicated whether or
|
||||
* not processing should continue should be returned.
|
||||
*
|
||||
* @return boolean indicating whether or not processing should continue.
|
||||
*/
|
||||
public abstract boolean read() throws Exception;
|
||||
|
||||
/**
|
||||
* Abstract process method to be implemented by batch developers. All
|
||||
* processing and writing out of data should be done within this method.
|
||||
*/
|
||||
public abstract void process() throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.batch.execution.tasklet;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.batch.item.ItemProvider;
|
||||
import org.springframework.batch.restart.GenericRestartData;
|
||||
import org.springframework.batch.restart.RestartData;
|
||||
import org.springframework.batch.restart.Restartable;
|
||||
import org.springframework.batch.support.PropertiesConverter;
|
||||
|
||||
/**
|
||||
* An extension of {@link ItemProviderProcessTasklet} that delegates calls to
|
||||
* {@link Restartable} to the provider and processor.
|
||||
*
|
||||
* @see ItemProvider
|
||||
* @see ItemProcessor
|
||||
* @see Restartable
|
||||
*
|
||||
* @author Lucas Ward
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class RestartableItemProviderTasklet extends ItemProviderProcessTasklet implements Restartable {
|
||||
|
||||
/**
|
||||
* @see Restartable#getRestartData()
|
||||
*/
|
||||
public RestartData getRestartData() {
|
||||
|
||||
RestartData itemProviderRestartData = null;
|
||||
RestartData itemProcessorRestartData = null;
|
||||
|
||||
if (itemProvider instanceof Restartable) {
|
||||
itemProviderRestartData = ((Restartable) itemProvider).getRestartData();
|
||||
}
|
||||
|
||||
if (itemProcessor instanceof Restartable) {
|
||||
itemProcessorRestartData = ((Restartable) itemProcessor).getRestartData();
|
||||
}
|
||||
|
||||
RestartableItemProviderTaskletRestartData restartData = new RestartableItemProviderTaskletRestartData(itemProviderRestartData, itemProcessorRestartData);
|
||||
|
||||
return restartData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Restartable#restoreFrom(RestartData)
|
||||
*/
|
||||
public void restoreFrom(RestartData data) {
|
||||
if (data == null || data.getProperties() == null)
|
||||
return;
|
||||
|
||||
RestartableItemProviderTaskletRestartData moduleRestartData;
|
||||
|
||||
if (data instanceof RestartableItemProviderTaskletRestartData) {
|
||||
moduleRestartData = (RestartableItemProviderTaskletRestartData) data;
|
||||
}
|
||||
else {
|
||||
moduleRestartData = new RestartableItemProviderTaskletRestartData(data.getProperties());
|
||||
}
|
||||
|
||||
if (itemProvider instanceof Restartable) {
|
||||
((Restartable) itemProvider).restoreFrom(moduleRestartData.providerData);
|
||||
}
|
||||
if (itemProcessor instanceof Restartable) {
|
||||
((Restartable) itemProcessor).restoreFrom(moduleRestartData.processorData);
|
||||
}
|
||||
}
|
||||
|
||||
private class RestartableItemProviderTaskletRestartData implements RestartData {
|
||||
|
||||
private static final String PROVIDER_KEY = "DATA_PROVIDER";
|
||||
|
||||
private static final String PROCESSOR_KEY = "DATA_PROCESSOR";
|
||||
|
||||
RestartData providerData;
|
||||
|
||||
RestartData processorData;
|
||||
|
||||
public RestartableItemProviderTaskletRestartData(RestartData providerData, RestartData processorData) {
|
||||
this.providerData = providerData;
|
||||
this.processorData = processorData;
|
||||
}
|
||||
|
||||
public RestartableItemProviderTaskletRestartData(Properties data) {
|
||||
providerData = new GenericRestartData(PropertiesConverter
|
||||
.stringToProperties(data.getProperty(PROVIDER_KEY)));
|
||||
processorData = new GenericRestartData(PropertiesConverter.stringToProperties(data
|
||||
.getProperty(PROCESSOR_KEY)));
|
||||
}
|
||||
|
||||
public Properties getProperties() {
|
||||
Properties props = new Properties();
|
||||
if (providerData != null) {
|
||||
props.setProperty(PROVIDER_KEY, PropertiesConverter.propertiesToString(providerData.getProperties()));
|
||||
}
|
||||
if (processorData != null) {
|
||||
props.setProperty(PROCESSOR_KEY, PropertiesConverter.propertiesToString(processorData.getProperties()));
|
||||
}
|
||||
return props;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Specific implementations of tasklet concerns.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,143 @@
|
||||
package org.springframework.batch.execution.tasklet.support;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.batch.restart.GenericRestartData;
|
||||
import org.springframework.batch.restart.RestartData;
|
||||
import org.springframework.batch.restart.Restartable;
|
||||
import org.springframework.batch.statistics.StatisticsProvider;
|
||||
|
||||
/**
|
||||
* Runs a collection of ItemProcessors in fixed-order sequence.
|
||||
*
|
||||
* @author Robert Kasanicky
|
||||
*/
|
||||
public class CompositeItemProcessor implements ItemProcessor, Restartable, StatisticsProvider {
|
||||
|
||||
private static final String SEPARATOR = "#";
|
||||
|
||||
private List itemProcessors;
|
||||
|
||||
/**
|
||||
* Calls injected ItemProcessors in order.
|
||||
*/
|
||||
public void process(Object data) throws Exception {
|
||||
for (Iterator iterator = itemProcessors.listIterator(); iterator.hasNext();) {
|
||||
((ItemProcessor) iterator.next()).process(data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compound restart data of all injected (Restartable) ItemProcessors, property keys are
|
||||
* prefixed with list index of the ItemProcessor.
|
||||
*/
|
||||
public RestartData getRestartData() {
|
||||
Properties props = createCompoundProperties(new PropertiesExtractor() {
|
||||
public Properties extractProperties(Object o) {
|
||||
if (o instanceof Restartable) {
|
||||
return ((Restartable)o).getRestartData().getProperties();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
});
|
||||
return new GenericRestartData(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param data contains values of restart data, property keys are expected to be prefixed with
|
||||
* list index of the ItemProcessor.
|
||||
*/
|
||||
public void restoreFrom(RestartData data) {
|
||||
if (data == null || data.getProperties() == null) {
|
||||
// do nothing
|
||||
return;
|
||||
}
|
||||
|
||||
List restartDataList = parseProperties(data.getProperties());
|
||||
|
||||
// iterators would make the loop below less readable
|
||||
for (int i=0; i < itemProcessors.size(); i++) {
|
||||
if (itemProcessors.get(i) instanceof Restartable) {
|
||||
((Restartable) itemProcessors.get(i)).restoreFrom((RestartData) restartDataList.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Properties containing statistics of all injected ItemProcessors,
|
||||
* property keys are prefixed with the list index of the ItemProcessor.
|
||||
*/
|
||||
public Properties getStatistics() {
|
||||
return createCompoundProperties(new PropertiesExtractor() {
|
||||
public Properties extractProperties(Object o) {
|
||||
if (o instanceof StatisticsProvider){
|
||||
return ((StatisticsProvider) o).getStatistics();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setItemProcessors(List itemProcessors) {
|
||||
this.itemProcessors = itemProcessors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses compound properties into a list of RestartData.
|
||||
*/
|
||||
private List parseProperties(Properties props) {
|
||||
List restartDataList = new ArrayList(itemProcessors.size());
|
||||
for (int i = 0; i<itemProcessors.size(); i++) {
|
||||
restartDataList.add(new GenericRestartData(new Properties()));
|
||||
}
|
||||
|
||||
for (Iterator iterator = props.entrySet().iterator(); iterator.hasNext();) {
|
||||
Map.Entry entry = (Map.Entry) iterator.next();
|
||||
String key = (String) entry.getKey();
|
||||
String value = (String) entry.getValue();
|
||||
int separatorIndex = key.indexOf(SEPARATOR);
|
||||
int i = Integer.valueOf(key.substring(0, separatorIndex)).intValue();
|
||||
((RestartData)restartDataList.get(i)).getProperties().setProperty(
|
||||
key.substring(separatorIndex + 1), value);
|
||||
}
|
||||
return restartDataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param extractor used to extract Properties from ItemProviders
|
||||
* @return compound Properties containing all the Properties from injected ItemProcessors
|
||||
* with property keys prefixed by list index.
|
||||
*/
|
||||
private Properties createCompoundProperties(PropertiesExtractor extractor) {
|
||||
Properties stats = new Properties();
|
||||
int index = 0;
|
||||
for (Iterator iterator = itemProcessors.listIterator(); iterator.hasNext();) {
|
||||
ItemProcessor processor = (ItemProcessor) iterator.next();
|
||||
Properties processorStats = extractor.extractProperties(processor);
|
||||
if (processorStats != null) {
|
||||
for (Iterator iterator2 = processorStats.entrySet().iterator(); iterator2.hasNext();) {
|
||||
Map.Entry entry = (Map.Entry) iterator2.next();
|
||||
stats.setProperty("" + index + SEPARATOR + entry.getKey(), (String) entry.getValue());
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts information from given object in the form of {@link Properties}. If the information
|
||||
* is not available (e.g. unexpected object class) return null.
|
||||
*/
|
||||
private interface PropertiesExtractor {
|
||||
Properties extractProperties(Object o);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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.support;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.batch.io.file.FieldSet;
|
||||
import org.springframework.batch.io.file.FieldSetMapper;
|
||||
import org.springframework.batch.item.provider.AbstractFieldSetItemProvider;
|
||||
import org.springframework.batch.item.validator.Validator;
|
||||
import org.springframework.batch.restart.RestartData;
|
||||
import org.springframework.batch.restart.Restartable;
|
||||
import org.springframework.batch.statistics.StatisticsProvider;
|
||||
|
||||
/**
|
||||
*
|
||||
* Uses a {@link FieldSetMapper} to convert each line from an input source. Also
|
||||
* adds {@link Restartable} as mandatory behaviour, delegating to the parent
|
||||
* provider's input source.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class DefaultFlatFileItemProvider extends AbstractFieldSetItemProvider implements Restartable,
|
||||
StatisticsProvider {
|
||||
|
||||
private FieldSetMapper mapper;
|
||||
private Validator validator;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.item.provider.AbstractFieldSetItemProvider#doNext(org.springframework.batch.io.line.FieldSet)
|
||||
*/
|
||||
protected Object transform(FieldSet fieldSet) {
|
||||
Object value = mapper.mapLine(fieldSet);
|
||||
if (validator!=null) {
|
||||
validator.validate(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mapper the mapper to set
|
||||
*/
|
||||
public void setMapper(FieldSetMapper mapper) {
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param validator the validator to set
|
||||
*/
|
||||
public void setValidator(Validator validator) {
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Restartable#getRestartData()
|
||||
* @throws IllegalStateException if the parent template is not itself
|
||||
* {@link Restartable}.
|
||||
*/
|
||||
public RestartData getRestartData() {
|
||||
if (!(source instanceof Restartable)) {
|
||||
throw new IllegalStateException("Input Template is not Restartable");
|
||||
}
|
||||
return ((Restartable) source).getRestartData();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Restartable#restoreFrom(RestartData)
|
||||
* @throws IllegalStateException if the parent template is not itself
|
||||
* {@link Restartable}.
|
||||
*/
|
||||
public void restoreFrom(RestartData data) {
|
||||
if (!(source instanceof Restartable)) {
|
||||
throw new IllegalStateException("Input Template is not Restartable");
|
||||
}
|
||||
((Restartable) source).restoreFrom(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return delegates to the parent template of it is a
|
||||
* {@link StatisticsProvider}, otherwise returns an empty
|
||||
* {@link Properties} instance.
|
||||
* @see StatisticsProvider#getStatistics()
|
||||
*/
|
||||
public Properties getStatistics() {
|
||||
if (!(source instanceof StatisticsProvider)) {
|
||||
return new Properties();
|
||||
}
|
||||
return ((StatisticsProvider) source).getStatistics();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.support;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.batch.io.InputSource;
|
||||
import org.springframework.batch.item.provider.AbstractItemProvider;
|
||||
import org.springframework.batch.restart.RestartData;
|
||||
import org.springframework.batch.restart.Restartable;
|
||||
import org.springframework.batch.statistics.StatisticsProvider;
|
||||
|
||||
/**
|
||||
* Simple wrapper around {@link InputSource}. The input source is expected to
|
||||
* take care of open and close operations. If necessary it should be registered
|
||||
* as a step scoped bean to ensure that the lifecycle methods are called.
|
||||
*
|
||||
* @auther Dave Syer
|
||||
*/
|
||||
public class InputSourceItemProvider extends AbstractItemProvider implements Restartable, StatisticsProvider {
|
||||
|
||||
private InputSource source;
|
||||
|
||||
/**
|
||||
* Get the next object from the input source.
|
||||
* @see org.springframework.batch.item.ItemProvider#next()
|
||||
*/
|
||||
public Object next() {
|
||||
Object value = source.read();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Restartable#getRestartData()
|
||||
* @throws IllegalStateException if the parent template is not itself
|
||||
* {@link Restartable}.
|
||||
*/
|
||||
public RestartData getRestartData() {
|
||||
if (!(source instanceof Restartable)) {
|
||||
throw new IllegalStateException("Input Template is not Restartable");
|
||||
}
|
||||
return ((Restartable) source).getRestartData();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Restartable#restoreFrom(RestartData)
|
||||
* @throws IllegalStateException if the parent template is not itself
|
||||
* {@link Restartable}.
|
||||
*/
|
||||
public void restoreFrom(RestartData data) {
|
||||
if (!(source instanceof Restartable)) {
|
||||
throw new IllegalStateException("Input Template is not Restartable");
|
||||
}
|
||||
((Restartable) source).restoreFrom(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return delegates to the parent template of it is a
|
||||
* {@link StatisticsProvider}, otherwise returns an empty
|
||||
* {@link Properties} instance.
|
||||
* @see StatisticsProvider#getStatistics()
|
||||
*/
|
||||
public Properties getStatistics() {
|
||||
if (!(source instanceof StatisticsProvider)) {
|
||||
return new Properties();
|
||||
}
|
||||
return ((StatisticsProvider) source).getStatistics();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for input source.
|
||||
* @param source
|
||||
*/
|
||||
public void setInputSource(InputSource source) {
|
||||
this.source = source;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.springframework.batch.execution.tasklet.support;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.batch.io.OutputSource;
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.batch.restart.RestartData;
|
||||
import org.springframework.batch.restart.Restartable;
|
||||
import org.springframework.batch.statistics.StatisticsProvider;
|
||||
|
||||
/**
|
||||
* Simple wrapper around {@link OutputSource} providing {@link Restartable} and
|
||||
* {@link StatisticsProvider} where the {@link OutputSource} does.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class OutputSourceItemProcessor implements ItemProcessor, Restartable,
|
||||
StatisticsProvider {
|
||||
|
||||
private OutputSource source;
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.item.ItemProcessor#process(java.lang.Object)
|
||||
*/
|
||||
public void process(Object data) throws Exception {
|
||||
source.write(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for output source.
|
||||
*
|
||||
* @param source
|
||||
*/
|
||||
public void setOutputSource(OutputSource source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Restartable#getRestartData()
|
||||
* @throws IllegalStateException if the parent template is not itself
|
||||
* {@link Restartable}.
|
||||
*/
|
||||
public RestartData getRestartData() {
|
||||
if (!(source instanceof Restartable)) {
|
||||
throw new IllegalStateException("Output Source is not Restartable");
|
||||
}
|
||||
return ((Restartable) source).getRestartData();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Restartable#restoreFrom(RestartData)
|
||||
* @throws IllegalStateException if the parent template is not itself
|
||||
* {@link Restartable}.
|
||||
*/
|
||||
public void restoreFrom(RestartData data) {
|
||||
if (!(source instanceof Restartable)) {
|
||||
throw new IllegalStateException("Output Source is not Restartable");
|
||||
}
|
||||
((Restartable) source).restoreFrom(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return delegates to the parent template of it is a
|
||||
* {@link StatisticsProvider}, otherwise returns an empty
|
||||
* {@link Properties} instance.
|
||||
* @see StatisticsProvider#getStatistics()
|
||||
*/
|
||||
public Properties getStatistics() {
|
||||
if (!(source instanceof StatisticsProvider)) {
|
||||
return new Properties();
|
||||
}
|
||||
return ((StatisticsProvider) source).getStatistics();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Specific implementations of support concerns.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
9
execution/src/main/java/overview.html
Normal file
9
execution/src/main/java/overview.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Reference implementations of the Core API. Provides basic support for
|
||||
runtime environments like JMX monitors and command-line launchers for
|
||||
batch processes.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user