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>
|
||||
9
execution/src/main/resources/batch.template.properties
Normal file
9
execution/src/main/resources/batch.template.properties
Normal file
@@ -0,0 +1,9 @@
|
||||
batch.jdbc.driver=
|
||||
batch.jdbc.url=
|
||||
batch.jdbc.user=
|
||||
batch.jdbc.password=
|
||||
batch.schema=
|
||||
batch.jndi.name=
|
||||
batch.naming.factory.initial=
|
||||
batch.naming.provider.url=
|
||||
batch.database.vendor=
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE hibernate-mapping PUBLIC
|
||||
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
|
||||
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" [
|
||||
<!ENTITY % globals SYSTEM "classpath://org/springframework/batch/execution/repository/dao/globals.dtd">
|
||||
%globals;
|
||||
]>
|
||||
<hibernate-mapping package="org.springframework.batch.core.domain">
|
||||
|
||||
<class name="JobExecution" table="BATCH_JOB_EXECUTION">
|
||||
<id name="id" type="long" column="ID">
|
||||
&job-execution-generator;
|
||||
</id>
|
||||
<version name="version" access="field"/>
|
||||
<property name="jobId" type="long" update="false" column="JOB_ID" />
|
||||
<property name="startTime" column="START_TIME" />
|
||||
<property name="endTime" column="END_TIME" />
|
||||
<property name="status" type="org.springframework.batch.execution.repository.dao.BatchStatusUserType" column="STATUS" />
|
||||
<property name="exitCode" column="EXIT_CODE" />
|
||||
</class>
|
||||
|
||||
</hibernate-mapping>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE hibernate-mapping PUBLIC
|
||||
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
|
||||
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" [
|
||||
<!ENTITY % globals SYSTEM "classpath://org/springframework/batch/execution/repository/dao/globals.dtd">
|
||||
%globals;
|
||||
]>
|
||||
<hibernate-mapping package="org.springframework.batch.core.domain">
|
||||
|
||||
<class name="JobInstance" table="BATCH_JOB">
|
||||
<id name="id" type="long" column="ID">
|
||||
&job-generator;
|
||||
</id>
|
||||
<version name="version" access="field" />
|
||||
<component name="identifier" class="org.springframework.batch.execution.runtime.ScheduledJobIdentifier">
|
||||
<property name="name" update="false" column="JOB_NAME" />
|
||||
<property name="jobStream" update="false" column="JOB_STREAM" />
|
||||
<property name="scheduleDate" update="false" column="SCHEDULE_DATE" />
|
||||
<property name="jobRun" update="false" column="JOB_RUN" />
|
||||
</component>
|
||||
<property name="status" type="org.springframework.batch.execution.repository.dao.BatchStatusUserType"
|
||||
insert="false" column="STATUS" />
|
||||
</class>
|
||||
|
||||
</hibernate-mapping>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE hibernate-mapping PUBLIC
|
||||
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
|
||||
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" [
|
||||
<!ENTITY % globals SYSTEM "classpath://org/springframework/batch/execution/repository/dao/globals.dtd">
|
||||
%globals;
|
||||
]>
|
||||
<hibernate-mapping package="org.springframework.batch.core.domain">
|
||||
|
||||
<class name="StepExecution" table="BATCH_STEP_EXECUTION">
|
||||
<id name="id" type="long" column="ID">
|
||||
&step-execution-generator;
|
||||
</id>
|
||||
<version name="version" access="field"/>
|
||||
<property name="stepId" type="long" update="false" column="STEP_ID" access="field"/>
|
||||
<property name="jobExecutionId" type="long" update="false" column="JOB_EXECUTION_ID" access="field"/>
|
||||
<property name="startTime" column="START_TIME" />
|
||||
<property name="endTime" column="END_TIME" />
|
||||
<property name="status" type="org.springframework.batch.execution.repository.dao.BatchStatusUserType" column="STATUS" />
|
||||
<property name="commitCount" column="COMMIT_COUNT" />
|
||||
<property name="taskCount" column="TASK_COUNT" />
|
||||
<property name="statistics" type="org.springframework.batch.execution.repository.dao.PropertiesUserType" column="TASK_STATISTICS" />
|
||||
<property name="exitCode" column="EXIT_CODE" />
|
||||
</class>
|
||||
|
||||
</hibernate-mapping>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE hibernate-mapping PUBLIC
|
||||
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
|
||||
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" [
|
||||
<!ENTITY % globals SYSTEM "classpath://org/springframework/batch/execution/repository/dao/globals.dtd">
|
||||
%globals;
|
||||
]>
|
||||
|
||||
<hibernate-mapping package="org.springframework.batch.core.domain">
|
||||
<class name="StepInstance" table="BATCH_STEP">
|
||||
<id name="id" type="long" column="ID">
|
||||
&step-generator;
|
||||
</id>
|
||||
<version name="version" access="field"/>
|
||||
<many-to-one name="job" update="false" column="JOB_ID" />
|
||||
<property name="name" update="false" column="STEP_NAME" />
|
||||
<property name="restartData"
|
||||
type="org.springframework.batch.execution.repository.dao.RestartDataUserType" insert="false"
|
||||
column="RESTART_DATA" />
|
||||
<property name="status" type="org.springframework.batch.execution.repository.dao.BatchStatusUserType"
|
||||
insert="false" column="STATUS" />
|
||||
</class>
|
||||
</hibernate-mapping>
|
||||
@@ -0,0 +1,20 @@
|
||||
<!-- To switch between native and sequence generators, switch the IGNORE/INCLUDE -->
|
||||
<!ENTITY % native 'INCLUDE'>
|
||||
<!ENTITY % sequence 'IGNORE'>
|
||||
|
||||
<![%native;[
|
||||
<!ENTITY % native-generator '<generator class="native" />'>
|
||||
<!ENTITY job-generator "%native-generator;">
|
||||
<!ENTITY job-execution-generator "%native-generator;">
|
||||
<!ENTITY step-generator "%native-generator;">
|
||||
<!ENTITY step-execution-generator "%native-generator;">
|
||||
]]>
|
||||
|
||||
<![%sequence;[
|
||||
<!ENTITY % open '<generator class="sequence"><param name="sequence">'>
|
||||
<!ENTITY % close '</param></generator>'>
|
||||
<!ENTITY job-generator "%open;BATCH_JOB_SEQ%close;">
|
||||
<!ENTITY job-execution-generator "%open;BATCH_JOB_EXECUTION_SEQ%close;">
|
||||
<!ENTITY step-generator "%open;BATCH_STEP_SEQ%close;">
|
||||
<!ENTITY step-execution-generator "%open;BATCH_STEP_EXECUTION_SEQ%close;">
|
||||
]]>
|
||||
56
execution/src/main/resources/schema-db2.sql
Normal file
56
execution/src/main/resources/schema-db2.sql
Normal file
@@ -0,0 +1,56 @@
|
||||
-- Autogenerated: do not edit this file
|
||||
DROP TABLE BATCH_STEP_EXECUTION ;
|
||||
DROP TABLE BATCH_JOB_EXECUTION ;
|
||||
DROP TABLE BATCH_STEP ;
|
||||
DROP TABLE BATCH_JOB ;
|
||||
|
||||
DROP SEQUENCE BATCH_STEP_EXECUTION_SEQ ;
|
||||
DROP SEQUENCE BATCH_STEP_SEQ ;
|
||||
DROP SEQUENCE BATCH_JOB_EXECUTION_SEQ ;
|
||||
DROP SEQUENCE BATCH_JOB_SEQ ;
|
||||
|
||||
-- Autogenerated: do not edit this file
|
||||
CREATE TABLE BATCH_JOB (
|
||||
ID BIGINT PRIMARY KEY ,
|
||||
VERSION BIGINT,
|
||||
JOB_NAME VARCHAR(100) NOT NULL ,
|
||||
JOB_STREAM VARCHAR(20) ,
|
||||
SCHEDULE_DATE DATE ,
|
||||
JOB_RUN CHAR(2),
|
||||
STATUS VARCHAR(10) );
|
||||
|
||||
CREATE TABLE BATCH_JOB_EXECUTION (
|
||||
ID BIGINT PRIMARY KEY ,
|
||||
VERSION BIGINT,
|
||||
JOB_ID BIGINT NOT NULL,
|
||||
START_TIME TIMESTAMP NOT NULL ,
|
||||
END_TIME TIMESTAMP ,
|
||||
STATUS VARCHAR(10),
|
||||
EXIT_CODE BIGINT);
|
||||
|
||||
CREATE TABLE BATCH_STEP (
|
||||
ID BIGINT PRIMARY KEY ,
|
||||
VERSION BIGINT,
|
||||
JOB_ID BIGINT NOT NULL,
|
||||
STEP_NAME VARCHAR(100) NOT NULL,
|
||||
STATUS VARCHAR(10),
|
||||
RESTART_DATA VARCHAR(200));
|
||||
|
||||
CREATE TABLE BATCH_STEP_EXECUTION (
|
||||
ID BIGINT PRIMARY KEY ,
|
||||
VERSION BIGINT NOT NULL,
|
||||
STEP_ID BIGINT NOT NULL,
|
||||
JOB_EXECUTION_ID BIGINT NOT NULL,
|
||||
START_TIME TIMESTAMP NOT NULL ,
|
||||
END_TIME TIMESTAMP ,
|
||||
STATUS VARCHAR(10),
|
||||
COMMIT_COUNT BIGINT ,
|
||||
TASK_COUNT BIGINT ,
|
||||
TASK_STATISTICS VARCHAR(250),
|
||||
EXIT_CODE BIGINT,
|
||||
EXIT_MESSAGE VARCHAR(250));
|
||||
|
||||
CREATE SEQUENCE BATCH_STEP_EXECUTION_SEQ;
|
||||
CREATE SEQUENCE BATCH_STEP_SEQ;
|
||||
CREATE SEQUENCE BATCH_JOB_EXECUTION_SEQ;
|
||||
CREATE SEQUENCE BATCH_JOB_SEQ;
|
||||
56
execution/src/main/resources/schema-derby.sql
Normal file
56
execution/src/main/resources/schema-derby.sql
Normal file
@@ -0,0 +1,56 @@
|
||||
-- Autogenerated: do not edit this file
|
||||
DROP TABLE BATCH_STEP_EXECUTION ;
|
||||
DROP TABLE BATCH_JOB_EXECUTION ;
|
||||
DROP TABLE BATCH_STEP ;
|
||||
DROP TABLE BATCH_JOB ;
|
||||
|
||||
DROP SEQUENCE BATCH_STEP_EXECUTION_SEQ ;
|
||||
DROP SEQUENCE BATCH_STEP_SEQ ;
|
||||
DROP SEQUENCE BATCH_JOB_EXECUTION_SEQ ;
|
||||
DROP SEQUENCE BATCH_JOB_SEQ ;
|
||||
|
||||
-- Autogenerated: do not edit this file
|
||||
CREATE TABLE BATCH_JOB (
|
||||
ID BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
|
||||
VERSION BIGINT,
|
||||
JOB_NAME VARCHAR(100) NOT NULL ,
|
||||
JOB_STREAM VARCHAR(20) ,
|
||||
SCHEDULE_DATE DATE ,
|
||||
JOB_RUN CHAR(2),
|
||||
STATUS VARCHAR(10) );
|
||||
|
||||
CREATE TABLE BATCH_JOB_EXECUTION (
|
||||
ID BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
|
||||
VERSION BIGINT,
|
||||
JOB_ID BIGINT NOT NULL,
|
||||
START_TIME TIMESTAMP NOT NULL ,
|
||||
END_TIME TIMESTAMP ,
|
||||
STATUS VARCHAR(10),
|
||||
EXIT_CODE BIGINT);
|
||||
|
||||
CREATE TABLE BATCH_STEP (
|
||||
ID BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
|
||||
VERSION BIGINT,
|
||||
JOB_ID BIGINT NOT NULL,
|
||||
STEP_NAME VARCHAR(100) NOT NULL,
|
||||
STATUS VARCHAR(10),
|
||||
RESTART_DATA VARCHAR(200));
|
||||
|
||||
CREATE TABLE BATCH_STEP_EXECUTION (
|
||||
ID BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
|
||||
VERSION BIGINT NOT NULL,
|
||||
STEP_ID BIGINT NOT NULL,
|
||||
JOB_EXECUTION_ID BIGINT NOT NULL,
|
||||
START_TIME TIMESTAMP NOT NULL ,
|
||||
END_TIME TIMESTAMP ,
|
||||
STATUS VARCHAR(10),
|
||||
COMMIT_COUNT BIGINT ,
|
||||
TASK_COUNT BIGINT ,
|
||||
TASK_STATISTICS VARCHAR(250),
|
||||
EXIT_CODE BIGINT,
|
||||
EXIT_MESSAGE VARCHAR(250));
|
||||
|
||||
CREATE SEQUENCE BATCH_STEP_EXECUTION_SEQ;
|
||||
CREATE SEQUENCE BATCH_STEP_SEQ;
|
||||
CREATE SEQUENCE BATCH_JOB_EXECUTION_SEQ;
|
||||
CREATE SEQUENCE BATCH_JOB_SEQ;
|
||||
64
execution/src/main/resources/schema-hsqldb.sql
Normal file
64
execution/src/main/resources/schema-hsqldb.sql
Normal file
@@ -0,0 +1,64 @@
|
||||
-- Autogenerated: do not edit this file
|
||||
DROP TABLE BATCH_STEP_EXECUTION IF EXISTS;
|
||||
DROP TABLE BATCH_JOB_EXECUTION IF EXISTS;
|
||||
DROP TABLE BATCH_STEP IF EXISTS;
|
||||
DROP TABLE BATCH_JOB IF EXISTS;
|
||||
|
||||
DROP TABLE BATCH_STEP_EXECUTION_SEQ IF EXISTS;
|
||||
DROP TABLE BATCH_STEP_SEQ IF EXISTS;
|
||||
DROP TABLE BATCH_JOB_EXECUTION_SEQ IF EXISTS;
|
||||
DROP TABLE BATCH_JOB_SEQ IF EXISTS;
|
||||
|
||||
-- Autogenerated: do not edit this file
|
||||
CREATE TABLE BATCH_JOB (
|
||||
ID BIGINT IDENTITY PRIMARY KEY ,
|
||||
VERSION BIGINT,
|
||||
JOB_NAME VARCHAR(100) NOT NULL ,
|
||||
JOB_STREAM VARCHAR(20) ,
|
||||
SCHEDULE_DATE DATE ,
|
||||
JOB_RUN CHAR(2),
|
||||
STATUS VARCHAR(10) );
|
||||
|
||||
CREATE TABLE BATCH_JOB_EXECUTION (
|
||||
ID BIGINT IDENTITY PRIMARY KEY ,
|
||||
VERSION BIGINT,
|
||||
JOB_ID BIGINT NOT NULL,
|
||||
START_TIME TIMESTAMP NOT NULL ,
|
||||
END_TIME TIMESTAMP ,
|
||||
STATUS VARCHAR(10),
|
||||
EXIT_CODE BIGINT);
|
||||
|
||||
CREATE TABLE BATCH_STEP (
|
||||
ID BIGINT IDENTITY PRIMARY KEY ,
|
||||
VERSION BIGINT,
|
||||
JOB_ID BIGINT NOT NULL,
|
||||
STEP_NAME VARCHAR(100) NOT NULL,
|
||||
STATUS VARCHAR(10),
|
||||
RESTART_DATA VARCHAR(200));
|
||||
|
||||
CREATE TABLE BATCH_STEP_EXECUTION (
|
||||
ID BIGINT IDENTITY PRIMARY KEY ,
|
||||
VERSION BIGINT NOT NULL,
|
||||
STEP_ID BIGINT NOT NULL,
|
||||
JOB_EXECUTION_ID BIGINT NOT NULL,
|
||||
START_TIME TIMESTAMP NOT NULL ,
|
||||
END_TIME TIMESTAMP ,
|
||||
STATUS VARCHAR(10),
|
||||
COMMIT_COUNT BIGINT ,
|
||||
TASK_COUNT BIGINT ,
|
||||
TASK_STATISTICS VARCHAR(250),
|
||||
EXIT_CODE BIGINT,
|
||||
EXIT_MESSAGE VARCHAR(250));
|
||||
|
||||
CREATE TABLE BATCH_STEP_EXECUTION_SEQ (
|
||||
ID BIGINT IDENTITY
|
||||
);
|
||||
CREATE TABLE BATCH_STEP_SEQ (
|
||||
ID BIGINT IDENTITY
|
||||
);
|
||||
CREATE TABLE BATCH_JOB_EXECUTION_SEQ (
|
||||
ID BIGINT IDENTITY
|
||||
);
|
||||
CREATE TABLE BATCH_JOB_SEQ (
|
||||
ID BIGINT IDENTITY
|
||||
);
|
||||
56
execution/src/main/resources/schema-oracle10g.sql
Normal file
56
execution/src/main/resources/schema-oracle10g.sql
Normal file
@@ -0,0 +1,56 @@
|
||||
-- Autogenerated: do not edit this file
|
||||
DROP TABLE BATCH_STEP_EXECUTION ;
|
||||
DROP TABLE BATCH_JOB_EXECUTION ;
|
||||
DROP TABLE BATCH_STEP ;
|
||||
DROP TABLE BATCH_JOB ;
|
||||
|
||||
DROP SEQUENCE BATCH_STEP_EXECUTION_SEQ ;
|
||||
DROP SEQUENCE BATCH_STEP_SEQ ;
|
||||
DROP SEQUENCE BATCH_JOB_EXECUTION_SEQ ;
|
||||
DROP SEQUENCE BATCH_JOB_SEQ ;
|
||||
|
||||
-- Autogenerated: do not edit this file
|
||||
CREATE TABLE BATCH_JOB (
|
||||
ID INT PRIMARY KEY ,
|
||||
VERSION INT,
|
||||
JOB_NAME VARCHAR(100) NOT NULL ,
|
||||
JOB_STREAM VARCHAR(20) ,
|
||||
SCHEDULE_DATE DATE ,
|
||||
JOB_RUN CHAR(2),
|
||||
STATUS VARCHAR(10) );
|
||||
|
||||
CREATE TABLE BATCH_JOB_EXECUTION (
|
||||
ID INT PRIMARY KEY ,
|
||||
VERSION INT,
|
||||
JOB_ID INT NOT NULL,
|
||||
START_TIME TIMESTAMP NOT NULL ,
|
||||
END_TIME TIMESTAMP ,
|
||||
STATUS VARCHAR(10),
|
||||
EXIT_CODE INT);
|
||||
|
||||
CREATE TABLE BATCH_STEP (
|
||||
ID INT PRIMARY KEY ,
|
||||
VERSION INT,
|
||||
JOB_ID INT NOT NULL,
|
||||
STEP_NAME VARCHAR(100) NOT NULL,
|
||||
STATUS VARCHAR(10),
|
||||
RESTART_DATA VARCHAR(200));
|
||||
|
||||
CREATE TABLE BATCH_STEP_EXECUTION (
|
||||
ID INT PRIMARY KEY ,
|
||||
VERSION INT NOT NULL,
|
||||
STEP_ID INT NOT NULL,
|
||||
JOB_EXECUTION_ID INT NOT NULL,
|
||||
START_TIME TIMESTAMP NOT NULL ,
|
||||
END_TIME TIMESTAMP ,
|
||||
STATUS VARCHAR(10),
|
||||
COMMIT_COUNT INT ,
|
||||
TASK_COUNT INT ,
|
||||
TASK_STATISTICS VARCHAR(250),
|
||||
EXIT_CODE INT,
|
||||
EXIT_MESSAGE VARCHAR(250));
|
||||
|
||||
CREATE SEQUENCE BATCH_STEP_EXECUTION_SEQ;
|
||||
CREATE SEQUENCE BATCH_STEP_SEQ;
|
||||
CREATE SEQUENCE BATCH_JOB_EXECUTION_SEQ;
|
||||
CREATE SEQUENCE BATCH_JOB_SEQ;
|
||||
56
execution/src/main/resources/schema-postgresql.sql
Normal file
56
execution/src/main/resources/schema-postgresql.sql
Normal file
@@ -0,0 +1,56 @@
|
||||
-- Autogenerated: do not edit this file
|
||||
DROP TABLE BATCH_STEP_EXECUTION ;
|
||||
DROP TABLE BATCH_JOB_EXECUTION ;
|
||||
DROP TABLE BATCH_STEP ;
|
||||
DROP TABLE BATCH_JOB ;
|
||||
|
||||
DROP SEQUENCE BATCH_STEP_EXECUTION_SEQ ;
|
||||
DROP SEQUENCE BATCH_STEP_SEQ ;
|
||||
DROP SEQUENCE BATCH_JOB_EXECUTION_SEQ ;
|
||||
DROP SEQUENCE BATCH_JOB_SEQ ;
|
||||
|
||||
-- Autogenerated: do not edit this file
|
||||
CREATE TABLE BATCH_JOB (
|
||||
ID BIGINT PRIMARY KEY ,
|
||||
VERSION BIGINT,
|
||||
JOB_NAME VARCHAR(100) NOT NULL ,
|
||||
JOB_STREAM VARCHAR(20) ,
|
||||
SCHEDULE_DATE DATE ,
|
||||
JOB_RUN CHAR(2),
|
||||
STATUS VARCHAR(10) );
|
||||
|
||||
CREATE TABLE BATCH_JOB_EXECUTION (
|
||||
ID BIGINT PRIMARY KEY ,
|
||||
VERSION BIGINT,
|
||||
JOB_ID BIGINT NOT NULL,
|
||||
START_TIME TIMESTAMP NOT NULL ,
|
||||
END_TIME TIMESTAMP ,
|
||||
STATUS VARCHAR(10),
|
||||
EXIT_CODE BIGINT);
|
||||
|
||||
CREATE TABLE BATCH_STEP (
|
||||
ID BIGINT PRIMARY KEY ,
|
||||
VERSION BIGINT,
|
||||
JOB_ID BIGINT NOT NULL,
|
||||
STEP_NAME VARCHAR(100) NOT NULL,
|
||||
STATUS VARCHAR(10),
|
||||
RESTART_DATA VARCHAR(200));
|
||||
|
||||
CREATE TABLE BATCH_STEP_EXECUTION (
|
||||
ID BIGINT PRIMARY KEY ,
|
||||
VERSION BIGINT NOT NULL,
|
||||
STEP_ID BIGINT NOT NULL,
|
||||
JOB_EXECUTION_ID BIGINT NOT NULL,
|
||||
START_TIME TIMESTAMP NOT NULL ,
|
||||
END_TIME TIMESTAMP ,
|
||||
STATUS VARCHAR(10),
|
||||
COMMIT_COUNT BIGINT ,
|
||||
TASK_COUNT BIGINT ,
|
||||
TASK_STATISTICS VARCHAR(250),
|
||||
EXIT_CODE BIGINT,
|
||||
EXIT_MESSAGE VARCHAR(250));
|
||||
|
||||
CREATE SEQUENCE BATCH_STEP_EXECUTION_SEQ;
|
||||
CREATE SEQUENCE BATCH_STEP_SEQ;
|
||||
CREATE SEQUENCE BATCH_JOB_EXECUTION_SEQ;
|
||||
CREATE SEQUENCE BATCH_JOB_SEQ;
|
||||
6
execution/src/main/sql/db2.properties
Normal file
6
execution/src/main/sql/db2.properties
Normal file
@@ -0,0 +1,6 @@
|
||||
platform=db2
|
||||
# SQL language oddities
|
||||
BIGINT = BIGINT
|
||||
IDENTITY =
|
||||
# for generating drop statements...
|
||||
SEQUENCE = SEQUENCE
|
||||
2
execution/src/main/sql/db2.vpp
Normal file
2
execution/src/main/sql/db2.vpp
Normal file
@@ -0,0 +1,2 @@
|
||||
#macro (sequence $name)CREATE SEQUENCE ${name};
|
||||
#end
|
||||
7
execution/src/main/sql/derby.properties
Normal file
7
execution/src/main/sql/derby.properties
Normal file
@@ -0,0 +1,7 @@
|
||||
platform=db2
|
||||
# SQL language oddities
|
||||
BIGINT = BIGINT
|
||||
IDENTITY =
|
||||
GENERATED = GENERATED BY DEFAULT AS IDENTITY
|
||||
# for generating drop statements...
|
||||
SEQUENCE = SEQUENCE
|
||||
2
execution/src/main/sql/derby.vpp
Normal file
2
execution/src/main/sql/derby.vpp
Normal file
@@ -0,0 +1,2 @@
|
||||
#macro (sequence $name)CREATE SEQUENCE ${name};
|
||||
#end
|
||||
10
execution/src/main/sql/destroy.sql.vpp
Normal file
10
execution/src/main/sql/destroy.sql.vpp
Normal file
@@ -0,0 +1,10 @@
|
||||
-- Autogenerated: do not edit this file
|
||||
DROP TABLE BATCH_STEP_EXECUTION $!{IFEXISTS};
|
||||
DROP TABLE BATCH_JOB_EXECUTION $!{IFEXISTS};
|
||||
DROP TABLE BATCH_STEP $!{IFEXISTS};
|
||||
DROP TABLE BATCH_JOB $!{IFEXISTS};
|
||||
|
||||
DROP ${SEQUENCE} BATCH_STEP_EXECUTION_SEQ $!{IFEXISTS};
|
||||
DROP ${SEQUENCE} BATCH_STEP_SEQ $!{IFEXISTS};
|
||||
DROP ${SEQUENCE} BATCH_JOB_EXECUTION_SEQ $!{IFEXISTS};
|
||||
DROP ${SEQUENCE} BATCH_JOB_SEQ $!{IFEXISTS};
|
||||
7
execution/src/main/sql/hsqldb.properties
Normal file
7
execution/src/main/sql/hsqldb.properties
Normal file
@@ -0,0 +1,7 @@
|
||||
platform=hsqldb
|
||||
# SQL language oddities
|
||||
BIGINT = BIGINT
|
||||
IDENTITY = IDENTITY
|
||||
IFEXISTS = IF EXISTS
|
||||
# for generating drop statements...
|
||||
SEQUENCE = TABLE
|
||||
4
execution/src/main/sql/hsqldb.vpp
Normal file
4
execution/src/main/sql/hsqldb.vpp
Normal file
@@ -0,0 +1,4 @@
|
||||
#macro (sequence $name)CREATE TABLE ${name} (
|
||||
ID BIGINT IDENTITY
|
||||
);
|
||||
#end
|
||||
45
execution/src/main/sql/init.sql.vpp
Normal file
45
execution/src/main/sql/init.sql.vpp
Normal file
@@ -0,0 +1,45 @@
|
||||
-- Autogenerated: do not edit this file
|
||||
CREATE TABLE BATCH_JOB (
|
||||
ID ${BIGINT} $!{IDENTITY} PRIMARY KEY $!{GENERATED},
|
||||
VERSION ${BIGINT},
|
||||
JOB_NAME VARCHAR(100) NOT NULL ,
|
||||
JOB_STREAM VARCHAR(20) ,
|
||||
SCHEDULE_DATE DATE ,
|
||||
JOB_RUN CHAR(2),
|
||||
STATUS VARCHAR(10) );
|
||||
|
||||
CREATE TABLE BATCH_JOB_EXECUTION (
|
||||
ID ${BIGINT} $!{IDENTITY} PRIMARY KEY $!{GENERATED},
|
||||
VERSION ${BIGINT},
|
||||
JOB_ID ${BIGINT} NOT NULL,
|
||||
START_TIME TIMESTAMP NOT NULL ,
|
||||
END_TIME TIMESTAMP ,
|
||||
STATUS VARCHAR(10),
|
||||
EXIT_CODE ${BIGINT});
|
||||
|
||||
CREATE TABLE BATCH_STEP (
|
||||
ID ${BIGINT} $!{IDENTITY} PRIMARY KEY $!{GENERATED},
|
||||
VERSION ${BIGINT},
|
||||
JOB_ID ${BIGINT} NOT NULL,
|
||||
STEP_NAME VARCHAR(100) NOT NULL,
|
||||
STATUS VARCHAR(10),
|
||||
RESTART_DATA VARCHAR(200));
|
||||
|
||||
CREATE TABLE BATCH_STEP_EXECUTION (
|
||||
ID ${BIGINT} $!{IDENTITY} PRIMARY KEY $!{GENERATED},
|
||||
VERSION ${BIGINT} NOT NULL,
|
||||
STEP_ID ${BIGINT} NOT NULL,
|
||||
JOB_EXECUTION_ID ${BIGINT} NOT NULL,
|
||||
START_TIME TIMESTAMP NOT NULL ,
|
||||
END_TIME TIMESTAMP ,
|
||||
STATUS VARCHAR(10),
|
||||
COMMIT_COUNT ${BIGINT} ,
|
||||
TASK_COUNT ${BIGINT} ,
|
||||
TASK_STATISTICS VARCHAR(250),
|
||||
EXIT_CODE ${BIGINT},
|
||||
EXIT_MESSAGE VARCHAR(250));
|
||||
|
||||
#sequence( "BATCH_STEP_EXECUTION_SEQ" )
|
||||
#sequence( "BATCH_STEP_SEQ" )
|
||||
#sequence( "BATCH_JOB_EXECUTION_SEQ" )
|
||||
#sequence( "BATCH_JOB_SEQ" )
|
||||
7
execution/src/main/sql/oracle10g.properties
Normal file
7
execution/src/main/sql/oracle10g.properties
Normal file
@@ -0,0 +1,7 @@
|
||||
platform=oracle10g
|
||||
# SQL language oddities
|
||||
BIGINT = INT
|
||||
IDENTITY =
|
||||
GENERATED =
|
||||
# for generating drop statements...
|
||||
SEQUENCE = SEQUENCE
|
||||
2
execution/src/main/sql/oracle10g.vpp
Normal file
2
execution/src/main/sql/oracle10g.vpp
Normal file
@@ -0,0 +1,2 @@
|
||||
#macro (sequence $name)CREATE SEQUENCE ${name};
|
||||
#end
|
||||
7
execution/src/main/sql/postgresql.properties
Normal file
7
execution/src/main/sql/postgresql.properties
Normal file
@@ -0,0 +1,7 @@
|
||||
platform=postgresql
|
||||
# SQL language oddities
|
||||
BIGINT = BIGINT
|
||||
IDENTITY =
|
||||
GENERATED =
|
||||
# for generating drop statements...
|
||||
SEQUENCE = SEQUENCE
|
||||
2
execution/src/main/sql/postgresql.vpp
Normal file
2
execution/src/main/sql/postgresql.vpp
Normal file
@@ -0,0 +1,2 @@
|
||||
#macro (sequence $name)CREATE SEQUENCE ${name};
|
||||
#end
|
||||
4
execution/src/main/sql/schema.sql.vpp
Normal file
4
execution/src/main/sql/schema.sql.vpp
Normal file
@@ -0,0 +1,4 @@
|
||||
#parse("${includes}/destroy.sql.vpp")
|
||||
|
||||
|
||||
#parse("${includes}/init.sql.vpp")
|
||||
7
execution/src/site/apt/changelog.apt
Normal file
7
execution/src/site/apt/changelog.apt
Normal file
@@ -0,0 +1,7 @@
|
||||
Changelog: Spring Batch Execution
|
||||
|
||||
* 1.0-M2
|
||||
|
||||
** 2007/07/12
|
||||
|
||||
* No-one uses this file: we should just switch to auto-generated changelogs?
|
||||
38
execution/src/site/apt/executable.apt
Normal file
38
execution/src/site/apt/executable.apt
Normal file
@@ -0,0 +1,38 @@
|
||||
|
||||
Tried to create an executable jar with this:
|
||||
|
||||
+---
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>org.springframework.batch.container.bootstrap.BatchCommandLineLauncher</mainClass>
|
||||
<addClasspath>true</addClasspath>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
+---
|
||||
|
||||
But the resulting MANIFEST.MF is rubbish. Look at the classpath
|
||||
(where did that come from)?
|
||||
|
||||
+---
|
||||
Manifest-Version: 1.0
|
||||
Archiver-Version: Plexus Archiver
|
||||
Created-By: Apache Maven
|
||||
Built-By: dsyer
|
||||
Build-Jdk: 1.5.0_09
|
||||
Main-Class: org.springframework.batch.container.bootstrap.BatchCommand
|
||||
LineLauncher
|
||||
Class-Path: spring-2.1-m2.jar commons-logging-1.1.jar log4j-1.2.12.jar
|
||||
dom4j-1.6.1.jar commons-lang-2.1.jar spring-batch-infrastructure-1.0
|
||||
-m2-SNAPSHOT.jar antlr-2.7.6.jar commons-collections-2.1.1.jar hibern
|
||||
ate-3.2.3.ga.jar spring-mock-2.1-m2.jar ehcache-1.2.3.jar
|
||||
+---
|
||||
46
execution/src/site/apt/glossary.apt
Normal file
46
execution/src/site/apt/glossary.apt
Normal file
@@ -0,0 +1,46 @@
|
||||
------
|
||||
Glossary
|
||||
------
|
||||
Wayne Lund
|
||||
------
|
||||
May 2007
|
||||
|
||||
|
||||
[[1]]<<Batch>>: An accumulation of business transactions over time.
|
||||
|
||||
[[2]]<<Batch Application Style>>: Term used to designate batch as an application style in its own right similar to online, Web or SOA. It has standard elements of input, validation, transformation of information to business model, business processing and output. In addition, it requires monitoring at a macro level.
|
||||
|
||||
[[3]]<<Batch Processing>>: The handling of a batch of many business transactions that have accumulated over a period of time (e.g. an hour, day, week, month, or year). It is the application of a process, or set of processes, to many data entities or objects in a repetitive and predictable fashion with either no manual element, or a separate manual element for error processing.
|
||||
|
||||
[[4]]<<Batch Window>>: The time frame within which a batch job must complete. This can be constrained by other systems coming online, other dependent jobs needing to execute or other factors specific to the batch environment.
|
||||
|
||||
[[5]]<<Step Controller>>: It is the main batch task or Unit of Work controller. It initializes the module, and controls the transaction environment based on commit interval setting, etc.
|
||||
|
||||
[[6]]<<Tasklet>>: The main application program created by application developer to process the business logic for each LUW.
|
||||
|
||||
[[7]]<<Batch Job Type>>: Job Types describe application of jobs for particular type of processing. Common areas are interface processing (typically flat files), forms processing (either for online pdf generation or print formats), report processing. s
|
||||
|
||||
[[8]]<<Driving Query>>: A driving query identifies the set of work for a job to do; the job then breaks that work into individual units of work. For instance, identify all financial transactions that have a status of "pending transmission" and send them to our partner system. The driving query returns a set of record IDs to process; each record ID then becomes a unit of work. A driving query may involve a join (if the criteria for selection falls across two or more tables) or it may work with a single table.
|
||||
|
||||
[[9]]<<Logicial Unit of Work (LUW)>>: A batch job iterates through a driving query (or another input source such as a file) to perform the set of work that the job must accomplish. Each iteration of work performed is a unit of work.
|
||||
|
||||
[[10]]<<Commit Interval>>: A set of LUWs constitute a commit interval.
|
||||
|
||||
[[11]]<<Partitioning>>: Splitting a job into multiple threads where each thread is responsible for a subset of the overall data to be processed. The threads of execution may be within the same JVM or they may span JVMs in a clustered environment that supports workload balancing.
|
||||
|
||||
[[12]]<<Staging Table>>: A table that holds temporary data while it is being processed.
|
||||
|
||||
[[13]]<<Restartable>>: - a job that can be executed again and will assume the same identity as when run initially. In othewords, it is has the same job instance id.
|
||||
|
||||
[[14]]Rerunnable - a job that is restartable and manages it's own state in terms of previous run's record processing. Note>>: Rerunnable is tied to the driving query. If the query can be formed so that it will limit the processed rows when the job is restarted than re-runnable = true. Often times a condition is added to the where statement to limit the rows returned by the driving query with something like "and processedFlag != true".
|
||||
|
||||
---------------------------------------------------------------------
|
||||
Note: If its false the architecture assumes responsibility for tracking which rows have been processed. There is a default strategy for tracking the last record processed by partition. Most batch jobs only have one partition. The option is only valid for a restartable job. The reason being is that we have to persist the restart data which is only available on a restartable job.
|
||||
|
||||
In DSL it is the following:
|
||||
StartOver ::= restartable = false. Restartable ::= true | false
|
||||
If (Restartable)
|
||||
re-runnable ::= true | false
|
||||
|
||||
We don't persist restart information for a non-restartable job. As you can see, it doesn't make sense. Rerunnable has always confused the best of us.
|
||||
----------------------------------------------------------------------------
|
||||
61
execution/src/site/apt/index.apt
Normal file
61
execution/src/site/apt/index.apt
Normal file
@@ -0,0 +1,61 @@
|
||||
------
|
||||
Simple Batch Execution Container Overview
|
||||
------
|
||||
Scott Wintermute
|
||||
------
|
||||
May 2007
|
||||
|
||||
Overview of the Spring Batch Simple Batch Execution Container
|
||||
|
||||
The diagram below provides an overview of the high level components, technical services, and basic operations required by a batch architecture. This architecture framework is a blueprint that has been proven through decades of implementations on the last several generations of platforms (COBOL/Mainframe, C++/Unix, and now Java/anywhere). The Simple Batch Execution Container provides a physical implementation of the layers, components and technical services commonly found in robust, maintainable systems used to address the creation of simple to complex batch applications, with the infrastructure and extensions to address very complex processing needs. The materials below will walk through the details of the diagram.
|
||||
|
||||
[images/simple-batch-execution-container.jpg] Simple Batch Execution Container high level flow and interaction of the architecture.
|
||||
|
||||
Tiers
|
||||
The application style is organized into four logical tiers, which include Run, Job, Application, and Data tiers. The primary goal for organizing an application according to the tiers is to embed what is known as "separation of concerns" within the system. Effective separation of concerns results in reducing the impact of change to the system.
|
||||
|
||||
* <<Run Tier:>> The Run Tier is concerned with the scheduling and launching of the application. A vendor product is typically used in this tier to allow time-based and interdependent scheduling of batch jobs as well as providing parallel processing capabilities.
|
||||
|
||||
* <<Job Tier:>> The Job Tier is responsible for the overall execution of a batch job. It sequentially executes batch steps, ensuring that all steps are in the correct state and all appropriate policies are enforced.
|
||||
|
||||
* <<Application Tier:>> The Application Tier contains components required to execute the program. It contains specific modules that address the required batch functionality and enforces policies around a module execution (e.g., commit intervals, capture of statistics, etc.)
|
||||
|
||||
* <<Data Tier:>> The Data Tier provides the integration with the physical data sources that might include databases, files, or queues. <<Note>>: In some cases the Job tier can be completely missing and in other cases one Job Script can start several Batch Job instances.
|
||||
|
||||
High Level Processing Flow
|
||||
|
||||
The diagram above illustrates the flow and architecture components in a typical batch run execution.
|
||||
|
||||
Standard interaction is described as follows:
|
||||
|
||||
<<1.>> In the Run tier, a Scheduler starts a batch application by invoking a Job Script. The Scheduler identifies what batch process it wants to run by passing the name of the batch process and any required additional parameters to the Job Script.
|
||||
|
||||
<<2.>> The Job Script initializes the program and executes any job specific scripts prior to calling the Batch Launcher.
|
||||
|
||||
<<3.>> The Batch Launcher starts the Batch Execution Container based upon any environment settings established in the script. (NOTE: A Batch Execution Container is not a Java EE container)
|
||||
|
||||
<<3.1>> The Batch Container starts and controls the batch execution. It initializes the Job execution environment with static configuration items such as database settings, logging levels and creates a Job based on the Job Configuration created by a Batch Developer.
|
||||
|
||||
<<4>> Based on configuration provided by a Batch Developer, the Job sequentially executes steps after checking policies to ensure that each step should be started. The status of the job and step (start time, end time, status such as "started" or "completed") is stored at various points during the process.
|
||||
|
||||
<<5.1>> In order to maintain data integrity, at the application tier, the Step acts as a controller to ensure that either an entire group of actions completes successfully or that none of the actions completes. This group of actions is referred to as a logical unit of work (LUW). The Step controls the overall execution of the Tasklet, ensuring that transaction are committed at the appropriate time, and restart and statistics information is stored appropriately. The first thing the Step is responsible for is the initialization of the data required to begin processing. The Step will interact with other architecture components, such as the Input Source, to setup the data required to be processed.
|
||||
|
||||
<<5.1.1>> The Input Source provides services to access various data sources. It provides location transparency to the Batch Tasklet and hides the physical location details of the data.
|
||||
|
||||
<<5.2>> Once the data is initialized by the Input Source, the Step will call into the Tasklet to begin processing. The Tasklet contains the business logic to define the LUW and the Step repeatedly calls the Tasklets LUW to finish the business function. The Step does this by first invoking the execute method on the Tasklet in order to acquire a single record/set of data for processing.
|
||||
|
||||
<<5.2.1>> Before a record is returned to the Tasklet, it may be validated by any number of validation Frameworks that can be provided to an input source. A single record/set of data is gathered by interacting with the Input Source.
|
||||
|
||||
<<5.3>> Once a record/set has been obtained, the step calls the module to begin processing.
|
||||
|
||||
<<5.3.1>> The Tasklet executes its internal business logic by calling other Business Logic components as necessary. Based on the business service, it can requests or persists objects from the data access components.
|
||||
|
||||
<<5.3.3>> Data Access components can be leveraged retrieve or persist domain objects.
|
||||
|
||||
<<5.3.4>> Once the business logic has been executed, the resulting output record is written out by utilizing the Output Source interface. The Step will repeatedly call steps 4.2 \-> 4.4 for every record provided by the Input Source.
|
||||
|
||||
<<5.4>> Once all of the records are processed, the Step calls the Tasklet to perform any clean up activities such as closing connections, exporting files, etc.
|
||||
|
||||
<<5.4.1>> The Step is responsible for committing data associated with the remaining logical units of work as well as performing any finalization and administrative functions (e.g. closing database connections).
|
||||
|
||||
Once the Step has completed finalization the control is passed back to the Job, where any necessary logging or clean up is executed for application termination and wrap-up -- provided there are no additional Steps to execute.
|
||||
201
execution/src/site/apt/introduction.apt
Normal file
201
execution/src/site/apt/introduction.apt
Normal file
@@ -0,0 +1,201 @@
|
||||
------
|
||||
Batch Processing Strategy
|
||||
------
|
||||
Scott Wintermute
|
||||
------
|
||||
May 2007
|
||||
|
||||
Batch Processing Strategy
|
||||
|
||||
To help design and implement batch systems, basic batch application building blocks and patterns should be provided to the designers and programmers in form of sample structure charts and code shells. When starting to design a batch job, the business logic should be decomposed into a series of steps which can be implemented using the following standard building blocks:
|
||||
|
||||
* Conversion Applications: For each type of file supplied by or generated to an external system, a conversion application will need to be created to convert the transaction records supplied into a standard format required for processing. This type of batch application can partly or entirely consist of translation utility modules (see Basic Batch Services).
|
||||
|
||||
* Validation Applications: Validation applications ensure that all input/output records are correct and consistent. Validation is typically based on file headers and trailers, checksums and validation algorithms as well as record level cross-checks.
|
||||
|
||||
* Extract Applications: An application that reads a set of records from a database or input file, selects records based on predefined rules, and writes the records to an output file.
|
||||
|
||||
* Extract/Update Applications: An application that reads records from a database or an input file, and makes changes to a database or an output file driven by the data found in each input record.
|
||||
|
||||
* Processing and Updating Applications: An application that performs processing on input transactions from an extract or a validation application. The processing will usually involve reading a database to obtain data required for processing, potentially updating the database and creating records for output processing.
|
||||
|
||||
* Output/Format Applications: Applications reading an input file, restructures data from this record according to a standard format, and produces an output file for printing or transmission to another program or system.
|
||||
|
||||
<<Pre-processing Capabilities>>
|
||||
|
||||
Additionally a basic application shell should be provided for business logic that cannot be built using the previously mentioned building blocks.
|
||||
|
||||
In addition to the main building blocks, each application may use one or more of standard utility steps, such as:
|
||||
|
||||
* Sort - A Program that reads an input file and produces an output file where records have been re-sequenced according to a sort key field in the records. Sorts are usually performed by standard system utilities.
|
||||
|
||||
* Split - A program that reads a single input file, and writes each record to one of several output files based on a field value. Splits can be tailored or performed by parameter-driven standard system utilities.
|
||||
|
||||
* Merge - A program that reads records from multiple input files and produces one output file with combined data from the input files. Merges can be tailored or performed by parameter-driven standard system utilities.
|
||||
|
||||
Batch applications can additionally be categorized by their input source:
|
||||
|
||||
* Database-driven applications are driven by rows or values retrieved from the database.
|
||||
|
||||
* File-driven applications are driven by records or values retrieved from a file
|
||||
|
||||
The foundation of any batch system is the processing strategy. Factors affecting the selection of the strategy include estimated batch system volume, concurrency with on-line or with another batch systems, available batch windows etc. Also with more enterprises wanting to be up and running 24x7, leaving no obvious batch windows.
|
||||
|
||||
Typical processing options for batch are:
|
||||
|
||||
* Normal processing in a batch window during off-line
|
||||
|
||||
* Concurrent batch / on-line processing
|
||||
|
||||
* Parallel processing of many different batch runs or jobs at the same time
|
||||
|
||||
* Streaming i.e. processing of many instances of the same job at the same time
|
||||
|
||||
* A combination of these
|
||||
|
||||
The order in the list above reflects the implementation complexity, processing in a batch window being the easiest and streaming the most complex to implement.
|
||||
|
||||
Some or all of these options may be supported by a commercial scheduler.
|
||||
|
||||
In the following section these processing options will be discussed in more detail. It is important to notice that the commit and locking strategy adopted by batch processes will be dependent on the type of processing performed and as a rule of thumb, the on-line locking should use the same principles. Therefore a batch architecture cannot be simply an afterthought when designing an overall architecture.
|
||||
|
||||
The locking strategy can use only normal database locks, or an additional custom locking service can be implemented in the architecture. The locking service would track database locking (for example by storing the necessary information in a dedicated db-table) and give or deny permissions to the application programs requesting a db operation. Retry logic could also be implemented by this architecture to avoid aborting a batch job in case of a lock situation.
|
||||
|
||||
<<1. Normal processing in a batch window>>
|
||||
For simple batch processes running in a separate batch window, where the data being updated is not required by on-line users or other batch processes, concurrency is not an issue and a single commit can be done at the end of the batch run.
|
||||
|
||||
In most cases a more robust approach is more appropriate. A thing to keep in mind is that batch systems have a tendency to grow as time goes by, both in terms of complexity and the data volumes they will handle. If no locking strategy is in place and the system still relies on a single commit point, modifying the batch programs can be painful. Therefore, even with the simplest batch systems, consider the need for commit logic depicted in the [Restart/Recovery section|Restart & Recovery] as well as the information concerning the more complex cases below.
|
||||
|
||||
<<2. Concurrent batch / on-line processing>>
|
||||
Batch applications processing data that can simultaneously be updated by on-line users, should not lock any data (either in the database or in files) which could be required by on-line users for more than a few seconds. Also updates should be committed to the database at the end of every few transaction. This minimizes the portion of data that is unavailable to other processes and the elapsed time the data is unavailable.
|
||||
|
||||
Another option to minimize physical locking is to have a logical row-level locking implemented using either an Optimistic Locking Pattern or a Pessimistic Locking Pattern.
|
||||
|
||||
* Optimistic locking assumes a low likelihood of record contention. It typically means inserting a timestamp column in each database table used concurrently by both batch and on-line processing. When an application fetches a row for processing, it also fetches the timestamp. As the application then tries to update the processed row, the update uses the original timestamp in the WHERE clause. If the timestamp matches, the data and the timestamp will be updated successfully. If the timestamp does not match, this indicates that another application has updated the same row between the fetch and the update attempt and therefore the update cannot be performed.
|
||||
|
||||
* Pessimistic locking is any locking strategy that assumes there is a high likelihood of record contention and therefore either a physical or logical lock needs to be obtained at retrieval time. One type of pessimistic logical locking uses a dedicated lock-column in the database table. When an application retrieves the row for update, it sets a flag in the lock column. With the flag in place, other applications attempting to retrieve the same row will logically fail. When the application that set the flag updates the row, it also clears the flag, enabling the row to be retrieved by other applications. Please note, that the integrity of data must be maintained also between the initial fetch and the setting of the flag, for example by using db locks (e.g.,SELECT FOR UPDATE). Note also that this method suffers from the same downside as physical locking except that it is somewhat easier to manage building a time-out mechanism that will get the lock released if the user goes to lunch while the record is locked.
|
||||
|
||||
These patterns are not necessarily suitable for batch processing, but they might be used for concurrent batch and on-line processing for example in cases where the database doesn't support row-level locking. As a general rule, optimistic locking is more suitable for on-line applications, while pessimistic locking is more suitable for batch applications. Whenever logical locking is used, the same scheme must be used for all applications accessing data entities protected by logical locks.
|
||||
|
||||
Note that both of these solutions only address locking a single record. Often we may need to lock a logically related group of records. With physical locks, you have to manage these very carefully in order to avoid potential deadlocks. With logical locks, it is usually best to build a logical lock manager that understands the logical record groups you want to protect and can ensure that locks are coherent and non-deadlocking. This logical lock manager usually uses its own tables for lock management, contention reporting, time-out mechanism, etc.
|
||||
|
||||
<<3. Parallel Processing>>
|
||||
Parallel processing allows multiple batch runs / jobs to run in parallel to minimize the total elapsed batch processing time. This is not a problem as long as the jobs are not sharing the same files, db-tables or index spaces. If they do, this service should be implemented using partitioned data. Another option is to build an architecture module for maintaining interdependencies using a control table. A control table should contain a row for each shared resource and whether it is in use by an application or not. The batch architecture (Control Program Tasklet) or the application in a parallel job would then retrieve information from that table to determine if it can get access to the resource it needs or not.
|
||||
|
||||
If the data access is not a problem, parallel processing can be implemented in a mainframe environment using parallel job classes, in order to ensure adequate CPU time for all the processes. In an environment other than the mainframe, a similar solution can be put in place with for example threads. The solution has to be robust enough to ensure time slices for all the running processes.
|
||||
|
||||
Other key issues in parallel processing include load balancing and the availability of general system resources such as files, database buffer pools etc. Also note that the control table itself can easily become a critical resource.
|
||||
|
||||
<<4. Partitioning>>
|
||||
Using partitioning allows multiple versions of large batch applications to run in concurrent. The purpose of this is to reduce the elapsed time required to process long batch jobs. Processes which can be successfully partitioned are those where the input file can be split and/or the main database tables partitioned to allow the application to run against different sets of data.
|
||||
|
||||
In addition, processes which are partitioned must be designed to only process their assigned data set. A partitioning architecture has to be closely tied to the database design and the database partitioning strategy. Please note, that the database partitioning doesn't necessarily mean physical partitioning of the database, although in most cases this is advisable. The following picture illustrates the partitioning approach:!app_style_batch_processing.png|align=center!
|
||||
|
||||
The architecture should be flexible enough to allow dynamic configuration of the number of partitions. Both automatic and user controlled configuration should be considered. Automatic configuration may be based on parameters such as the input file size and/or the number of input records.
|
||||
|
||||
<<4.1 Streaming Approaches>>
|
||||
The following lists some of the possible streaming approaches. Selecting a streaming approach has to be done on a case-by-case basis.
|
||||
|
||||
<1. Fixed and Even Break-Up of Record Set>
|
||||
|
||||
This involves breaking the input record set into an even number of portions (e.g. 10, where each portion will have exactly 1/10th of the entire record set). Each portion is then processed by one instance of the batch/extract application.
|
||||
|
||||
In order to use this approach, preprocessing will be required to split the recordset up. The result of this split will be a lower and upper bound placement number which can be used as input to the batch/extract application in order to restrict its processing to its portion alone.
|
||||
|
||||
Preprocessing could be a large overhead as it has to calculate and determine the bounds of each portion of the record set.
|
||||
|
||||
<2. Breakup by a Key Column>
|
||||
|
||||
This involves breaking up the input record set by a key column such as a location code, and assigning data from each key to a batch instance. In order to achieve this, column values can either be
|
||||
|
||||
<3. Assigned to a batch instance via a streaming table (see below for details).>
|
||||
|
||||
<4. Assigned to a batch instance by a portion of the value (e.g. values 0000-0999, 1000 - 1999, etc.)>
|
||||
|
||||
Under option 1, addition of new values will mean a manual reconfiguration of the batch/extract to ensure that the new value is added to a particular instance.
|
||||
|
||||
Under option 2, this will ensure that all values are covered via an instance of the batch job. However, the number of values processed by one instance is dependent on the distribution of column values (i.e. there may be a large number of locations in the 0000-0999 range, and few in the 1000-1999 range). Under this option, the data range should be designed with streaming in mind.
|
||||
|
||||
Under both options, the optimal even distribution of records to batch instances cannot be realized. There is no dynamic configuration of the number of batch instances used.
|
||||
|
||||
<5. Breakup by Views>
|
||||
|
||||
This approach is basically breakup by a key column, but on the database level. It involves breaking up the recordset into views. These views will be used by each instance of the batch application during its processing. The breakup will be done by grouping the data.
|
||||
|
||||
With this option, each instance of a batch application will have to be configured to hit a particular view (instead of the master table). Also, with the addition of new data values, this new group of data will have to be included into a view. There is no dynamic configuration capability, as a change in the number of instances will result in a change to the views.
|
||||
|
||||
<6. Addition of a Processing Indicator>
|
||||
|
||||
This involves the addition of a new column to the input table, which acts as an indicator. As a preprocessing step, all indicators would be marked to non-processed. During the record fetch stage of the batch application, records are read on the condition that that record is marked non-processed, and once they are read (with lock), they are marked processing. When that record is completed, the indicator is updated to either complete or error. Many instances of a batch application can be started without an change, as the additional column ensures that a record is only processed once.
|
||||
|
||||
With this option, I/O on the table increased dynamically. In the case of a updating batch application, this impact is reduced, as a write will have to occur anyway.
|
||||
|
||||
<7. Extract Table to a Flat File>
|
||||
|
||||
This involves the extraction of the table into a file. This file can then be split into multiple segments and used as input to the batch instances.
|
||||
|
||||
With this option, the additional overhead of extracting the table into a file, and splitting it, may cancel out the effect of multi-streaming. Dynamic configuration can be achieved via changing the file splitting script.
|
||||
|
||||
<8. Use of a Hashing Column>
|
||||
|
||||
This scheme involves the addition of a hash column (key/index) to the database tables used to retrieve the driver record. This hash column will have an indicator to determine which instance of the batch application will process this particular row. For example, if there are three batch instances to be started, then an indicator of 'A' will mark that row for processing by instance 1, an indicator of 'B' will mark that row for processing by instance 2, etc.
|
||||
|
||||
The procedure used to retrieve the records would then have an additional WHERE clause to select all rows marked by a particular indicator. The inserts in this table would involve the addition of the marker field, which would be defaulted to one of the instances (e.g. 'A').
|
||||
|
||||
A simple batch application would be used to update the indicators such as to redistribute the load between the different instances. When a sufficiently large number of new rows have been added, this batch can be run (anytime, except in the batch window) to redistribute the new rows to other instances.
|
||||
|
||||
Additional instances of the batch application only require the running of the batch application as above to redistribute the indicators to cater for a new number of instances.
|
||||
|
||||
|
||||
4.2 Database and Application design Principles
|
||||
|
||||
An architecture that supports multi-streamed applications which run against partitioned database tables using the key column approach, should include a central streaming repository for storing streaming parameters. This provides flexibility and ensures maintainability. The repository will generally consist of a single table known as the streaming table.
|
||||
|
||||
Information stored in the streaming table will be static and in general should be maintained by the DBA. The table should consist of one row of information for each stream of a multi-streamed application. The table should have a similar layout to the following table:
|
||||
|
||||
{center}
|
||||
|| Streaming Table ||
|
||||
| Program ID Code
|
||||
Stream Number (Logical ID of the stream)
|
||||
Low Value of the db key column for this stream
|
||||
High Value of the db key column for this stream |
|
||||
{center}
|
||||
|
||||
On program start-up the program id and stream number should be passed to the application from the architecture (Control Processing Tasklet). These variables are used to read the streaming table, to determine what range of data the application is to process (if a key column approach is used). In addition the stream number must be used throughout the processing to:
|
||||
|
||||
* Add to the output files/database updates in order for the merge process to work properly
|
||||
|
||||
* Report normal processing to the batch log and any errors that occur during execution to the architecture error handler
|
||||
|
||||
4.3 Minimizing Deadlocks
|
||||
When applications run in parallel or streamed, contention in database resources and deadlocks may occur. It is critical that the database design team eliminates potential contention situations as far as possible as part of the database design.
|
||||
|
||||
Also ensure that the database index tables are designed with deadlock prevention and performance in mind.
|
||||
|
||||
Deadlocks or hot spots often occur in administration or architecture tables such as log tables, control tables, lock tables etc.. The implications of these should be taken into account as well. A realistic stress test is crucial for identifying the possible bottlenecks in the architecture.
|
||||
|
||||
To minimize the impact of conflicts on data, the architecture should provide services such as wait-and-retry intervals when attaching to a database or when encountering a deadlock. This means a built-in mechanism to react to certain database return codes and instead of issuing an immediate error handling, waiting a predetermined amount of time and retrying the database operation.
|
||||
|
||||
4.4 Parameter Passing and Validation
|
||||
|
||||
The streaming architecture should be relatively transparent to application developers. The architecture should perform all tasks associated with running the application in a streamed mode i.e.
|
||||
|
||||
* Retrieve streaming parameters before application start-up
|
||||
|
||||
* Validate streaming parameters before application start-up
|
||||
|
||||
* Pass parameters to application at start-up
|
||||
|
||||
The validation should include checks to ensure that:
|
||||
|
||||
* the application has sufficient streams to cover the whole data range
|
||||
|
||||
* there are no gaps between streams
|
||||
|
||||
If the database is partitioned, some additional validation may be necessary to ensure that a single stream does not span database partitions.
|
||||
|
||||
Also the architecture should take into consideration the consolidation of streams. Key questions include:
|
||||
|
||||
* Must all the streams be finished before going into the next job step?
|
||||
|
||||
* What happens if one of the streams aborts?
|
||||
124
execution/src/site/apt/outline.apt
Normal file
124
execution/src/site/apt/outline.apt
Normal file
@@ -0,0 +1,124 @@
|
||||
------------------------------------------
|
||||
The Spring Batch - Reference Documentation
|
||||
----------------------------------------
|
||||
Wayne Lund, Waseem Malik, Lucas Ward, Scott Wintermute,
|
||||
Kerry O'Brien, Tomi Vanek
|
||||
-------------------------------------------
|
||||
May 2007
|
||||
|
||||
Preface
|
||||
|
||||
*1. {{{introduction.html}Spring Container Batch Processing}}
|
||||
|
||||
*1.1. Overview
|
||||
|
||||
*1.2 Usage Scenarios
|
||||
|
||||
*2. {{{overview.html}Architecture Overview}}
|
||||
|
||||
**2.1. Introduction to Architecture Layers
|
||||
|
||||
**2.2. Batch Applications
|
||||
|
||||
**2.3 Container Application layer
|
||||
|
||||
**2.4 Container Support Layer
|
||||
|
||||
**2.5 The Container Core Layer
|
||||
|
||||
**2.6 Using the Spring-batch infrastructure
|
||||
|
||||
*2.6.1 Infrastructure Provided I/O Support
|
||||
|
||||
*2.6.2 Infrastucture Provided Base Services
|
||||
|
||||
*2.7. Batch Execution Container Configurations
|
||||
|
||||
*2.7.1. Single VM Simple Batch Execution Container - One Job, One Step, One Partition
|
||||
|
||||
*2.7.2. Single VM Multi-threaded Batch Execution Container Configuration - One Job, One Step, Multiple Partitions
|
||||
|
||||
*2.7.3 Batch Execution Container Hosted in J2EE Container - managed environment
|
||||
|
||||
|
||||
*3. Core Batch Services
|
||||
|
||||
*3.1. Launching Batch Jobs
|
||||
|
||||
*3.2. Mapping Batch Error Codes to Launch Client Error Codes
|
||||
|
||||
*3.2.1. Returning error codes to Enterprise Schedulers with command line interfaces
|
||||
|
||||
*3.2.2. Web Request - returning error codes to "On-Demand Batch Requests"
|
||||
|
||||
*3.3. Job Services
|
||||
|
||||
*3.3.1. Step Configuration
|
||||
|
||||
*3.3.2. Job Status Service
|
||||
|
||||
*3.3.3. Job Status
|
||||
|
||||
*3.3.4. Job Statistics
|
||||
|
||||
*3.4. Step Services
|
||||
|
||||
*3.4.1. Step Execution
|
||||
|
||||
*3.4.2. Restart Services
|
||||
|
||||
*3.4.3. Skip Services
|
||||
|
||||
*3.4.5. Step Statistics
|
||||
|
||||
*3.5. Data Providers - Operations, Templates and Convenience Callbacks
|
||||
|
||||
*3.5.1. Wrapping input sources
|
||||
|
||||
*3.5.2. Validation of input
|
||||
|
||||
*3.5.3. Delimited File Data Providers
|
||||
|
||||
*3.5.4. Fixed Position File Data Providers
|
||||
|
||||
*3.5.5. XML File Data Providers
|
||||
|
||||
*3.5.6. SQL Input Source Data Provider
|
||||
|
||||
*3.6. Transaction Management
|
||||
|
||||
*3.6.1. Transaction Synchronization with non-transactional resources
|
||||
|
||||
*3.7. Partitioning Batch Jobs
|
||||
|
||||
*3.7.1. Partitioning Strategies
|
||||
|
||||
*3.7.2. Partitioning Job Steps
|
||||
|
||||
*3.7.3. Partition Status
|
||||
|
||||
*3.7.4. Partition Statistics
|
||||
|
||||
*3.7.5. Handling Exceptions within partitions
|
||||
|
||||
*4. [Batch in a J2EE Container]
|
||||
|
||||
*5. [Testing Batch Jobs]
|
||||
|
||||
*5.1. Unit Testing & Mock Objects Provided by the Framework
|
||||
|
||||
*5.2. Integration Testing
|
||||
|
||||
*5.3. Performance Testing Batch Jobs
|
||||
|
||||
*6. {{{samples.html}Practical Examples for Spring Batch}}
|
||||
|
||||
*6.1. Sample Applications|Spring Reference-Application Job-Map
|
||||
|
||||
*6.2. Running the Sample Batch Applications
|
||||
|
||||
*7. [INCUB:Batch XML Schema]
|
||||
|
||||
*8. {{{glossary.html}Glossary}}
|
||||
|
||||
|
||||
145
execution/src/site/apt/overview.apt
Normal file
145
execution/src/site/apt/overview.apt
Normal file
@@ -0,0 +1,145 @@
|
||||
------
|
||||
Architecture Overview
|
||||
------
|
||||
Wayne Lund
|
||||
------
|
||||
May 2007
|
||||
|
||||
2. Architecture Overview
|
||||
|
||||
|
||||
*2.1 Introduction
|
||||
|
||||
This chapter covers the overall spring batch architecture. The Spring Container Archtiecture is made up of five logical layers; 1) the Batch Application, 2) the Batch Application Layer, 3) the batch core layer, and 4) the batch infrastucture layer.
|
||||
|
||||
*----------*----------------*------------+
|
||||
|Provided By | Layer | Description
|
||||
*----------*----------------*------------*
|
||||
| Application Developer | Batch Application | This is where the application writes their batch jobs and modules. |
|
||||
*----------*----------------*------------*
|
||||
| Spring Batch Execution Container | Container Application Layer | Allows for extending and overwriting of the batch support layer for custom requirements. Facilities implemented in this layer could migrate down to Batch Support Layer. This is also the layer to add the project specific jars required by job types (e.g. reporting jars like Crystal, Brio, etc, form generation jars like Central Pro or Adobe, etc). |
|
||||
*----------*----------------*------------*
|
||||
| Spring Batch Execution Container | Container Support Layer | Provides default implementations of batch core services including I/O, Restart, Partitioning, Statistics, and configurations |
|
||||
*----------*----------------*------------*
|
||||
| Spring Batch Execution Container | Container Core Layer | Enables configuration, Common Services & Interfaces, management |
|
||||
*----------*----------------*------------*
|
||||
| Spring Batch Infrastructure | Batch-Infrastructure | Provides IO support, Batch style transactions, advanced exception handling, batch-template, batch-retry |
|
||||
*----------*----------------*------------*
|
||||
|
||||
[Figure 2.0] - Batch Architecture Layers
|
||||
|
||||
The batch architecture is modeled after a container architecture, meaning that there are managed resources essential to high performance batch architectures that are configured through a spring context. The following sections will provide a quick review of each layer and their role in the batch architecture.
|
||||
|
||||
*2.2 Batch Applications
|
||||
|
||||
*2.3 Container Application Layer
|
||||
|
||||
*2.4 Container Support Layer
|
||||
|
||||
The batch support layer provides default implementations for all interfaces, interceptors, advice and other core batch services. Figure 2.3.1 illustrates the following logical packages. !Batch Support.png!
|
||||
Although physically they break out into many more than depicted, logically you can think of the groupings in the following manner:
|
||||
* I/O Support packages
|
||||
* Restart Support
|
||||
* Lifecycle Support packages
|
||||
* DAO support layer
|
||||
|
||||
**2.4.1 I/O Support Packages
|
||||
|
||||
The I/O related packages are currently the richest packages in the batch architecture. They are modeled after Spring Patterns of Operations and Templates. For example, you'll see FlatFileInputOperations accompanied with a FlatFileInputTemplate. The FlatFileInputTemplate is wired up with a File Descriptor, which contains a Record Descriptor along with various other properties. With the File and Record Descriptors the InputTemplate supports a callback method that allows for the mapping of a record into an object. This support applies to fixed length records, delimited records and XML records. To further simplify this a DefaultFlatFileDataProvider is supplied an input template, which contains the field and record descriptions, along with a line mapper that knows how to map the line to an object. The next() operation on a record simply needs to readAndMap(lineMapper) a record. This pattern is used over again for XML and SQL input for simple mapping of input records to objects.
|
||||
|
||||
In addition to declarative descriptions of the records that can be re-used by multiple batch jobs, the I/O facilities also support configurable validation strategies. The two currently supported are Apache Commons Validator and Spring's VALang.
|
||||
|
||||
**2.4.2 Restart Support
|
||||
|
||||
The Restart Support provides implementations for a few common restart strategies that will be discussed further in the respective section. The following are provided out-of-the-box:
|
||||
* IDList Restart Strategy - a strategy that supports a batch application where the application does not have a "process" flag and needs the batch architecture to track which records have been processed. This is not the ideal scenario.
|
||||
* Last Processed Restart Strategy - when the record can be identified through a where and order by only the last record(s) processed needs to be saved for restart.
|
||||
* No Restart Strategy - some batch jobs simply can't support restart. When they are re-run they are considered to be a new instance of a batch job.
|
||||
* Sql Restart Strategy - \[need some additional javadoc for this strategy\].
|
||||
|
||||
**2.4.3 Lifecycle Support
|
||||
|
||||
*2.5. The Container Core Layer
|
||||
|
||||
The Batch Core interfaces and services are illiustrated in a simplied view of a package diagram. There are roughly seven logical packages:
|
||||
* Core Spring Extensions
|
||||
* Core Batch Advice
|
||||
* Core Batch Configuration
|
||||
* Core Batch Repository
|
||||
* Core Batch Tasklet
|
||||
\\ !Batch Core.png!
|
||||
[Figure 2.5] Batch Core Layer
|
||||
|
||||
In the actual physical packaging there are a few more packages but the above illistration serves as an overview of the logical services that the batch container provides. The following sections will provide an introduction into each set of core batch facilities.
|
||||
|
||||
**2.5.1 Core Spring Extensions
|
||||
|
||||
The Core Spring extensions provide the scaffolding for a batch container. This includes facilities for managing the batch architecture in terms of launching, suspending and stopping batch jobs. There is house keeping that goes on, especially in concurrent batch jobs, related to ensuring that batch jobs quiese properly. The lifecycle management provides services for the proper initialization and subsequent shutdown of batch resources and services. The batch architecture is flexible in terms of how batch jobs may be launched. For example, batch jobs can be started via JMX facilities, scripts from the command line that launch a Java VM. It can also support launching batch jobs through web services or http. There are no restrictions. Finally, there are standard batch error codes. These error codes can be exposed to external utilities, like Schedulers, to ensure that batch jobs expose the status of jobs to an operational environment. This is especially important in the batch context where the modus operandi is headless, meaning unattended operation.
|
||||
|
||||
**2.5.2 Core Batch Advice
|
||||
|
||||
Core Batch Advice is an inventory of the type of advice that batch architectures will inject during the runtime of a batch application. These are defined as a set of extensible interfaces, with a number of default implementations in the support layer that provide some of the most common types of advice. Partition Advice is helpful with large datasets that need to be "chunked" up and run concurrently for better through put. Resource Advice is helpful for registering interest in transactional information so that file locations can be kept in sync with information processed within a transaction. In addition, the resource is associated with the correct step context and its associated configuration properties. Skip advice is applied for records that the module is unable to process. Restart Advice is helpful for Restartable jobs where for advising the job on how to restart. There is considerable variability on how restart can occur. For example, a job may be marking records as "processed" and the restart advice will advise the process with query that restarts the job at the last successfully processed record. Finally, Statistics are vital in operational environments to report on records processed, records skipped and total number of records read. In addition, certain batch jobs lend themselves to custom reporting to expose additional business level information like the number of trades processed or cases opened, etc.
|
||||
|
||||
**2.5.3 Core Batch Configuration
|
||||
|
||||
Batch configuration is considerably different from online web applications or SOA based applications. The Core Batch Configuration provides a place for configuring runtime properties related to the batch application style. This includes the ability to add Commit Policy. In a batch style application it is often advantageous to keep the commit interval as high as possible when processing Logical Units of Work. Whereas in an online web application with declarative transaction the transaction scope would be at the entrance to a business service, a batch transaction scope may include many logical units of work before a transaction commit is executed. A Start Policy allows a configuration to tell the batch job whether it is Restartable, and if so, what type of restart to initiate. Some jobs are not restartable and care should be taken to ensure that information is not applied multiple times when the business rules do not allow for it. Exception policies deal with what to do when exceptions occur. This impacts logging policies and exception handling. The architecture defines a common set of exceptions that projects can apply handlers to like processing errors, validation errors, parsing errors, missing configuration parameters, etc.
|
||||
|
||||
**2.5.4 Container Repository
|
||||
|
||||
This is an internal package for storing the state of a batch job and any associated partition and step status.
|
||||
|
||||
**2.5.5 Core Batch Tasklet
|
||||
|
||||
The core batch module is where control is handed off to the application. There are a number of patterns that have been observed in processing batch data. Spring Core Batch Tasklet implements the most common patterns and provides and extension point for additional Tasklet processing implementations. The basic idea of module provides the facilities for reading and processing data. The simplest implementation of Tasklet, the ReadProcessTasklet, handles both the input and output of data within one class. An alternative implementation, the DataProviderProcessTasklet, provides functionality for 'split processing'. This type of processing is characterized by separating the reading and processing of batch data into two seperate classes: DataProvider and TaskletProcessor. The DataProvider class provides a solid means for reusablility and enforces good architecture practices. Because an object \*must\* be returned by the DataProider to continue processing, (Returning null indicates processing should end) a developer is forced to read in all relevant data, place it into domain or value objects, and return the object. The TaskletProcessor will then use this object within the business logic and final output.
|
||||
|
||||
2.6 Container's Use of batch infrastructure
|
||||
|
||||
**2.6.1 Infrastructure Provided I/O
|
||||
|
||||
The I/O core interfaces and implementations provide facilities for simplifying the extraction of data from I/O sources like files and database tables. The key concepts are FieldDescriptors and FieldSets along with appropriate CallBack Handlers. These are modeled after common spring operations and templates like JdbcTemplate. Through the use of LineMappers a developer needs only to describe a record format and write the appropriate callback method that maps the parsed record into an object of their choice. These can either be true POJO objects or Value Objects (structures) that are subsequently available for the module to processs. The interface for Field Descriptors also allows for a level of validation through the use of Spring's VALang or Apache's Common Validator.
|
||||
|
||||
**2.6.2 Core Batch Interceptors & Interceptor Services
|
||||
|
||||
* Batch Operations & Batch Template
|
||||
|
||||
Interceptors and the associated services are the key to how advise is applied in the batch architecture. The interceptors are Point Cuts in the batch lifecycle that allow the injection of advise. The shared lifecycle behavior abstracted through the BatchLifeCycleInterceptor defineds three methods; init, onError and finalize. All subclasses of LifeCycleInterceptor define default behavior for these three methods. The JobLifecycleInterceptor further exposes the methods beforeJob(), beforeStep(), afterJob(), and afterStep() allowing hooks into the lifecycle for specific advise. The Batch Architecture provides default implementations for all lifecycle point cuts, or interception points. The Tasklet Interceptor, in addition to the standard lifecycle methods, implements logic around beforeLuw(), afterLuw(), commitIntervalStarted() and commitIntervalCompleted(). Having well defined lifecycle interception points allows for the easy insertion of custom advice into the batch runtime environment.
|
||||
|
||||
2.7 Batch Esecution Container Configurations
|
||||
|
||||
In addition to core facilities for configuring or wiring together jobs and steps with required resources, policies, and interceptors, spring batch allows considerable flexibility in how scalability is achieved. More options for scalability will be available in the future. The important key for scalability in Java is the recognition that there is a limit to what one JVM may scale up to in terms of number of threads, managed resources, memory configuration, etc. The spring batch architecture allows for the configuration of simple batch jobs where one VM and one process is sufficient to do perform the work within a batch window all the way through many threads distributed within a cluster of JEE servers. The figure below illistrates the scalability spectrum.
|
||||
|
||||
This is not to be understood as the only way to scale batch jobs as there are many factors. For example, other federated java architectures hold potential like Teracotta or Gigaspaces although there is no current implementation for these distributed models in the current batch architecture.
|
||||
!scalability-model.png!
|
||||
[Figure 2.3.1] - Scalability Model
|
||||
|
||||
*2.7.1. Single VM Simple Batch Execution Container - One Job, One Step, One Partition
|
||||
|
||||
The simplest configuration is one job with with step and hence, one implied partition. Implied means that there is nothing for the developer to consider because the default number of partitions is one. There is typically one input source and one output source in this simple configuration. See the Simple Tasklet Job for an example of what this configuration looks like. A simple configuration still typically configures a datasource context, the batch configuration for describing the Job, Step, along with the associated configured policies, field descriptors, and line mappers. !SimpleTradeConfiguration.jpg!
|
||||
[Figure 2.3.1] Simple Container Configuration
|
||||
|
||||
The details of this configuration will be covered thoroughly in subsequent sections of the document but for now it should be understood that Job, the Step, the input template, the file descriptor with its associated line mapper, and the output (e.g. the TradeWriter).
|
||||
|
||||
*2.3.2. Single VM Multi-threaded Batch Execution Container Configuration - One Job, One Step, Multiple Partitions
|
||||
|
||||
In a Single JVM using partitioning a multi-threaded execution is supported. \[This is still work in progress\]
|
||||
|
||||
*2.3.3. Batch Execution Container Hosted in J2EE Container - managed environment
|
||||
|
||||
The J2EE container model has fallen under fire over the past few years for many valid reasons. There are some things that the J2EE container do very well though that projects should consider when planning for scalability with batch architectures. Commercial and open source containers like WebSphere, BEA and JBOSS typically:
|
||||
|
||||
* manage datasources effectively along with attendent services like prepared statement caching.
|
||||
|
||||
* manage transactions effectively including many configurable properties for long lived transactions.
|
||||
|
||||
* manage thread pools more effectively.
|
||||
|
||||
* supply robust implementations of JTA, a requirement when batch jobs output to multiple XA resources like JMS and JDBC.
|
||||
|
||||
* manage distribution effectively including domains, clusters and cells
|
||||
|
||||
* provide robust JMX management for configuring, managing and administering distributed applications.
|
||||
|
||||
* workload management facilities (clusters) provided by J2EE vendors
|
||||
|
||||
Projects are encouraged to deploy batch applications with the simplest configuration possible, but when federated JVMs are a requirement to process volumes of data within a batch window, batch-in-container provides an effective way of distributing the processing. Spring Batch supports this through a simple change in configuration. \[Work in progress on the exact implementation - being released as part of M2\].
|
||||
|
||||
266
execution/src/site/apt/samples.apt
Normal file
266
execution/src/site/apt/samples.apt
Normal file
@@ -0,0 +1,266 @@
|
||||
------
|
||||
Sample Container Applications
|
||||
------
|
||||
Wayne Lund
|
||||
------
|
||||
May 2007
|
||||
|
||||
Overview of Batch Reference Applications
|
||||
|
||||
There is considerable variability in the types of input and output formats in batch jobs. There is also a number of options to consider in terms of how the types of strategies that will be used to handle skips, recovery, and statistics. However, when approaching a new batch job there are a few standard questions to answer to help determine how the job will be written and how to utilize the services offered by the spring batch framework. Consider the following:
|
||||
|
||||
* How do I configure this batch job? In the reference applications the pattern is to follow the convention of <nameOf>Job.xml. Each section with identify the XML definition.
|
||||
|
||||
* What is the input source? Each sample batch job will identify its input source.
|
||||
|
||||
* What is my output source? Each sample batch job will identify its output source.
|
||||
|
||||
* How are records read and validated from the input source? This refers to the input type and its format (e.g. flat file with fixed position, comma separated or XML, etc.)
|
||||
|
||||
* What is the policy of the job if a input record fails the validation step? The most important aspect is whether the record can be skipped so that processing can be continued.
|
||||
|
||||
* How will I process the data and write to the output source? How and what business logic is being applied to the processing of a record.
|
||||
|
||||
* How do I recover from an exception while operating on the output source? There are numerous recovery strategies that can be applied to handling errors on transactional targets. The reference applications will provide a feeling for some of the choices.
|
||||
|
||||
* Can I restart the job and if so which strategy will I use to restart the job? The reference applications will show some of the options available to jobs and what the decision criteria is for the respective choices.
|
||||
|
||||
|
||||
[Samples] Reference Applications Table of Features
|
||||
|
||||
|
||||
*--------------*-----------------*--------------------*-----------*------------------------*-----------------*------------------*---------------------*-----------*------*---------*-------------------*
|
||||
| Job / Feature | delimited input | fixed-length input | xml input | db driving query input | db cursor input | delimited output | fixed-length output | db output | skip | restart | quartz scheduling |
|
||||
*--------------*-----------------*--------------------*-----------*------------------------*-----------------*------------------*---------------------*-----------*------*---------*-------------------*
|
||||
| simpleTaskletJob | | | | | | | | | | | |
|
||||
*--------------*-----------------*--------------------*-----------*------------------------*-----------------*------------------*---------------------*-----------*------*---------*-------------------*
|
||||
| fixedLengthImport | | | | | | | | | | | |
|
||||
*--------------*-----------------*--------------------*-----------*------------------------*-----------------*------------------*---------------------*-----------*------*---------*-------------------*
|
||||
| multi-line order | | | | | | | | | | | |
|
||||
*--------------*-----------------*--------------------*-----------*------------------------*-----------------*------------------*---------------------*-----------*------*---------*-------------------*
|
||||
| quartzBatch | | | | | | | | | | | |
|
||||
*--------------*-----------------*--------------------*-----------*------------------------*-----------------*------------------*---------------------*-----------*------*---------*-------------------*
|
||||
| simple skip sample | | | | | | | | | | | |
|
||||
*--------------*-----------------*--------------------*-----------*------------------------*-----------------*------------------*---------------------*-----------*------*---------*-------------------*
|
||||
| Skip And Restart Sample | | | | | | | | | | | |
|
||||
*--------------*-----------------*--------------------*-----------*------------------------*-----------------*------------------*---------------------*-----------*------*---------*-------------------*
|
||||
| SQL Cursor Trade Job | | | | | | | | | | | |
|
||||
*--------------*-----------------*--------------------*-----------*------------------------*-----------------*------------------*---------------------*-----------*------*---------*-------------------*
|
||||
| Trade Job | | | | | | | | | | | |
|
||||
*--------------*-----------------*--------------------*-----------*------------------------*-----------------*------------------*---------------------*-----------*------*---------*-------------------*
|
||||
| XML Job | | | | | | | | | | | |
|
||||
*--------------*-----------------*--------------------*-----------*------------------------*-----------------*------------------*---------------------*-----------*------*---------*-------------------*
|
||||
|
||||
{Simple Tasklet Job}
|
||||
|
||||
The goal is to show the simplest use of the batch framework with a single job with a single step where the module processes one input source to one output source.
|
||||
|
||||
<<Description:>> This job is defined by simpleTaskletJob.xml file. Job itself is defined by element simpleTaskletJob. Each job consists of several steps, these steps are defined in steps property. In this example we have only one step. Each step defines module that is responsible for . In this case processing will be handled by SimpleTradeTasklet class. Each module must implement execute() method. All processing of business data should be handled by this method. In this example execute() method tries to read the data from defined input source using read() method and if the data exists, it is processed using process() method. If there is no data to read, method returns false to signal, that there is nothing for further processing.
|
||||
|
||||
<<Method read()>> gets the data from the input template defined and maps it to an object using mapper defined in XML definition. This sample uses FlatFileInputTemplate class as input template. This template reads the whole line from the file and pass it to tokenizer which knows the structure of the line. Location of the file is defined by fileLocatorStrategy property, structurte of the line is defined by fixedFileDescriptor. Result of parsing the line is stored in FieldSet, which is used by mapper to create value object. In our example we use DefaultLineMapper which creates an instance of Trade class.
|
||||
|
||||
<<Method process()>> is quite simple - just writes trade object using DbTradeWriter class. This class writes values obtained from an object to the database.
|
||||
|
||||
<<Specific information:>> This job has whole logic implemented in Tasklet. It is not using Data provider as well as Tasklet processor, which is typical way how to handle data.
|
||||
|
||||
|
||||
<<XML definition:>> simpleTaskletJob.xml
|
||||
|
||||
\[Note: we need to document Spring IDE in setup and installation so we can use to describe the project. Also, if we could also publish we can provide links to the graphics from docs. This is a sample only\].
|
||||
|
||||
Visualization of the spring configuration through Spring-IDE exposes the structure of a job configuration. The following is the visualization of the Simple Tasklet Job configuration. See {{{http://springide.org/blog/}Spring IDE}}.
|
||||
|
||||
[images/simple-module-job-configuration.jpg]Spring IDE Graph of Simple Tasklet Job Configuration.
|
||||
|
||||
[Figure:]\ Simple Tasklet Job Configuration
|
||||
|
||||
For simplicity we are only displaying the job configuration itself and leaving out the details of the supporting container configuration. The source view of the configuration is as follows:
|
||||
|
||||
[]
|
||||
--------------------------------------------------------------------------------------
|
||||
<import resource="BatchArchConfig.xml" />
|
||||
<bean id="simpleTaskletJob" parent="Job">
|
||||
<property name="name" value="fixedLengthImportJob" />
|
||||
<property name="steps">
|
||||
<list>
|
||||
<bean id="tradeStep" parent="Step">
|
||||
<property name="name" value="ImportTradeDataStep" />
|
||||
<property name="module">
|
||||
<bean class="com.accenture.adsj.refapp.batch.module.SimpleTradeTasklet">
|
||||
<property name="inputTemplate" ref="fileInputTemplate" />
|
||||
<property name="tradeDbWriter" ref="tradeWriter" />
|
||||
</bean>
|
||||
</property>
|
||||
<property name="commitFrequency" value="5" />
|
||||
<property name="startPolicy">
|
||||
<bean class="org.springframework.batch.container.conf.StartPolicy">
|
||||
<property name="ignoreComplete" value="true" />
|
||||
<property name="restartEnabled" value="true" />
|
||||
<property name="startlimit" value="12" />
|
||||
</bean>
|
||||
</property>
|
||||
<property name="exceptionPolicy">
|
||||
<bean class="org.springframework.batch.container.conf.ExceptionPolicy">
|
||||
<property name="totalExceptionLimit" value="20" />
|
||||
<property name="transactionInvalidExceptionLimit" value="20" />
|
||||
<property name="transactionValidExceptionLimit" value="5" />
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
<bean id="tradeWriter" class="com.accenture.adsj.refapp.batch.dao.DbTradeWriter">
|
||||
<property name="jdbcTemplate" ref="jdbcTemplate" />
|
||||
<property name="incrementer">
|
||||
<bean parent="incrementerParent">
|
||||
<property name="incrementerName" value="TRADE_SEQ" />
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
<bean id="fileInputTemplate" class="org.springframework.batch.container.io.file.support.FlatFileInputTemplate">
|
||||
<property name="name" value="FileInputSource" />
|
||||
<property name="fileLocatorStrategy" ref="fileLocator" />
|
||||
<property name="tokenizer">
|
||||
<bean class="org.springframework.batch.container.io.file.support.FixedLineTokenizer">
|
||||
<property name="fileDescriptor" ref="fixedFileDescriptor" />
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="fixedFileDescriptor" class="org.springframework.batch.container.io.support.DefaultFileDescriptor">
|
||||
<property name="recordDescriptors">
|
||||
<bean class="org.springframework.batch.container.io.support.DefaultRecordDescriptor">
|
||||
<property name="fieldDescriptors">
|
||||
<list>
|
||||
<bean class="org.springframework.batch.container.io.support.DefaultFieldDescriptor">
|
||||
<property name="name" value="ISIN" />
|
||||
<property name="length" value="12" />
|
||||
</bean>
|
||||
<bean class="org.springframework.batch.container.io.support.DefaultFieldDescriptor">
|
||||
<property name="name" value="Quantity" />
|
||||
<property name="length" value="3" />
|
||||
</bean>
|
||||
<bean class="org.springframework.batch.container.io.support.DefaultFieldDescriptor">
|
||||
<property name="name" value="Price" />
|
||||
<property name="length" value="5" />
|
||||
</bean>
|
||||
<bean class="org.springframework.batch.container.io.support.DefaultFieldDescriptor">
|
||||
<property name="name" value="Customer" />
|
||||
<property name="length" value="9" />
|
||||
</bean>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
<bean id="tradeLineMapper" class="com.accenture.adsj.refapp.batch.mapping.TradeRowMapper" />
|
||||
<bean class="com.accenture.adsj.refapp.batch.advice.LogAdvice" id="logAdvice" />
|
||||
<aop:config>
|
||||
<aop:aspect id="logging" ref="logAdvice">
|
||||
<aop:around pointcut-ref="pointcut" method="doBasicLogging" />
|
||||
<aop:pointcut id="pointcut" expression="execution(* org.springframework.batch.container.dao.*.*(..))" />
|
||||
</aop:aspect>
|
||||
</aop:config>
|
||||
</beans>
|
||||
-----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
You should take the time to make sure you understand the relationship of the xml configuration with the visualization as provided by Spring IDE. \[Note: this will be updated when we use the namespace handler\].
|
||||
|
||||
|
||||
<<Input source:>> file with fixed row structure
|
||||
|
||||
In this example we are using a simple fixed length record structure that can be found in the project at <REFAPP_INSTALL_HOME>/testBatchRoot/job_data/simpleTaskletJob/input/20070122.teststream.ImportTradeDataStep.txt. There's generally a considerable amount of thought that goes into architecting the folder structures for batch file management. See [provide a link to DefaultFileStrategy]. The only point to note here is the ImportTradeDataStep matches the name of the step in the configuration and the fixed length records look like:
|
||||
|
||||
[]
|
||||
|
||||
------------------------------------------------------------------------------------
|
||||
20070122.teststream.ImportTradeDataStep.txt
|
||||
|
||||
UK21341EAH4597898.34customer1
|
||||
UK21341EAH4611218.12customer2
|
||||
UK21341EAH4724512.78customer2
|
||||
UK21341EAH48108109.25customer3
|
||||
UK21341EAH49854123.39customer4
|
||||
------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
Looking back to the configuration file you will see where this is documented in the propery of the DefaultRecordDescriptor. You can see the following:
|
||||
|
||||
*--------------*-----------------*
|
||||
|| FieldName | Length ||
|
||||
*--------------*-----------------*
|
||||
| ISIN | 12 |
|
||||
*--------------*-----------------*
|
||||
| Quantity | 3 |
|
||||
*--------------*-----------------*
|
||||
| Price | 5 |
|
||||
*--------------*-----------------*
|
||||
| Customer | 9 |
|
||||
*--------------*-----------------*
|
||||
|
||||
<<Output target:>> database
|
||||
|
||||
<<Data Provider:>> data provider is not used, all functionality is implemented directly in Tasklet.
|
||||
|
||||
<<Tasklet processor:>> module processor is not used, all functionality is implemented directly in Tasklet.
|
||||
|
||||
Fixed Length Import Job
|
||||
|
||||
The goal is to demonstrate a typical scenarion of importing data from a fixed-length file to database
|
||||
|
||||
<<Description:>> This job shows a more typical scenario, when reading input data and processing the data is cleanly separated. The data provider is responsible for reading input and mapping each record to a domain object, which is then passed to the module processor. The module processor handles the processing of the domain objects, in this case it only writes them to database.
|
||||
|
||||
<<XML definition:>> fixedLengthImportJob.xml
|
||||
|
||||
<<Input source:>> file with fixed row structure
|
||||
|
||||
<<Output target:>> database
|
||||
|
||||
<<Data Provider:>> DefaultFlatFileDataProvider which uses the injected FlatFileInputTemplate to read input and the DefaultLineMapper to map each line to an object according to the file descriptor.
|
||||
|
||||
<<Tasklet processor:>> module processor does not do any special processing, it just writes the data to database using a DAO object (called OutputSource in this case, because it is specialized for writing to database, it has no methods for reading data).
|
||||
|
||||
Multiline Order Job
|
||||
|
||||
The goal is to demostrate how to handle a more complex file input format, where a record meant for processing inludes nested records and spans multiple lines
|
||||
|
||||
<<XML definition:>> multilineOrderJob.xml
|
||||
|
||||
<<Input source:>> file with multiline records
|
||||
|
||||
<<Output target:>> file with multiline records
|
||||
|
||||
<<Data Provider:>> OrderDataProvider is an example of a non-default programmatic data provider. It reads input until it detects that the multiline record has finished and encapsulates the record in a single domain object.
|
||||
|
||||
<<Tasklet processor:>> module processor passes the object to a an injected 'report service' which in this case writes the output to a file do demonstrate how to use the FlatFileOutputTemplate for writing multiline output according to a file descriptor.
|
||||
|
||||
Quartz Batch
|
||||
|
||||
The goal is to demonstrate how to schedule job execution using Quartz scheduler
|
||||
|
||||
<<XML definition:>> quartzBatch.xml
|
||||
|
||||
<<Description:>> First, declares launcher beans. Each launcher bean is able to launch a job using injected arguments. Second, triggers are declared saying when the launchers should be run. Last, there is the scheduler bean, where the triggers are registered.
|
||||
|
||||
Simple Skip Sample
|
||||
|
||||
|
||||
Skip And Restart Sample
|
||||
|
||||
|
||||
SQL Cursor Trade Job
|
||||
|
||||
|
||||
Trade Job
|
||||
|
||||
The goal is to show a reasonably complex scenario, that would resemble the real-life usage of the framework.
|
||||
|
||||
<<Description:>> This job has 3 steps. First, data about trades is imported from a file to database. Second, the data about trades is read from the database and credit on customer accounts is decreased appropriately. Last, a report about customers is exported to a file.
|
||||
|
||||
<<XML definition:>> tradeJob.xml - the job definition, tradeJobIo.xml - input and output configuration, tradeJobAop.xml - optional AOP logging
|
||||
|
||||
<<Description:>> This job has 3 steps. First, data about trades is imported from a file to database. Second, the data about trades is read from the database and credit on customer accounts is decreased appropriately. Last, a report about customers is exported to a file.
|
||||
|
||||
|
||||
XML Job
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
31
execution/src/site/site.xml
Normal file
31
execution/src/site/site.xml
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<project name="Spring Batch: ${project.name}">
|
||||
<bannerLeft>
|
||||
<name>Spring Batch: ${project.name}</name>
|
||||
</bannerLeft>
|
||||
<bannerRight>
|
||||
<src>images/shim.gif</src>
|
||||
</bannerRight>
|
||||
<poweredBy>
|
||||
<logo name="" href="" img="images/shim.gif"/>
|
||||
</poweredBy>
|
||||
<skin>
|
||||
<groupId>org.springframework.maven.skins</groupId>
|
||||
<artifactId>maven-spring-skin</artifactId>
|
||||
<version>1.0.3</version>
|
||||
</skin>
|
||||
<body>
|
||||
|
||||
<links>
|
||||
<item name="Home" href="../index.html"/>
|
||||
<item name="${project.name}" href="index.html"/>
|
||||
</links>
|
||||
|
||||
<menu name="Spring Batch Execution">
|
||||
<item name="${project.name}" href="index.html"/>
|
||||
<item name="Changelog" href="changelog.html"/>
|
||||
</menu>
|
||||
<menu ref="reports"/>
|
||||
|
||||
</body>
|
||||
</project>
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.execution.bootstrap.BatchCommandLineLauncher;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class BatchCommandLineLauncherTests extends TestCase {
|
||||
|
||||
BatchCommandLineLauncher commandLine = new BatchCommandLineLauncher();
|
||||
|
||||
int count = 0;
|
||||
|
||||
/**
|
||||
* Test method for {@link org.springframework.batch.execution.bootstrap.BatchCommandLineLauncher#main(java.lang.String[])}.
|
||||
* @throws Exception
|
||||
*/
|
||||
public void testMainWithDefaultArguments() throws Exception {
|
||||
BatchCommandLineLauncher.main(new String[0]);
|
||||
// TODO: find a way to assert something. No error actually
|
||||
// means the test was successful...
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link org.springframework.batch.execution.bootstrap.BatchCommandLineLauncher#main(java.lang.String[])}.
|
||||
* @throws Exception
|
||||
*/
|
||||
public void testMainWithParentContext() throws Exception {
|
||||
// Try an XML file name for the parent context with no suffix
|
||||
BatchCommandLineLauncher.main(new String[]{"job-configuration"});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link org.springframework.batch.execution.bootstrap.BatchCommandLineLauncher#main(java.lang.String[])}.
|
||||
* @throws Exception
|
||||
*/
|
||||
public void testMainWithParentContextAndValidJobId() throws Exception {
|
||||
// Try a job id as the second argument
|
||||
BatchCommandLineLauncher.main(new String[]{"job-configuration", "test-job"});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link org.springframework.batch.execution.bootstrap.BatchCommandLineLauncher#main(java.lang.String[])}.
|
||||
* @throws Exception
|
||||
*/
|
||||
public void testMainWithParentContextAndInvalidJobId() throws Exception {
|
||||
// Try a job id as the second argument test-job
|
||||
try {
|
||||
BatchCommandLineLauncher.main(new String[]{"job-configuration", "foo-bar-spam"});
|
||||
fail("Expected NoSuchJobConfigurationException");
|
||||
}
|
||||
catch (NoSuchJobConfigurationException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 junit.framework.TestCase;
|
||||
|
||||
import org.springframework.batch.execution.bootstrap.BatchExecutionRequestEvent;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class BatchExecutionRequestEventTests extends TestCase {
|
||||
|
||||
/**
|
||||
* Test method for {@link org.springframework.batch.execution.bootstrap.BatchExecutionRequestEvent#BatchContainerRequestEvent(java.lang.Object)}.
|
||||
*/
|
||||
public void testBatchContainerRequestEvent() {
|
||||
ApplicationEvent event = new BatchExecutionRequestEvent(this);
|
||||
assertEquals(this, event.getSource());
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user