Add listener to persist relationship between job and task

This introduces a listener that stores the association between a Spring
Batch job and the task it was executed within.

Resolves spring-cloud/spring-cloud-task#46

Merge Changes based on code review.
This commit is contained in:
Michael Minella
2016-03-04 14:52:21 -06:00
committed by Glenn Renfro
parent bb76cef391
commit 7c8fc5f50e
39 changed files with 1579 additions and 17 deletions

View File

@@ -70,6 +70,7 @@
<module>spring-cloud-task-core</module>
<module>spring-cloud-task-samples</module>
<module>spring-cloud-task-docs</module>
<module>spring-cloud-task-batch</module>
</modules>
<properties>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>spring-cloud-task-parent</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>1.0.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-cloud-task-batch</artifactId>
<packaging>jar</packaging>
<name>Spring Cloud Task Batch</name>
<description>Module for use when combining Spring Cloud Task with Spring Batch</description>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-task-core</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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}. <note>This is intended for testing
* purposes only!</note>
*
* @author Michael Minella
*/
public class MapTaskBatchDao implements TaskBatchDao {
private Map<Long, Set<Long>> relationships;
public MapTaskBatchDao(Map<Long, Set<Long>> 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<Long> jobExecutionIds = new TreeSet<>();
jobExecutionIds.add(jobExecution.getId());
this.relationships.put(taskExecution.getExecutionId(), jobExecutionIds);
}
}
}

View File

@@ -0,0 +1 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.springframework.cloud.task.batch.configuration.TaskBatchAutoConfiguration

View File

@@ -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<TaskExecution> page = taskExplorer.findTaskExecutionsByName("application", new PageRequest(0, 1));
Set<Long> 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<TaskExecution> page = taskExplorer.findTaskExecutionsByName("application", new PageRequest(0, 1));
Set<Long> 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<TaskExecution> page = taskExplorer.findTaskExecutionsByName("application", new PageRequest(0, 1));
Set<Long> 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<TaskExecution> page = taskExplorer.findTaskExecutionsByName("application", new PageRequest(0, 1));
Set<Long> jobExecutionIds = taskExplorer.getJobExecutionIdsByTaskExecutionId(page.iterator().next().getExecutionId());
assertEquals(2, jobExecutionIds.size());
Iterator<Long> 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();
}
}
}

View File

@@ -13,7 +13,6 @@
<artifactId>spring-cloud-task-core</artifactId>
<packaging>jar</packaging>
<name>Spring Cloud Task Core</name>
<version>1.0.0.BUILD-SNAPSHOT</version>
<description>Spring Cloud Task</description>
<dependencies>

View File

@@ -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<TaskExecution> findRunningTaskExecutions(String taskName, Pageable pageable);
Page<TaskExecution> findRunningTaskExecutions(String taskName, Pageable pageable);
/**
* Retrieve a list of available task names.
*
* @return the set of task names that have been executed
*/
public List<String> getTaskNames();
List<String> 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<TaskExecution> findTaskExecutionsByName(String taskName, Pageable pageable);
Page<TaskExecution> 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<TaskExecution> findAll(Pageable pageable);
Page<TaskExecution> 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 <code>Set</code> of the ids of the job executions executed within the task.
*/
Set<Long> getJobExecutionIdsByTaskExecutionId(long taskExecutionId);
}

View File

@@ -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<Long> getJobExecutionIdsByTaskExecutionId(long taskExecutionId) {
try {
return jdbcTemplate.query(
getQuery(FIND_JOB_EXECUTION_BY_TASK_EXECUTION_ID),
new Object[] {taskExecutionId},
new ResultSetExtractor<Set<Long>>() {
@Override
public Set<Long> extractData(ResultSet resultSet) throws SQLException, DataAccessException {
Set<Long> jobExecutionIds = new TreeSet<>();
while(resultSet.next()) {
jobExecutionIds.add(resultSet.getLong("JOB_EXECUTION_ID"));
}
return jobExecutionIds;
}
});
}
catch (DataAccessException e) {
return Collections.emptySet();
}
}
private Page<TaskExecution> queryForPageableResults(Pageable pageable,
String selectClause,
String fromClause,

View File

@@ -42,10 +42,13 @@ public class MapTaskExecutionDao implements TaskExecutionDao {
private ConcurrentMap<Long, TaskExecution> taskExecutions;
private ConcurrentMap<Long, Set<Long>> 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<Long, Set<Long>> association : batchJobAssociations.entrySet()) {
for (Long curJobExecutionId : association.getValue()) {
if(curJobExecutionId.equals(jobExecutionId)) {
taskId = association.getKey();
break found;
}
}
}
return taskId;
}
@Override
public Set<Long> getJobExecutionIdsByTaskExecutionId(long taskExecutionId) {
if(batchJobAssociations.containsKey(taskExecutionId)) {
return Collections.unmodifiableSet(batchJobAssociations.get(taskExecutionId));
}
else {
return new TreeSet<>();
}
}
public ConcurrentMap<Long, Set<Long>> getBatchJobAssociations() {
return batchJobAssociations;
}
private TreeSet<TaskExecution> getTaskExecutionTreeSet() {
return new TreeSet<>(new Comparator<TaskExecution>() {
@Override

View File

@@ -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<String> parameters);
@@ -105,7 +106,7 @@ public interface TaskExecutionDao {
*
* @return a list of distinct task names from the task repository..
*/
public List<String> getTaskNames();
List<String> 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<TaskExecution> findAll(Pageable pageable);
Page<TaskExecution> 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 <code>Set</code> of the ids of the job executions executed within the task.
*/
Set<Long> getJobExecutionIdsByTaskExecutionId(long taskExecutionId);
}

View File

@@ -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) {

View File

@@ -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<Long> getJobExecutionIdsByTaskExecutionId(long taskExecutionId) {
return taskExecutionDao.getJobExecutionIdsByTaskExecutionId(taskExecutionId);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

View File

@@ -0,0 +1 @@
distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.3/apache-maven-3.3.3-bin.zip

View File

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

233
spring-cloud-task-samples/batch-job/mvnw vendored Executable file
View File

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

View File

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

View File

@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.cloud</groupId>
<artifactId>batch-job</artifactId>
<packaging>jar</packaging>
<version>1.0.0.BUILD-SNAPSHOT</version>
<description>Spring Cloud Task Batch Example</description>
<name>batch-job</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.3.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-task-core</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-task-batch</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.3</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<!--skip deploy (this is just a test module) -->
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

View File

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

View File

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

View File

@@ -0,0 +1 @@
spring.application.name=Demo Batch Job Task

View File

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

View File

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

View File

@@ -23,6 +23,7 @@
<modules>
<module>timestamp</module>
<module>batch-job</module>
</modules>
<build>

View File

@@ -1,4 +1,4 @@
= Timestamp Job
= Timestamp Task
This is a Spring Cloud Task application that logs a timestamp.

View File

@@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.2.RELEASE</version>
<version>1.3.3.RELEASE</version>
</parent>
<properties>

View File

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