From ce16ad2b03b2370498e192ff7ad48052a4951f77 Mon Sep 17 00:00:00 2001 From: Michael Minella Date: Tue, 30 Jul 2013 11:00:38 -0500 Subject: [PATCH] BATCH-2007: Added a JobOperator implementation that complies with JSR-352. * Added JsrJobOperator * Added a ParametersConverter (and JSR-352 implementation) that converts parameters as specified by the JSR to JobParameters and back * Added a method to the JobRepository to allow the direct creation of a JobInstance (since the JSR requires a new instance for each call to JobOperator#start * Added a base context to be bootstrapped when the JobOperator is first referenced. It provides things like a JobRepository, etc. --- .../StepExecutionSerializationUtilsTests.java | 5 +- .../batch/core/JobExecution.java | 23 +- .../batch/core/explore/JobExplorer.java | 33 +- .../explore/support/SimpleJobExplorer.java | 11 +- .../batch/core/jsr/JobContext.java | 34 +- .../batch/core/jsr/JobExecution.java | 34 +- .../core/jsr/JsrJobParametersConverter.java | 132 +++++ .../batch/core/jsr/ParametersConverter.java | 51 ++ .../batch/core/jsr/SimpleMetric.java | 63 +++ .../batch/core/jsr/StepContext.java | 111 ++++ .../batch/core/jsr/StepExecution.java | 99 ++++ .../batch/core/jsr/launch/JsrJobOperator.java | 472 ++++++++++++++---- .../launch/support/SimpleJobLauncher.java | 21 +- .../launch/support/SimpleJobOperator.java | 3 +- .../batch/core/repository/JobRepository.java | 56 ++- .../repository/dao/JdbcJobExecutionDao.java | 20 +- .../repository/dao/JdbcJobInstanceDao.java | 18 + .../core/repository/dao/JobInstanceDao.java | 38 +- .../repository/dao/MapJobInstanceDao.java | 25 +- .../support/SimpleJobRepository.java | 36 +- .../src/main/resources/baseContext.xml | 72 +++ .../src/main/resources/batch-hsql.properties | 19 + .../src/main/resources/beanRefContext.xml | 30 ++ .../springframework/batch/core/schema-db2.sql | 1 + .../batch/core/schema-derby.sql | 1 + .../springframework/batch/core/schema-h2.sql | 1 + .../batch/core/schema-hsqldb.sql | 1 + .../batch/core/schema-mysql.sql | 1 + .../batch/core/schema-oracle10g.sql | 1 + .../batch/core/schema-postgresql.sql | 1 + .../batch/core/schema-sqlf.sql | 1 + .../batch/core/schema-sqlserver.sql | 1 + .../batch/core/schema-sybase.sql | 1 + .../batch/core/JobExecutionTests.java | 10 +- .../batch/core/JobParametersBuilderTests.java | 25 - .../batch/core/JobParametersTests.java | 15 - .../batch/core/StepExecutionTests.java | 2 +- .../xml/AbstractJobParserTests.java | 8 +- .../configuration/xml/DummyJobRepository.java | 11 + .../support/SimpleJobExplorerTests.java | 38 +- .../batch/core/jsr/JobContextTests.java | 9 +- .../batch/core/jsr/JobExecutionTests.java | 4 +- .../jsr/JsrJobParametersConverterTests.java | 110 ++++ .../core/jsr/ParametersConverterSupport.java | 40 ++ .../batch/core/jsr/SimpleMetricTests.java | 24 + .../batch/core/jsr/StepContextTests.java | 94 ++++ .../batch/core/jsr/StepExecutionTests.java | 100 ++++ .../core/jsr/launch/JsrJobOperatorTests.java | 340 +++++++++++++ .../jsr/step/batchlet/RestartBatchlet.java | 23 + .../support/CommandLineJobRunnerTests.java | 27 +- .../support/SimpleJobOperatorTests.java | 27 +- .../repository/dao/AbstractJobDaoTests.java | 4 +- .../dao/AbstractStepExecutionDaoTests.java | 2 +- .../dao/JdbcJobInstanceDaoTests.java | 4 +- .../support/SimpleJobRepositoryTests.java | 6 +- .../core/scope/context/ChunkContextTests.java | 2 +- .../core/scope/context/StepContextTests.java | 2 +- .../batch/core/step/JobRepositorySupport.java | 13 +- .../step/item/TaskletStepExceptionTests.java | 12 + .../batch-jobs/jsrJobOperatorTestJob.xml | 6 + .../jsrJobOperatorTestRestartJob.xml | 6 + .../src/test/resources/META-INF/batch.xml | 4 + .../repository/dao/schema-prefix-hsqldb.sql | 1 + .../batch/test/MetaDataInstanceFactory.java | 2 +- 64 files changed, 2098 insertions(+), 289 deletions(-) create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/jsr/JsrJobParametersConverter.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/jsr/ParametersConverter.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/jsr/SimpleMetric.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/jsr/StepContext.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/jsr/StepExecution.java create mode 100644 spring-batch-core/src/main/resources/baseContext.xml create mode 100644 spring-batch-core/src/main/resources/batch-hsql.properties create mode 100644 spring-batch-core/src/main/resources/beanRefContext.xml create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/jsr/JsrJobParametersConverterTests.java create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/jsr/ParametersConverterSupport.java create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/jsr/SimpleMetricTests.java create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/jsr/StepContextTests.java create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/jsr/StepExecutionTests.java create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/jsr/launch/JsrJobOperatorTests.java create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/jsr/step/batchlet/RestartBatchlet.java create mode 100644 spring-batch-core/src/test/resources/META-INF/batch-jobs/jsrJobOperatorTestJob.xml create mode 100644 spring-batch-core/src/test/resources/META-INF/batch-jobs/jsrJobOperatorTestRestartJob.xml create mode 100644 spring-batch-core/src/test/resources/META-INF/batch.xml diff --git a/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/step/StepExecutionSerializationUtilsTests.java b/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/step/StepExecutionSerializationUtilsTests.java index c2df1a748..ebda63632 100644 --- a/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/step/StepExecutionSerializationUtilsTests.java +++ b/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/step/StepExecutionSerializationUtilsTests.java @@ -41,7 +41,7 @@ public class StepExecutionSerializationUtilsTests { @Test public void testCycle() throws Exception { StepExecution stepExecution = new StepExecution("step", new JobExecution(new JobInstance(123L, - "job"), 321L, new JobParameters()), 11L); + "job"), 321L, new JobParameters(), null), 11L); stepExecution.getExecutionContext().put("foo.bar.spam", 123); StepExecution result = getCopy(stepExecution); assertEquals(stepExecution, result); @@ -58,9 +58,10 @@ public class StepExecutionSerializationUtilsTests { CompletionService completionService = new ExecutorCompletionService(executor); for (int i = 0; i < repeats; i++) { - final JobExecution jobExecution = new JobExecution(new JobInstance(123L, "job"), 321L, new JobParameters()); + final JobExecution jobExecution = new JobExecution(new JobInstance(123L, "job"), 321L, new JobParameters(), null); for (int j = 0; j < threads; j++) { completionService.submit(new Callable() { + @Override public StepExecution call() throws Exception { final StepExecution stepExecution = jobExecution.createStepExecution("step"); stepExecution.getExecutionContext().put("foo.bar.spam", 123); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecution.java b/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecution.java index ba50b838f..cbc9e8a9c 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecution.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecution.java @@ -62,16 +62,27 @@ public class JobExecution extends Entity { private transient volatile List failureExceptions = new CopyOnWriteArrayList(); + private final String jobConfigurationName; + /** * Because a JobExecution isn't valid unless the job is set, this * constructor is the only valid one from a modeling point of view. * * @param job the job of which this execution is a part */ - public JobExecution(JobInstance job, Long id, JobParameters jobParameters) { + public JobExecution(JobInstance job, Long id, JobParameters jobParameters, String jobConfigurationName) { super(id); this.jobInstance = job; this.jobParameters = jobParameters == null ? new JobParameters() : jobParameters; + this.jobConfigurationName = jobConfigurationName; + } + + public JobExecution(JobInstance job, JobParameters jobParameters, String jobConfigurationName) { + this(job, null, jobParameters, jobConfigurationName); + } + + public JobExecution(Long id, JobParameters jobParameters, String jobConfigurationName) { + this(null, id, jobParameters, jobConfigurationName); } /** @@ -80,15 +91,15 @@ public class JobExecution extends Entity { * @param job the enclosing {@link JobInstance} */ public JobExecution(JobInstance job, JobParameters jobParameters) { - this(job, null, jobParameters); + this(job, null, jobParameters, null); } public JobExecution(Long id, JobParameters jobParameters) { - this(null, id, jobParameters); + this(null, id, jobParameters, null); } public JobExecution(Long id) { - this(null, id, null); + this(null, id, null, null); } public JobParameters getJobParameters() { @@ -256,6 +267,10 @@ public class JobExecution extends Entity { this.createTime = createTime; } + public String getJobConfigurationName() { + return this.jobConfigurationName; + } + /** * Package private method for re-constituting the step executions from * existing instances. diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/JobExplorer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/explore/JobExplorer.java index f1d81479d..c94a12741 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/JobExplorer.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/explore/JobExplorer.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2007 the original author or authors. + * Copyright 2006-2013 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. @@ -21,15 +21,17 @@ import java.util.Set; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobInstance; import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.launch.NoSuchJobException; import org.springframework.batch.item.ExecutionContext; /** * Entry point for browsing executions of running or historical jobs and steps. * Since the data may be re-hydrated from persistent storage, it may not contain * volatile fields that would have been present when the execution was active. - * + * * @author Dave Syer - * + * @author Michael Minella + * * @since 2.0 */ public interface JobExplorer { @@ -37,7 +39,7 @@ public interface JobExplorer { /** * Fetch {@link JobInstance} values in descending order of creation (and * therefore usually of first execution). - * + * * @param jobName the name of the job to query * @param start the start index of the instances to return * @param count the maximum number of instances to return @@ -51,7 +53,7 @@ public interface JobExplorer { * the parent {@link JobInstance} and associated {@link ExecutionContext} * and {@link StepExecution} instances (also including their execution * contexts). - * + * * @param executionId the job execution id * @return the {@link JobExecution} with this id, or null if not found */ @@ -62,11 +64,11 @@ public interface JobExplorer { * {@link JobExecution} id. The execution context for the step should be * available in the result, and the parent job execution should have its * primitive properties, but may not contain the job instance information. - * + * * @param jobExecutionId the parent job execution id * @param stepExecutionId the step execution id * @return the {@link StepExecution} with this id, or null if not found - * + * * @see #getJobExecution(Long) */ StepExecution getStepExecution(Long jobExecutionId, Long stepExecutionId); @@ -82,7 +84,7 @@ public interface JobExplorer { * executions may not be fully hydrated (e.g. their execution context may be * missing), depending on the implementation. Use * {@link #getStepExecution(Long, Long)} to hydrate them in that case. - * + * * @param jobInstance the {@link JobInstance} to query * @return the set of all executions for the specified {@link JobInstance} */ @@ -93,7 +95,7 @@ public interface JobExplorer { * not be fully hydrated (e.g. their execution context may be missing), * depending on the implementation. Use * {@link #getStepExecution(Long, Long)} to hydrate them in that case. - * + * * @param jobName the name of the job * @return the set of running executions for jobs with the specified name */ @@ -102,9 +104,20 @@ public interface JobExplorer { /** * Query the repository for all unique {@link JobInstance} names (sorted * alphabetically). - * + * * @return the set of job names that have been executed */ List getJobNames(); + /** + * Query the repository for the number of unique {@link JobInstance}s + * associated with the supplied job name. + * + * @param jobName the name of the job to query for + * @return the number of {@link JobInstance}s that exist within the + * associated job repository + * @throws NoSuchJobException + */ + int getJobInstanceCount(String jobName) throws NoSuchJobException; + } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/SimpleJobExplorer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/SimpleJobExplorer.java index 93bad72fa..82479888b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/SimpleJobExplorer.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/SimpleJobExplorer.java @@ -23,6 +23,7 @@ import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobInstance; import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.launch.NoSuchJobException; import org.springframework.batch.core.repository.dao.ExecutionContextDao; import org.springframework.batch.core.repository.dao.JobExecutionDao; import org.springframework.batch.core.repository.dao.JobInstanceDao; @@ -33,6 +34,7 @@ import org.springframework.batch.core.repository.dao.StepExecutionDao; * * @author Dave Syer * @author Lucas Ward + * @author Michael Minella * * @see JobExplorer * @see JobInstanceDao @@ -180,6 +182,14 @@ public class SimpleJobExplorer implements JobExplorer { return jobInstanceDao.getJobNames(); } + /* (non-Javadoc) + * @see org.springframework.batch.core.explore.JobExplorer#getJobInstanceCount(java.lang.String) + */ + @Override + public int getJobInstanceCount(String jobName) throws NoSuchJobException { + return jobInstanceDao.getJobInstanceCount(jobName); + } + /* * Find all dependencies for a JobExecution, including JobInstance (which * requires JobParameters) plus StepExecutions @@ -198,5 +208,4 @@ public class SimpleJobExplorer implements JobExplorer { stepExecution.setExecutionContext(ecDao.getExecutionContext(stepExecution)); } } - } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/JobContext.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/JobContext.java index bb2b79827..42293bc98 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/JobContext.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/JobContext.java @@ -35,56 +35,86 @@ public class JobContext implements javax.batch.runtime.context.JobContext { private JobExecution jobExecution; private Object transientUserData; + private ParametersConverter jobParametersConverter; /** * @param jobExecution for the related job */ - public JobContext(JobExecution jobExecution) { + public JobContext(JobExecution jobExecution, ParametersConverter jobParametersConverter) { Assert.notNull(jobExecution, "A JobExecution is required"); + Assert.notNull(jobParametersConverter, "A ParametersConverter is required"); this.jobExecution = jobExecution; + this.jobParametersConverter = jobParametersConverter; } + /* (non-Javadoc) + * @see javax.batch.runtime.context.JobContext#getJobName() + */ @Override public String getJobName() { return jobExecution.getJobInstance().getJobName(); } + /* (non-Javadoc) + * @see javax.batch.runtime.context.JobContext#getTransientUserData() + */ @Override public Object getTransientUserData() { return transientUserData; } + /* (non-Javadoc) + * @see javax.batch.runtime.context.JobContext#setTransientUserData(java.lang.Object) + */ @Override public void setTransientUserData(Object data) { transientUserData = data; } + /* (non-Javadoc) + * @see javax.batch.runtime.context.JobContext#getInstanceId() + */ @Override public long getInstanceId() { return jobExecution.getJobInstance().getId(); } + /* (non-Javadoc) + * @see javax.batch.runtime.context.JobContext#getExecutionId() + */ @Override public long getExecutionId() { return jobExecution.getId(); } + /* (non-Javadoc) + * @see javax.batch.runtime.context.JobContext#getProperties() + */ @Override public Properties getProperties() { - return jobExecution.getJobParameters().toProperties(); + return jobParametersConverter.convert(this.jobExecution.getJobParameters()); } + /* (non-Javadoc) + * @see javax.batch.runtime.context.JobContext#getBatchStatus() + */ @Override public BatchStatus getBatchStatus() { return jobExecution.getStatus().getBatchStatus(); } + /* (non-Javadoc) + * @see javax.batch.runtime.context.JobContext#getExitStatus() + */ @Override public String getExitStatus() { return jobExecution.getExitStatus().getExitCode(); } + /* (non-Javadoc) + * @see javax.batch.runtime.context.JobContext#setExitStatus(java.lang.String) + */ @Override public void setExitStatus(String status) { jobExecution.setExitStatus(new ExitStatus(status)); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/JobExecution.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/JobExecution.java index b489a30cf..99a4c14cb 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/JobExecution.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/JobExecution.java @@ -32,57 +32,87 @@ import org.springframework.util.Assert; public class JobExecution implements javax.batch.runtime.JobExecution { private org.springframework.batch.core.JobExecution execution; + private ParametersConverter parametersConverter; /** * @param execution for all information to be delegated from */ - public JobExecution(org.springframework.batch.core.JobExecution execution) { + public JobExecution(org.springframework.batch.core.JobExecution execution, ParametersConverter parametersConverter) { Assert.notNull(execution, "A JobExecution is required"); this.execution = execution; + + this.parametersConverter = parametersConverter; } + /* (non-Javadoc) + * @see javax.batch.runtime.JobExecution#getExecutionId() + */ @Override public long getExecutionId() { return this.execution.getId(); } + /* (non-Javadoc) + * @see javax.batch.runtime.JobExecution#getJobName() + */ @Override public String getJobName() { return this.execution.getJobInstance().getJobName(); } + /* (non-Javadoc) + * @see javax.batch.runtime.JobExecution#getBatchStatus() + */ @Override public BatchStatus getBatchStatus() { return this.execution.getStatus().getBatchStatus(); } + /* (non-Javadoc) + * @see javax.batch.runtime.JobExecution#getStartTime() + */ @Override public Date getStartTime() { return this.execution.getStartTime(); } + /* (non-Javadoc) + * @see javax.batch.runtime.JobExecution#getEndTime() + */ @Override public Date getEndTime() { return this.execution.getEndTime(); } + /* (non-Javadoc) + * @see javax.batch.runtime.JobExecution#getExitStatus() + */ @Override public String getExitStatus() { return this.execution.getExitStatus().getExitCode(); } + /* (non-Javadoc) + * @see javax.batch.runtime.JobExecution#getCreateTime() + */ @Override public Date getCreateTime() { return this.execution.getCreateTime(); } + /* (non-Javadoc) + * @see javax.batch.runtime.JobExecution#getLastUpdatedTime() + */ @Override public Date getLastUpdatedTime() { return this.execution.getLastUpdated(); } + /* (non-Javadoc) + * @see javax.batch.runtime.JobExecution#getJobParameters() + */ @Override public Properties getJobParameters() { - return this.execution.getJobParameters().toProperties(); + return parametersConverter.convert(this.execution.getJobParameters()); } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/JsrJobParametersConverter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/JsrJobParametersConverter.java new file mode 100644 index 000000000..a992de933 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/JsrJobParametersConverter.java @@ -0,0 +1,132 @@ +/* + * Copyright 2013 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.core.jsr; + +import java.util.Map; +import java.util.Properties; + +import javax.sql.DataSource; + +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao; +import org.springframework.batch.item.database.support.DataFieldMaxValueIncrementerFactory; +import org.springframework.batch.item.database.support.DefaultDataFieldMaxValueIncrementerFactory; +import org.springframework.batch.support.DatabaseType; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer; +import org.springframework.util.Assert; + +/** + * Provides default conversion methodology for JSR-352's implementation. + * + * Since Spring Batch uses job parameters as a way of identifying a job + * instance, this converter will add an additional identifying parameter if + * it does not exist already in the list. The id for the identifying parameter + * will come from the JOB_SEQ sequence as used to generate the unique ids + * for BATCH_JOB_INSTANCE records. + * + * @author Michael Minella + * @since 3.0 + */ +public class JsrJobParametersConverter implements ParametersConverter, InitializingBean { + + public static final String JOB_RUN_ID = "jsr_batch_run_id"; + public DataFieldMaxValueIncrementer incremeter; + public String tablePrefix = AbstractJdbcBatchMetadataDao.DEFAULT_TABLE_PREFIX; + public DataSource dataSource; + + /** + * Main constructor. + * + * @param dataSource used to gain access to the database to get unique ids. + */ + public JsrJobParametersConverter(DataSource dataSource) { + Assert.notNull(dataSource, "A DataSource is required"); + this.dataSource = dataSource; + } + + /** + * The table prefix used in the current {@link JobRepository} + * + * @param tablePrefix the table prefix used for the job repository tables + */ + public void setTablePrefix(String tablePrefix) { + this.tablePrefix = tablePrefix; + } + + @Override + public void afterPropertiesSet() throws Exception { + DataFieldMaxValueIncrementerFactory factory = new DefaultDataFieldMaxValueIncrementerFactory(dataSource); + + this.incremeter = factory.getIncrementer(DatabaseType.fromMetaData(dataSource).name(), tablePrefix + "JOB_SEQ"); + } + + /* (non-Javadoc) + * @see org.springframework.batch.core.jsr.ParametersConverter#convert(java.util.Properties) + */ + @Override + public JobParameters convert(Properties parameters) { + JobParametersBuilder builder = new JobParametersBuilder(); + boolean runIdFound = false; + + if(parameters != null) { + for (Map.Entry curParameter : parameters.entrySet()) { + if(curParameter.getValue() != null) { + if(curParameter.getKey().equals(JOB_RUN_ID)) { + runIdFound = true; + builder.addLong(curParameter.getKey().toString(), Long.valueOf((String) curParameter.getValue()), true); + } else { + builder.addString(curParameter.getKey().toString(), curParameter.getValue().toString(), false); + } + } + } + } + + if(!runIdFound) { + builder.addLong(JOB_RUN_ID, incremeter.nextLongValue()); + } + + return builder.toJobParameters(); + } + + /* (non-Javadoc) + * @see org.springframework.batch.core.jsr.ParametersConverter#convert(org.springframework.batch.core.JobParameters) + */ + @Override + public Properties convert(JobParameters parameters) { + Properties properties = new Properties(); + boolean runIdFound = false; + + if(parameters != null) { + for(Map.Entry curParameter: parameters.getParameters().entrySet()) { + if(curParameter.getKey().equals(JOB_RUN_ID)) { + runIdFound = true; + } + + properties.setProperty(curParameter.getKey(), curParameter.getValue().getValue().toString()); + } + } + + if(!runIdFound) { + properties.setProperty(JOB_RUN_ID, String.valueOf(incremeter.nextLongValue())); + } + + return properties; + } +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/ParametersConverter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/ParametersConverter.java new file mode 100644 index 000000000..327e8040d --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/ParametersConverter.java @@ -0,0 +1,51 @@ +/* + * Copyright 2013 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.core.jsr; + +import java.util.Properties; + +import org.springframework.batch.core.JobParameters; + +/** + * Strategy interface used to provide the functionality of converting a + * {@link Properties} object as used by JSR-352 to provide job parameters + * to a {@link JobParameters} object as used by Spring Batch. This interface + * defines methods for conversion both ways. + * + * @author Michael Minella + * @since 3.0 + */ +public interface ParametersConverter { + + /** + * Convert a {@link Properties} object to a {@link JobParameters} object + * for use internally. + * + * @param parameters a {@link} Properties object to be converted + * @return {@link JobParameters} a collection of parameters + */ + JobParameters convert(Properties parameters); + + /** + * Convert a {@link JobParameters} object to a {@link Properties} object for + * exposure via the JSR-352 API. + * + * @param parameters + * @return a collection of parameters in the form of a {@link Properties} + * object + */ + Properties convert(JobParameters parameters); +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/SimpleMetric.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/SimpleMetric.java new file mode 100644 index 000000000..2fc0b04dc --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/SimpleMetric.java @@ -0,0 +1,63 @@ +/* + * Copyright 2013 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.core.jsr; + +import javax.batch.runtime.Metric; + +import org.springframework.util.Assert; + +/** + * Simple implementation of the {@link Metric} interface as required by JSR-352. + * + * @author Michael Minella + * @since 3.0 + */ +public class SimpleMetric implements Metric { + + private final MetricType type; + private final long value; + + /** + * Basic constructor. The attributes are immutable so this class is + * threadsafe. + * + * @param type as defined by JSR-352 + * @param value the count of the times the related type has occured. + */ + public SimpleMetric(MetricType type, long value) { + Assert.notNull(type, "A MetricType is required"); + + this.type = type; + this.value = value; + } + + /* (non-Javadoc) + * @see javax.batch.runtime.Metric#getType() + */ + @Override + public MetricType getType() { + return type; + } + + /* (non-Javadoc) + * @see javax.batch.runtime.Metric#getValue() + */ + @Override + public long getValue() { + return value; + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/StepContext.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/StepContext.java new file mode 100644 index 000000000..f5bfb82da --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/StepContext.java @@ -0,0 +1,111 @@ +/* + * Copyright 2013 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.core.jsr; + +import java.io.Serializable; +import java.util.Properties; + +import javax.batch.runtime.BatchStatus; +import javax.batch.runtime.Metric; + +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepExecution; +import org.springframework.util.Assert; + +public class StepContext implements javax.batch.runtime.context.StepContext { + + private StepExecution stepExecution; + private Object transientUserData; + private ParametersConverter jobParametersConveter; + + public StepContext(StepExecution stepExecution, ParametersConverter jobParametersConveter) { + Assert.notNull(stepExecution, "A StepExecution is required"); + Assert.notNull(jobParametersConveter, "A ParametersConverter is required"); + + this.stepExecution = stepExecution; + this.jobParametersConveter = jobParametersConveter; + } + + @Override + public String getStepName() { + return stepExecution.getStepName(); + } + + @Override + public Object getTransientUserData() { + return transientUserData; + } + + @Override + public void setTransientUserData(Object data) { + this.transientUserData = data; + } + + @Override + public long getStepExecutionId() { + return stepExecution.getId(); + } + + @Override + public Properties getProperties() { + return jobParametersConveter.convert(this.stepExecution.getJobParameters()); + } + + @Override + public Serializable getPersistentUserData() { + return null; + } + + @Override + public void setPersistentUserData(Serializable data) { + } + + @Override + public BatchStatus getBatchStatus() { + return stepExecution.getStatus().getBatchStatus(); + } + + @Override + public String getExitStatus() { + return stepExecution.getExitStatus().getExitCode(); + } + + @Override + public void setExitStatus(String status) { + stepExecution.setExitStatus(new ExitStatus(status)); + } + + @Override + public Exception getException() { + return null; + } + + @Override + public Metric[] getMetrics() { + Metric[] metrics = new Metric[8]; + + metrics[0] = new SimpleMetric(javax.batch.runtime.Metric.MetricType.COMMIT_COUNT, stepExecution.getCommitCount()); + metrics[1] = new SimpleMetric(javax.batch.runtime.Metric.MetricType.FILTER_COUNT, stepExecution.getFilterCount()); + metrics[2] = new SimpleMetric(javax.batch.runtime.Metric.MetricType.PROCESS_SKIP_COUNT, stepExecution.getProcessSkipCount()); + metrics[3] = new SimpleMetric(javax.batch.runtime.Metric.MetricType.READ_COUNT, stepExecution.getReadCount()); + metrics[4] = new SimpleMetric(javax.batch.runtime.Metric.MetricType.READ_SKIP_COUNT, stepExecution.getReadSkipCount()); + metrics[5] = new SimpleMetric(javax.batch.runtime.Metric.MetricType.ROLLBACK_COUNT, stepExecution.getRollbackCount()); + metrics[6] = new SimpleMetric(javax.batch.runtime.Metric.MetricType.WRITE_COUNT, stepExecution.getWriteCount()); + metrics[7] = new SimpleMetric(javax.batch.runtime.Metric.MetricType.WRITE_SKIP_COUNT, stepExecution.getWriteSkipCount()); + + return metrics; + } +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/StepExecution.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/StepExecution.java new file mode 100644 index 000000000..fcbf9899c --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/StepExecution.java @@ -0,0 +1,99 @@ +/* + * Copyright 2013 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.core.jsr; + +import java.io.Serializable; +import java.util.Date; + +import javax.batch.runtime.BatchStatus; +import javax.batch.runtime.Metric; + +import org.springframework.batch.core.ExitStatus; +import org.springframework.util.Assert; + +/** + * + * @author Michael Minella + * @since 3.0 + */ +public class StepExecution implements javax.batch.runtime.StepExecution{ + + private final org.springframework.batch.core.StepExecution stepExecution; + + public StepExecution(org.springframework.batch.core.StepExecution stepExecution) { + Assert.notNull(stepExecution, "A StepExecution is required"); + + this.stepExecution = stepExecution; + } + + @Override + public long getStepExecutionId() { + return stepExecution.getId(); + } + + @Override + public String getStepName() { + return stepExecution.getStepName(); + } + + @Override + public BatchStatus getBatchStatus() { + return stepExecution.getStatus().getBatchStatus(); + } + + @Override + public Date getStartTime() { + return stepExecution.getStartTime(); + } + + @Override + public Date getEndTime() { + return stepExecution.getEndTime(); + } + + @Override + public String getExitStatus() { + ExitStatus status = stepExecution.getExitStatus(); + + if(status == null) { + return null; + } else { + return status.getExitCode(); + } + } + + //TODO: Implement this + @Override + public Serializable getPersistentUserData() { + return null; + } + + @Override + public Metric[] getMetrics() { + Metric[] metrics = new Metric[8]; + + metrics[0] = new SimpleMetric(javax.batch.runtime.Metric.MetricType.COMMIT_COUNT, stepExecution.getCommitCount()); + metrics[1] = new SimpleMetric(javax.batch.runtime.Metric.MetricType.FILTER_COUNT, stepExecution.getFilterCount()); + metrics[2] = new SimpleMetric(javax.batch.runtime.Metric.MetricType.PROCESS_SKIP_COUNT, stepExecution.getProcessSkipCount()); + metrics[3] = new SimpleMetric(javax.batch.runtime.Metric.MetricType.READ_COUNT, stepExecution.getReadCount()); + metrics[4] = new SimpleMetric(javax.batch.runtime.Metric.MetricType.READ_SKIP_COUNT, stepExecution.getReadSkipCount()); + metrics[5] = new SimpleMetric(javax.batch.runtime.Metric.MetricType.ROLLBACK_COUNT, stepExecution.getRollbackCount()); + metrics[6] = new SimpleMetric(javax.batch.runtime.Metric.MetricType.WRITE_COUNT, stepExecution.getWriteCount()); + metrics[7] = new SimpleMetric(javax.batch.runtime.Metric.MetricType.WRITE_SKIP_COUNT, stepExecution.getWriteSkipCount()); + + return metrics; + } +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/launch/JsrJobOperator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/launch/JsrJobOperator.java index c1d4347d7..46fcdcac2 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/launch/JsrJobOperator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/launch/JsrJobOperator.java @@ -16,12 +16,12 @@ package org.springframework.batch.core.jsr.launch; import java.util.ArrayList; +import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Properties; import java.util.Set; -import javax.batch.operations.BatchRuntimeException; import javax.batch.operations.JobExecutionAlreadyCompleteException; import javax.batch.operations.JobExecutionIsRunningException; import javax.batch.operations.JobExecutionNotMostRecentException; @@ -33,148 +33,278 @@ import javax.batch.operations.JobStartException; import javax.batch.operations.NoSuchJobException; import javax.batch.operations.NoSuchJobExecutionException; import javax.batch.operations.NoSuchJobInstanceException; +import javax.batch.runtime.BatchRuntime; import javax.batch.runtime.JobExecution; import javax.batch.runtime.JobInstance; import javax.batch.runtime.StepExecution; -import javax.sql.DataSource; +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.ExitStatus; import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.configuration.JobRegistry; -import org.springframework.batch.core.configuration.annotation.DefaultBatchConfigurer; -import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.explore.JobExplorer; -import org.springframework.batch.core.explore.support.JobExplorerFactoryBean; -import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.batch.core.launch.support.SimpleJobLauncher; -import org.springframework.batch.core.launch.support.SimpleJobOperator; +import org.springframework.batch.core.jsr.JobContext; +import org.springframework.batch.core.jsr.ParametersConverter; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; import org.springframework.batch.core.repository.JobRepository; +import org.springframework.beans.factory.access.BeanFactoryLocator; +import org.springframework.beans.factory.access.BeanFactoryReference; import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.support.GenericApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.access.ContextSingletonBeanFactoryLocator; import org.springframework.context.support.GenericXmlApplicationContext; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.core.task.SyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.core.task.TaskRejectedException; +import org.springframework.util.Assert; /** * The entrance for executing batch jobs as defined by JSR-352. This class provides - * a base {@link ApplicationContext} that is the equivalent to the following: + * a single base {@link ApplicationContext} that is the equivalent to the following: * - *
- * 	@Configuration
- * 	@EnableBatchProcessing
- * 	public static class BaseConfiguration extends DefaultBatchConfigurer {
+ * <beans>
+ * 	<batch:job-repository id="jobRepository" ... />
  *
- * 		@Bean
- * 		JobLauncher jobLauncher() { ... }
+ *  	<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
+ *  		...
+ *  	</bean>
  *
- * 		@Bean
- * 		org.springframework.batch.core.launch.JobOperator batchJobOperator(JobExplorer jobExplorer,
- * 																		   JobLauncher jobLauncher,
- * 																		   JobRepository jobRepository,
- * 																		   JobRegistry jobRegistry)  { ... }
+ *  	<bean id="batchJobOperator" class="org.springframework.batch.core.launch.support.SimpleJobOperator">
+ *  		...
+ *  	</bean>
  *
- * 		@Bean
- * 		JobExplorerFactoryBean jobExplorer(final DataSource dataSource)  { ... }
+ * 	<bean id="jobExplorer" class="org.springframework.batch.core.explore.support.JobExplorerFactoryBean">
+ * 		...
+ * 	</bean>
  *
- * 		@Bean
- * 		DataSource dataSource()  { ... }
- * 	}
- * 
+ * <bean id="dataSource" + * class="org.apache.commons.dbcp.BasicDataSource"> + * ... + * </bean> + * + * <bean id="transactionManager" + * class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> + * ... + * </bean> + * + * <bean id="jobParametersConverter" class="org.springframework.batch.core.jsr.JsrJobParametersConverter"/> + * + * <bean id="jobRegistry" class="org.springframework.batch.core.configuration.support.MapJobRegistry"/> + * + * <bean id="placeholderProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> + * ... + * </bean> + * </beans> + * + * Calls to {@link JobOperator#start(String, Properties)} will provide a child context to the above context + * using the job definition and batch.xml if provided. + * + * By default, calls to start/restart will result in synchronous execution of the batch job (via a synchronous {@link TaskExecutor}. + * For asynchronous behavior, a different {@link TaskExecutor} implementation is required to be provided. + * + * Note: This class is intended to only be used for JSR-352 configured jobs. Use of + * this {@link JobOperator} to start/stop/restart Spring Batch jobs may result in unexpected behaviors due to + * how job instances are identified differently. * * @author Michael Minella * @since 3.0 - * @see EnableBatchProcessing */ public class JsrJobOperator implements JobOperator { private org.springframework.batch.core.launch.JobOperator batchJobOperator; private JobExplorer jobExplorer; - private JobLauncher jobLauncher; - private GenericApplicationContext baseContext; + private JobRepository jobRepository; + private TaskExecutor taskExecutor; + private ParametersConverter jobParametersConverter; + private static ApplicationContext baseContext; + /** + * Public constructor used by {@link BatchRuntime#getJobOperator()}. This will bootstrap a + * singleton ApplicationContext if one has not already been created (and will utilize the existing + * one if it has) to populate itself. + */ public JsrJobOperator() { - baseContext = new AnnotationConfigApplicationContext(BaseConfiguration.class); - jobLauncher = baseContext.getBean(JobLauncher.class); - jobExplorer = baseContext.getBean(JobExplorer.class); - batchJobOperator = baseContext.getBean(org.springframework.batch.core.launch.JobOperator.class); - try { - ((SimpleJobLauncher) jobLauncher).afterPropertiesSet(); - ((SimpleJobOperator) batchJobOperator).afterPropertiesSet(); - } catch (Exception e) { - throw new BatchRuntimeException("Unable to bootstrap JobOperator", e); + BeanFactoryLocator beanFactoryLocactor = ContextSingletonBeanFactoryLocator.getInstance(); + BeanFactoryReference ref = beanFactoryLocactor.useBeanFactory("baseContext"); + baseContext = (ApplicationContext) ref.getFactory(); + + baseContext.getAutowireCapableBeanFactory().autowireBeanProperties(this, + AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false); + + if(taskExecutor == null) { + taskExecutor = new SyncTaskExecutor(); } } + /** + * The no-arg constructor is used by the {@link BatchRuntime#getJobOperator()} and so bootstraps + * an {@link ApplicationContext}. This constructor does not and is therefore dependency injection + * friendly. Also useful for unit testing. + * + * @param jobExplorer an instance of Spring Batch's {@link JobExplorer} + * @param jobRepository an instance of Spring Batch's {@link JobOperator} + * @param jobOperator an instance of Spring Batch's {@link org.springframework.batch.core.launch.JobOperator} + */ + public JsrJobOperator(JobExplorer jobExplorer, JobRepository jobRepository, org.springframework.batch.core.launch.JobOperator jobOperator, ParametersConverter jobParametersConverter) { + Assert.notNull(jobExplorer, "A JobExplorer is required"); + Assert.notNull(jobRepository, "A JobRepository is required"); + Assert.notNull(jobOperator, "A JobOperator is required"); + Assert.notNull(jobParametersConverter, "A ParametersConverter is required"); + + this.jobExplorer = jobExplorer; + this.jobRepository = jobRepository; + this.batchJobOperator = jobOperator; + this.jobParametersConverter = jobParametersConverter; + } + + public void setJobExplorer(JobExplorer jobExplorer) { + Assert.notNull(jobExplorer, "A JobExplorer is required"); + + this.jobExplorer = jobExplorer; + } + + public void setJobRepository(JobRepository jobRepository) { + Assert.notNull(jobRepository, "A JobRepository is required"); + + this.jobRepository = jobRepository; + } + + public void setJobOperator(org.springframework.batch.core.launch.JobOperator jobOperator) { + Assert.notNull(jobOperator, "A JobOperator is required"); + + this.batchJobOperator = jobOperator; + } + + /** + * Used to convert the {@link Properties} objects used by JSR-352 to the {@link JobParameters} + * objects used in Spring Batch. The default implementation used will configure all parameters + * to be non-identifying (per the JSR). + * + * @param converter A {@link Converter} implementation used to convert {@link Properties} to + * {@link JobParameters} + */ + public void setJobParametersConverter(ParametersConverter converter) { + Assert.notNull(converter, "A Converter is required"); + + this.jobParametersConverter = converter; + } + + /* (non-Javadoc) + * @see javax.batch.operations.JobOperator#abandon(long) + */ @Override public void abandon(long jobExecutionId) throws NoSuchJobExecutionException, JobExecutionIsRunningException, JobSecurityException { try { batchJobOperator.abandon(jobExecutionId); } catch (org.springframework.batch.core.launch.NoSuchJobExecutionException e) { - throw new NoSuchJobException(e); + throw new NoSuchJobExecutionException(e); } catch (JobExecutionAlreadyRunningException e) { throw new JobExecutionIsRunningException(e); } } + /* (non-Javadoc) + * @see javax.batch.operations.JobOperator#getJobExecution(long) + */ @Override public JobExecution getJobExecution(long executionId) throws NoSuchJobExecutionException, JobSecurityException { org.springframework.batch.core.JobExecution jobExecution = jobExplorer.getJobExecution(executionId); if(jobExecution == null) { - throw new NoSuchJobException("No execution was found for executionId " + executionId); + throw new NoSuchJobExecutionException("No execution was found for executionId " + executionId); } - return new org.springframework.batch.core.jsr.JobExecution(jobExecution); + return new org.springframework.batch.core.jsr.JobExecution(jobExecution, jobParametersConverter); } + /* (non-Javadoc) + * @see javax.batch.operations.JobOperator#getJobExecutions(javax.batch.runtime.JobInstance) + */ @Override public List getJobExecutions(JobInstance jobInstance) throws NoSuchJobInstanceException, JobSecurityException { + if(jobInstance == null) { + throw new NoSuchJobInstanceException("A null JobInstance was provided"); + } + org.springframework.batch.core.JobInstance instance = (org.springframework.batch.core.JobInstance) jobInstance; List batchExecutions = jobExplorer.getJobExecutions(instance); - if(batchExecutions == null) { + if(batchExecutions == null || batchExecutions.size() == 0) { throw new NoSuchJobInstanceException("Unable to find JobInstance " + jobInstance.getInstanceId()); } List results = new ArrayList(batchExecutions.size()); for (org.springframework.batch.core.JobExecution jobExecution : batchExecutions) { - results.add(new org.springframework.batch.core.jsr.JobExecution(jobExecution)); + results.add(new org.springframework.batch.core.jsr.JobExecution(jobExecution, jobParametersConverter)); } return results; } + /* (non-Javadoc) + * @see javax.batch.operations.JobOperator#getJobInstance(long) + */ @Override - public JobInstance getJobInstance(long instanceId) + public JobInstance getJobInstance(long executionId) throws NoSuchJobExecutionException, JobSecurityException { - return jobExplorer.getJobInstance(instanceId); + org.springframework.batch.core.JobExecution execution = jobExplorer.getJobExecution(executionId); + + if(execution == null) { + throw new NoSuchJobExecutionException("The JobExecution was not found"); + } + + return jobExplorer.getJobInstance(execution.getJobInstance().getId()); } + /* (non-Javadoc) + * @see javax.batch.operations.JobOperator#getJobInstanceCount(java.lang.String) + */ @Override - public int getJobInstanceCount(String arg0) throws NoSuchJobException, + public int getJobInstanceCount(String jobName) throws NoSuchJobException, JobSecurityException { - return 0; + try { + return jobExplorer.getJobInstanceCount(jobName); + } catch (org.springframework.batch.core.launch.NoSuchJobException e) { + throw new NoSuchJobException("No job instances were found for job name " + jobName); + } } + /* (non-Javadoc) + * @see javax.batch.operations.JobOperator#getJobInstances(java.lang.String, int, int) + */ @Override - public List getJobInstances(String arg0, int arg1, int arg2) + public List getJobInstances(String jobName, int start, int count) throws NoSuchJobException, JobSecurityException { - return null; + List jobInstances = jobExplorer.getJobInstances(jobName, start, count); + + if(jobInstances == null || jobInstances.size() == 0) { + throw new NoSuchJobException("The job was not found"); + } + + return new ArrayList(jobInstances); } + /* (non-Javadoc) + * @see javax.batch.operations.JobOperator#getJobNames() + */ @Override public Set getJobNames() throws JobSecurityException { return new HashSet(jobExplorer.getJobNames()); } + /* (non-Javadoc) + * @see javax.batch.operations.JobOperator#getParameters(long) + */ @Override public Properties getParameters(long executionId) throws NoSuchJobExecutionException, JobSecurityException { @@ -184,13 +314,17 @@ public class JsrJobOperator implements JobOperator { throw new NoSuchJobExecutionException("Unable to find the JobExecution for id " + executionId); } - return execution.getJobParameters().toProperties(); + return jobParametersConverter.convert(execution.getJobParameters()); } + /* (non-Javadoc) + * @see javax.batch.operations.JobOperator#getRunningExecutions(java.lang.String) + */ @Override public List getRunningExecutions(String name) throws NoSuchJobException, JobSecurityException { Set findRunningJobExecutions = jobExplorer.findRunningJobExecutions(name); + List results = new ArrayList(findRunningJobExecutions.size()); for (org.springframework.batch.core.JobExecution jobExecution : findRunningJobExecutions) { @@ -200,6 +334,9 @@ public class JsrJobOperator implements JobOperator { return results; } + /* (non-Javadoc) + * @see javax.batch.operations.JobOperator#getStepExecutions(long) + */ @Override public List getStepExecutions(long executionId) throws NoSuchJobExecutionException, JobSecurityException { @@ -209,39 +346,200 @@ public class JsrJobOperator implements JobOperator { throw new NoSuchJobException("JobExecution with the id " + executionId + " was not found"); } - return null; - // return execution.getStepExecutions(); + Collection executions = execution.getStepExecutions(); + + List batchExecutions = new ArrayList(); + + if(executions != null) { + for (org.springframework.batch.core.StepExecution stepExecution : executions) { + batchExecutions.add(new org.springframework.batch.core.jsr.StepExecution(stepExecution)); + } + } + + return batchExecutions; } + /** + * Creates a child {@link ApplicationContext} for the job being requested based upon + * the /META-INF/batch.xml (if exists) and the /META-INF/batch-jobs/<jobName>.xml + * configuration and restart the job. + * + * @param executionId the database id of the job execution to be restarted. + * @param params any job parameters to be used during the execution of this job. + * @throws JobExecutionAlreadyCompleteException thrown if the requested job execution has + * a status of COMPLETE + * @throws NoSuchJobExecutionException throw if the requested job execution does not exist + * in the repository + * @throws JobExecutionNotMostRecentException thrown if the requested job execution is not + * the most recent attempt for the job instance it's related to. + * @throws JobRestartException thrown for any general errors during the job restart process + */ @Override - public long restart(long arg0, Properties arg1) + @SuppressWarnings("resource") + public long restart(long executionId, Properties params) throws JobExecutionAlreadyCompleteException, NoSuchJobExecutionException, JobExecutionNotMostRecentException, JobRestartException, JobSecurityException { - return 0; + + org.springframework.batch.core.JobExecution previousJobExecution = jobExplorer.getJobExecution(executionId); + + if (previousJobExecution == null) { + throw new NoSuchJobExecutionException("No JobExecution found for id: [" + executionId + "]"); + } else if(previousJobExecution.getStatus().equals(BatchStatus.COMPLETED)) { + throw new JobExecutionAlreadyCompleteException("The requested job has already completed"); + } + + String jobName = previousJobExecution.getJobInstance().getJobName(); + + GenericXmlApplicationContext batchContext = new GenericXmlApplicationContext(); + batchContext.setValidating(false); + + Resource batchXml = new ClassPathResource("/META-INF/batch.xml"); + Resource jobXml = new ClassPathResource(previousJobExecution.getJobConfigurationName()); + + if(batchXml.exists()) { + batchContext.load(batchXml); + } + + if(jobXml.exists()) { + batchContext.load(jobXml); + } + + batchContext.setParent(baseContext); + GenericBeanDefinition bd = new GenericBeanDefinition(); + bd.setBeanClass(AutowiredAnnotationBeanPostProcessor.class); + batchContext.registerBeanDefinition("postProcessor", bd); + batchContext.refresh(); + final Job job = batchContext.getBean(Job.class); + + if(!job.isRestartable()) { + throw new JobRestartException("Job " + jobName + " is not restartable"); + } + + final org.springframework.batch.core.JobExecution jobExecution; + + try { + JobParameters jobParameters = jobParametersConverter.convert(params); + jobExecution = jobRepository.createJobExecution(previousJobExecution.getJobInstance(), jobParameters, previousJobExecution.getJobConfigurationName()); + } catch (Exception e) { + throw new JobRestartException(e); + } + + try { + ConfigurableListableBeanFactory factory = ((ConfigurableApplicationContext)batchContext).getBeanFactory(); + factory.registerSingleton(job.getName() + "_" + jobExecution.getId() + "_jobContext", new JobContext(jobExecution, jobParametersConverter)); + + taskExecutor.execute(new Runnable() { + + @Override + public void run() { + try { + job.execute(jobExecution); + } + catch (Throwable t) { + throw new JobRestartException(t); + } + } + }); + } + catch (TaskRejectedException e) { + jobExecution.upgradeStatus(BatchStatus.FAILED); + if (jobExecution.getExitStatus().equals(ExitStatus.UNKNOWN)) { + jobExecution.setExitStatus(ExitStatus.FAILED.addExitDescription(e)); + } + jobRepository.update(jobExecution); + } + + batchContext.close(); + + return jobExecution.getId(); } + /** + * Creates a child {@link ApplicationContext} for the job being requested based upon + * the /META-INF/batch.xml (if exists) and the /META-INF/batch-jobs/<jobName>.xml + * configuration and launches the job. Per JSR-352, calls to this method will always + * create a new {@link JobInstance} (and related {@link JobExecution}). + * + * @param jobName the name of the job XML file without the .xml that is located within the + * /META-INF/batch-jobs directory. + * @param params any job parameters to be used during the execution of this job. + */ @Override @SuppressWarnings("resource") public long start(String jobName, Properties params) throws JobStartException, JobSecurityException { GenericXmlApplicationContext batchContext = new GenericXmlApplicationContext(); batchContext.setValidating(false); - batchContext.load(new String[] {"/META-INF/batch.xml", "META-INF/batch-jobs/" + jobName + ".xml"}); + + Resource batchXml = new ClassPathResource("/META-INF/batch.xml"); + String jobConfigurationLocation = "/META-INF/batch-jobs/" + jobName + ".xml"; + Resource jobXml = new ClassPathResource(jobConfigurationLocation); + + if(batchXml.exists()) { + batchContext.load(batchXml); + } + + if(jobXml.exists()) { + batchContext.load(jobXml); + } + batchContext.setParent(baseContext); GenericBeanDefinition bd = new GenericBeanDefinition(); bd.setBeanClass(AutowiredAnnotationBeanPostProcessor.class); batchContext.registerBeanDefinition("postProcessor", bd); batchContext.refresh(); - Job job = batchContext.getBean(jobName, Job.class); + final Job job = batchContext.getBean(Job.class); + + Assert.notNull(jobName, "The job name must not be null."); + + final org.springframework.batch.core.JobExecution jobExecution; + try { - return jobLauncher.run(job, new JobParametersBuilder(params).toJobParameters()).getId(); + JobParameters jobParameters = jobParametersConverter.convert(params); + org.springframework.batch.core.JobInstance jobInstance = jobRepository.createJobInstance(job.getName(), jobParameters); + jobExecution = jobRepository.createJobExecution(jobInstance, jobParameters, jobConfigurationLocation); } catch (Exception e) { - e.printStackTrace(); throw new JobStartException(e); } + + try { + ConfigurableListableBeanFactory factory = ((ConfigurableApplicationContext)batchContext).getBeanFactory(); + factory.registerSingleton(job.getName() + "_" + jobExecution.getId() + "_jobContext", new JobContext(jobExecution, jobParametersConverter)); + + taskExecutor.execute(new Runnable() { + + @Override + public void run() { + try { + job.execute(jobExecution); + } + catch (Throwable t) { + throw new JobStartException(t); + } + } + }); + } + catch (TaskRejectedException e) { + jobExecution.upgradeStatus(BatchStatus.FAILED); + if (jobExecution.getExitStatus().equals(ExitStatus.UNKNOWN)) { + jobExecution.setExitStatus(ExitStatus.FAILED.addExitDescription(e)); + } + jobRepository.update(jobExecution); + } + + batchContext.close(); + + return jobExecution.getId(); } + /** + * Delegates to {@link org.springframework.batch.core.launch.JobOperator#stop(long)} + * + * @param executionId the database id for the {@link JobExecution} to be stopped. + * @throws NoSuchJobExecutionException + * @throws JobExecutionNotRunningException + */ @Override public void stop(long executionId) throws NoSuchJobExecutionException, JobExecutionNotRunningException, JobSecurityException { @@ -253,48 +551,4 @@ public class JsrJobOperator implements JobOperator { throw new JobExecutionNotRunningException(e); } } - - @Configuration - @EnableBatchProcessing - public static class BaseConfiguration extends DefaultBatchConfigurer { - - @Bean - JobLauncher jobLauncher() { - SimpleJobLauncher jobLauncher = new SimpleJobLauncher(); - jobLauncher.setJobRepository(super.getJobRepository()); - try { - jobLauncher.afterPropertiesSet(); - } catch (Exception e) { - e.printStackTrace(); - } - return jobLauncher; - } - - @Bean - org.springframework.batch.core.launch.JobOperator batchJobOperator(JobExplorer jobExplorer, JobLauncher jobLauncher, JobRepository jobRepository, JobRegistry jobRegistry) { - SimpleJobOperator operator = new SimpleJobOperator(); - - operator.setJobExplorer(jobExplorer); - operator.setJobLauncher(jobLauncher); - operator.setJobRepository(jobRepository); - operator.setJobRegistry(jobRegistry); - - return operator; - } - - @Bean - JobExplorerFactoryBean jobExplorer(final DataSource dataSource) { - return new JobExplorerFactoryBean() {{ - setDataSource(dataSource); - }}; - } - - @Bean - DataSource dataSource() { - return new EmbeddedDatabaseBuilder(). - addScript("classpath:org/springframework/batch/core/schema-drop-hsqldb.sql"). - addScript("classpath:org/springframework/batch/core/schema-hsqldb.sql"). - build(); - } - } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobLauncher.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobLauncher.java index 9500ccd42..39ea3ae4e 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobLauncher.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobLauncher.java @@ -25,18 +25,12 @@ import org.springframework.batch.core.JobInstance; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.JobParametersInvalidException; import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.jsr.JobContext; 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.BeansException; import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.task.SyncTaskExecutor; import org.springframework.core.task.TaskExecutor; import org.springframework.core.task.TaskRejectedException; @@ -68,7 +62,7 @@ import org.springframework.util.Assert; * @see JobRepository * @see TaskExecutor */ -public class SimpleJobLauncher implements JobLauncher, InitializingBean, ApplicationContextAware { +public class SimpleJobLauncher implements JobLauncher, InitializingBean { protected static final Log logger = LogFactory.getLog(SimpleJobLauncher.class); @@ -76,8 +70,6 @@ public class SimpleJobLauncher implements JobLauncher, InitializingBean, Applica private TaskExecutor taskExecutor; - private ApplicationContext context; - /** * Run the provided job with the given {@link JobParameters}. The * {@link JobParameters} will be used to determine if this is an execution @@ -132,11 +124,6 @@ public class SimpleJobLauncher implements JobLauncher, InitializingBean, Applica */ jobExecution = jobRepository.createJobExecution(job.getName(), jobParameters); - if(context != null && context instanceof ConfigurableApplicationContext) { - ConfigurableListableBeanFactory factory = ((ConfigurableApplicationContext)context).getBeanFactory(); - factory.registerSingleton(job.getName() + "_" + jobExecution.getId() + "_jobContext", new JobContext(jobExecution)); - } - try { taskExecutor.execute(new Runnable() { @@ -209,10 +196,4 @@ public class SimpleJobLauncher implements JobLauncher, InitializingBean, Applica taskExecutor = new SyncTaskExecutor(); } } - - @Override - public void setApplicationContext(ApplicationContext context) - throws BeansException { - this.context = context; - } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java index 0735e7f2e..820526fd0 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java @@ -178,7 +178,8 @@ public class SimpleJobOperator implements JobOperator, InitializingBean { @Override public List getJobInstances(String jobName, int start, int count) throws NoSuchJobException { List list = new ArrayList(); - for (JobInstance jobInstance : jobExplorer.getJobInstances(jobName, start, count)) { + List jobInstances = jobExplorer.getJobInstances(jobName, start, count); + for (JobInstance jobInstance : jobInstances) { list.add(jobInstance.getId()); } if (list.isEmpty() && !jobRegistry.getJobNames().contains(jobName)) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobRepository.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobRepository.java index 688c7c8c5..b970de23f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobRepository.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobRepository.java @@ -33,22 +33,23 @@ import org.springframework.transaction.annotation.Isolation; *

* Repository responsible for persistence of batch meta-data entities. *

- * + * * @see JobInstance * @see JobExecution * @see StepExecution - * + * * @author Lucas Ward * @author Dave Syer * @author Robert Kasanicky * @author David Turanski + * @author Michael Minella */ public interface JobRepository { /** * Check if an instance of this job already exists with the parameters * provided. - * + * * @param jobName the name of the job * @param jobParameters the parameters to match * @return true if a {@link JobInstance} already exists for this job name @@ -56,6 +57,27 @@ public interface JobRepository { */ boolean isJobInstanceExists(String jobName, JobParameters jobParameters); + /** + * Create a new {@link JobInstance} with the name and job parameters provided. + * + * @param jobName logical name of the job + * @param jobParameters parameters used to execute the job + * @return the new {@link JobInstance} + */ + JobInstance createJobInstance(String jobName, JobParameters jobParameters); + + /** + * Create a new {@link JobExecution} based upon the {@link JobInstance} it's associated + * with, the {@link JobParameters} used to execute it with and the location of the configuration + * file that defines the job. + * + * @param jobInstance + * @param jobParameters + * @param jobConfigurationLocation + * @return the new {@link JobExecution} + */ + JobExecution createJobExecution(JobInstance jobInstance, JobParameters jobParameters, String jobConfigurationLocation); + /** *

* Create a {@link JobExecution} for a given {@link Job} and @@ -64,7 +86,7 @@ public interface JobRepository { * completed. If matching {@link JobInstance} does not exist yet it will be * created. *

- * + * *

* If this method is run in a transaction (as it normally would be) with * isolation level at {@link Isolation#REPEATABLE_READ} or better, then this @@ -77,11 +99,11 @@ public interface JobRepository { * (e.g. if using a non-relational data-store, or if the platform does not * support the higher isolation levels). *

- * + * * @param jobName the name of the job that is to be executed

- * + * * @param jobParameters the runtime parameters for the job - * + * * @return a valid {@link JobExecution} for the arguments provided * @throws JobExecutionAlreadyRunningException if there is a * {@link JobExecution} already running for the job instance with the @@ -91,17 +113,17 @@ public interface JobRepository { * false. * @throws JobInstanceAlreadyCompleteException if a {@link JobInstance} is * found and was already completed successfully. - * + * */ JobExecution createJobExecution(String jobName, JobParameters jobParameters) throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException; /** * Update the {@link JobExecution} (but not its {@link ExecutionContext}). - * + * * Preconditions: {@link JobExecution} must contain a valid * {@link JobInstance} and be saved (have an id assigned). - * + * * @param jobExecution */ void update(JobExecution jobExecution); @@ -111,9 +133,9 @@ public interface JobRepository { * be assigned - it is not permitted that an ID be assigned before calling * this method. Instead, it should be left blank, to be assigned by a * {@link JobRepository}. - * + * * Preconditions: {@link StepExecution} must have a valid {@link Step}. - * + * * @param stepExecution */ void add(StepExecution stepExecution); @@ -122,18 +144,18 @@ public interface JobRepository { * Save a collection of {@link StepExecution}s and each {@link ExecutionContext}. The * StepExecution ID will be assigned - it is not permitted that an ID be assigned before calling * this method. Instead, it should be left blank, to be assigned by {@link JobRepository}. - * + * * Preconditions: {@link StepExecution} must have a valid {@link Step}. - * + * * @param stepExecution */ void addAll(Collection stepExecutions); /** * Update the {@link StepExecution} (but not its {@link ExecutionContext}). - * + * * Preconditions: {@link StepExecution} must be saved (have an id assigned). - * + * * @param stepExecution */ void update(StepExecution stepExecution); @@ -141,7 +163,7 @@ public interface JobRepository { /** * Persist the updated {@link ExecutionContext}s of the given * {@link StepExecution}. - * + * * @param stepExecution */ void updateExecutionContext(StepExecution stepExecution); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobExecutionDao.java index 1bfda6996..0e7f6bf9c 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobExecutionDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobExecutionDao.java @@ -63,7 +63,7 @@ public class JdbcJobExecutionDao extends AbstractJdbcBatchMetadataDao implements private static final Log logger = LogFactory.getLog(JdbcJobExecutionDao.class); private static final String SAVE_JOB_EXECUTION = "INSERT into %PREFIX%JOB_EXECUTION(JOB_EXECUTION_ID, JOB_INSTANCE_ID, START_TIME, " - + "END_TIME, STATUS, EXIT_CODE, EXIT_MESSAGE, VERSION, CREATE_TIME, LAST_UPDATED) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + "END_TIME, STATUS, EXIT_CODE, EXIT_MESSAGE, VERSION, CREATE_TIME, LAST_UPDATED, JOB_CONFIGURATION_LOCATION) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; private static final String CHECK_JOB_EXECUTION_EXISTS = "SELECT COUNT(*) FROM %PREFIX%JOB_EXECUTION WHERE JOB_EXECUTION_ID = ?"; @@ -72,17 +72,17 @@ public class JdbcJobExecutionDao extends AbstractJdbcBatchMetadataDao implements private static final String UPDATE_JOB_EXECUTION = "UPDATE %PREFIX%JOB_EXECUTION set START_TIME = ?, END_TIME = ?, " + " STATUS = ?, EXIT_CODE = ?, EXIT_MESSAGE = ?, VERSION = ?, CREATE_TIME = ?, LAST_UPDATED = ? where JOB_EXECUTION_ID = ? and VERSION = ?"; - private static final String FIND_JOB_EXECUTIONS = "SELECT JOB_EXECUTION_ID, START_TIME, END_TIME, STATUS, EXIT_CODE, EXIT_MESSAGE, CREATE_TIME, LAST_UPDATED, VERSION" + private static final String FIND_JOB_EXECUTIONS = "SELECT JOB_EXECUTION_ID, START_TIME, END_TIME, STATUS, EXIT_CODE, EXIT_MESSAGE, CREATE_TIME, LAST_UPDATED, VERSION, JOB_CONFIGURATION_LOCATION" + " from %PREFIX%JOB_EXECUTION where JOB_INSTANCE_ID = ? order by JOB_EXECUTION_ID desc"; - private static final String GET_LAST_EXECUTION = "SELECT JOB_EXECUTION_ID, START_TIME, END_TIME, STATUS, EXIT_CODE, EXIT_MESSAGE, CREATE_TIME, LAST_UPDATED, VERSION " + private static final String GET_LAST_EXECUTION = "SELECT JOB_EXECUTION_ID, START_TIME, END_TIME, STATUS, EXIT_CODE, EXIT_MESSAGE, CREATE_TIME, LAST_UPDATED, VERSION, JOB_CONFIGURATION_LOCATION " + "from %PREFIX%JOB_EXECUTION E where JOB_INSTANCE_ID = ? and JOB_EXECUTION_ID in (SELECT max(JOB_EXECUTION_ID) from %PREFIX%JOB_EXECUTION E2 where E2.JOB_INSTANCE_ID = ?)"; - private static final String GET_EXECUTION_BY_ID = "SELECT JOB_EXECUTION_ID, START_TIME, END_TIME, STATUS, EXIT_CODE, EXIT_MESSAGE, CREATE_TIME, LAST_UPDATED, VERSION" + private static final String GET_EXECUTION_BY_ID = "SELECT JOB_EXECUTION_ID, START_TIME, END_TIME, STATUS, EXIT_CODE, EXIT_MESSAGE, CREATE_TIME, LAST_UPDATED, VERSION, JOB_CONFIGURATION_LOCATION" + " from %PREFIX%JOB_EXECUTION where JOB_EXECUTION_ID = ?"; private static final String GET_RUNNING_EXECUTIONS = "SELECT E.JOB_EXECUTION_ID, E.START_TIME, E.END_TIME, E.STATUS, E.EXIT_CODE, E.EXIT_MESSAGE, E.CREATE_TIME, E.LAST_UPDATED, E.VERSION, " - + "E.JOB_INSTANCE_ID from %PREFIX%JOB_EXECUTION E, %PREFIX%JOB_INSTANCE I where E.JOB_INSTANCE_ID=I.JOB_INSTANCE_ID and I.JOB_NAME=? and E.END_TIME is NULL order by E.JOB_EXECUTION_ID desc"; + + "E.JOB_INSTANCE_ID, E.JOB_CONFIGURATION_LOCATION from %PREFIX%JOB_EXECUTION E, %PREFIX%JOB_INSTANCE I where E.JOB_INSTANCE_ID=I.JOB_INSTANCE_ID and I.JOB_NAME=? and E.END_TIME is NULL order by E.JOB_EXECUTION_ID desc"; private static final String CURRENT_VERSION_JOB_EXECUTION = "SELECT VERSION FROM %PREFIX%JOB_EXECUTION WHERE JOB_EXECUTION_ID=?"; @@ -151,12 +151,13 @@ public class JdbcJobExecutionDao extends AbstractJdbcBatchMetadataDao implements Object[] parameters = new Object[] { jobExecution.getId(), jobExecution.getJobId(), jobExecution.getStartTime(), jobExecution.getEndTime(), jobExecution.getStatus().toString(), jobExecution.getExitStatus().getExitCode(), jobExecution.getExitStatus().getExitDescription(), - jobExecution.getVersion(), jobExecution.getCreateTime(), jobExecution.getLastUpdated() }; + jobExecution.getVersion(), jobExecution.getCreateTime(), jobExecution.getLastUpdated(), + jobExecution.getJobConfigurationName() }; getJdbcTemplate().update( getQuery(SAVE_JOB_EXECUTION), parameters, new int[] { Types.BIGINT, Types.BIGINT, Types.TIMESTAMP, Types.TIMESTAMP, Types.VARCHAR, - Types.VARCHAR, Types.VARCHAR, Types.INTEGER, Types.TIMESTAMP, Types.TIMESTAMP }); + Types.VARCHAR, Types.VARCHAR, Types.INTEGER, Types.TIMESTAMP, Types.TIMESTAMP, Types.VARCHAR }); insertJobParameters(jobExecution.getId(), jobExecution.getJobParameters()); } @@ -405,16 +406,17 @@ public class JdbcJobExecutionDao extends AbstractJdbcBatchMetadataDao implements @Override public JobExecution mapRow(ResultSet rs, int rowNum) throws SQLException { Long id = rs.getLong(1); + String jobConfigurationLocation = rs.getString(10); JobExecution jobExecution; if (jobParameters == null) { jobParameters = getJobParameters(id); } if (jobInstance == null) { - jobExecution = new JobExecution(id, jobParameters); + jobExecution = new JobExecution(id, jobParameters, jobConfigurationLocation); } else { - jobExecution = new JobExecution(jobInstance, id, jobParameters); + jobExecution = new JobExecution(jobInstance, id, jobParameters, jobConfigurationLocation); } jobExecution.setStartTime(rs.getTimestamp(2)); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDao.java index 874042341..ddba1ff31 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDao.java @@ -27,6 +27,7 @@ import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobInstance; import org.springframework.batch.core.JobKeyGenerator; import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.launch.NoSuchJobException; import org.springframework.beans.factory.InitializingBean; import org.springframework.dao.DataAccessException; import org.springframework.dao.EmptyResultDataAccessException; @@ -61,6 +62,8 @@ JobInstanceDao, InitializingBean { private static final String FIND_JOBS_WITH_KEY = FIND_JOBS_WITH_NAME + " and JOB_KEY = ?"; + private static final String COUNT_JOBS_WITH_NAME = "SELECT COUNT(*) from %PREFIX%JOB_INSTANCE where JOB_NAME = ?"; + private static final String FIND_JOBS_WITH_EMPTY_KEY = "SELECT JOB_INSTANCE_ID, JOB_NAME from %PREFIX%JOB_INSTANCE where JOB_NAME = ? and (JOB_KEY = ? OR JOB_KEY is NULL)"; private static final String GET_JOB_FROM_ID = "SELECT JOB_INSTANCE_ID, JOB_NAME, JOB_KEY, VERSION from %PREFIX%JOB_INSTANCE where JOB_INSTANCE_ID = ?"; @@ -244,6 +247,21 @@ JobInstanceDao, InitializingBean { } } + /* (non-Javadoc) + * @see org.springframework.batch.core.repository.dao.JobInstanceDao#getJobInstanceCount(java.lang.String) + */ + @Override + public int getJobInstanceCount(String jobName) throws NoSuchJobException { + + try { + return getJdbcTemplate().queryForInt( + getQuery(COUNT_JOBS_WITH_NAME), + jobName); + } catch (EmptyResultDataAccessException e) { + throw new NoSuchJobException("No job instances were found for job name " + jobName); + } + } + /** * Setter for {@link DataFieldMaxValueIncrementer} to be used when * generating primary keys for {@link JobInstance} instances. diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JobInstanceDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JobInstanceDao.java index e1e3f7033..f5cb89dcf 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JobInstanceDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JobInstanceDao.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2007 the original author or authors. + * Copyright 2006-2013 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. @@ -21,25 +21,27 @@ import java.util.List; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobInstance; import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.launch.NoSuchJobException; /** * Data Access Object for job instances. - * + * * @author Lucas Ward * @author Robert Kasanicky - * + * @author Michael Minella + * */ public interface JobInstanceDao { /** * Create a JobInstance with given name and parameters. - * + * * PreConditions: JobInstance for given name and parameters must not already * exist - * + * * PostConditions: A valid job instance will be returned which has been * persisted and contains an unique Id. - * + * * @param jobName * @param jobParameters * @return JobInstance @@ -49,7 +51,7 @@ public interface JobInstanceDao { /** * Find the job instance that matches the given name and parameters. If no * matching job instances are found, then returns null. - * + * * @param jobName the name of the job * @param jobParameters the parameters with which the job was executed * @return {@link JobInstance} object matching the job name and @@ -59,7 +61,7 @@ public interface JobInstanceDao { /** * Fetch the job instance with the provided identifier. - * + * * @param instanceId the job identifier * @return the job instance with this identifier or null if it doesn't exist */ @@ -67,17 +69,17 @@ public interface JobInstanceDao { /** * Fetch the JobInstance for the provided JobExecution. - * + * * @param jobExecution the JobExecution * @return the JobInstance for the provided execution or null if it doesn't exist. */ JobInstance getJobInstance(JobExecution jobExecution); - + /** * Fetch the last job instances with the provided name, sorted backwards by * primary key. - * - * + * + * * @param jobName the job name * @param start the start index of the instances to return * @param count the maximum number of objects to return @@ -92,4 +94,16 @@ public interface JobInstanceDao { */ List getJobNames(); + + /** + * Query the repository for the number of unique {@link JobInstance}s + * associated with the supplied job name. + * + * @param jobName the name of the job to query for + * @return the number of {@link JobInstance}s that exist within the + * associated job repository + * @throws NoSuchJobException + */ + int getJobInstanceCount(String jobName) throws NoSuchJobException; + } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MapJobInstanceDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MapJobInstanceDao.java index 6848de662..2da7b7443 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MapJobInstanceDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MapJobInstanceDao.java @@ -29,6 +29,7 @@ import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobInstance; import org.springframework.batch.core.JobKeyGenerator; import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.launch.NoSuchJobException; import org.springframework.util.Assert; /** @@ -38,7 +39,6 @@ public class MapJobInstanceDao implements JobInstanceDao { // JDK6 Make a ConcurrentSkipListSet: tends to add on end private final Map jobInstances = new ConcurrentHashMap(); - // private final Set jobInstances = new CopyOnWriteArraySet(); private JobKeyGenerator jobKeyGenerator = new DefaultJobKeyGenerator(); @@ -55,14 +55,14 @@ public class MapJobInstanceDao implements JobInstanceDao { JobInstance jobInstance = new JobInstance(currentId.getAndIncrement(), jobName); jobInstance.incrementVersion(); - jobInstances.put(jobName + jobKeyGenerator.generateKey(jobParameters), jobInstance); + jobInstances.put(jobName + "|" + jobKeyGenerator.generateKey(jobParameters), jobInstance); return jobInstance; } @Override public JobInstance getJobInstance(String jobName, JobParameters jobParameters) { - return jobInstances.get(jobName + jobKeyGenerator.generateKey(jobParameters)); + return jobInstances.get(jobName + "|" + jobKeyGenerator.generateKey(jobParameters)); } @Override @@ -113,4 +113,23 @@ public class MapJobInstanceDao implements JobInstanceDao { return jobExecution.getJobInstance(); } + @Override + public int getJobInstanceCount(String jobName) throws NoSuchJobException { + int count = 0; + + for (Map.Entry instanceEntry : jobInstances.entrySet()) { + String key = instanceEntry.getKey(); + String curJobName = key.substring(0, key.lastIndexOf("|")); + + if(curJobName.equals(jobName)) { + count++; + } + } + + if(count == 0) { + throw new NoSuchJobException("No job instances for job name " + jobName + " were found"); + } else { + return count; + } + } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/SimpleJobRepository.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/SimpleJobRepository.java index a54a95255..e76e0c76c 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/SimpleJobRepository.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/SimpleJobRepository.java @@ -122,7 +122,7 @@ public class SimpleJobRepository implements JobRepository { } BatchStatus status = execution.getStatus(); - if (status == BatchStatus.COMPLETED || status == BatchStatus.ABANDONED) { + if (execution.getJobParameters().getParameters().size() > 0 && (status == BatchStatus.COMPLETED || status == BatchStatus.ABANDONED)) { throw new JobInstanceAlreadyCompleteException( "A job instance already exists and is complete for parameters=" + jobParameters + ". If you want to run this job again, change the parameters."); @@ -136,7 +136,7 @@ public class SimpleJobRepository implements JobRepository { executionContext = new ExecutionContext(); } - JobExecution jobExecution = new JobExecution(jobInstance, jobParameters); + JobExecution jobExecution = new JobExecution(jobInstance, jobParameters, null); jobExecution.setExecutionContext(executionContext); jobExecution.setLastUpdated(new Date(System.currentTimeMillis())); @@ -258,7 +258,7 @@ public class SimpleJobRepository implements JobRepository { return count; } - /* + /** * Check to determine whether or not the JobExecution that is the parent of * the provided StepExecution has been interrupted. If, after synchronizing * the status with the database, the status has been updated to STOPPING, @@ -289,4 +289,34 @@ public class SimpleJobRepository implements JobRepository { return jobExecution; } + + @Override + public JobInstance createJobInstance(String jobName, JobParameters jobParameters) { + Assert.notNull(jobName, "A job name is required to create a JobInstance"); + Assert.notNull(jobParameters, "Job parameters are required to create a JobInstance"); + + JobInstance jobInstance = jobInstanceDao.createJobInstance(jobName, jobParameters); + + return jobInstance; + } + + @Override + public JobExecution createJobExecution(JobInstance jobInstance, + JobParameters jobParameters, String jobConfigurationLocation) { + + Assert.notNull(jobInstance, "A JobInstance is required to associate the JobExecution with"); + Assert.notNull(jobParameters, "A JobParameters object is required to create a JobExecution"); + + JobExecution jobExecution = new JobExecution(jobInstance, jobParameters, jobConfigurationLocation); + ExecutionContext executionContext = new ExecutionContext(); + jobExecution.setExecutionContext(executionContext); + jobExecution.setLastUpdated(new Date(System.currentTimeMillis())); + + // Save the JobExecution so that it picks up an ID (useful for clients + // monitoring asynchronous executions): + jobExecutionDao.saveJobExecution(jobExecution); + ecDao.saveExecutionContext(jobExecution); + + return jobExecution; + } } diff --git a/spring-batch-core/src/main/resources/baseContext.xml b/spring-batch-core/src/main/resources/baseContext.xml new file mode 100644 index 000000000..c77193bfe --- /dev/null +++ b/spring-batch-core/src/main/resources/baseContext.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + classpath:batch-${ENVIRONMENT:hsql}.properties + + + + + + + + diff --git a/spring-batch-core/src/main/resources/batch-hsql.properties b/spring-batch-core/src/main/resources/batch-hsql.properties new file mode 100644 index 000000000..e9af7f5a7 --- /dev/null +++ b/spring-batch-core/src/main/resources/batch-hsql.properties @@ -0,0 +1,19 @@ +# Placeholders batch.* +# for HSQLDB: +batch.jdbc.driver=org.hsqldb.jdbcDriver +batch.jdbc.url=jdbc:hsqldb:mem:testdb;sql.enforce_strict_size=true +# Override and use this one in 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.database.incrementer.class=org.springframework.jdbc.support.incrementer.HsqlMaxValueIncrementer +batch.schema.script=classpath*:/org/springframework/batch/core/schema-hsqldb.sql +batch.drop.script=classpath*:/org/springframework/batch/core/schema-drop-hsqldb.sql +batch.business.schema.script= +batch.jdbc.testWhileIdle=true +batch.jdbc.validationQuery= + + +# Non-platform dependent settings that you might like to change +batch.data.source.init=true diff --git a/spring-batch-core/src/main/resources/beanRefContext.xml b/spring-batch-core/src/main/resources/beanRefContext.xml new file mode 100644 index 000000000..6c75169c4 --- /dev/null +++ b/spring-batch-core/src/main/resources/beanRefContext.xml @@ -0,0 +1,30 @@ + + + + + + + + baseContext.xml + + + + + diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-db2.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-db2.sql index 264b52ad2..1fb37d895 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-db2.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-db2.sql @@ -19,6 +19,7 @@ CREATE TABLE BATCH_JOB_EXECUTION ( EXIT_CODE VARCHAR(100) , EXIT_MESSAGE VARCHAR(2500) , LAST_UPDATED TIMESTAMP, + JOB_CONFIGURATION_LOCATION VARCHAR(500) NULL, constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) ) ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-derby.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-derby.sql index 8794edb69..735e8a11d 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-derby.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-derby.sql @@ -19,6 +19,7 @@ CREATE TABLE BATCH_JOB_EXECUTION ( EXIT_CODE VARCHAR(100) , EXIT_MESSAGE VARCHAR(2500) , LAST_UPDATED TIMESTAMP, + JOB_CONFIGURATION_LOCATION VARCHAR(500) NULL, constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) ) ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-h2.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-h2.sql index a8bf52427..36c1296c4 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-h2.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-h2.sql @@ -19,6 +19,7 @@ CREATE TABLE BATCH_JOB_EXECUTION ( EXIT_CODE VARCHAR(100) , EXIT_MESSAGE VARCHAR(2500) , LAST_UPDATED TIMESTAMP, + JOB_CONFIGURATION_LOCATION VARCHAR(500) NULL, constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) ) ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-hsqldb.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-hsqldb.sql index 5ac3d3544..18e838554 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-hsqldb.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-hsqldb.sql @@ -19,6 +19,7 @@ CREATE TABLE BATCH_JOB_EXECUTION ( EXIT_CODE VARCHAR(100) , EXIT_MESSAGE VARCHAR(2500) , LAST_UPDATED TIMESTAMP, + JOB_CONFIGURATION_LOCATION VARCHAR(500) NULL, constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) ) ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mysql.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mysql.sql index 9d7e9009b..19780ceb6 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mysql.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mysql.sql @@ -19,6 +19,7 @@ CREATE TABLE BATCH_JOB_EXECUTION ( EXIT_CODE VARCHAR(100) , EXIT_MESSAGE VARCHAR(2500) , LAST_UPDATED DATETIME, + JOB_CONFIGURATION_LOCATION VARCHAR(500) NULL, constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) ) ENGINE=InnoDB; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-oracle10g.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-oracle10g.sql index 73403b90b..0dc4b2a07 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-oracle10g.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-oracle10g.sql @@ -19,6 +19,7 @@ CREATE TABLE BATCH_JOB_EXECUTION ( EXIT_CODE VARCHAR2(100) , EXIT_MESSAGE VARCHAR2(2500) , LAST_UPDATED TIMESTAMP, + JOB_CONFIGURATION_LOCATION VARCHAR(500) NULL, constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) ) ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-postgresql.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-postgresql.sql index d0435b3f8..57dfea1c4 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-postgresql.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-postgresql.sql @@ -19,6 +19,7 @@ CREATE TABLE BATCH_JOB_EXECUTION ( EXIT_CODE VARCHAR(100) , EXIT_MESSAGE VARCHAR(2500) , LAST_UPDATED TIMESTAMP, + JOB_CONFIGURATION_LOCATION VARCHAR(500) NULL, constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) ) ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-sqlf.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-sqlf.sql index 8794edb69..735e8a11d 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-sqlf.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-sqlf.sql @@ -19,6 +19,7 @@ CREATE TABLE BATCH_JOB_EXECUTION ( EXIT_CODE VARCHAR(100) , EXIT_MESSAGE VARCHAR(2500) , LAST_UPDATED TIMESTAMP, + JOB_CONFIGURATION_LOCATION VARCHAR(500) NULL, constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) ) ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-sqlserver.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-sqlserver.sql index 560a304bb..8bf494709 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-sqlserver.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-sqlserver.sql @@ -19,6 +19,7 @@ CREATE TABLE BATCH_JOB_EXECUTION ( EXIT_CODE VARCHAR(100) , EXIT_MESSAGE VARCHAR(2500) , LAST_UPDATED DATETIME, + JOB_CONFIGURATION_LOCATION VARCHAR(500) NULL, constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) ) ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-sybase.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-sybase.sql index 6f122e523..ce6fb0c2b 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-sybase.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-sybase.sql @@ -19,6 +19,7 @@ CREATE TABLE BATCH_JOB_EXECUTION ( EXIT_CODE VARCHAR(100) NULL, EXIT_MESSAGE VARCHAR(2500) NULL, LAST_UPDATED DATETIME, + JOB_CONFIGURATION_LOCATION VARCHAR(500) NULL, constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) ) ; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/JobExecutionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/JobExecutionTests.java index 76cc1c64b..3609e8755 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/JobExecutionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/JobExecutionTests.java @@ -35,7 +35,7 @@ import org.springframework.batch.support.SerializationUtils; public class JobExecutionTests { private JobExecution execution = new JobExecution(new JobInstance(new Long(11), "foo"), - new Long(12), new JobParameters()); + new Long(12), new JobParameters(), null); @Test public void testJobExecution() { @@ -53,6 +53,12 @@ public class JobExecutionTests { assertEquals(100L, execution.getEndTime().getTime()); } + @Test + public void testGetJobConfigurationName() { + execution = new JobExecution(new JobInstance(null, "foo"), null, "/META-INF/batch-jobs/someJob.xml"); + assertEquals("/META-INF/batch-jobs/someJob.xml", execution.getJobConfigurationName()); + } + /** * Test method for * {@link org.springframework.batch.core.JobExecution#getEndTime()}. @@ -126,7 +132,7 @@ public class JobExecutionTests { @Test public void testGetJobId() { assertEquals(11, execution.getJobId().longValue()); - execution = new JobExecution(new JobInstance(new Long(23), "testJob"), null, new JobParameters()); + execution = new JobExecution(new JobInstance(new Long(23), "testJob"), null, new JobParameters(), null); assertEquals(23, execution.getJobId().longValue()); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersBuilderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersBuilderTests.java index bc563f4df..aca692377 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersBuilderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersBuilderTests.java @@ -6,7 +6,6 @@ import static org.junit.Assert.assertFalse; import java.util.Date; import java.util.Iterator; import java.util.Map; -import java.util.Properties; import org.junit.Test; @@ -21,30 +20,6 @@ public class JobParametersBuilderTests { Date date = new Date(System.currentTimeMillis()); - @Test - public void testFromProperties() { - Properties props = new Properties(); - props.put("SCHEDULE_DATE", date.toString()); - props.put("LONG", "1"); - props.put("STRING", "string value"); - - JobParametersBuilder builder = new JobParametersBuilder(props); - JobParameters parameters = builder.toJobParameters(); - assertEquals(date.toString(), parameters.getString("SCHEDULE_DATE")); - assertEquals("1", parameters.getString("LONG").toString()); - assertEquals("string value", parameters.getString("STRING")); - assertFalse(parameters.getParameters().get("SCHEDULE_DATE").isIdentifying()); - assertFalse(parameters.getParameters().get("LONG").isIdentifying()); - assertFalse(parameters.getParameters().get("STRING").isIdentifying()); - } - - @Test - public void testFromNullProperties() { - JobParametersBuilder builder = new JobParametersBuilder((Properties) null); - JobParameters parameters = builder.toJobParameters(); - assertEquals(0, parameters.getParameters().size()); - } - @Test public void testNonIdentifyingParameters() { parametersBuilder.addDate("SCHEDULE_DATE", date, false); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersTests.java index 119584b1a..e7a8c62d7 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersTests.java @@ -10,7 +10,6 @@ import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; -import java.util.Properties; import org.junit.Before; import org.junit.Test; @@ -213,18 +212,4 @@ public class JobParametersTests { public void testDateReturnsNullWhenKeyDoesntExit(){ assertNull(new JobParameters().getDate("keythatdoesntexist")); } - - @Test - public void testToProperties() { - Properties results = parameters.toProperties(); - - assertEquals(results.get("string.key1"), "value1"); - assertEquals(results.get("string.key2"), "value2"); - assertEquals(results.get("long.key1"), "1"); - assertEquals(results.get("long.key2"), "2"); - assertEquals(results.get("double.key1"), "1.1"); - assertEquals(results.get("double.key2"), "2.2"); - assertEquals(results.get("date.key1"), String.valueOf(date1.getTime())); - assertEquals(results.get("date.key2"), String.valueOf(date2.getTime())); - } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/StepExecutionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/StepExecutionTests.java index 043e60860..f0aa2c80f 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/StepExecutionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/StepExecutionTests.java @@ -305,7 +305,7 @@ public class StepExecutionTests { private StepExecution newStepExecution(Step step, Long jobExecutionId, long stepExecutionId) { JobInstance job = new JobInstance(3L, "testJob"); - StepExecution execution = new StepExecution(step.getName(), new JobExecution(job, jobExecutionId, new JobParameters()), stepExecutionId); + StepExecution execution = new StepExecution(step.getName(), new JobExecution(job, jobExecutionId, new JobParameters(), null), stepExecutionId); return execution; } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/AbstractJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/AbstractJobParserTests.java index 42a1aeea6..caa1e4209 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/AbstractJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/AbstractJobParserTests.java @@ -22,7 +22,7 @@ import java.util.ArrayList; import org.junit.Before; import org.springframework.batch.core.Job; import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; @@ -42,7 +42,7 @@ public abstract class AbstractJobParserTests { @Autowired private JobRepository jobRepository; - + @Autowired private MapJobRepositoryFactoryBean mapJobRepositoryFactoryBean; @@ -59,8 +59,8 @@ public abstract class AbstractJobParserTests { * @return JobExecution */ protected JobExecution createJobExecution() throws JobInstanceAlreadyCompleteException, JobRestartException, - JobExecutionAlreadyRunningException { - return jobRepository.createJobExecution(job.getName(), new JobParameters()); + JobExecutionAlreadyRunningException { + return jobRepository.createJobExecution(job.getName(), new JobParametersBuilder().addLong("key1", 1l).toJobParameters()); } /** diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyJobRepository.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyJobRepository.java index 8a0f04aaa..4497c440f 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyJobRepository.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyJobRepository.java @@ -95,4 +95,15 @@ public class DummyJobRepository implements JobRepository, BeanNameAware { public void addAll(Collection stepExecutions) { } + @Override + public JobInstance createJobInstance(String jobName, + JobParameters jobParameters) { + return null; + } + + @Override + public JobExecution createJobExecution(JobInstance jobInstance, + JobParameters jobParameters, String jobConfigurationLocation) { + return null; + } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/SimpleJobExplorerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/SimpleJobExplorerTests.java index a8dfb51ee..312557bae 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/SimpleJobExplorerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/SimpleJobExplorerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2007 the original author or authors. + * Copyright 2006-2013 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. @@ -16,11 +16,11 @@ package org.springframework.batch.core.explore.support; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.verify; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import java.util.Collections; @@ -30,6 +30,7 @@ import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobInstance; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.launch.NoSuchJobException; import org.springframework.batch.core.repository.dao.ExecutionContextDao; import org.springframework.batch.core.repository.dao.JobExecutionDao; import org.springframework.batch.core.repository.dao.JobInstanceDao; @@ -40,7 +41,7 @@ import org.springframework.batch.core.repository.dao.StepExecutionDao; * * @author Dave Syer * @author Will Schipp - * + * @author Michael Minella * */ public class SimpleJobExplorerTests { @@ -57,7 +58,7 @@ public class SimpleJobExplorerTests { private ExecutionContextDao ecDao; - private JobExecution jobExecution = new JobExecution(jobInstance, 1234L, new JobParameters()); + private JobExecution jobExecution = new JobExecution(jobInstance, 1234L, new JobParameters(), null); @Before public void setUp() throws Exception { @@ -93,13 +94,13 @@ public class SimpleJobExplorerTests { when(jobInstanceDao.getJobInstance(jobExecution)).thenReturn(jobInstance); StepExecution stepExecution = jobExecution.createStepExecution("foo"); when(stepExecutionDao.getStepExecution(jobExecution, 123L)) - .thenReturn(stepExecution); + .thenReturn(stepExecution); when(ecDao.getExecutionContext(stepExecution)).thenReturn(null); stepExecution = jobExplorer.getStepExecution(jobExecution.getId(), 123L); - - assertEquals(jobInstance, - stepExecution.getJobExecution().getJobInstance()); - + + assertEquals(jobInstance, + stepExecution.getJobExecution().getJobInstance()); + verify(jobInstanceDao).getJobInstance(jobExecution); } @@ -107,7 +108,7 @@ public class SimpleJobExplorerTests { public void testGetStepExecutionMissing() throws Exception { when(jobExecutionDao.getJobExecution(jobExecution.getId())).thenReturn(jobExecution); when(stepExecutionDao.getStepExecution(jobExecution, 123L)) - .thenReturn(null); + .thenReturn(null); assertNull(jobExplorer.getStepExecution(jobExecution.getId(), 123L)); } @@ -161,4 +162,17 @@ public class SimpleJobExplorerTests { jobExplorer.getJobNames(); } + @Test + public void testGetJobInstanceCount() throws Exception { + when(jobInstanceDao.getJobInstanceCount("myJob")).thenReturn(4); + + assertEquals(4, jobExplorer.getJobInstanceCount("myJob")); + } + + @Test(expected=NoSuchJobException.class) + public void testGetJobInstanceCountException() throws Exception { + when(jobInstanceDao.getJobInstanceCount("throwException")).thenThrow(new NoSuchJobException("expected")); + + jobExplorer.getJobInstanceCount("throwException"); + } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/JobContextTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/JobContextTests.java index fb6f137c9..d3f364251 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/JobContextTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/JobContextTests.java @@ -24,17 +24,19 @@ public class JobContextTests { private JobExecution execution; @Mock private JobInstance instance; + @Mock + private ParametersConverter converter; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - context = new JobContext(execution); + context = new JobContext(execution, converter); when(execution.getJobInstance()).thenReturn(instance); } @Test(expected=IllegalArgumentException.class) public void testCreateWithNull() { - context = new JobContext(null); + context = new JobContext(null, null); } @Test @@ -69,8 +71,11 @@ public class JobContextTests { JobParameters params = new JobParametersBuilder() .addString("key1", "value1") .toJobParameters(); + Properties results = new Properties(); + results.put("key1", "value1"); when(execution.getJobParameters()).thenReturn(params); + when(converter.convert(params)).thenReturn(results); Properties props = context.getProperties(); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/JobExecutionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/JobExecutionTests.java index 0bb970121..8252028cf 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/JobExecutionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/JobExecutionTests.java @@ -34,12 +34,12 @@ public class JobExecutionTests { execution.setStatus(BatchStatus.FAILED); execution.setVersion(21); - adapter = new JobExecution(execution); + adapter = new JobExecution(execution, new ParametersConverterSupport()); } @Test(expected=IllegalArgumentException.class) public void testCreateWithNull() { - adapter = new JobExecution(null); + adapter = new JobExecution(null, new ParametersConverterSupport()); } @Test diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/JsrJobParametersConverterTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/JsrJobParametersConverterTests.java new file mode 100644 index 000000000..24224cb84 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/JsrJobParametersConverterTests.java @@ -0,0 +1,110 @@ +package org.springframework.batch.core.jsr; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; + +import javax.sql.DataSource; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; + +public class JsrJobParametersConverterTests { + + private JsrJobParametersConverter converter; + private static DataSource dataSource; + + @BeforeClass + public static void setupDatabase() { + dataSource = new EmbeddedDatabaseBuilder(). + addScript("classpath:org/springframework/batch/core/schema-drop-hsqldb.sql"). + addScript("classpath:org/springframework/batch/core/schema-hsqldb.sql"). + build(); + } + + @Before + public void setUp() throws Exception { + converter = new JsrJobParametersConverter(dataSource); + converter.afterPropertiesSet(); + } + + @Test + public void testNullJobParameters() { + Properties props = converter.convert((JobParameters) null); + assertNotNull(props); + Set> properties = props.entrySet(); + assertEquals(1, properties.size()); + assertTrue(props.containsKey(JsrJobParametersConverter.JOB_RUN_ID)); + } + + @Test + public void testStringJobParameters() { + JobParameters parameters = new JobParametersBuilder().addString("key", "value", false).toJobParameters(); + Properties props = converter.convert(parameters); + assertNotNull(props); + Set> properties = props.entrySet(); + assertEquals(2, properties.size()); + assertTrue(props.containsKey(JsrJobParametersConverter.JOB_RUN_ID)); + assertEquals("value", props.getProperty("key")); + } + + @Test + public void testNonStringJobParameters() { + JobParameters parameters = new JobParametersBuilder().addLong("key", 5l, false).toJobParameters(); + Properties props = converter.convert(parameters); + assertNotNull(props); + Set> properties = props.entrySet(); + assertEquals(2, properties.size()); + assertTrue(props.containsKey(JsrJobParametersConverter.JOB_RUN_ID)); + assertEquals("5", props.getProperty("key")); + } + + @Test + public void testJobParametersWithRunId() { + JobParameters parameters = new JobParametersBuilder().addLong("key", 5l, false).addLong(JsrJobParametersConverter.JOB_RUN_ID, 2l).toJobParameters(); + Properties props = converter.convert(parameters); + assertNotNull(props); + Set> properties = props.entrySet(); + assertEquals(2, properties.size()); + assertEquals("2", props.getProperty(JsrJobParametersConverter.JOB_RUN_ID)); + assertEquals("5", props.getProperty("key")); + } + + @Test + public void testNullProperties() { + JobParameters parameters = converter.convert((Properties)null); + assertNotNull(parameters); + assertEquals(1, parameters.getParameters().size()); + assertTrue(parameters.getParameters().containsKey(JsrJobParametersConverter.JOB_RUN_ID)); + } + + @Test + public void testProperties() { + Properties properties = new Properties(); + properties.put("key", "value"); + JobParameters parameters = converter.convert(properties); + assertEquals(2, parameters.getParameters().size()); + assertEquals("value", parameters.getString("key")); + assertTrue(parameters.getParameters().containsKey(JsrJobParametersConverter.JOB_RUN_ID)); + } + + @Test + public void testPropertiesWithRunId() { + Properties properties = new Properties(); + properties.put("key", "value"); + properties.put(JsrJobParametersConverter.JOB_RUN_ID, "3"); + JobParameters parameters = converter.convert(properties); + assertEquals(2, parameters.getParameters().size()); + assertEquals("value", parameters.getString("key")); + assertEquals(Long.valueOf(3l), parameters.getLong(JsrJobParametersConverter.JOB_RUN_ID)); + assertTrue(parameters.getParameters().get(JsrJobParametersConverter.JOB_RUN_ID).isIdentifying()); + } +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/ParametersConverterSupport.java b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/ParametersConverterSupport.java new file mode 100644 index 000000000..3e3e0210e --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/ParametersConverterSupport.java @@ -0,0 +1,40 @@ +package org.springframework.batch.core.jsr; + +import java.util.Map; +import java.util.Properties; + +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; + +public class ParametersConverterSupport implements ParametersConverter { + + @Override + public JobParameters convert(Properties parameters) { + JobParametersBuilder builder = new JobParametersBuilder(); + + if(parameters != null) { + for (Map.Entry curParameter : parameters.entrySet()) { + if(curParameter.getValue() != null) { + + builder.addString(curParameter.getKey().toString(), curParameter.getValue().toString(), false); + } + } + } + + return builder.toJobParameters(); + } + + @Override + public Properties convert(JobParameters parameters) { + Properties properties = new Properties(); + + if(properties != null) { + for(Map.Entry curParameter: parameters.getParameters().entrySet()) { + properties.setProperty(curParameter.getKey(), curParameter.getValue().getValue().toString()); + } + } + + return properties; + } +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/SimpleMetricTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/SimpleMetricTests.java new file mode 100644 index 000000000..ef588366e --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/SimpleMetricTests.java @@ -0,0 +1,24 @@ +package org.springframework.batch.core.jsr; + +import static org.junit.Assert.assertEquals; + +import javax.batch.runtime.Metric; +import javax.batch.runtime.Metric.MetricType; + +import org.junit.Test; + +public class SimpleMetricTests { + + @Test(expected=IllegalArgumentException.class) + public void testNullType() { + Metric metric = new SimpleMetric(null, 0); + } + + @Test + public void test() { + Metric metric = new SimpleMetric(MetricType.FILTER_COUNT, 3); + + assertEquals(3, metric.getValue()); + assertEquals(MetricType.FILTER_COUNT, metric.getType()); + } +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/StepContextTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/StepContextTests.java new file mode 100644 index 000000000..ad1f27908 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/StepContextTests.java @@ -0,0 +1,94 @@ +package org.springframework.batch.core.jsr; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.util.Properties; + +import javax.batch.runtime.Metric; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.StepExecution; + +public class StepContextTests { + + private StepExecution stepExecution; + private StepContext stepContext; + + @Before + public void setUp() throws Exception { + JobExecution jobExecution = new JobExecution(1l, new JobParametersBuilder().addString("key", "value").toJobParameters()); + + stepExecution = new StepExecution("testStep", jobExecution); + stepExecution.setId(5l); + stepExecution.setStatus(BatchStatus.STARTED); + stepExecution.setExitStatus(new ExitStatus("customExitStatus")); + stepExecution.setCommitCount(1); + stepExecution.setFilterCount(2); + stepExecution.setProcessSkipCount(3); + stepExecution.setReadCount(4); + stepExecution.setReadSkipCount(5); + stepExecution.setRollbackCount(6); + stepExecution.setWriteCount(7); + stepExecution.setWriteSkipCount(8); + + stepContext = new StepContext(stepExecution, new ParametersConverterSupport()); + stepContext.setTransientUserData("This is my transient data"); + } + + @Test + public void testBasicProperties() { + assertEquals(javax.batch.runtime.BatchStatus.STARTED, stepContext.getBatchStatus()); + assertEquals("customExitStatus", stepContext.getExitStatus()); + assertEquals(5l, stepContext.getStepExecutionId()); + assertEquals("testStep", stepContext.getStepName()); + assertEquals("This is my transient data", stepContext.getTransientUserData()); + + Properties params = stepContext.getProperties(); + assertEquals("value", params.get("key")); + + Metric[] metrics = stepContext.getMetrics(); + + for (Metric metric : metrics) { + switch (metric.getType()) { + case COMMIT_COUNT: + assertEquals(1, metric.getValue()); + break; + case FILTER_COUNT: + assertEquals(2, metric.getValue()); + break; + case PROCESS_SKIP_COUNT: + assertEquals(3, metric.getValue()); + break; + case READ_COUNT: + assertEquals(4, metric.getValue()); + break; + case READ_SKIP_COUNT: + assertEquals(5, metric.getValue()); + break; + case ROLLBACK_COUNT: + assertEquals(6, metric.getValue()); + break; + case WRITE_COUNT: + assertEquals(7, metric.getValue()); + break; + case WRITE_SKIP_COUNT: + assertEquals(8, metric.getValue()); + break; + default: + fail("Invalid metric type"); + } + } + } + + @Test + public void testSetExitStatus() { + stepContext.setExitStatus("new Exit Status"); + assertEquals("new Exit Status", stepExecution.getExitStatus().getExitCode()); + } +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/StepExecutionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/StepExecutionTests.java new file mode 100644 index 000000000..2543e2eb6 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/StepExecutionTests.java @@ -0,0 +1,100 @@ +package org.springframework.batch.core.jsr; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +import java.util.Date; + +import javax.batch.runtime.Metric; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.StepExecution; + +public class StepExecutionTests { + + private StepExecution stepExecution; + private javax.batch.runtime.StepExecution jsrStepExecution; + + @Before + public void setUp() throws Exception { + JobExecution jobExecution = new JobExecution(1l, new JobParametersBuilder().addString("key", "value").toJobParameters()); + + stepExecution = new StepExecution("testStep", jobExecution); + stepExecution.setId(5l); + stepExecution.setStatus(BatchStatus.STARTED); + stepExecution.setExitStatus(new ExitStatus("customExitStatus")); + stepExecution.setCommitCount(1); + stepExecution.setFilterCount(2); + stepExecution.setProcessSkipCount(3); + stepExecution.setReadCount(4); + stepExecution.setReadSkipCount(5); + stepExecution.setRollbackCount(6); + stepExecution.setWriteCount(7); + stepExecution.setWriteSkipCount(8); + stepExecution.setStartTime(new Date(0)); + stepExecution.setEndTime(new Date(10000000)); + + jsrStepExecution = new org.springframework.batch.core.jsr.StepExecution(stepExecution); + } + + @Test(expected=IllegalArgumentException.class) + public void testWithNullStepExecution() { + new org.springframework.batch.core.jsr.StepExecution(null); + } + + @Test + public void testNullExitStatus() { + stepExecution.setExitStatus(null); + + assertNull(jsrStepExecution.getExitStatus()); + } + + @Test + public void testBaseValues() { + assertEquals(5l, jsrStepExecution.getStepExecutionId()); + assertEquals("testStep", jsrStepExecution.getStepName()); + assertEquals(javax.batch.runtime.BatchStatus.STARTED, jsrStepExecution.getBatchStatus()); + assertEquals(new Date(0), jsrStepExecution.getStartTime()); + assertEquals(new Date(10000000), jsrStepExecution.getEndTime()); + assertEquals("customExitStatus", jsrStepExecution.getExitStatus()); + + Metric[] metrics = jsrStepExecution.getMetrics(); + + for (Metric metric : metrics) { + switch (metric.getType()) { + case COMMIT_COUNT: + assertEquals(1, metric.getValue()); + break; + case FILTER_COUNT: + assertEquals(2, metric.getValue()); + break; + case PROCESS_SKIP_COUNT: + assertEquals(3, metric.getValue()); + break; + case READ_COUNT: + assertEquals(4, metric.getValue()); + break; + case READ_SKIP_COUNT: + assertEquals(5, metric.getValue()); + break; + case ROLLBACK_COUNT: + assertEquals(6, metric.getValue()); + break; + case WRITE_COUNT: + assertEquals(7, metric.getValue()); + break; + case WRITE_SKIP_COUNT: + assertEquals(8, metric.getValue()); + break; + default: + fail("Invalid metric type"); + } + } + } +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/launch/JsrJobOperatorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/launch/JsrJobOperatorTests.java new file mode 100644 index 000000000..a99f8750c --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/launch/JsrJobOperatorTests.java @@ -0,0 +1,340 @@ +package org.springframework.batch.core.jsr.launch; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Properties; +import java.util.Set; + +import javax.batch.operations.JobExecutionIsRunningException; +import javax.batch.operations.JobOperator; +import javax.batch.operations.NoSuchJobException; +import javax.batch.operations.NoSuchJobExecutionException; +import javax.batch.operations.NoSuchJobInstanceException; +import javax.batch.runtime.BatchRuntime; +import javax.batch.runtime.BatchStatus; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobInstance; +import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.explore.support.SimpleJobExplorer; +import org.springframework.batch.core.jsr.ParametersConverter; +import org.springframework.batch.core.jsr.ParametersConverterSupport; +import org.springframework.batch.core.launch.support.SimpleJobOperator; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.JobRepositorySupport; + +public class JsrJobOperatorTests { + + private JobOperator jsrJobOperator; + @Mock + private org.springframework.batch.core.launch.JobOperator jobOperator; + @Mock + private JobExplorer jobExplorer; + @Mock + private JobRepository jobRepository; + private ParametersConverter parameterConverter; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + parameterConverter = new ParametersConverterSupport(); + jsrJobOperator = new JsrJobOperator(jobExplorer, jobRepository, jobOperator, parameterConverter); + } + + @Test + public void testLoadingWithBatchRuntime() { + jsrJobOperator = BatchRuntime.getJobOperator(); + assertNotNull(jsrJobOperator); + } + + @Test + public void testNullsInConstructor() { + try { + new JsrJobOperator(null, new JobRepositorySupport(), new SimpleJobOperator(), parameterConverter); + fail("JobExplorer should be required"); + } catch (IllegalArgumentException correct) { + } + + try { + new JsrJobOperator(new SimpleJobExplorer(null, null, null, null), null, new SimpleJobOperator(), parameterConverter); + fail("JobRepository should be required"); + } catch (IllegalArgumentException correct) { + } + + try { + new JsrJobOperator(new SimpleJobExplorer(null, null, null, null), new JobRepositorySupport(), null, parameterConverter); + fail("JobOperator should be required"); + } catch (IllegalArgumentException correct) { + } + + try { + new JsrJobOperator(new SimpleJobExplorer(null, null, null, null), new JobRepositorySupport(), new SimpleJobOperator(), null); + fail("ParameterConverter should be required"); + } catch (IllegalArgumentException correct) { + } + + new JsrJobOperator(new SimpleJobExplorer(null, null, null, null), new JobRepositorySupport(), new SimpleJobOperator(), parameterConverter); + } + + @Test + public void testAbandonRoseyScenario() throws Exception { + jsrJobOperator.abandon(5l); + + verify(jobOperator).abandon(5l); + } + + @Test(expected=NoSuchJobExecutionException.class) + public void testAbandonNoSuchJob() throws Exception { + when(jobOperator.abandon(5l)).thenThrow(new org.springframework.batch.core.launch.NoSuchJobExecutionException("expected")); + + jsrJobOperator.abandon(5l); + } + + @Test(expected=JobExecutionIsRunningException.class) + public void testAbandonJobRunning() throws Exception { + when(jobOperator.abandon(5l)).thenThrow(new JobExecutionAlreadyRunningException("expected")); + + jsrJobOperator.abandon(5l); + } + + @Test + public void testGetJobExecutionRoseyScenario() { + when(jobExplorer.getJobExecution(5l)).thenReturn(new JobExecution(5l)); + + assertEquals(5l, jsrJobOperator.getJobExecution(5l).getExecutionId()); + } + + @Test(expected=NoSuchJobExecutionException.class) + public void testGetJobExecutionNoExecutionFound() { + jsrJobOperator.getJobExecution(5l); + } + + @Test + public void testGetJobExecutionsRoseyScenario() { + org.springframework.batch.core.JobInstance jobInstance = new org.springframework.batch.core.JobInstance(5l, "my job"); + List executions = new ArrayList(); + executions.add(new JobExecution(2l)); + + when(jobExplorer.getJobExecutions(jobInstance)).thenReturn(executions); + + List jobExecutions = jsrJobOperator.getJobExecutions(jobInstance); + assertEquals(1, jobExecutions.size()); + assertEquals(2l, executions.get(0).getId().longValue()); + } + + @Test(expected=NoSuchJobInstanceException.class) + public void testGetJobExecutionsNullJobInstance() { + jsrJobOperator.getJobExecutions(null); + } + + @Test(expected=NoSuchJobInstanceException.class) + public void testGetJobExecutionsNullReturned() { + org.springframework.batch.core.JobInstance jobInstance = new org.springframework.batch.core.JobInstance(5l, "my job"); + + jsrJobOperator.getJobExecutions(jobInstance); + } + + @Test(expected=NoSuchJobInstanceException.class) + public void testGetJobExecutionsNoneReturned() { + org.springframework.batch.core.JobInstance jobInstance = new org.springframework.batch.core.JobInstance(5l, "my job"); + List executions = new ArrayList(); + + when(jobExplorer.getJobExecutions(jobInstance)).thenReturn(executions); + + jsrJobOperator.getJobExecutions(jobInstance); + } + + @Test + public void testGetJobInstanceRoseyScenario() { + JobInstance instance = new JobInstance(1l, "my job"); + JobExecution execution = new JobExecution(5l); + execution.setJobInstance(instance); + + when(jobExplorer.getJobExecution(5l)).thenReturn(execution); + when(jobExplorer.getJobInstance(1l)).thenReturn(instance); + + javax.batch.runtime.JobInstance jobInstance = jsrJobOperator.getJobInstance(5l); + + assertEquals(1l, jobInstance.getInstanceId()); + assertEquals("my job", jobInstance.getJobName()); + } + + @Test(expected=NoSuchJobExecutionException.class) + public void testGetJobInstanceNoExecution() { + JobInstance instance = new JobInstance(1l, "my job"); + JobExecution execution = new JobExecution(5l); + execution.setJobInstance(instance); + + jsrJobOperator.getJobInstance(5l); + } + + @Test + public void testGetJobInstanceCount() throws Exception { + when(jobExplorer.getJobInstanceCount("myJob")).thenReturn(4); + + assertEquals(4, jsrJobOperator.getJobInstanceCount("myJob")); + } + + @Test(expected=NoSuchJobException.class) + public void testGetJobInstanceCountNoSuchJob() throws Exception { + when(jobExplorer.getJobInstanceCount("myJob")).thenThrow(new org.springframework.batch.core.launch.NoSuchJobException("expected")); + + jsrJobOperator.getJobInstanceCount("myJob"); + } + + @Test + public void testGetJobInstancesRoseyScenario() { + List instances = new ArrayList(); + instances.add(new JobInstance(1l, "myJob")); + instances.add(new JobInstance(2l, "myJob")); + instances.add(new JobInstance(3l, "myJob")); + + when(jobExplorer.getJobInstances("myJob", 0, 3)).thenReturn(instances); + + List jobInstances = jsrJobOperator.getJobInstances("myJob", 0, 3); + + assertEquals(3, jobInstances.size()); + assertEquals(1l, jobInstances.get(0).getInstanceId()); + assertEquals(2l, jobInstances.get(1).getInstanceId()); + assertEquals(3l, jobInstances.get(2).getInstanceId()); + } + + @Test(expected=NoSuchJobException.class) + public void testGetJobInstancesNullInstancesReturned() { + jsrJobOperator.getJobInstances("myJob", 0, 3); + } + + @Test(expected=NoSuchJobException.class) + public void testGetJobInstancesZeroInstancesReturned() { + List instances = new ArrayList(); + + when(jobExplorer.getJobInstances("myJob", 0, 3)).thenReturn(instances); + + jsrJobOperator.getJobInstances("myJob", 0, 3); + } + + @Test + public void testGetJobNames() { + List jobNames = new ArrayList(); + jobNames.add("job1"); + jobNames.add("job2"); + + when(jobExplorer.getJobNames()).thenReturn(jobNames); + + Set result = jsrJobOperator.getJobNames(); + + assertEquals(2, result.size()); + assertTrue(result.contains("job1")); + assertTrue(result.contains("job2")); + } + + @Test + public void testGetParametersRoseyScenario() { + JobExecution jobExecution = new JobExecution(5l, new JobParametersBuilder().addString("key1", "value1").toJobParameters()); + + when(jobExplorer.getJobExecution(5l)).thenReturn(jobExecution); + + Properties params = jsrJobOperator.getParameters(5l); + + assertEquals("value1", params.get("key1")); + } + + @Test(expected=NoSuchJobExecutionException.class) + public void testGetParametersNoExecution() { + jsrJobOperator.getParameters(5l); + } + + @Test + public void testGetRunningExecutions() { + Set executions = new HashSet(); + executions.add(new JobExecution(5l)); + + when(jobExplorer.findRunningJobExecutions("myJob")).thenReturn(executions); + + assertEquals(5l, jsrJobOperator.getRunningExecutions("myJob").get(0).longValue()); + } + + @Test + public void testGetStepExecutionsRoseyScenario() { + JobExecution jobExecution = new JobExecution(5l); + List stepExecutions = new ArrayList(); + stepExecutions.add(new StepExecution("step1", jobExecution)); + stepExecutions.add(new StepExecution("step2", jobExecution)); + jobExecution.addStepExecutions(stepExecutions); + + when(jobExplorer.getJobExecution(5l)).thenReturn(jobExecution); + + List results = jsrJobOperator.getStepExecutions(5l); + + assertEquals("step1", results.get(0).getStepName()); + assertEquals("step2", results.get(1).getStepName()); + } + + @Test(expected=NoSuchJobException.class) + public void testGetStepExecutionsNoExecutionReturned() { + jsrJobOperator.getStepExecutions(5l); + } + + @Test + public void testGetStepExecutionsNoStepExecutions() { + JobExecution jobExecution = new JobExecution(5l); + + when(jobExplorer.getJobExecution(5l)).thenReturn(jobExecution); + + List results = jsrJobOperator.getStepExecutions(5l); + + assertEquals(0, results.size()); + } + + @Test + public void testStartRoseyScenario() { + jsrJobOperator = BatchRuntime.getJobOperator(); + + long executionId = jsrJobOperator.start("jsrJobOperatorTestJob", null); + + assertEquals(BatchStatus.COMPLETED, jsrJobOperator.getJobExecution(executionId).getBatchStatus()); + } + + @Test + public void testStartMultipleTimesSameParameters() { + jsrJobOperator = BatchRuntime.getJobOperator(); + + long run1 = jsrJobOperator.start("jsrJobOperatorTestJob", null); + long run2 = jsrJobOperator.start("jsrJobOperatorTestJob", null); + long run3 = jsrJobOperator.start("jsrJobOperatorTestJob", null); + + assertEquals(BatchStatus.COMPLETED, jsrJobOperator.getJobExecution(run1).getBatchStatus()); + assertEquals(BatchStatus.COMPLETED, jsrJobOperator.getJobExecution(run2).getBatchStatus()); + assertEquals(BatchStatus.COMPLETED, jsrJobOperator.getJobExecution(run3).getBatchStatus()); + + assertTrue(3 >= jsrJobOperator.getJobInstanceCount("jsrJobOperatorTestJob")); + } + + @Test + public void testRestartRoseyScenario() { + jsrJobOperator = BatchRuntime.getJobOperator(); + + long executionId = jsrJobOperator.start("jsrJobOperatorTestRestartJob", null); + + assertEquals(BatchStatus.FAILED, jsrJobOperator.getJobExecution(executionId).getBatchStatus()); + + long finalExecutionId = jsrJobOperator.restart(executionId, null); + + assertEquals(BatchStatus.COMPLETED, jsrJobOperator.getJobExecution(finalExecutionId).getBatchStatus()); + } +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/step/batchlet/RestartBatchlet.java b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/step/batchlet/RestartBatchlet.java new file mode 100644 index 000000000..65a56c0fe --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/step/batchlet/RestartBatchlet.java @@ -0,0 +1,23 @@ +package org.springframework.batch.core.jsr.step.batchlet; + +import javax.batch.api.Batchlet; + +public class RestartBatchlet implements Batchlet { + + private static int runCount = 0; + + @Override + public String process() throws Exception { + runCount++; + + if(runCount == 1) { + throw new RuntimeException("This is expected"); + } + + return null; + } + + @Override + public void stop() throws Exception { + } +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/CommandLineJobRunnerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/CommandLineJobRunnerTests.java index 7e2a85e38..b50c45f7e 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/CommandLineJobRunnerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/CommandLineJobRunnerTests.java @@ -42,6 +42,7 @@ import org.springframework.batch.core.converter.DefaultJobParametersConverter; import org.springframework.batch.core.converter.JobParametersConverter; import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.NoSuchJobException; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; import org.springframework.batch.core.step.JobRepositorySupport; import org.springframework.util.ClassUtils; @@ -69,7 +70,7 @@ public class CommandLineJobRunnerTests { @Before public void setUp() throws Exception { - JobExecution jobExecution = new JobExecution(null, new Long(1), null); + JobExecution jobExecution = new JobExecution(null, new Long(1), null, null); ExitStatus exitStatus = ExitStatus.COMPLETED; jobExecution.setExitStatus(exitStatus); StubJobLauncher.jobExecution = jobExecution; @@ -284,7 +285,7 @@ public class CommandLineJobRunnerTests { public void testRestartExecution() throws Throwable { String[] args = new String[] { jobPath, "-restart", "11" }; JobParameters jobParameters = new JobParametersBuilder().addString("foo", "bar").toJobParameters(); - JobExecution jobExecution = new JobExecution(new JobInstance(0L, jobName), 11L, jobParameters); + JobExecution jobExecution = new JobExecution(new JobInstance(0L, jobName), 11L, jobParameters, null); jobExecution.setStatus(BatchStatus.FAILED); StubJobExplorer.jobExecution = jobExecution; CommandLineJobRunner.main(args); @@ -296,7 +297,7 @@ public class CommandLineJobRunnerTests { public void testRestartExecutionNotFailed() throws Throwable { String[] args = new String[] { jobPath, "-restart", "11" }; JobParameters jobParameters = new JobParametersBuilder().addString("foo", "bar").toJobParameters(); - JobExecution jobExecution = new JobExecution(new JobInstance(0L, jobName), 11L, jobParameters); + JobExecution jobExecution = new JobExecution(new JobInstance(0L, jobName), 11L, jobParameters, null); jobExecution.setStatus(BatchStatus.COMPLETED); StubJobExplorer.jobExecution = jobExecution; CommandLineJobRunner.main(args); @@ -451,7 +452,7 @@ public class CommandLineJobRunnerTests { } private JobExecution createJobExecution(JobInstance jobInstance, BatchStatus status) { - JobExecution jobExecution = new JobExecution(jobInstance, 1L, jobParameters); + JobExecution jobExecution = new JobExecution(jobInstance, 1L, jobParameters, null); jobExecution.setStatus(status); jobExecution.setStartTime(new Date()); if (status != BatchStatus.STARTED) { @@ -485,6 +486,24 @@ public class CommandLineJobRunnerTests { throw new UnsupportedOperationException(); } + @Override + public int getJobInstanceCount(String jobName) + throws NoSuchJobException { + int count = 0; + + for (JobInstance jobInstance : jobInstances) { + if(jobInstance.getJobName().equals(jobName)) { + count++; + } + } + + if(count == 0) { + throw new NoSuchJobException("Unable to find job instances for " + jobName); + } else { + return count; + } + } + } public static class StubJobParametersConverter implements JobParametersConverter { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/SimpleJobOperatorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/SimpleJobOperatorTests.java index 197153ac9..96813e5af 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/SimpleJobOperatorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/SimpleJobOperatorTests.java @@ -15,13 +15,12 @@ */ package org.springframework.batch.core.launch.support; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.util.Arrays; import java.util.Collections; @@ -112,7 +111,7 @@ public class SimpleJobOperatorTests { @Override public JobExecution run(Job job, JobParameters jobParameters) throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException { - return new JobExecution(new JobInstance(123L, job.getName()), 999L, jobParameters); + return new JobExecution(new JobInstance(123L, job.getName()), 999L, jobParameters, null); } }); @@ -192,7 +191,7 @@ public class SimpleJobOperatorTests { @Test public void testResumeSunnyDay() throws Exception { jobParameters = new JobParameters(); - when(jobExplorer.getJobExecution(111l)).thenReturn(new JobExecution(new JobInstance(123L, job.getName()), 111L, jobParameters)); + when(jobExplorer.getJobExecution(111l)).thenReturn(new JobExecution(new JobInstance(123L, job.getName()), 111L, jobParameters, null)); jobExplorer.getJobExecution(111L); Long value = jobOperator.restart(111L); assertEquals(999, value.longValue()); @@ -201,7 +200,7 @@ public class SimpleJobOperatorTests { @Test public void testGetSummarySunnyDay() throws Exception { jobParameters = new JobParameters(); - JobExecution jobExecution = new JobExecution(new JobInstance(123L, job.getName()), 111L, jobParameters); + JobExecution jobExecution = new JobExecution(new JobInstance(123L, job.getName()), 111L, jobParameters, null); when(jobExplorer.getJobExecution(111L)).thenReturn(jobExecution); jobExplorer.getJobExecution(111L); String value = jobOperator.getSummary(111L); @@ -224,7 +223,7 @@ public class SimpleJobOperatorTests { public void testGetStepExecutionSummariesSunnyDay() throws Exception { jobParameters = new JobParameters(); - JobExecution jobExecution = new JobExecution(new JobInstance(123L, job.getName()), 111L, jobParameters); + JobExecution jobExecution = new JobExecution(new JobInstance(123L, job.getName()), 111L, jobParameters, null); jobExecution.createStepExecution("step1"); jobExecution.createStepExecution("step2"); jobExecution.getStepExecutions().iterator().next().setId(21L); @@ -248,7 +247,7 @@ public class SimpleJobOperatorTests { @Test public void testFindRunningExecutionsSunnyDay() throws Exception { jobParameters = new JobParameters(); - JobExecution jobExecution = new JobExecution(new JobInstance(123L, job.getName()), 111L, jobParameters); + JobExecution jobExecution = new JobExecution(new JobInstance(123L, job.getName()), 111L, jobParameters, null); when(jobExplorer.findRunningJobExecutions("foo")).thenReturn(Collections.singleton(jobExecution)); Set value = jobOperator.getRunningExecutions("foo"); assertEquals(111L, value.iterator().next().longValue()); @@ -269,7 +268,7 @@ public class SimpleJobOperatorTests { @Test public void testGetJobParametersSunnyDay() throws Exception { final JobParameters jobParameters = new JobParameters(); - when(jobExplorer.getJobExecution(111L)).thenReturn(new JobExecution(new JobInstance(123L, job.getName()), 111L, jobParameters)); + when(jobExplorer.getJobExecution(111L)).thenReturn(new JobExecution(new JobInstance(123L, job.getName()), 111L, jobParameters, null)); String value = jobOperator.getParameters(111L); assertEquals("a=b", value); } @@ -319,8 +318,8 @@ public class SimpleJobOperatorTests { public void testGetExecutionsSunnyDay() throws Exception { JobInstance jobInstance = new JobInstance(123L, job.getName()); when(jobExplorer.getJobInstance(123L)).thenReturn(jobInstance); - - JobExecution jobExecution = new JobExecution(jobInstance, 111L, jobParameters); + + JobExecution jobExecution = new JobExecution(jobInstance, 111L, jobParameters, null); when(jobExplorer.getJobExecutions(jobInstance)).thenReturn(Collections.singletonList(jobExecution)); List value = jobOperator.getExecutions(123L); assertEquals(111L, value.iterator().next().longValue()); @@ -341,7 +340,7 @@ public class SimpleJobOperatorTests { @Test public void testStop() throws Exception{ JobInstance jobInstance = new JobInstance(123L, job.getName()); - JobExecution jobExecution = new JobExecution(jobInstance, 111L, jobParameters); + JobExecution jobExecution = new JobExecution(jobInstance, 111L, jobParameters, null); when(jobExplorer.getJobExecution(111L)).thenReturn(jobExecution); jobExplorer.getJobExecution(111L); jobRepository.update(jobExecution); @@ -352,7 +351,7 @@ public class SimpleJobOperatorTests { @Test public void testAbort() throws Exception { JobInstance jobInstance = new JobInstance(123L, job.getName()); - JobExecution jobExecution = new JobExecution(jobInstance, 111L, jobParameters); + JobExecution jobExecution = new JobExecution(jobInstance, 111L, jobParameters, null); jobExecution.setStatus(BatchStatus.STOPPING); when(jobExplorer.getJobExecution(123L)).thenReturn(jobExecution); jobRepository.update(jobExecution); @@ -364,7 +363,7 @@ public class SimpleJobOperatorTests { @Test(expected = JobExecutionAlreadyRunningException.class) public void testAbortNonStopping() throws Exception { JobInstance jobInstance = new JobInstance(123L, job.getName()); - JobExecution jobExecution = new JobExecution(jobInstance, 111L, jobParameters); + JobExecution jobExecution = new JobExecution(jobInstance, 111L, jobParameters, null); jobExecution.setStatus(BatchStatus.STARTED); when(jobExplorer.getJobExecution(123L)).thenReturn(jobExecution); jobRepository.update(jobExecution); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobDaoTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobDaoTests.java index d4397ee46..b90cc45c6 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobDaoTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobDaoTests.java @@ -84,7 +84,6 @@ public abstract class AbstractJobDaoTests { @Before public void onSetUpInTransaction() throws Exception { - // Create job. jobInstance = jobInstanceDao.createJobInstance(jobName, jobParameters); @@ -119,7 +118,6 @@ public abstract class AbstractJobDaoTests { @Transactional @Test public void testFindJob() { - JobInstance instance = jobInstanceDao.getJobInstance(jobName, jobParameters); assertNotNull(instance); assertTrue(jobInstance.equals(instance)); @@ -187,7 +185,7 @@ public abstract class AbstractJobDaoTests { public void testUpdateInvalidJobExecution() { // id is invalid - JobExecution execution = new JobExecution(jobInstance, (long) 29432, jobParameters); + JobExecution execution = new JobExecution(jobInstance, (long) 29432, jobParameters, null); execution.incrementVersion(); try { jobExecutionDao.updateJobExecution(execution); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractStepExecutionDaoTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractStepExecutionDaoTests.java index 5c4f8389e..967af7925 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractStepExecutionDaoTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractStepExecutionDaoTests.java @@ -188,7 +188,7 @@ public abstract class AbstractStepExecutionDaoTests extends AbstractTransactiona @Transactional @Test public void testGetForNotExistingJobExecution() { - assertNull(dao.getStepExecution(new JobExecution(jobInstance, (long) 777, new JobParameters()), 11L)); + assertNull(dao.getStepExecution(new JobExecution(jobInstance, (long) 777, new JobParameters(), null), 11L)); } /** diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDaoTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDaoTests.java index d0ffa096b..c454d84d4 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDaoTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDaoTests.java @@ -39,7 +39,7 @@ public class JdbcJobInstanceDaoTests extends AbstractJobInstanceDaoTests { @Override protected JobInstanceDao getJobInstanceDao() { JdbcTestUtils.deleteFromTables(jdbcTemplate, "BATCH_JOB_EXECUTION_CONTEXT", - "BATCH_STEP_EXECUTION_CONTEXT", "BATCH_STEP_EXECUTION", "BATCH_JOB_EXECUTION_PARAMS", + "BATCH_STEP_EXECUTION_CONTEXT", "BATCH_STEP_EXECUTION", "BATCH_JOB_EXECUTION_PARAMS", "BATCH_JOB_EXECUTION", "BATCH_JOB_INSTANCE"); return jobInstanceDao; } @@ -51,7 +51,7 @@ public class JdbcJobInstanceDaoTests extends AbstractJobInstanceDaoTests { JobParameters jobParameters = new JobParameters(); JobInstance jobInstance = dao.createJobInstance("testInstance", jobParameters); - JobExecution jobExecution = new JobExecution(jobInstance, 2L, jobParameters); + JobExecution jobExecution = new JobExecution(jobInstance, 2L, jobParameters, null); jobExecutionDao.saveJobExecution(jobExecution); JobInstance returnedInstance = dao.getJobInstance(jobExecution); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryTests.java index d7ff292b6..a3bd419af 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryTests.java @@ -50,7 +50,7 @@ import org.springframework.batch.core.step.StepSupport; * * @author Lucas Ward * @author Will Schipp - * + * */ public class SimpleJobRepositoryTests { @@ -117,7 +117,7 @@ public class SimpleJobRepositoryTests { steps.add(databaseStep1); steps.add(databaseStep2); - jobExecution = new JobExecution(new JobInstance(1L, job.getName()), 1L, jobParameters); + jobExecution = new JobExecution(new JobInstance(1L, job.getName()), 1L, jobParameters, null); } @Test @@ -137,7 +137,7 @@ public class SimpleJobRepositoryTests { @Test public void testUpdateValidJobExecution() throws Exception { - JobExecution jobExecution = new JobExecution(new JobInstance(1L, job.getName()), 1L, jobParameters); + JobExecution jobExecution = new JobExecution(new JobInstance(1L, job.getName()), 1L, jobParameters, null); // new execution - call update on job dao jobExecutionDao.updateJobExecution(jobExecution); jobRepository.update(jobExecution); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/ChunkContextTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/ChunkContextTests.java index 8a754b999..d1e5ed669 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/ChunkContextTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/ChunkContextTests.java @@ -35,7 +35,7 @@ import org.springframework.batch.core.JobParameters; public class ChunkContextTests { private ChunkContext context = new ChunkContext(new StepContext(new JobExecution(new JobInstance(0L, - "job"), 1L, new JobParameters(Collections.singletonMap("foo", new JobParameter("bar")))) + "job"), 1L, new JobParameters(Collections.singletonMap("foo", new JobParameter("bar"))), null) .createStepExecution("foo"))); @Test diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/StepContextTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/StepContextTests.java index 8e962bb3b..85c78b3b1 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/StepContextTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/StepContextTests.java @@ -40,7 +40,7 @@ public class StepContextTests { private List list = new ArrayList(); - private StepExecution stepExecution = new StepExecution("step", new JobExecution(new JobInstance(2L, "job"), 0L, null), 1L); + private StepExecution stepExecution = new StepExecution("step", new JobExecution(new JobInstance(2L, "job"), 0L, null, null), 1L); private StepContext context = new StepContext(stepExecution); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/JobRepositorySupport.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/JobRepositorySupport.java index 8ac2eaec5..3dee9b048 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/JobRepositorySupport.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/JobRepositorySupport.java @@ -36,7 +36,7 @@ public class JobRepositorySupport implements JobRepository { @Override public JobExecution createJobExecution(String jobName, JobParameters jobParameters) { JobInstance jobInstance = new JobInstance(0L, jobName); - return new JobExecution(jobInstance, 11L, jobParameters); + return new JobExecution(jobInstance, 11L, jobParameters, null); } /* (non-Javadoc) @@ -103,4 +103,15 @@ public class JobRepositorySupport implements JobRepository { public void addAll(Collection stepExecutions) { } + @Override + public JobInstance createJobInstance(String jobName, + JobParameters jobParameters) { + return null; + } + + @Override + public JobExecution createJobExecution(JobInstance jobInstance, + JobParameters jobParameters, String jobConfigurationLocation) { + return null; + } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/TaskletStepExceptionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/TaskletStepExceptionTests.java index 922d18f8c..08653c86b 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/TaskletStepExceptionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/TaskletStepExceptionTests.java @@ -471,6 +471,18 @@ public class TaskletStepExceptionTests { @Override public void addAll(Collection stepExecutions) { } + + @Override + public JobInstance createJobInstance(String jobName, + JobParameters jobParameters) { + return null; + } + + @Override + public JobExecution createJobExecution(JobInstance jobInstance, + JobParameters jobParameters, String jobConfigurationLocation) { + return null; + } } } diff --git a/spring-batch-core/src/test/resources/META-INF/batch-jobs/jsrJobOperatorTestJob.xml b/spring-batch-core/src/test/resources/META-INF/batch-jobs/jsrJobOperatorTestJob.xml new file mode 100644 index 000000000..730f521c8 --- /dev/null +++ b/spring-batch-core/src/test/resources/META-INF/batch-jobs/jsrJobOperatorTestJob.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/spring-batch-core/src/test/resources/META-INF/batch-jobs/jsrJobOperatorTestRestartJob.xml b/spring-batch-core/src/test/resources/META-INF/batch-jobs/jsrJobOperatorTestRestartJob.xml new file mode 100644 index 000000000..2c240a046 --- /dev/null +++ b/spring-batch-core/src/test/resources/META-INF/batch-jobs/jsrJobOperatorTestRestartJob.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/spring-batch-core/src/test/resources/META-INF/batch.xml b/spring-batch-core/src/test/resources/META-INF/batch.xml new file mode 100644 index 000000000..8cffa788d --- /dev/null +++ b/spring-batch-core/src/test/resources/META-INF/batch.xml @@ -0,0 +1,4 @@ + + + + diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/schema-prefix-hsqldb.sql b/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/schema-prefix-hsqldb.sql index ba2a35821..852e05179 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/schema-prefix-hsqldb.sql +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/schema-prefix-hsqldb.sql @@ -28,6 +28,7 @@ CREATE TABLE PREFIX_JOB_EXECUTION ( EXIT_CODE VARCHAR(20) , EXIT_MESSAGE VARCHAR(2500) , LAST_UPDATED TIMESTAMP, + JOB_CONFIGURATION_LOCATION VARCHAR(500) NULL, constraint PREFIX_JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) references PREFIX_JOB_INSTANCE(JOB_INSTANCE_ID) ) ; diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/MetaDataInstanceFactory.java b/spring-batch-test/src/main/java/org/springframework/batch/test/MetaDataInstanceFactory.java index de2d72f63..8b7ee7581 100644 --- a/spring-batch-test/src/main/java/org/springframework/batch/test/MetaDataInstanceFactory.java +++ b/spring-batch-test/src/main/java/org/springframework/batch/test/MetaDataInstanceFactory.java @@ -138,7 +138,7 @@ public class MetaDataInstanceFactory { */ public static JobExecution createJobExecution(String jobName, Long instanceId, Long executionId, JobParameters jobParameters) { - return new JobExecution(createJobInstance(jobName, instanceId), executionId, jobParameters); + return new JobExecution(createJobInstance(jobName, instanceId), executionId, jobParameters, null); } /**