diff --git a/spring-batch-test/.classpath b/spring-batch-test/.classpath new file mode 100755 index 000000000..638acf57b --- /dev/null +++ b/spring-batch-test/.classpath @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/spring-batch-test/.project b/spring-batch-test/.project new file mode 100755 index 000000000..e468810c7 --- /dev/null +++ b/spring-batch-test/.project @@ -0,0 +1,29 @@ + + + spring-batch-test + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.devzuz.q.maven.jdt.core.mavenIncrementalBuilder + + + + + org.springframework.ide.eclipse.core.springbuilder + + + + + + org.springframework.ide.eclipse.core.springnature + org.eclipse.jdt.core.javanature + org.devzuz.q.maven.jdt.core.mavenNature + + diff --git a/spring-batch-test/.springBeans b/spring-batch-test/.springBeans new file mode 100644 index 000000000..6151e7570 --- /dev/null +++ b/spring-batch-test/.springBeans @@ -0,0 +1,30 @@ + + + 1 + + + + + + + src/test/resources/data-source-context-init.xml + src/test/resources/data-source-context.xml + src/test/resources/simple-job-launcher-context.xml + src/test/resources/jobs/sampleJob.xml + src/test/resources/org/springframework/batch/sample/config/common-context.xml + + + + + true + false + + src/test/resources/data-source-context.xml + src/test/resources/data-source-context-init.xml + src/test/resources/jobs/sampleJob.xml + src/test/resources/org/springframework/batch/sample/config/common-context.xml + src/test/resources/simple-job-launcher-context.xml + + + + diff --git a/spring-batch-test/pom.xml b/spring-batch-test/pom.xml new file mode 100755 index 000000000..d8cf3917a --- /dev/null +++ b/spring-batch-test/pom.xml @@ -0,0 +1,86 @@ + + + + spring-batch + org.springframework.batch + 2.0.0.CI-SNAPSHOT + .. + + 4.0.0 + spring-batch-test + Core + 2.0.0.CI-SNAPSHOT + Domain for batch job testing + + + + com.springsource.bundlor + com.springsource.bundlor.maven + + + + + + + clover + + + + + maven-clover-plugin + + + + + + + maven-clover-plugin + + + + + + + + org.springframework.batch + org.springframework.batch.core + ${project.version} + + + junit + junit + + + org.springframework + spring-test + + + commons-io + commons-io + + + org.springframework + spring-jdbc + + + commons-dbcp + commons-dbcp + + + hsqldb + hsqldb + + + commons-collections + commons-collections + + + + + + org.codehaus.mojo + emma-maven-plugin + + + + \ No newline at end of file diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/AbstractSimpleJobTests.java b/spring-batch-test/src/main/java/org/springframework/batch/test/AbstractSimpleJobTests.java new file mode 100755 index 000000000..fb1fae886 --- /dev/null +++ b/spring-batch-test/src/main/java/org/springframework/batch/test/AbstractSimpleJobTests.java @@ -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 stepMap = new HashMap(); + private List stepList = new ArrayList(); + + @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 parameters = new HashMap(); + parameters.put("timestamp", new JobParameter(new Date().getTime())); + return new JobParameters(parameters); + } +} diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/StepRunner.java b/spring-batch-test/src/main/java/org/springframework/batch/test/StepRunner.java new file mode 100755 index 000000000..dfb50bb2b --- /dev/null +++ b/spring-batch-test/src/main/java/org/springframework/batch/test/StepRunner.java @@ -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. + * + *
    + *
  • launchStep(Step step): Launch the step with new parameters each time. (The current system + * time will be used) + *
  • launchStep(Step step, JobParameters jobParameters): Launch the specified step with the provided + * JobParameters. This may be useful if your step requires a certain parameter during runtime. + *
+ * + * 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 stepsToExecute = new ArrayList(); + 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 parameters = new HashMap(); + parameters.put("timestamp", new JobParameter(new Date().getTime())); + return new JobParameters(parameters); + } +} diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/SampleJobTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/SampleJobTests.java new file mode 100755 index 000000000..c9b1e867e --- /dev/null +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/SampleJobTests.java @@ -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); + } +} diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/SampleStepTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/SampleStepTests.java new file mode 100755 index 000000000..3af16a91c --- /dev/null +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/SampleStepTests.java @@ -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; + } + +} diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/common/LogAdvice.java b/spring-batch-test/src/test/java/org/springframework/batch/test/common/LogAdvice.java new file mode 100755 index 000000000..146045bad --- /dev/null +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/common/LogAdvice.java @@ -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); + } + +} diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/jmx/JobExecutionNotificationPublisher.java b/spring-batch-test/src/test/java/org/springframework/batch/test/jmx/JobExecutionNotificationPublisher.java new file mode 100755 index 000000000..1fe1d04b5 --- /dev/null +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/jmx/JobExecutionNotificationPublisher.java @@ -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); + } + } + +} diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/jmx/SimpleMessageApplicationEvent.java b/spring-batch-test/src/test/java/org/springframework/batch/test/jmx/SimpleMessageApplicationEvent.java new file mode 100755 index 000000000..ba1a9ea64 --- /dev/null +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/jmx/SimpleMessageApplicationEvent.java @@ -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(); + } + +} \ No newline at end of file diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/jmx/StepExecutionApplicationEventAdvice.java b/spring-batch-test/src/test/java/org/springframework/batch/test/jmx/StepExecutionApplicationEventAdvice.java new file mode 100755 index 000000000..815552b00 --- /dev/null +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/jmx/StepExecutionApplicationEventAdvice.java @@ -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)); + } + +} diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/sample/SampleTasklet.java b/spring-batch-test/src/test/java/org/springframework/batch/test/sample/SampleTasklet.java new file mode 100755 index 000000000..663f5e6a2 --- /dev/null +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/sample/SampleTasklet.java @@ -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; + } +} diff --git a/spring-batch-test/src/test/java/test/jdbc/datasource/DataSourceInitializer.java b/spring-batch-test/src/test/java/test/jdbc/datasource/DataSourceInitializer.java new file mode 100755 index 000000000..ccfdef88f --- /dev/null +++ b/spring-batch-test/src/test/java/test/jdbc/datasource/DataSourceInitializer.java @@ -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

+ * + * 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 + * hsql.server project. Then you can right click in Eclipse and + * Run As -> 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 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; + } + +} diff --git a/spring-batch-test/src/test/resources/batch-hsql.properties b/spring-batch-test/src/test/resources/batch-hsql.properties new file mode 100755 index 000000000..7f28d4f4a --- /dev/null +++ b/spring-batch-test/src/test/resources/batch-hsql.properties @@ -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 diff --git a/spring-batch-test/src/test/resources/data-source-context-init.xml b/spring-batch-test/src/test/resources/data-source-context-init.xml new file mode 100755 index 000000000..d6103fd9c --- /dev/null +++ b/spring-batch-test/src/test/resources/data-source-context-init.xml @@ -0,0 +1,16 @@ + + + + + + + + ${batch.schema.script} + ${batch.business.schema.script} + + + + + \ No newline at end of file diff --git a/spring-batch-test/src/test/resources/data-source-context.xml b/spring-batch-test/src/test/resources/data-source-context.xml new file mode 100755 index 000000000..f4e0730cb --- /dev/null +++ b/spring-batch-test/src/test/resources/data-source-context.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + environment + + + environment + + + + hsql + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-batch-test/src/test/resources/jobs/sampleJob.xml b/spring-batch-test/src/test/resources/jobs/sampleJob.xml new file mode 100755 index 000000000..e569d7db3 --- /dev/null +++ b/spring-batch-test/src/test/resources/jobs/sampleJob.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-batch-test/src/test/resources/org/springframework/batch/sample/config/common-context.xml b/spring-batch-test/src/test/resources/org/springframework/batch/sample/config/common-context.xml new file mode 100755 index 000000000..366f8c8aa --- /dev/null +++ b/spring-batch-test/src/test/resources/org/springframework/batch/sample/config/common-context.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-batch-test/src/test/resources/simple-job-launcher-context.xml b/spring-batch-test/src/test/resources/simple-job-launcher-context.xml new file mode 100755 index 000000000..e9405da86 --- /dev/null +++ b/spring-batch-test/src/test/resources/simple-job-launcher-context.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file