Initial move over from i21 repo.

This commit is contained in:
dsyer
2007-08-15 20:04:43 +00:00
parent 3237c34eb5
commit 170c815916
781 changed files with 67769 additions and 181 deletions

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -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();
}
}

View File

@@ -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));
}
}
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Specific implementations of bootstrap concerns.
</p>
</body>
</html>

View File

@@ -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;
}
}

View File

@@ -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()));
}
}
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Specific implementations of configuration concerns.
</p>
</body>
</html>

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Specific implementations of facade concerns.
</p>
</body>
</html>

View File

@@ -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;
}
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Specific implementations of job concerns.
</p>
</body>
</html>

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Reference implementation of the Spring Batch Core.
</p>
</body>
</html>

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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.");
}
}

View File

@@ -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.");
}
}

View File

@@ -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);
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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.");
}
}

View File

@@ -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.");
}
}

View File

@@ -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);
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Specific implementations of dao concerns.
</p>
</body>
</html>

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Specific implementations of repository concerns.
</p>
</body>
</html>

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Specific implementations of runtime concerns.
</p>
</body>
</html>

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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 &lt;aop:scoped-proxy/&gt;
* 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;
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Specific implementations of scope concerns.
</p>
</body>
</html>

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Specific implementations of step concerns.
</p>
</body>
</html>

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Specific implementations of simple concerns.
</p>
</body>
</html>

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Specific implementations of tasklet concerns.
</p>
</body>
</html>

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Specific implementations of support concerns.
</p>
</body>
</html>

View 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>