OPEN - issue BATCH-903: Create test project

http://jira.springframework.org/browse/BATCH-903

Added a new test project, with some basic implementation to help enable end to end testing of jobs and steps.
This commit is contained in:
lucasward
2008-11-05 21:17:27 +00:00
parent 4f0b055cb7
commit 028983efb9
20 changed files with 1256 additions and 0 deletions

10
spring-batch-test/.classpath Executable file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry including="**/*.java" kind="src" output="target/test-classes" path="src/test/java"/>
<classpathentry kind="src" path="src/test/resources"/>
<classpathentry kind="src" path="src/main/resources"/>
<classpathentry including="**/*.java" kind="src" path="src/main/java"/>
<classpathentry kind="con" path="org.devzuz.q.maven.jdt.core.mavenClasspathContainer"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>

29
spring-batch-test/.project Executable file
View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>spring-batch-test</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.devzuz.q.maven.jdt.core.mavenIncrementalBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.springframework.ide.eclipse.core.springbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.springframework.ide.eclipse.core.springnature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.devzuz.q.maven.jdt.core.mavenNature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<beansProjectDescription>
<version>1</version>
<pluginVersion><![CDATA[2.2.0.v200809261800]]></pluginVersion>
<configSuffixes>
<configSuffix><![CDATA[xml]]></configSuffix>
</configSuffixes>
<enableImports><![CDATA[false]]></enableImports>
<configs>
<config>src/test/resources/data-source-context-init.xml</config>
<config>src/test/resources/data-source-context.xml</config>
<config>src/test/resources/simple-job-launcher-context.xml</config>
<config>src/test/resources/jobs/sampleJob.xml</config>
<config>src/test/resources/org/springframework/batch/sample/config/common-context.xml</config>
</configs>
<configSets>
<configSet>
<name><![CDATA[SampleJob]]></name>
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
<incomplete>false</incomplete>
<configs>
<config>src/test/resources/data-source-context.xml</config>
<config>src/test/resources/data-source-context-init.xml</config>
<config>src/test/resources/jobs/sampleJob.xml</config>
<config>src/test/resources/org/springframework/batch/sample/config/common-context.xml</config>
<config>src/test/resources/simple-job-launcher-context.xml</config>
</configs>
</configSet>
</configSets>
</beansProjectDescription>

86
spring-batch-test/pom.xml Executable file
View File

@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<project>
<parent>
<artifactId>spring-batch</artifactId>
<groupId>org.springframework.batch</groupId>
<version>2.0.0.CI-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-batch-test</artifactId>
<name>Core</name>
<version>2.0.0.CI-SNAPSHOT</version>
<description>Domain for batch job testing</description>
<build>
<plugins>
<plugin>
<groupId>com.springsource.bundlor</groupId>
<artifactId>com.springsource.bundlor.maven
</artifactId>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>clover</id>
<activation />
<build>
<plugins>
<plugin>
<artifactId>maven-clover-plugin</artifactId>
</plugin>
</plugins>
</build>
<reporting>
<plugins>
<plugin>
<artifactId>maven-clover-plugin</artifactId>
</plugin>
</plugins>
</reporting>
</profile>
</profiles>
<dependencies>
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>org.springframework.batch.core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
</dependency>
<dependency>
<groupId>hsqldb</groupId>
<artifactId>hsqldb</artifactId>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</dependency>
</dependencies>
<reporting>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>emma-maven-plugin</artifactId>
</plugin>
</plugins>
</reporting>
</project>

View File

@@ -0,0 +1,154 @@
package org.springframework.batch.test;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameter;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.SimpleJob;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Base class for testing batch jobs using the SimpleJob implementation.
* It provides methods for launching a Job, or individual Steps within a Job on their own,
* allowing for end to end testing of individual steps, without having to run every step
* in the job. Any test classes inheriting from this class should make sure they are part
* of an ApplicationContext, which is generally expected to be done as part of the Spring
* test framework. Furthermore, the ApplicationContext in which it is a part of is expected
* to have one {@link JobLauncher}, {@link JobRepository}, and a single Job implementation.
* It should be noted that using any of the methods that don't conain {@link JobParameters}
* in their signature, will result in one being created with the current system time as a
* parameter.
*
* @author Lucas Ward
* @author Dan Garrette
* @since 2.0
*/
public abstract class AbstractSimpleJobTests {
/** Logger */
protected final Log logger = LogFactory.getLog(getClass());
private JobLauncher launcher;
private JobRepository jobRepository;
private SimpleJob job;
private StepRunner stepRunner;
private Map<String, Step> stepMap = new HashMap<String, Step>();
private List<Step> stepList = new ArrayList<Step>();
@Autowired
public void setLauncher(JobLauncher bootstrap) {
this.launcher = bootstrap;
}
@Autowired
public void setJobRepository(JobRepository jobRepository) {
this.jobRepository = jobRepository;
}
@Autowired
public void setJob(SimpleJob job) {
this.job = job;
for (Step step : job.getSteps()) {
stepMap.put(step.getName(), step);
stepList.add(step);
}
}
public StepRunner getStepRunner() {
if(stepRunner == null){
stepRunner = new StepRunner(launcher, jobRepository);
}
return stepRunner;
}
public SimpleJob getJob() {
return job;
}
/**
* Public getter for the launcher.
*
* @return the launcher
*/
protected JobLauncher getLauncher() {
return launcher;
}
public Step getStep(String stepName){
if(!stepMap.containsKey(stepName)){
throw new IllegalStateException("No Step found with name: [" + stepName + "]");
}
return stepMap.get(stepName);
}
/**
* Launch the entire job, including all steps, in order.
*
* @return JobExecution, so that the test may validate the exit status
*/
public JobExecution launchJob() {
return this.launchJob(this.makeUniqueJobParameters());
}
/**
* Launch the entire job, including all steps, in order.
*
* @param jobParameters
* @return JobExecution, so that the test may validate the exit status
*/
public JobExecution launchJob(JobParameters jobParameters) {
try {
return getLauncher().run(job, jobParameters);
} catch (JobExecutionAlreadyRunningException e) {
throw new RuntimeException(e);
} catch (JobRestartException e) {
throw new RuntimeException(e);
} catch (JobInstanceAlreadyCompleteException e) {
throw new RuntimeException(e);
}
}
/**
* Launch just the specified step in the job.
*
* @param stepName
*/
public JobExecution launchStep(String stepName) {
return getStepRunner().launchStep(getStep(stepName));
}
/**
* Launch just the specified step in the job.
*
* @param stepName
* @param jobParameters
*/
public JobExecution launchStep(String stepName, JobParameters jobParameters) {
return getStepRunner().launchStep(getStep(stepName), jobParameters);
}
/**
* @return a new JobParameters object containing only a parameter for the
* current timestamp, to ensure that the job instance will be unique
*/
private JobParameters makeUniqueJobParameters() {
Map<String, JobParameter> parameters = new HashMap<String, JobParameter>();
parameters.put("timestamp", new JobParameter(new Date().getTime()));
return new JobParameters(parameters);
}
}

View File

@@ -0,0 +1,117 @@
package org.springframework.batch.test;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameter;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.SimpleJob;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.JobRestartException;
/**
* Utility class for executing steps outside of a {@link Job}. This is useful
* in end to end testing in order to allow for the testing of a step individually
* without running every Step in a job.
*
* <ul>
* <li><b>launchStep(Step step)</b>: Launch the step with new parameters each time. (The current system
* time will be used)
* <li><b>launchStep(Step step, JobParameters jobParameters)</b>: Launch the specified step with the provided
* JobParameters. This may be useful if your step requires a certain parameter during runtime.
* </ul>
*
* It should be noted that any checked exceptions encountered while running the Step will wrapped with
* RuntimeException. Any checked exception thrown will be due to a framework error, not the logic of the
* step, and thus requiring a throws declaration in clients of this class is unnecessary.
*
* @author Dan Garrette
* @author Lucas Ward
* @since 2.0
* @see SimpleJob
*/
public class StepRunner{
/** Logger */
protected final Log logger = LogFactory.getLog(getClass());
private JobLauncher launcher;
private JobRepository jobRepository;
public StepRunner(JobLauncher launcher, JobRepository jobRepository) {
this.launcher = launcher;
this.jobRepository = jobRepository;
}
/**
* Launcher
*
* @param stepName
*/
public JobExecution launchStep(Step step) {
return this.launchStep(step, this.makeUniqueJobParameters());
}
/**
* Launch just the specified step in the job.
*
* @param stepName
* @param jobParameters
*/
public JobExecution launchStep(Step step, JobParameters jobParameters) {
//
// Create a fake job
//
SimpleJob job = new SimpleJob();
job.setName("TestJob");
job.setJobRepository(this.jobRepository);
List<Step> stepsToExecute = new ArrayList<Step>();
stepsToExecute.add(step);
job.setSteps(stepsToExecute);
//
// Launch the job
//
return this.launchJob(job, jobParameters);
}
/**
* Launch the given job
*
* @param job
* @param jobParameters
*/
private JobExecution launchJob(Job job, JobParameters jobParameters) {
try {
return this.launcher.run(job, jobParameters);
} catch (JobExecutionAlreadyRunningException e) {
throw new RuntimeException(e);
} catch (JobRestartException e) {
throw new RuntimeException(e);
} catch (JobInstanceAlreadyCompleteException e) {
throw new RuntimeException(e);
}
}
/**
* @return a new JobParameters object containing only a parameter for the
* current timestamp, to ensure that the job instance will be unique
*/
private JobParameters makeUniqueJobParameters() {
Map<String, JobParameter> parameters = new HashMap<String, JobParameter>();
parameters.put("timestamp", new JobParameter(new Date().getTime()));
return new JobParameters(parameters);
}
}

View File

@@ -0,0 +1,63 @@
package org.springframework.batch.test;
import static org.junit.Assert.assertEquals;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.BatchStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/simple-job-launcher-context.xml", "/jobs/sampleJob.xml" })
public class SampleJobTests extends AbstractSimpleJobTests {
private SimpleJdbcTemplate jdbcTemplate;
@Autowired
public void setJdbcTemplate(SimpleJdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Before
public void setUp() {
this.jdbcTemplate.update("create table TESTS (ID integer, NAME varchar(40))");
}
@After
public void tearDown() {
this.jdbcTemplate.update("drop table TESTS");
}
@Test
public void test1() {
assertEquals(BatchStatus.COMPLETED, this.launchStep("step1").getStatus());
this.verifyTasklet(1);
}
private void verifyTasklet(int id) {
assertEquals(id, jdbcTemplate.queryForInt("SELECT ID from TESTS where NAME = 'SampleTasklet" + id + "'"));
}
@Test
public void testJob() {
assertEquals(BatchStatus.COMPLETED,this.launchJob().getStatus());
this.verifyTasklet(1);
this.verifyTasklet(2);
}
@Test(expected=IllegalStateException.class)
public void voidTestNonExistentStep(){
launchStep("nonExistent");
}
@Test
public void test2() {
assertEquals(BatchStatus.COMPLETED, this.launchStep("step2").getStatus());
this.verifyTasklet(2);
}
}

View File

@@ -0,0 +1,60 @@
package org.springframework.batch.test;
import static org.junit.Assert.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/simple-job-launcher-context.xml", "/jobs/sampleJob.xml" })
public class SampleStepTests implements ApplicationContextAware{
@Autowired
private SimpleJdbcTemplate jdbcTemplate;
private StepRunner stepRunner;
private ApplicationContext context;
@Autowired
private JobLauncher jobLauncher;
@Autowired
private JobRepository jobRepository;
@Before
public void setUp() {
jdbcTemplate.update("create table TESTS (ID integer, NAME varchar(40))");
stepRunner = new StepRunner(jobLauncher, jobRepository);
}
@After
public void tearDown() {
this.jdbcTemplate.update("drop table TESTS");
}
@Test
public void testTasklet() {
Step step = (Step)context.getBean("step2");
assertEquals(BatchStatus.COMPLETED, stepRunner.launchStep(step).getStatus());
assertEquals(2, jdbcTemplate.queryForInt("SELECT ID from TESTS where NAME = 'SampleTasklet2'"));
}
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.context = applicationContext;
}
}

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.test.common;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
/**
* Wraps calls for 'Processing' methods which output a single Object to write
* the string representation of the object to the log.
*
* @author Lucas Ward
*/
public class LogAdvice {
private static Log log = LogFactory.getLog(LogAdvice.class);
/*
* Wraps original method and adds logging both before and after method
*/
public void doBasicLogging(JoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
StringBuffer output = new StringBuffer();
output.append(pjp.getTarget().getClass().getName()).append(": ");
output.append(pjp.toShortString()).append(": ");
for (Object arg : args) {
output.append(arg).append(" ");
}
log.info("Basic: " + output.toString());
}
public void doStronglyTypedLogging(Object item){
log.info("Processed: " + item);
}
}

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.test.jmx;
import javax.management.Notification;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.jmx.export.notification.NotificationPublisher;
import org.springframework.jmx.export.notification.NotificationPublisherAware;
/**
* JMX notification broadcaster
*
* @author Dave Syer
* @since 1.0
*/
public class JobExecutionNotificationPublisher implements ApplicationListener, NotificationPublisherAware {
protected static final Log logger = LogFactory.getLog(JobExecutionNotificationPublisher.class);
private NotificationPublisher notificationPublisher;
private int notificationCount = 0;
/**
* Injection setter.
*
* @see org.springframework.jmx.export.notification.NotificationPublisherAware#setNotificationPublisher(org.springframework.jmx.export.notification.NotificationPublisher)
*/
public void setNotificationPublisher(NotificationPublisher notificationPublisher) {
this.notificationPublisher = notificationPublisher;
}
/**
* If the event is a {@link SimpleMessageApplicationEvent} for open and
* close we log the event at INFO level and send a JMX notification if we
* are also an MBean.
*
* @see ApplicationListener#onApplicationEvent(ApplicationEvent)
*/
public void onApplicationEvent(ApplicationEvent applicationEvent) {
if (applicationEvent instanceof SimpleMessageApplicationEvent) {
String message = applicationEvent.toString();
logger.info(message);
publish(message);
}
}
/**
* Publish the provided message to an external listener if there is one.
*
* @param message the message to publish
*/
private void publish(String message) {
if (notificationPublisher != null) {
Notification notification = new Notification("JobExecutionApplicationEvent", this, notificationCount++,
message);
/*
* We can't create a notification with a null source, but we can set
* it to null after creation(!). We want it to be null so that
* Spring will replace it automatically with the ObjectName (in
* ModelMBeanNotificationPublisher).
*/
notification.setSource(null);
notificationPublisher.sendNotification(notification);
}
}
}

View File

@@ -0,0 +1,25 @@
package org.springframework.batch.test.jmx;
import org.springframework.context.ApplicationEvent;
/**
* @author Dave Syer
*
*/
public class SimpleMessageApplicationEvent extends ApplicationEvent {
private String message;
public SimpleMessageApplicationEvent(Object source, String message) {
super(source);
this.message = message;
}
/* (non-Javadoc)
* @see java.util.EventObject#toString()
*/
public String toString() {
return "message=["+message+"], " + super.toString();
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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.test.jmx;
import org.aspectj.lang.JoinPoint;
import org.springframework.batch.core.StepExecution;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
/**
* Wraps calls for methods taking {@link StepExecution} as an argument and
* publishes notifications in the form of {@link ApplicationEvent}.
*
* @author Dave Syer
*/
public class StepExecutionApplicationEventAdvice implements ApplicationEventPublisherAware {
private ApplicationEventPublisher applicationEventPublisher;
/*
* (non-Javadoc)
* @see org.springframework.context.ApplicationEventPublisherAware#setApplicationEventPublisher(org.springframework.context.ApplicationEventPublisher)
*/
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
public void before(JoinPoint jp, StepExecution stepExecution) {
String msg = "Before: " + jp.toShortString() + " with: " + stepExecution;
publish(jp.getTarget(), msg);
}
public void after(JoinPoint jp, StepExecution stepExecution) {
String msg = "After: " + jp.toShortString() + " with: " + stepExecution;
publish(jp.getTarget(), msg);
}
public void onError(JoinPoint jp, StepExecution stepExecution, Throwable t) {
String msg = "Error in: " + jp.toShortString() + " with: " + stepExecution + " (" + t.getClass() + ":" + t.getMessage() + ")";
publish(jp.getTarget(), msg);
}
/*
* Publish a {@link SimpleMessageApplicationEvent} with the given
* parameters.
*/
private void publish(Object source, String message) {
applicationEventPublisher.publishEvent(new SimpleMessageApplicationEvent(source, message));
}
}

View File

@@ -0,0 +1,26 @@
package org.springframework.batch.test.sample;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.AttributeAccessor;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
public class SampleTasklet implements Tasklet {
@Autowired
private SimpleJdbcTemplate jdbcTemplate;
private int id = 0;
public SampleTasklet(int id) {
this.id = id;
}
public ExitStatus execute(StepContribution contribution, AttributeAccessor attributes) throws Exception {
System.err.println("SampleTasklet1.execute()");
this.jdbcTemplate.update("insert into TESTS(ID, NAME) values (?, 'SampleTasklet" + id + "')", id);
return ExitStatus.FINISHED;
}
}

View File

@@ -0,0 +1,191 @@
/*
* 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 test.jdbc.datasource;
import java.io.IOException;
import java.util.List;
import javax.sql.DataSource;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* Wrapper for a {@link DataSource} that can run scripts on start up and shut
* down. Us as a bean definition <br/><br/>
*
* Run this class to initialize a database in a running server process.
* Make sure the server is running first by launching the "hsql-server" from the
* <code>hsql.server</code> project. Then you can right click in Eclipse and
* Run As -&gt; Java Application. Do the same any time you want to wipe the
* database and start again.
*
* @author Dave Syer
*
*/
public class DataSourceInitializer implements InitializingBean, DisposableBean {
private static final Log logger = LogFactory.getLog(DataSourceInitializer.class);
private Resource[] initScripts;
private Resource[] destroyScripts;
private DataSource dataSource;
private boolean ignoreFailedDrop = true;
private static boolean initialized = false;
/**
* Main method as convenient entry point.
*
* @param args
*/
public static void main(String... args) {
new ClassPathXmlApplicationContext(ClassUtils.addResourcePathToPackagePath(DataSourceInitializer.class,
DataSourceInitializer.class.getSimpleName() + "-context.xml"));
}
/**
* @throws Throwable
* @see java.lang.Object#finalize()
*/
protected void finalize() throws Throwable {
super.finalize();
initialized = false;
logger.debug("finalize called");
}
public void destroy() {
if (destroyScripts==null) return;
for (int i = 0; i < destroyScripts.length; i++) {
Resource destroyScript = initScripts[i];
try {
doExecuteScript(destroyScript);
}
catch (Exception e) {
if (logger.isDebugEnabled()) {
logger.warn("Could not execute destroy script [" + destroyScript + "]", e);
}
else {
logger.warn("Could not execute destroy script [" + destroyScript + "]");
}
}
}
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(dataSource);
initialize();
}
private void initialize() {
if (!initialized) {
destroy();
if (initScripts != null) {
for (int i = 0; i < initScripts.length; i++) {
Resource initScript = initScripts[i];
doExecuteScript(initScript);
}
}
initialized = true;
}
}
private void doExecuteScript(final Resource scriptResource) {
if (scriptResource == null || !scriptResource.exists())
return;
TransactionTemplate transactionTemplate = new TransactionTemplate(new DataSourceTransactionManager(dataSource));
transactionTemplate.execute(new TransactionCallback() {
@SuppressWarnings("unchecked")
public Object doInTransaction(TransactionStatus status) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
String[] scripts;
try {
scripts = StringUtils.delimitedListToStringArray(stripComments(IOUtils.readLines(scriptResource
.getInputStream())), ";");
}
catch (IOException e) {
throw new BeanInitializationException("Cannot load script from [" + scriptResource + "]", e);
}
for (int i = 0; i < scripts.length; i++) {
String script = scripts[i].trim();
if (StringUtils.hasText(script)) {
try {
jdbcTemplate.execute(script);
}
catch (DataAccessException e) {
if (ignoreFailedDrop && script.toLowerCase().startsWith("drop")) {
logger.debug("DROP script failed (ignoring): " + script);
}
else {
throw e;
}
}
}
}
return null;
}
});
}
private String stripComments(List<String> list) {
StringBuffer buffer = new StringBuffer();
for (String line : list) {
if (!line.startsWith("//") && !line.startsWith("--")) {
buffer.append(line + "\n");
}
}
return buffer.toString();
}
public void setInitScripts(Resource[] initScripts) {
this.initScripts = initScripts;
}
public void setDestroyScripts(Resource[] destroyScripts) {
this.destroyScripts = destroyScripts;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void setIgnoreFailedDrop(boolean ignoreFailedDrop) {
this.ignoreFailedDrop = ignoreFailedDrop;
}
}

View File

@@ -0,0 +1,21 @@
# Placeholders batch.*
# for HSQLDB:
batch.jdbc.driver=org.hsqldb.jdbcDriver
batch.jdbc.url=jdbc:hsqldb:mem:testdb;sql.enforce_strict_size=true
# use this one for a separate server process so you can inspect the results
# (or add it to system properties with -D to override at run time).
# batch.jdbc.url=jdbc:hsqldb:hsql://localhost:9005/samples
batch.jdbc.user=sa
batch.jdbc.password=
batch.schema=
batch.jndi.name=
batch.naming.factory.initial=
batch.naming.provider.url=
batch.schema.script=schema-hsqldb.sql
batch.business.schema.script=business-schema-hsqldb.sql
batch.database.incrementer.class=org.springframework.jdbc.support.incrementer.HsqlMaxValueIncrementer
batch.lob.handler.class=org.springframework.jdbc.support.lob.DefaultLobHandler
# Bean Properties for override
# when not using sequences:
incrementerParent.columnName=ID

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="dataSourceInitializer" class="test.jdbc.datasource.DataSourceInitializer">
<property name="dataSource" ref="dataSource"/>
<property name="initScripts">
<list>
<value>${batch.schema.script}</value>
<value>${batch.business.schema.script}</value>
</list>
</property>
</bean>
</beans>

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd">
<!-- Initialise the database before every test case: -->
<import resource="data-source-context-init.xml" />
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${batch.jdbc.driver}" />
<property name="url" value="${batch.jdbc.url}" />
<property name="username" value="${batch.jdbc.user}" />
<property name="password" value="${batch.jdbc.password}" />
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" lazy-init="true">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- Set up or detect a System property called "environment" used to construct a properties file on the classpath. The default is "hsql". -->
<bean id="environment" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetClass" value="java.lang.System" />
<property name="targetMethod" value="setProperty" />
<property name="arguments">
<list>
<value>environment</value>
<bean class="java.lang.System" factory-method="getProperty">
<constructor-arg>
<value>environment</value>
</constructor-arg>
<!-- The default value of the environment property -->
<constructor-arg>
<value>hsql</value>
</constructor-arg>
</bean>
</list>
</property>
</bean>
<!-- Use this to set additional properties on beans at run time -->
<bean id="overrideProperties" class="org.springframework.beans.factory.config.PropertyOverrideConfigurer"
depends-on="environment">
<property name="location" value="classpath:batch-${environment}.properties" />
<!-- Allow system properties (-D) to override those from file -->
<property name="localOverride" value="true" />
<property name="properties">
<bean class="java.lang.System" factory-method="getProperties" />
</property>
<property name="ignoreInvalidKeys" value="true" />
<property name="order" value="2" />
</bean>
<bean id="placeholderProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
depends-on="environment">
<property name="location" value="classpath:batch-${environment}.properties" />
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="ignoreUnresolvablePlaceholders" value="true" />
<property name="order" value="1" />
</bean>
<bean id="lobHandler" class="${batch.lob.handler.class}" />
<bean id="incrementerParent" class="${batch.database.incrementer.class}">
<property name="dataSource" ref="dataSource" />
<property name="incrementerName" value="ID" />
</bean>
<!--import resource="alt-data-source-context.xml" /-->
</beans>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<bean id="sampleJob" parent="simpleJob">
<property name="steps">
<list>
<bean id="step1" parent="taskletStep">
<property name="tasklet">
<bean class="org.springframework.batch.test.sample.SampleTasklet">
<constructor-arg value="1" />
</bean>
</property>
</bean>
<ref bean="step2" />
</list>
</property>
</bean>
<bean id="step2" parent="taskletStep">
<property name="tasklet">
<bean class="org.springframework.batch.test.sample.SampleTasklet">
<constructor-arg value="2" />
</bean>
</property>
</bean>
</beans>

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<bean id="simpleJob" class="org.springframework.batch.core.job.SimpleJob" abstract="true">
<property name="jobRepository" ref="jobRepository" />
<property name="restartable" value="true" />
</bean>
<bean id="taskletStep" class="org.springframework.batch.core.step.tasklet.TaskletStep" abstract="true">
<property name="transactionManager" ref="transactionManager" />
<property name="jobRepository" ref="jobRepository" />
<property name="allowStartIfComplete" value="true" />
</bean>
<bean id="simpleStep" class="org.springframework.batch.core.step.item.SimpleStepFactoryBean"
abstract="true">
<property name="transactionManager" ref="transactionManager" />
<property name="jobRepository" ref="jobRepository" />
<property name="startLimit" value="100" />
<property name="commitInterval" value="1" />
</bean>
<bean id="skipLimitStep" class="org.springframework.batch.core.step.item.FaultTolerantStepFactoryBean"
parent="simpleStep" abstract="true">
<property name="skipLimit" value="0" />
</bean>
<bean id="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="int[]">
<bean class="org.springframework.batch.support.IntArrayPropertyEditor" />
</entry>
<entry key="org.springframework.batch.item.file.transform.Range[]">
<bean class="org.springframework.batch.item.file.transform.RangeArrayPropertyEditor" />
</entry>
<entry key="java.util.Date">
<bean class="org.springframework.beans.propertyeditors.CustomDateEditor">
<constructor-arg>
<bean class="java.text.SimpleDateFormat">
<constructor-arg value="yyyyMMdd" />
</bean>
</constructor-arg>
<constructor-arg value="false" />
</bean>
</entry>
</map>
</property>
</bean>
</beans>

View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<import resource="classpath:/data-source-context.xml" />
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.simple.SimpleJdbcTemplate">
<constructor-arg ref="dataSource" />
</bean>
<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
</bean>
<bean id="jobRepository" class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="transactionManager" ref="transactionManager" />
<property name="databaseType" value="hsql" />
</bean>
<bean id="simpleJob" class="org.springframework.batch.core.job.SimpleJob" abstract="true">
<property name="jobRepository" ref="jobRepository" />
<property name="restartable" value="true" />
</bean>
<bean id="taskletStep" class="org.springframework.batch.core.step.tasklet.TaskletStep" abstract="true">
<property name="transactionManager" ref="transactionManager" />
<property name="jobRepository" ref="jobRepository" />
<property name="allowStartIfComplete" value="true" />
</bean>
<bean id="itemOrientedStep" class="org.springframework.batch.core.step.item.FaultTolerantStepFactoryBean" abstract="true">
<property name="transactionManager" ref="transactionManager" />
<property name="jobRepository" ref="jobRepository" />
</bean>
<bean id="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="int[]">
<bean class="org.springframework.batch.support.IntArrayPropertyEditor" />
</entry>
<entry key="org.springframework.batch.item.file.transform.Range[]">
<bean class="org.springframework.batch.item.file.transform.RangeArrayPropertyEditor" />
</entry>
<entry key="java.util.Date">
<bean class="org.springframework.beans.propertyeditors.CustomDateEditor">
<constructor-arg>
<bean class="java.text.SimpleDateFormat">
<constructor-arg value="yyyyMMdd" />
</bean>
</constructor-arg>
<constructor-arg value="false" />
</bean>
</entry>
</map>
</property>
</bean>
</beans>