diff --git a/pom.xml b/pom.xml index 57223556..f09d62eb 100755 --- a/pom.xml +++ b/pom.xml @@ -70,6 +70,7 @@ spring-cloud-task-core spring-cloud-task-samples spring-cloud-task-docs + spring-cloud-task-batch diff --git a/spring-cloud-task-batch/pom.xml b/spring-cloud-task-batch/pom.xml new file mode 100644 index 00000000..6a361f8b --- /dev/null +++ b/spring-cloud-task-batch/pom.xml @@ -0,0 +1,35 @@ + + + + 4.0.0 + + + spring-cloud-task-parent + org.springframework.cloud + 1.0.0.BUILD-SNAPSHOT + + + spring-cloud-task-batch + jar + Spring Cloud Task Batch + Module for use when combining Spring Cloud Task with Spring Batch + + + + org.springframework.cloud + spring-cloud-task-core + + + junit + junit + test + + + com.h2database + h2 + test + + + diff --git a/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/configuration/TaskBatchAutoConfiguration.java b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/configuration/TaskBatchAutoConfiguration.java new file mode 100644 index 00000000..3be0bcff --- /dev/null +++ b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/configuration/TaskBatchAutoConfiguration.java @@ -0,0 +1,47 @@ +/* + * Copyright 2016 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.cloud.task.batch.configuration; + +import org.springframework.batch.core.Job; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cloud.task.batch.listener.TaskBatchExecutionListener; +import org.springframework.cloud.task.configuration.EnableTask; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Provides auto configuration for the {@link TaskBatchExecutionListener}. + * + * @author Michael Minella + */ +@Configuration +@ConditionalOnClass({Job.class, EnableTask.class}) +public class TaskBatchAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public TaskBatchExecutionListenerBeanPostProcessor batchTaskExecutionListenerBeanPostProcessor() { + return new TaskBatchExecutionListenerBeanPostProcessor(); + } + + @Bean + @ConditionalOnMissingBean + public TaskBatchExecutionListenerFactoryBean taskBatchExecutionListener(ConfigurableApplicationContext context) { + return new TaskBatchExecutionListenerFactoryBean(context); + } +} diff --git a/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/configuration/TaskBatchExecutionListenerBeanPostProcessor.java b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/configuration/TaskBatchExecutionListenerBeanPostProcessor.java new file mode 100644 index 00000000..b4f474cd --- /dev/null +++ b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/configuration/TaskBatchExecutionListenerBeanPostProcessor.java @@ -0,0 +1,64 @@ +/* + * Copyright 2016 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.cloud.task.batch.configuration; + +import org.springframework.batch.core.job.AbstractJob; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.cloud.task.batch.listener.TaskBatchExecutionListener; +import org.springframework.context.ApplicationContext; + +/** + * Injects a configured {@link TaskBatchExecutionListener} into any batch jobs (beans + * assignable to {@link AbstractJob}) that are executed within the scope of a task. The + * context this is used within is expected to have only one bean of type + * {@link TaskBatchExecutionListener}. + * + * @author Michael Minella + */ +public class TaskBatchExecutionListenerBeanPostProcessor implements BeanPostProcessor { + + @Autowired + private ApplicationContext applicationContext; + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + + int length = this.applicationContext + .getBeanNamesForType(TaskBatchExecutionListener.class).length; + + if(bean instanceof AbstractJob) { + if(length != 1) { + throw new IllegalStateException("The application context is required to " + + "have exactly 1 instance of the TaskBatchExecutionListener but has " + + length); + } + + ((AbstractJob) bean).registerJobExecutionListener( + this.applicationContext.getBean(TaskBatchExecutionListener.class)); + } + + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + return bean; + } +} diff --git a/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/configuration/TaskBatchExecutionListenerFactoryBean.java b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/configuration/TaskBatchExecutionListenerFactoryBean.java new file mode 100644 index 00000000..8ab333d4 --- /dev/null +++ b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/configuration/TaskBatchExecutionListenerFactoryBean.java @@ -0,0 +1,122 @@ +/* + * Copyright 2016 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.cloud.task.batch.configuration; + +import java.lang.reflect.Field; +import javax.sql.DataSource; + +import org.springframework.aop.framework.Advised; +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.cloud.task.batch.listener.TaskBatchExecutionListener; +import org.springframework.cloud.task.batch.listener.support.JdbcTaskBatchDao; +import org.springframework.cloud.task.batch.listener.support.MapTaskBatchDao; +import org.springframework.cloud.task.repository.TaskExplorer; +import org.springframework.cloud.task.repository.dao.MapTaskExecutionDao; +import org.springframework.cloud.task.repository.support.SimpleTaskExplorer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +/** + * {@link FactoryBean} for a {@link TaskBatchExecutionListener}. Provides a jdbc based + * listener if there is a {@link DataSource} available. Otherwise, builds a listener that + * uses the map based implementation. + * + * @author Michael Minella + */ +public class TaskBatchExecutionListenerFactoryBean implements FactoryBean { + + private ConfigurableApplicationContext context; + + private TaskBatchExecutionListener listener; + + private String dataSourceName; + + /** + * @param context the current application context + */ + public TaskBatchExecutionListenerFactoryBean(ConfigurableApplicationContext context) { + Assert.notNull(context, "A ConfigurableApplicationContext is required"); + + this.context = context; + } + + @Override + public TaskBatchExecutionListener getObject() throws Exception { + if(listener != null){ + return listener; + } + if(this.context.getBeanNamesForType(DataSource.class).length == 0) { + this.listener = new TaskBatchExecutionListener(getMapTaskBatchDao()); + } + else { + DataSource dataSource; + + if(StringUtils.hasText(this.dataSourceName)) { + dataSource = (DataSource) this.context.getBean(this.dataSourceName); + } + else { + if(this.context.getBeanNamesForType(DataSource.class).length == 1) { + dataSource = this.context.getBean(DataSource.class); + } + else { + throw new IllegalStateException("Unable to determine what DataSource to use"); + } + } + this.listener = new TaskBatchExecutionListener(new JdbcTaskBatchDao(dataSource)); + } + + return listener; + } + + @Override + public Class getObjectType() { + return TaskBatchExecutionListener.class; + } + + @Override + public boolean isSingleton() { + return true; + } + + public void setDataSourceName(String dataSourceName) { + this.dataSourceName = dataSourceName; + } + + private MapTaskBatchDao getMapTaskBatchDao() throws Exception { + Field taskExecutionDaoField = ReflectionUtils.findField(SimpleTaskExplorer.class, "taskExecutionDao"); + taskExecutionDaoField.setAccessible(true); + + MapTaskExecutionDao taskExecutionDao; + + TaskExplorer taskExplorer = this.context.getBean(TaskExplorer.class); + + if(AopUtils.isJdkDynamicProxy(taskExplorer)) { + SimpleTaskExplorer dereferencedTaskRepository = (SimpleTaskExplorer) ((Advised) taskExplorer).getTargetSource().getTarget(); + + taskExecutionDao = + (MapTaskExecutionDao) ReflectionUtils.getField(taskExecutionDaoField, dereferencedTaskRepository); + } + else { + taskExecutionDao = + (MapTaskExecutionDao) ReflectionUtils.getField(taskExecutionDaoField, taskExplorer); + } + + return new MapTaskBatchDao(taskExecutionDao.getBatchJobAssociations()); + } +} diff --git a/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/listener/TaskBatchDao.java b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/listener/TaskBatchDao.java new file mode 100644 index 00000000..940793f0 --- /dev/null +++ b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/listener/TaskBatchDao.java @@ -0,0 +1,36 @@ +/* + * Copyright 2016 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.cloud.task.batch.listener; + +import org.springframework.batch.core.JobExecution; +import org.springframework.cloud.task.repository.TaskExecution; + +/** + * Maintains the association between a {@link TaskExecution} and a {@link JobExecution} + * executed within it. + * + * @author Michael Minella + */ +public interface TaskBatchDao { + + /** + * Saves the relationship between a task execution and a job execution. + * + * @param taskExecution task execution + * @param jobExecution job execution + */ + void saveRelationship(TaskExecution taskExecution, JobExecution jobExecution); +} diff --git a/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/listener/TaskBatchExecutionListener.java b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/listener/TaskBatchExecutionListener.java new file mode 100644 index 00000000..af09ffe5 --- /dev/null +++ b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/listener/TaskBatchExecutionListener.java @@ -0,0 +1,66 @@ +/* + * Copyright 2016 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.cloud.task.batch.listener; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.listener.JobExecutionListenerSupport; +import org.springframework.cloud.task.listener.annotation.BeforeTask; +import org.springframework.cloud.task.repository.TaskExecution; +import org.springframework.util.Assert; + +/** + * Responsible for storing the relationship between a Spring Batch job and the Spring + * Cloud task it was executed within. + * + * @author Michael Minella + */ +public class TaskBatchExecutionListener extends JobExecutionListenerSupport { + + private TaskExecution taskExecution; + + private TaskBatchDao taskBatchDao; + + private final static Log logger = LogFactory.getLog(TaskBatchExecutionListener.class); + + /** + * @param taskBatchDao dao used to persist the relationship. Must not be null + */ + public TaskBatchExecutionListener(TaskBatchDao taskBatchDao) { + Assert.notNull(taskBatchDao, "A TaskBatchDao is required"); + + this.taskBatchDao = taskBatchDao; + } + + @BeforeTask + public void onTaskStartup(TaskExecution taskExecution) { + this.taskExecution = taskExecution; + } + + @Override + public void beforeJob(JobExecution jobExecution) { + if(this.taskExecution == null) { + logger.warn("This job was executed outside the scope of a task but still used the task listener."); + } + else { + logger.info(String.format("The job execution id %s was run within the task execution %s", + jobExecution.getId(), + this.taskExecution.getExecutionId())); + taskBatchDao.saveRelationship(taskExecution, jobExecution); + } + } +} diff --git a/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/listener/support/JdbcTaskBatchDao.java b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/listener/support/JdbcTaskBatchDao.java new file mode 100644 index 00000000..d74170a4 --- /dev/null +++ b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/listener/support/JdbcTaskBatchDao.java @@ -0,0 +1,73 @@ +/* + * Copyright 2016 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.cloud.task.batch.listener.support; + +import javax.sql.DataSource; + +import org.springframework.batch.core.JobExecution; +import org.springframework.cloud.task.batch.listener.TaskBatchDao; +import org.springframework.cloud.task.repository.TaskExecution; +import org.springframework.cloud.task.repository.dao.JdbcTaskExecutionDao; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * JDBC based implementation of the {@link TaskBatchDao}. Intended to be used in + * conjunction with the JDBC based + * {@link org.springframework.cloud.task.repository.TaskRepository} + * + * @author Michael Minella + */ +public class JdbcTaskBatchDao implements TaskBatchDao { + + private String tablePrefix = JdbcTaskExecutionDao.DEFAULT_TABLE_PREFIX; + + private static final String INSERT_STATEMENT = "INSERT INTO %PREFIX%TASK_BATCH VALUES(?, ?)"; + + private JdbcOperations jdbcTemplate; + + /** + * @param dataSource {@link DataSource} where the task batch table resides. + */ + public JdbcTaskBatchDao(DataSource dataSource) { + Assert.notNull(dataSource, "A dataSource is required"); + + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + + @Override + public void saveRelationship(TaskExecution taskExecution, JobExecution jobExecution) { + Assert.notNull(taskExecution, "A taskExecution is required"); + Assert.notNull(jobExecution, "A jobExecution is required"); + jdbcTemplate.update(getQuery(INSERT_STATEMENT), taskExecution.getExecutionId(), jobExecution.getId()); + } + + /** + * The table prefix for the task batch table. + * + * @param tablePrefix defaults to {@link JdbcTaskExecutionDao#DEFAULT_TABLE_PREFIX}. + */ + public void setTablePrefix(String tablePrefix) { + Assert.notNull(tablePrefix, "Null is not allowed as a tablePrefix (use an empty string if you don't want a prefix at all)."); + this.tablePrefix = tablePrefix; + } + + private String getQuery(String base) { + return StringUtils.replace(base, "%PREFIX%", tablePrefix); + } +} diff --git a/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/listener/support/MapTaskBatchDao.java b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/listener/support/MapTaskBatchDao.java new file mode 100644 index 00000000..e6bf2561 --- /dev/null +++ b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/listener/support/MapTaskBatchDao.java @@ -0,0 +1,57 @@ +/* + * Copyright 2016 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.cloud.task.batch.listener.support; + +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.springframework.batch.core.JobExecution; +import org.springframework.cloud.task.batch.listener.TaskBatchDao; +import org.springframework.cloud.task.repository.TaskExecution; +import org.springframework.util.Assert; + +/** + * Map implementation of the {@link TaskBatchDao}. This is intended for testing + * purposes only! + * + * @author Michael Minella + */ +public class MapTaskBatchDao implements TaskBatchDao { + + private Map> relationships; + + public MapTaskBatchDao(Map> relationships) { + Assert.notNull(relationships, "Relationships must not be null"); + this.relationships = relationships; + } + + @Override + public void saveRelationship(TaskExecution taskExecution, JobExecution jobExecution) { + Assert.notNull(taskExecution, "A taskExecution is required"); + Assert.notNull(jobExecution, "A jobExecution is required"); + + if(this.relationships.containsKey(taskExecution.getExecutionId())) { + this.relationships.get(taskExecution.getExecutionId()).add(jobExecution.getId()); + } + else { + TreeSet jobExecutionIds = new TreeSet<>(); + jobExecutionIds.add(jobExecution.getId()); + + this.relationships.put(taskExecution.getExecutionId(), jobExecutionIds); + } + } +} diff --git a/spring-cloud-task-batch/src/main/resources/META-INF/spring.factories b/spring-cloud-task-batch/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..262efdef --- /dev/null +++ b/spring-cloud-task-batch/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.springframework.cloud.task.batch.configuration.TaskBatchAutoConfiguration diff --git a/spring-cloud-task-batch/src/test/java/org/springframework/cloud/task/batch/listener/BatchTaskExecutionListenerTests.java b/spring-cloud-task-batch/src/test/java/org/springframework/cloud/task/batch/listener/BatchTaskExecutionListenerTests.java new file mode 100644 index 00000000..cb0615ef --- /dev/null +++ b/spring-cloud-task-batch/src/test/java/org/springframework/cloud/task/batch/listener/BatchTaskExecutionListenerTests.java @@ -0,0 +1,189 @@ +/* + * Copyright 2016 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.cloud.task.batch.listener; + +import java.util.Iterator; +import java.util.Set; + +import org.junit.After; +import org.junit.Test; + +import org.springframework.batch.core.Job; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; +import org.springframework.cloud.task.batch.configuration.TaskBatchAutoConfiguration; +import org.springframework.cloud.task.configuration.EnableTask; +import org.springframework.cloud.task.repository.TaskExecution; +import org.springframework.cloud.task.repository.TaskExplorer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; + +import static org.junit.Assert.assertEquals; + +/** + * @author Michael Minella + */ +public class BatchTaskExecutionListenerTests { + + private ConfigurableApplicationContext applicationContext; + + @After + public void tearDown() { + if(this.applicationContext != null) { + this.applicationContext.close(); + } + } + + @Test + public void testAutobuiltDataSource() { + this.applicationContext = SpringApplication.run(new Object[] {JobConfiguration.class, PropertyPlaceholderAutoConfiguration.class, EmbeddedDataSourceConfiguration.class, TaskBatchAutoConfiguration.class, BatchAutoConfiguration.class, TaskBatchAutoConfiguration.class}, new String[0]); + + TaskExplorer taskExplorer = this.applicationContext.getBean(TaskExplorer.class); + + Page page = taskExplorer.findTaskExecutionsByName("application", new PageRequest(0, 1)); + + Set jobExecutionIds = taskExplorer.getJobExecutionIdsByTaskExecutionId(page.iterator().next().getExecutionId()); + + assertEquals(1, jobExecutionIds.size()); + assertEquals(1, taskExplorer.getTaskExecution(jobExecutionIds.iterator().next()).getExecutionId()); + } + + @Test + public void testAutobuiltDataSourceNoJob() { + this.applicationContext = SpringApplication.run(new Object[] {NoJobConfiguration.class, PropertyPlaceholderAutoConfiguration.class, EmbeddedDataSourceConfiguration.class, TaskBatchAutoConfiguration.class, BatchAutoConfiguration.class, TaskBatchAutoConfiguration.class}, new String[0]); + + TaskExplorer taskExplorer = this.applicationContext.getBean(TaskExplorer.class); + + Page page = taskExplorer.findTaskExecutionsByName("application", new PageRequest(0, 1)); + + Set jobExecutionIds = taskExplorer.getJobExecutionIdsByTaskExecutionId(page.iterator().next().getExecutionId()); + + assertEquals(0, jobExecutionIds.size()); + } + + @Test + public void testMapBased() { + this.applicationContext = SpringApplication.run(new Object[] {JobConfiguration.class, PropertyPlaceholderAutoConfiguration.class, BatchAutoConfiguration.class, TaskBatchAutoConfiguration.class}, new String[0]); + + TaskExplorer taskExplorer = this.applicationContext.getBean(TaskExplorer.class); + + Page page = taskExplorer.findTaskExecutionsByName("application", new PageRequest(0, 1)); + + Set jobExecutionIds = taskExplorer.getJobExecutionIdsByTaskExecutionId(page.iterator().next().getExecutionId()); + + assertEquals(1, jobExecutionIds.size()); + assertEquals(0, (long) taskExplorer.getTaskExecutionIdByJobExecutionId(jobExecutionIds.iterator().next())); + } + + @Test + public void testMultipleJobs() { + this.applicationContext = SpringApplication.run(new Object[] {MultipleJobConfiguration.class, PropertyPlaceholderAutoConfiguration.class, BatchAutoConfiguration.class, TaskBatchAutoConfiguration.class}, new String[0]); + + TaskExplorer taskExplorer = this.applicationContext.getBean(TaskExplorer.class); + + Page page = taskExplorer.findTaskExecutionsByName("application", new PageRequest(0, 1)); + + Set jobExecutionIds = taskExplorer.getJobExecutionIdsByTaskExecutionId(page.iterator().next().getExecutionId()); + + assertEquals(2, jobExecutionIds.size()); + Iterator jobExecutionIdsIterator = jobExecutionIds.iterator(); + assertEquals(0, (long) taskExplorer.getTaskExecutionIdByJobExecutionId(jobExecutionIdsIterator.next())); + assertEquals(0, (long) taskExplorer.getTaskExecutionIdByJobExecutionId(jobExecutionIdsIterator.next())); + } + + @Configuration + @EnableBatchProcessing + @EnableTask + public static class NoJobConfiguration { + + } + + @Configuration + @EnableBatchProcessing + @EnableTask + public static class JobConfiguration { + + @Autowired + private JobBuilderFactory jobBuilderFactory; + + @Autowired + private StepBuilderFactory stepBuilderFactory; + + @Bean + public Job job() { + return jobBuilderFactory.get("job") + .start(stepBuilderFactory.get("step1").tasklet(new Tasklet() { + @Override + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { + System.out.println("Executed"); + return RepeatStatus.FINISHED; + } + }).build()) + .build(); + } + } + + @Configuration + @EnableBatchProcessing + @EnableTask + public static class MultipleJobConfiguration { + + @Autowired + private JobBuilderFactory jobBuilderFactory; + + @Autowired + private StepBuilderFactory stepBuilderFactory; + + @Bean + public Job job1() { + return jobBuilderFactory.get("job1") + .start(stepBuilderFactory.get("job1step1").tasklet(new Tasklet() { + @Override + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { + System.out.println("Executed job1"); + return RepeatStatus.FINISHED; + } + }).build()) + .build(); + } + + @Bean + public Job job2() { + return jobBuilderFactory.get("job2") + .start(stepBuilderFactory.get("job2step1").tasklet(new Tasklet() { + @Override + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { + System.out.println("Executed job2"); + return RepeatStatus.FINISHED; + } + }).build()) + .build(); + } + } +} diff --git a/spring-cloud-task-core/pom.xml b/spring-cloud-task-core/pom.xml index 84006337..e59ea816 100755 --- a/spring-cloud-task-core/pom.xml +++ b/spring-cloud-task-core/pom.xml @@ -13,7 +13,6 @@ spring-cloud-task-core jar Spring Cloud Task Core - 1.0.0.BUILD-SNAPSHOT Spring Cloud Task diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/TaskExplorer.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/TaskExplorer.java index daf3baa5..e998def1 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/TaskExplorer.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/TaskExplorer.java @@ -17,6 +17,7 @@ package org.springframework.cloud.task.repository; import java.util.List; +import java.util.Set; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -25,6 +26,7 @@ import org.springframework.data.domain.Pageable; * Offers methods that allow users to query the task executions that are available. * * @author Glenn Renfro + * @author Michael Minella */ public interface TaskExplorer { @@ -34,7 +36,7 @@ public interface TaskExplorer { * @param executionId the task execution id * @return the {@link TaskExecution} with this id, or null if not found */ - public TaskExecution getTaskExecution(long executionId); + TaskExecution getTaskExecution(long executionId); /** @@ -44,14 +46,14 @@ public interface TaskExplorer { * @param pageable the constraints for the search * @return the set of running executions for tasks with the specified name */ - public Page findRunningTaskExecutions(String taskName, Pageable pageable); + Page findRunningTaskExecutions(String taskName, Pageable pageable); /** * Retrieve a list of available task names. * * @return the set of task names that have been executed */ - public List getTaskNames(); + List getTaskNames(); /** * Get number of executions for a taskName. @@ -59,7 +61,7 @@ public interface TaskExplorer { * @param taskName the name of the task to be searched * @return the number of running tasks that have the taskname specified */ - public long getTaskExecutionCountByTaskName(String taskName); + long getTaskExecutionCountByTaskName(String taskName); /** * Retrieves current number of task executions. @@ -75,7 +77,7 @@ public interface TaskExplorer { * @param pageable the constraints for the search * @return list of task executions */ - public Page findTaskExecutionsByName(String taskName, Pageable pageable); + Page findTaskExecutionsByName(String taskName, Pageable pageable); /** * Retrieves all the task executions within the pageable constraints sorted by @@ -84,6 +86,23 @@ public interface TaskExplorer { * @param pageable the constraints for the search * @return page containing the results from the search */ - public Page findAll(Pageable pageable); + Page findAll(Pageable pageable); + /** + * Returns the id of the TaskExecution that the requested Spring Batch job execution + * was executed within the context of. Returns null if none were found. + * + * @param jobExecutionId the id of the JobExecution + * @return the id of the {@link TaskExecution} + */ + Long getTaskExecutionIdByJobExecutionId(long jobExecutionId); + + /** + * Returns a Set of JobExecution ids for the jobs that were executed within the scope + * of the requested task. + * + * @param taskExecutionId id of the {@link TaskExecution} + * @return a Set of the ids of the job executions executed within the task. + */ + Set getJobExecutionIdsByTaskExecutionId(long taskExecutionId); } diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/JdbcTaskExecutionDao.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/JdbcTaskExecutionDao.java index 874b5a5b..b0a9741a 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/JdbcTaskExecutionDao.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/JdbcTaskExecutionDao.java @@ -20,10 +20,13 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.TreeMap; +import java.util.TreeSet; import javax.sql.DataSource; @@ -31,12 +34,14 @@ import org.springframework.batch.item.database.Order; import org.springframework.cloud.task.repository.TaskExecution; import org.springframework.cloud.task.repository.database.PagingQueryProvider; import org.springframework.cloud.task.repository.database.support.SqlPagingQueryProviderFactoryBean; +import org.springframework.dao.DataAccessException; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowCallbackHandler; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer; @@ -95,7 +100,10 @@ public class JdbcTaskExecutionDao implements TaskExecutionDao { private static final String FIND_TASK_NAMES = "SELECT distinct TASK_NAME from %PREFIX%EXECUTION order by TASK_NAME"; - private static final String DEFAULT_TABLE_PREFIX = "TASK_"; + private static final String FIND_TASK_EXECUTION_BY_JOB_EXECUTION_ID = "SELECT TASK_EXECUTION_ID FROM %PREFIX%TASK_BATCH WHERE JOB_EXECUTION_ID = ?"; + private static final String FIND_JOB_EXECUTION_BY_TASK_EXECUTION_ID = "SELECT JOB_EXECUTION_ID FROM %PREFIX%TASK_BATCH WHERE TASK_EXECUTION_ID = ?"; + + public static final String DEFAULT_TABLE_PREFIX = "TASK_"; private String tablePrefix = DEFAULT_TABLE_PREFIX; @@ -241,6 +249,43 @@ public class JdbcTaskExecutionDao implements TaskExecutionDao { return taskIncrementer.nextLongValue(); } + @Override + public Long getTaskExecutionIdByJobExecutionId(long jobExecutionId) { + try { + return jdbcTemplate.queryForObject( + getQuery(FIND_TASK_EXECUTION_BY_JOB_EXECUTION_ID), + new Object[] { jobExecutionId }, + Long.class); + } + catch (EmptyResultDataAccessException e) { + return null; + } + } + + @Override + public Set getJobExecutionIdsByTaskExecutionId(long taskExecutionId) { + try { + return jdbcTemplate.query( + getQuery(FIND_JOB_EXECUTION_BY_TASK_EXECUTION_ID), + new Object[] {taskExecutionId}, + new ResultSetExtractor>() { + @Override + public Set extractData(ResultSet resultSet) throws SQLException, DataAccessException { + Set jobExecutionIds = new TreeSet<>(); + + while(resultSet.next()) { + jobExecutionIds.add(resultSet.getLong("JOB_EXECUTION_ID")); + } + + return jobExecutionIds; + } + }); + } + catch (DataAccessException e) { + return Collections.emptySet(); + } + } + private Page queryForPageableResults(Pageable pageable, String selectClause, String fromClause, diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/MapTaskExecutionDao.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/MapTaskExecutionDao.java index 5ce382b7..1df21568 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/MapTaskExecutionDao.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/MapTaskExecutionDao.java @@ -42,10 +42,13 @@ public class MapTaskExecutionDao implements TaskExecutionDao { private ConcurrentMap taskExecutions; + private ConcurrentMap> batchJobAssociations; + private final AtomicLong currentId = new AtomicLong(0L); public MapTaskExecutionDao() { taskExecutions = new ConcurrentHashMap<>(); + batchJobAssociations = new ConcurrentHashMap<>(); } @Override @@ -68,7 +71,7 @@ public class MapTaskExecutionDao implements TaskExecutionDao { @Override public TaskExecution getTaskExecution(long executionId) { - return taskExecutions.get(executionId); + return taskExecutions.get(executionId); } @Override @@ -149,6 +152,38 @@ public class MapTaskExecutionDao implements TaskExecutionDao { return currentId.getAndIncrement(); } + @Override + public Long getTaskExecutionIdByJobExecutionId(long jobExecutionId) { + Long taskId = null; + + found: + + for (Map.Entry> association : batchJobAssociations.entrySet()) { + for (Long curJobExecutionId : association.getValue()) { + if(curJobExecutionId.equals(jobExecutionId)) { + taskId = association.getKey(); + break found; + } + } + } + + return taskId; + } + + @Override + public Set getJobExecutionIdsByTaskExecutionId(long taskExecutionId) { + if(batchJobAssociations.containsKey(taskExecutionId)) { + return Collections.unmodifiableSet(batchJobAssociations.get(taskExecutionId)); + } + else { + return new TreeSet<>(); + } + } + + public ConcurrentMap> getBatchJobAssociations() { + return batchJobAssociations; + } + private TreeSet getTaskExecutionTreeSet() { return new TreeSet<>(new Comparator() { @Override diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/TaskExecutionDao.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/TaskExecutionDao.java index 7073e710..a5633c12 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/TaskExecutionDao.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/dao/TaskExecutionDao.java @@ -18,6 +18,7 @@ package org.springframework.cloud.task.repository.dao; import java.util.Date; import java.util.List; +import java.util.Set; import org.springframework.cloud.task.repository.TaskExecution; import org.springframework.data.domain.Page; @@ -36,7 +37,7 @@ public interface TaskExecutionDao { * @param taskName the name that associated with the task execution. * @param startTime the time task began. * @param parameters list of key/value pairs that configure the task. - * @return A fully qualified {@linkTaskExecution} instance. + * @return A fully qualified {@link TaskExecution} instance. */ TaskExecution createTaskExecution( String taskName, Date startTime, List parameters); @@ -105,7 +106,7 @@ public interface TaskExecutionDao { * * @return a list of distinct task names from the task repository.. */ - public List getTaskNames(); + List getTaskNames(); /** * Retrieves all the task executions within the pageable constraints. @@ -113,6 +114,27 @@ public interface TaskExecutionDao { * @return page containing the results from the search */ - public Page findAll(Pageable pageable); + Page findAll(Pageable pageable); + /** + * Retrieves the next available execution id for a task execution. + * @return long containing the executionId. + */ + long getNextExecutionId(); + + /** + * Returns the id of the TaskExecution that the requested Spring Batch job execution + * was executed within the context of. Returns null if non were found. + * + * @param jobExecutionId the id of the JobExecution + * @return the id of the {@link TaskExecution} + */ + Long getTaskExecutionIdByJobExecutionId(long jobExecutionId); + + /** + * Returns the job execution ids associated with a task execution id. + * @param taskExecutionId id of the {@link TaskExecution} + * @return a Set of the ids of the job executions executed within the task. + */ + Set getJobExecutionIdsByTaskExecutionId(long taskExecutionId); } diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/SqlPagingQueryUtils.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/SqlPagingQueryUtils.java index f66f23fe..cd200bc8 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/SqlPagingQueryUtils.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/database/support/SqlPagingQueryUtils.java @@ -71,7 +71,6 @@ public class SqlPagingQueryUtils { * Generates WHERE clause for queries that require sub selects. * * @param provider the paging query provider that will provide the base where clause - * @return a String for the where clause. */ public static void buildWhereClause( AbstractSqlPagingQueryProvider provider, boolean remainingPageQuery, StringBuilder sql) { diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/SimpleTaskExplorer.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/SimpleTaskExplorer.java index 55f12b26..49f70e7d 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/SimpleTaskExplorer.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/repository/support/SimpleTaskExplorer.java @@ -17,6 +17,7 @@ package org.springframework.cloud.task.repository.support; import java.util.List; +import java.util.Set; import org.springframework.cloud.task.repository.TaskExecution; import org.springframework.cloud.task.repository.TaskExplorer; @@ -29,8 +30,9 @@ import org.springframework.util.Assert; * TaskExplorer for that gathers task information from a task repository. * * @author Glenn Renfro + * @author Michael Minella */ -public class SimpleTaskExplorer implements TaskExplorer{ +public class SimpleTaskExplorer implements TaskExplorer { private TaskExecutionDao taskExecutionDao; @@ -80,4 +82,14 @@ public class SimpleTaskExplorer implements TaskExplorer{ return taskExecutionDao.findAll(pageable); } + @Override + public Long getTaskExecutionIdByJobExecutionId(long jobExecutionId) { + return taskExecutionDao.getTaskExecutionIdByJobExecutionId(jobExecutionId); + } + + @Override + public Set getJobExecutionIdsByTaskExecutionId(long taskExecutionId) { + return taskExecutionDao.getJobExecutionIdsByTaskExecutionId(taskExecutionId); + } + } diff --git a/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-h2.sql b/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-h2.sql index eb94a6cc..6f20d612 100644 --- a/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-h2.sql +++ b/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-h2.sql @@ -16,4 +16,11 @@ CREATE TABLE TASK_EXECUTION_PARAMS ( references TASK_EXECUTION(TASK_EXECUTION_ID) ) ; +CREATE TABLE TASK_TASK_BATCH ( + TASK_EXECUTION_ID BIGINT NOT NULL , + JOB_EXECUTION_ID BIGINT NOT NULL , + constraint TASK_EXEC_BATCH_FK foreign key (TASK_EXECUTION_ID) + references TASK_EXECUTION(TASK_EXECUTION_ID) +) ; + CREATE SEQUENCE TASK_SEQ ; diff --git a/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-hsqldb.sql b/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-hsqldb.sql index 37c7abe2..1b2dfb1e 100644 --- a/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-hsqldb.sql +++ b/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-hsqldb.sql @@ -16,6 +16,13 @@ CREATE TABLE TASK_EXECUTION_PARAMS ( references TASK_EXECUTION(TASK_EXECUTION_ID) ) ; +CREATE TABLE TASK_TASK_BATCH ( + TASK_EXECUTION_ID BIGINT NOT NULL , + JOB_EXECUTION_ID BIGINT NOT NULL , + constraint TASK_EXEC_BATCH_FK foreign key (TASK_EXECUTION_ID) + references TASK_EXECUTION(TASK_EXECUTION_ID) +) ; + CREATE TABLE TASK_SEQ ( ID BIGINT IDENTITY ); diff --git a/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-mysql.sql b/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-mysql.sql index abcb033f..3896873e 100644 --- a/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-mysql.sql +++ b/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-mysql.sql @@ -16,6 +16,12 @@ CREATE TABLE TASK_EXECUTION_PARAMS ( references TASK_EXECUTION(TASK_EXECUTION_ID) ) ENGINE=InnoDB; +CREATE TABLE TASK_TASK_BATCH ( + TASK_EXECUTION_ID BIGINT NOT NULL , + JOB_EXECUTION_ID BIGINT NOT NULL , + constraint TASK_EXEC_BATCH_FK foreign key (TASK_EXECUTION_ID) + references TASK_EXECUTION(TASK_EXECUTION_ID) +) ENGINE=InnoDB; CREATE TABLE TASK_SEQ ( ID BIGINT NOT NULL, diff --git a/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-oracle10g.sql b/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-oracle10g.sql index 690f8ae7..2dd491bd 100644 --- a/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-oracle10g.sql +++ b/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-oracle10g.sql @@ -16,4 +16,11 @@ CREATE TABLE TASK_EXECUTION_PARAMS ( references TASK_EXECUTION(TASK_EXECUTION_ID) ) ; +CREATE TABLE TASK_TASK_BATCH ( + TASK_EXECUTION_ID NUMBER NOT NULL , + JOB_EXECUTION_ID NUMBER NOT NULL , + constraint TASK_EXEC_BATCH_FK foreign key (TASK_EXECUTION_ID) + references TASK_EXECUTION(TASK_EXECUTION_ID) +) ; + CREATE SEQUENCE TASK_SEQ START WITH 0 MINVALUE 0 MAXVALUE 9223372036854775807 NOCACHE NOCYCLE; \ No newline at end of file diff --git a/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-postgresql.sql b/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-postgresql.sql index 9c01284b..5b8d407a 100644 --- a/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-postgresql.sql +++ b/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-postgresql.sql @@ -16,4 +16,11 @@ CREATE TABLE TASK_EXECUTION_PARAMS ( references TASK_EXECUTION(TASK_EXECUTION_ID) ) ; +CREATE TABLE TASK_TASK_BATCH ( + TASK_EXECUTION_ID BIGINT NOT NULL , + JOB_EXECUTION_ID BIGINT NOT NULL , + constraint TASK_EXEC_BATCH_FK foreign key (TASK_EXECUTION_ID) + references TASK_EXECUTION(TASK_EXECUTION_ID) +) ; + CREATE SEQUENCE TASK_SEQ MAXVALUE 9223372036854775807 NO CYCLE; \ No newline at end of file diff --git a/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-sqlserver.sql b/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-sqlserver.sql index 92e11a78..1656f080 100644 --- a/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-sqlserver.sql +++ b/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task/schema-sqlserver.sql @@ -15,4 +15,11 @@ CREATE TABLE TASK_EXECUTION_PARAMS ( references TASK_EXECUTION(TASK_EXECUTION_ID) ) ; +CREATE TABLE TASK_TASK_BATCH ( + TASK_EXECUTION_ID BIGINT NOT NULL , + JOB_EXECUTION_ID BIGINT NOT NULL , + constraint TASK_EXEC_BATCH_FK foreign key (TASK_EXECUTION_ID) + references TASK_EXECUTION(TASK_EXECUTION_ID) +) ; + CREATE TABLE TASK_SEQ (ID BIGINT IDENTITY); diff --git a/spring-cloud-task-samples/batch-job/.mvn/wrapper/maven-wrapper.jar b/spring-cloud-task-samples/batch-job/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 00000000..5fd4d502 Binary files /dev/null and b/spring-cloud-task-samples/batch-job/.mvn/wrapper/maven-wrapper.jar differ diff --git a/spring-cloud-task-samples/batch-job/.mvn/wrapper/maven-wrapper.properties b/spring-cloud-task-samples/batch-job/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..eb919476 --- /dev/null +++ b/spring-cloud-task-samples/batch-job/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.3/apache-maven-3.3.3-bin.zip \ No newline at end of file diff --git a/spring-cloud-task-samples/batch-job/README.adoc b/spring-cloud-task-samples/batch-job/README.adoc new file mode 100644 index 00000000..599732d0 --- /dev/null +++ b/spring-cloud-task-samples/batch-job/README.adoc @@ -0,0 +1,26 @@ += Spring Batch Job Task + +This is a Spring Cloud Task application that executes two simple Spring Batch Jobs. + +== Requirements: + +* Java 7 or Above + +== Classes: + +* BatchJobApplication - the Spring Boot Main Application +* JobConfiguration - the configuration for the Spring Batch jobs + +== Build: + +[source,shell,indent=2] +---- +$ mvn clean package +---- + +== Run: + +[source,shell,indent=2] +---- +$ java -jar target/batch-job-1.0.0.BUILD-SNAPSHOT.jar +---- diff --git a/spring-cloud-task-samples/batch-job/mvnw b/spring-cloud-task-samples/batch-job/mvnw new file mode 100755 index 00000000..a1ba1bf5 --- /dev/null +++ b/spring-cloud-task-samples/batch-job/mvnw @@ -0,0 +1,233 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # + # Look for the Apple JDKs first to preserve the existing behaviour, and then look + # for the new JDKs provided by Oracle. + # + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then + # + # Oracle JDKs + # + export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then + # + # Apple JDKs + # + export JAVA_HOME=`/usr/libexec/java_home` + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + local basedir=$(pwd) + local wdir=$(pwd) + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + wdir=$(cd "$wdir/.."; pwd) + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} "$@" diff --git a/spring-cloud-task-samples/batch-job/mvnw.cmd b/spring-cloud-task-samples/batch-job/mvnw.cmd new file mode 100644 index 00000000..2b934e89 --- /dev/null +++ b/spring-cloud-task-samples/batch-job/mvnw.cmd @@ -0,0 +1,145 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +set MAVEN_CMD_LINE_ARGS=%* + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" + +set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar"" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% \ No newline at end of file diff --git a/spring-cloud-task-samples/batch-job/pom.xml b/spring-cloud-task-samples/batch-job/pom.xml new file mode 100644 index 00000000..9f7acfb8 --- /dev/null +++ b/spring-cloud-task-samples/batch-job/pom.xml @@ -0,0 +1,103 @@ + + + 4.0.0 + + org.springframework.cloud + batch-job + jar + 1.0.0.BUILD-SNAPSHOT + Spring Cloud Task Batch Example + + batch-job + + + org.springframework.boot + spring-boot-starter-parent + 1.3.3.RELEASE + + + + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-batch + + + org.springframework.cloud + spring-cloud-task-core + 1.0.0.BUILD-SNAPSHOT + + + org.springframework.cloud + spring-cloud-task-batch + 1.0.0.BUILD-SNAPSHOT + + + com.h2database + h2 + runtime + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.3 + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + + + + + maven-deploy-plugin + + true + + + + maven-install-plugin + + true + + + + + + + + diff --git a/spring-cloud-task-samples/batch-job/src/main/java/demo/BatchJobApplication.java b/spring-cloud-task-samples/batch-job/src/main/java/demo/BatchJobApplication.java new file mode 100644 index 00000000..670fcef9 --- /dev/null +++ b/spring-cloud-task-samples/batch-job/src/main/java/demo/BatchJobApplication.java @@ -0,0 +1,16 @@ +package demo; + +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.task.configuration.EnableTask; + +@SpringBootApplication +@EnableTask +@EnableBatchProcessing +public class BatchJobApplication { + + public static void main(String[] args) { + SpringApplication.run(BatchJobApplication.class, args); + } +} diff --git a/spring-cloud-task-samples/batch-job/src/main/java/demo/configuration/JobConfiguration.java b/spring-cloud-task-samples/batch-job/src/main/java/demo/configuration/JobConfiguration.java new file mode 100644 index 00000000..84052d8f --- /dev/null +++ b/spring-cloud-task-samples/batch-job/src/main/java/demo/configuration/JobConfiguration.java @@ -0,0 +1,75 @@ +/* + * Copyright 2016 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 demo.configuration; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.batch.core.Job; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Michael Minella + */ +@Configuration +public class JobConfiguration { + + private static final Log logger = LogFactory.getLog(JobConfiguration.class); + + @Autowired + public JobBuilderFactory jobBuilderFactory; + + @Autowired + public StepBuilderFactory stepBuilderFactory; + + @Bean + public Job job1() { + return jobBuilderFactory.get("job1") + .start(stepBuilderFactory.get("job1step1") + .tasklet(new Tasklet() { + @Override + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { + logger.info("Job1 was run"); + return RepeatStatus.FINISHED; + } + }) + .build()) + .build(); + } + + @Bean + public Job job2() { + return jobBuilderFactory.get("job2") + .start(stepBuilderFactory.get("job2step1") + .tasklet(new Tasklet() { + @Override + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { + logger.info("Job2 was run"); + return RepeatStatus.FINISHED; + } + }) + .build()) + .build(); + } +} diff --git a/spring-cloud-task-samples/batch-job/src/main/resources/application.properties b/spring-cloud-task-samples/batch-job/src/main/resources/application.properties new file mode 100644 index 00000000..c7864221 --- /dev/null +++ b/spring-cloud-task-samples/batch-job/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=Demo Batch Job Task diff --git a/spring-cloud-task-samples/batch-job/src/test/java/demo/BatchJobApplicationTests.java b/spring-cloud-task-samples/batch-job/src/test/java/demo/BatchJobApplicationTests.java new file mode 100644 index 00000000..8dd87e70 --- /dev/null +++ b/spring-cloud-task-samples/batch-job/src/test/java/demo/BatchJobApplicationTests.java @@ -0,0 +1,79 @@ +/* + * Copyright 2015 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 demo; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.junit.Rule; +import org.junit.Test; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.test.OutputCapture; + +/** + * Verifies that the Task Application outputs the correct task log entries. + * + * @author Michael Minella + */ +public class BatchJobApplicationTests { + + @Rule + public OutputCapture outputCapture = new OutputCapture(); + + @Test + public void testTimeStampApp() throws Exception { + final String JOB_RUN_MESSAGE = " was run"; + final String CREATE_TASK_MESSAGE = "Creating: TaskExecution{executionId="; + final String UPDATE_TASK_MESSAGE = "Updating: TaskExecution with executionId="; + final String JOB_ASSOCIATION_MESSAGE = "The job execution id "; + + assertEquals(0, SpringApplication.exit(SpringApplication + .run(BatchJobApplication.class))); + + String output = this.outputCapture.toString(); + assertTrue("Unable to find the timestamp: " + output, + output.contains(JOB_RUN_MESSAGE)); + assertTrue("Test results do not show create task message: " + output, + output.contains(CREATE_TASK_MESSAGE)); + assertTrue("Test results do not show success message: " + output, + output.contains(UPDATE_TASK_MESSAGE)); + + int i = output.indexOf(JOB_ASSOCIATION_MESSAGE); + + assertTrue("Test results do not show the listener message: " + output, + i > 0); + + int j = output.indexOf(JOB_ASSOCIATION_MESSAGE, i + 1); + + assertTrue("Test results do not show the listener message: " + output, + j > i); + + + String taskTitle = "Demo Batch Job Task"; + Pattern pattern = Pattern.compile(taskTitle); + Matcher matcher = pattern.matcher(output); + int count = 0; + while (matcher.find()) { + count++; + } + assertEquals("The number of task titles did not match expected: ", 3, count); + } + +} diff --git a/spring-cloud-task-samples/batch-job/src/test/resources/application.properties b/spring-cloud-task-samples/batch-job/src/test/resources/application.properties new file mode 100644 index 00000000..de3e2f84 --- /dev/null +++ b/spring-cloud-task-samples/batch-job/src/test/resources/application.properties @@ -0,0 +1,18 @@ +# +# Copyright 2016 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. +# + +logging.level.root=DEBUG +spring.application.name=Demo Batch Job Task diff --git a/spring-cloud-task-samples/pom.xml b/spring-cloud-task-samples/pom.xml index 9cf2dfbb..542eb8a8 100644 --- a/spring-cloud-task-samples/pom.xml +++ b/spring-cloud-task-samples/pom.xml @@ -23,6 +23,7 @@ timestamp + batch-job diff --git a/spring-cloud-task-samples/timestamp/README.adoc b/spring-cloud-task-samples/timestamp/README.adoc index bc428848..4bb0e0ee 100644 --- a/spring-cloud-task-samples/timestamp/README.adoc +++ b/spring-cloud-task-samples/timestamp/README.adoc @@ -1,4 +1,4 @@ -= Timestamp Job += Timestamp Task This is a Spring Cloud Task application that logs a timestamp. diff --git a/spring-cloud-task-samples/timestamp/pom.xml b/spring-cloud-task-samples/timestamp/pom.xml index b779e3e3..37972b42 100644 --- a/spring-cloud-task-samples/timestamp/pom.xml +++ b/spring-cloud-task-samples/timestamp/pom.xml @@ -15,7 +15,7 @@ org.springframework.boot spring-boot-starter-parent - 1.3.2.RELEASE + 1.3.3.RELEASE diff --git a/spring-cloud-task-samples/timestamp/src/main/java/org/springframework/cloud/task/timestamp/TaskApplication.java b/spring-cloud-task-samples/timestamp/src/main/java/org/springframework/cloud/task/timestamp/TaskApplication.java index c07bfb48..22e0cb0d 100644 --- a/spring-cloud-task-samples/timestamp/src/main/java/org/springframework/cloud/task/timestamp/TaskApplication.java +++ b/spring-cloud-task-samples/timestamp/src/main/java/org/springframework/cloud/task/timestamp/TaskApplication.java @@ -40,6 +40,8 @@ import org.springframework.context.annotation.Bean; @EnableConfigurationProperties({ TimestampTaskProperties.class }) public class TaskApplication { + private static final Log logger = LogFactory.getLog(TaskApplication.class); + @Bean public TimestampTask timeStampTask() { return new TimestampTask(); @@ -53,7 +55,6 @@ public class TaskApplication { * A commandline runner that prints a timestamp. */ public class TimestampTask implements CommandLineRunner { - private final Log logger = LogFactory.getLog(TimestampTask.class); @Autowired private TimestampTaskProperties config;