Allows failed batch apps to set exit code of task app.

resolves #201
 Please enter the commit message for your changes. Lines starting

Cleanup Removed taskProperties from TaskBatchAutoConfiguration
This commit is contained in:
Glenn Renfro
2018-01-22 10:53:36 -05:00
committed by Michael Minella
parent b6f6d68385
commit cd4dbe6a93
12 changed files with 870 additions and 13 deletions

View File

@@ -70,5 +70,14 @@
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2018 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.
@@ -56,14 +56,11 @@ public class TaskBatchAutoConfiguration {
@Autowired
private ApplicationContext context;
@Autowired
private TaskProperties taskProperties;
@Bean
public TaskBatchExecutionListenerFactoryBean taskBatchExecutionListener(TaskExplorer taskExplorer) {
TaskConfigurer taskConfigurer = null;
if(!context.getBeansOfType(TaskConfigurer.class).isEmpty()) {
taskConfigurer = context.getBean(TaskConfigurer.class);
if(!this.context.getBeansOfType(TaskConfigurer.class).isEmpty()) {
taskConfigurer = this.context.getBean(TaskConfigurer.class);
}
if(taskConfigurer != null && taskConfigurer.getTaskDataSource() != null) {
return new TaskBatchExecutionListenerFactoryBean(
@@ -74,6 +71,5 @@ public class TaskBatchAutoConfiguration {
return new TaskBatchExecutionListenerFactoryBean(null, taskExplorer);
}
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2018 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.boot.context.properties.ConfigurationProperties;
/**
* Establish properties to be used for how Tasks work with
* Spring Batch.
*
* @author Glenn Renfro
*/
@ConfigurationProperties(prefix = "spring.cloud.task.batch")
public class TaskBatchProperties {
/**
* Comma-separated list of job names to execute on startup (for instance,
* `job1,job2`). By default, all Jobs found in the context are executed.
*/
private String jobNames = "";
public String getJobNames() {
return this.jobNames;
}
public void setJobNames(String jobNames) {
this.jobNames = jobNames;
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2018 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.util.List;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Provides auto configuration for the
* {@link org.springframework.cloud.task.batch.handler.TaskJobLauncherCommandLineRunner}.
*
* @author Glenn Renfro
*/
@Configuration
@ConditionalOnProperty(name = "spring.cloud.task.batch.commandLineRunnerEnabled", havingValue = "true", matchIfMissing = false)
@EnableConfigurationProperties(TaskBatchProperties.class)
public class TaskJobLauncherAutoConfiguration {
@Autowired
private TaskBatchProperties properties;
@Bean
public TaskJobLauncherCommandLineRunnerFactoryBean jobLauncherCommandLineRunner(JobLauncher jobLauncher,
JobExplorer jobExplorer, List<Job> jobs, JobRegistry jobRegistry) {
TaskJobLauncherCommandLineRunnerFactoryBean taskJobLauncherCommandLineRunner = new TaskJobLauncherCommandLineRunnerFactoryBean(
jobLauncher, jobExplorer, jobs, this.properties.getJobNames(), jobRegistry);
return taskJobLauncherCommandLineRunner;
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2018 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.util.List;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.cloud.task.batch.handler.TaskJobLauncherCommandLineRunner;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Factory bean for creating an instance of {@link TaskJobLauncherCommandLineRunner}.
*
* @author Glenn Renfro
*/
public class TaskJobLauncherCommandLineRunnerFactoryBean implements FactoryBean<TaskJobLauncherCommandLineRunner> {
private JobLauncher jobLauncher;
private JobExplorer jobExplorer;
private List<Job> jobs;
private String jobNames;
private JobRegistry jobRegistry;
public TaskJobLauncherCommandLineRunnerFactoryBean(JobLauncher jobLauncher,
JobExplorer jobExplorer, List<Job> jobs, String jobNames,
JobRegistry jobRegistry) {
this.jobLauncher = jobLauncher;
this.jobExplorer = jobExplorer;
Assert.notEmpty(jobs, "jobs must not be null nor empty");
this.jobs = jobs;
this.jobNames = jobNames;
this.jobRegistry = jobRegistry;
}
@Override
public TaskJobLauncherCommandLineRunner getObject() throws Exception {
TaskJobLauncherCommandLineRunner taskJobLauncherCommandLineRunner =
new TaskJobLauncherCommandLineRunner(this.jobLauncher, this.jobExplorer);
taskJobLauncherCommandLineRunner.setJobs(this.jobs);
if(StringUtils.hasText(this.jobNames)) {
taskJobLauncherCommandLineRunner.setJobNames(this.jobNames);
}
taskJobLauncherCommandLineRunner.setJobRegistry(this.jobRegistry);
return taskJobLauncherCommandLineRunner;
}
@Override
public Class<?> getObjectType() {
return TaskJobLauncherCommandLineRunner.class;
}
}

View File

@@ -0,0 +1,192 @@
/*
* Copyright 2018 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.handler;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionException;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.converter.DefaultJobParametersConverter;
import org.springframework.batch.core.converter.JobParametersConverter;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.JobParametersNotFoundException;
import org.springframework.batch.core.launch.NoSuchJobException;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.batch.JobExecutionEvent;
import org.springframework.cloud.task.listener.TaskException;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.core.Ordered;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.StringUtils;
/**
* {@link CommandLineRunner} to {@link JobLauncher launch} Spring Batch jobs. Runs all
* jobs in the surrounding context by default and throw an exception upon the
* first job that returns an {@link ExitStatus} of FAILED.
* Can also be used to launch a specific job by providing a jobName. The
* TaskJobLaunchercommandLineRunner takes the place of the
* {@link org.springframework.boot.autoconfigure.batch.JobLauncherCommandLineRunner}
* when it is in use.
*
* @author Glenn Renfro
* @since 2.0.0
*/
public class TaskJobLauncherCommandLineRunner implements CommandLineRunner, Ordered, ApplicationEventPublisherAware{
/**
* The default order for the command line runner.
*/
public static final int DEFAULT_ORDER = 0;
private static final Log logger = LogFactory
.getLog(TaskJobLauncherCommandLineRunner.class);
private JobParametersConverter converter = new DefaultJobParametersConverter();
private JobLauncher jobLauncher;
private JobRegistry jobRegistry;
private JobExplorer jobExplorer;
private String jobNames;
private Collection<Job> jobs = Collections.emptySet();
private int order = DEFAULT_ORDER;
private ApplicationEventPublisher publisher;
public TaskJobLauncherCommandLineRunner(JobLauncher jobLauncher,
JobExplorer jobExplorer) {
this.jobLauncher = jobLauncher;
this.jobExplorer = jobExplorer;
}
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void setJobRegistry(JobRegistry jobRegistry) {
this.jobRegistry = jobRegistry;
}
public void setJobNames(String jobNames) {
this.jobNames = jobNames;
}
public void setJobParametersConverter(JobParametersConverter converter) {
this.converter = converter;
}
public void setJobs(Collection<Job> jobs) {
this.jobs = jobs;
}
@Override
public void run(String... args) throws JobExecutionException {
logger.info("Running default command line with: " + Arrays.asList(args));
launchJobFromProperties(StringUtils.splitArrayElementsIntoProperties(args, "="));
}
protected void launchJobFromProperties(Properties properties)
throws JobExecutionException {
JobParameters jobParameters = this.converter.getJobParameters(properties);
executeLocalJobs(jobParameters);
executeRegisteredJobs(jobParameters);
}
private void executeRegisteredJobs(JobParameters jobParameters)
throws JobExecutionException {
if (this.jobRegistry != null && StringUtils.hasText(this.jobNames)) {
String[] jobsToRun = this.jobNames.split(",");
for (String jobName : jobsToRun) {
try {
Job job = this.jobRegistry.getJob(jobName);
if (this.jobs.contains(job)) {
continue;
}
execute(job, jobParameters);
}
catch (NoSuchJobException ex) {
logger.debug("No job found in registry for job name: " + jobName);
}
}
}
}
protected void execute(Job job, JobParameters jobParameters)
throws JobExecutionAlreadyRunningException, JobRestartException,
JobInstanceAlreadyCompleteException, JobParametersInvalidException,
JobParametersNotFoundException {
JobParameters nextParameters = new JobParametersBuilder(jobParameters,
this.jobExplorer).getNextJobParameters(job).toJobParameters();
JobExecution execution = this.jobLauncher.run(job, nextParameters);
if (this.publisher != null) {
this.publisher.publishEvent(new JobExecutionEvent(execution));
}
if(execution.getExitStatus().getExitCode().equals(ExitStatus.FAILED.getExitCode())) {
String message = String.format("Job %s failed during " +
"execution for jobId %s with jobExecutionId of %s",
execution.getJobInstance().getJobName(),
execution.getJobId(), execution.getId());
logger.error(message);
throw new TaskException(message);
}
}
private void executeLocalJobs(JobParameters jobParameters)
throws JobExecutionException {
for (Job job : this.jobs) {
if (StringUtils.hasText(this.jobNames)) {
String[] jobsToRun = this.jobNames.split(",");
if (!PatternMatchUtils.simpleMatch(jobsToRun, job.getName())) {
logger.debug("Skipped job: " + job.getName());
continue;
}
}
execute(job, jobParameters);
}
}
}

View File

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

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2018 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.junit.Test;
import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration;
import org.springframework.boot.autoconfigure.batch.JobLauncherCommandLineRunner;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.task.batch.handler.TaskJobLauncherCommandLineRunner;
import org.springframework.cloud.task.batch.listener.TaskBatchExecutionListenerTests;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Glenn Renfro
*/
public class TaskJobLauncherAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().
withUserConfiguration(TaskBatchExecutionListenerTests.JobConfiguration.class,
PropertyPlaceholderAutoConfiguration.class,
EmbeddedDataSourceConfiguration.class,
BatchAutoConfiguration.class,
TaskJobLauncherAutoConfiguration.class);
@Test
public void testAutoBuiltDataSourceWithTaskJobLauncherCLR() {
this.contextRunner.
withPropertyValues("spring.cloud.task.batch.commandLineRunnerEnabled=true").
run(context -> {
assertThat(context).hasSingleBean(TaskJobLauncherCommandLineRunner.class);
assertThat(context).doesNotHaveBean(JobLauncherCommandLineRunner.class);
});
}
@Test
public void testAutoBuiltDataSourceWithTaskJobLauncherCLRDisabled() {
this.contextRunner.run(context -> {
assertThat(context).hasSingleBean(JobLauncherCommandLineRunner.class);
assertThat(context).doesNotHaveBean(TaskJobLauncherCommandLineRunner.class);
});
}
}

View File

@@ -0,0 +1,210 @@
/*
* Copyright 2018 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.handler;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.BatchConfigurer;
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.explore.JobExplorer;
import org.springframework.batch.core.explore.support.MapJobExplorerFactoryBean;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.launch.support.SimpleJobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.task.listener.TaskException;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.PlatformTransactionManager;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Glenn Renfro
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {TaskJobLauncherCommandLineRunnerCoreTests.BatchConfiguration.class})
public class TaskJobLauncherCommandLineRunnerCoreTests {
@Autowired
private JobRepository jobRepository;
@Autowired
private JobLauncher jobLauncher;
@Autowired
private JobExplorer jobExplorer;
@Autowired
PlatformTransactionManager transactionManager;
private TaskJobLauncherCommandLineRunner runner;
private JobBuilderFactory jobs;
private StepBuilderFactory steps;
private Job job;
private Step step;
@Before
public void init() {
this.jobs = new JobBuilderFactory(this.jobRepository);
this.steps = new StepBuilderFactory(this.jobRepository, this.transactionManager);
Tasklet tasklet = (contribution, chunkContext) -> null;
this.step = this.steps.get("step").tasklet(tasklet).build();
this.job = this.jobs.get("job").start(this.step).build();
this.runner = new TaskJobLauncherCommandLineRunner(this.jobLauncher, this.jobExplorer);
}
@DirtiesContext
@Test
public void basicExecution() throws Exception {
this.runner.execute(this.job, new JobParameters());
assertThat(this.jobExplorer.getJobInstances("job", 0, 100)).hasSize(1);
this.runner.execute(this.job,
new JobParametersBuilder().addLong("id", 1L).toJobParameters());
assertThat(this.jobExplorer.getJobInstances("job", 0, 100)).hasSize(2);
}
@DirtiesContext
@Test
public void incrementExistingExecution() throws Exception {
this.job = this.jobs.get("job").start(this.step)
.incrementer(new RunIdIncrementer()).build();
this.runner.execute(this.job, new JobParameters());
this.runner.execute(this.job, new JobParameters());
assertThat(this.jobExplorer.getJobInstances("job", 0, 100)).hasSize(2);
}
@DirtiesContext
@Test
public void retryFailedExecution() throws Exception {
this.job = this.jobs.get("job")
.start(this.steps.get("step").tasklet(throwingTasklet()).build())
.incrementer(new RunIdIncrementer()).build();
runFailedJob(new JobParameters());
runFailedJob(new JobParameters());
assertThat(this.jobExplorer.getJobInstances("job", 0, 100)).hasSize(1);
}
@DirtiesContext
@Test
public void retryFailedExecutionOnNonRestartableJob() throws Exception {
this.job = this.jobs.get("job").preventRestart()
.start(this.steps.get("step").tasklet(throwingTasklet()).build())
.incrementer(new RunIdIncrementer()).build();
runFailedJob(new JobParameters());
runFailedJob(new JobParameters());
// A failed job that is not restartable does not re-use the job params of
// the last execution, but creates a new job instance when running it again.
assertThat(this.jobExplorer.getJobInstances("job", 0, 100)).hasSize(2);
}
@DirtiesContext
@Test
public void retryFailedExecutionWithNonIdentifyingParameters() throws Exception {
this.job = this.jobs.get("job")
.start(this.steps.get("step").tasklet(throwingTasklet()).build())
.incrementer(new RunIdIncrementer()).build();
JobParameters jobParameters = new JobParametersBuilder().addLong("id", 1L, false)
.addLong("foo", 2L, false).toJobParameters();
runFailedJob(new JobParameters());
runFailedJob(new JobParameters());
assertThat(this.jobExplorer.getJobInstances("job", 0, 100)).hasSize(1);
}
private Tasklet throwingTasklet() {
return (contribution, chunkContext) -> {
throw new RuntimeException("Planned");
};
}
private void runFailedJob(JobParameters jobParameters) throws Exception {
boolean isExceptionThrown = false;
try {
this.runner.execute(this.job, new JobParameters());
}
catch (TaskException taskException) {
isExceptionThrown = true;
}
assertThat(isExceptionThrown).isTrue();
}
@Configuration
@EnableBatchProcessing
protected static class BatchConfiguration implements BatchConfigurer {
private ResourcelessTransactionManager transactionManager =
new ResourcelessTransactionManager();
private JobRepository jobRepository;
private MapJobRepositoryFactoryBean jobRepositoryFactory =
new MapJobRepositoryFactoryBean(
this.transactionManager);
public BatchConfiguration() throws Exception {
this.jobRepository = this.jobRepositoryFactory.getObject();
}
public void clear() {
this.jobRepositoryFactory.clear();
}
@Override
public JobRepository getJobRepository() {
return this.jobRepository;
}
@Override
public PlatformTransactionManager getTransactionManager() {
return this.transactionManager;
}
@Override
public JobLauncher getJobLauncher() {
SimpleJobLauncher launcher = new SimpleJobLauncher();
launcher.setJobRepository(this.jobRepository);
launcher.setTaskExecutor(new SyncTaskExecutor());
return launcher;
}
@Override
public JobExplorer getJobExplorer() throws Exception {
return new MapJobExplorerFactoryBean(this.jobRepositoryFactory).getObject();
}
}
}

View File

@@ -0,0 +1,213 @@
/*
* Copyright 2018 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.handler;
import java.util.Set;
import org.assertj.core.api.Assertions;
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.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration;
import org.springframework.boot.autoconfigure.batch.JobLauncherCommandLineRunner;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.task.batch.configuration.TaskBatchAutoConfiguration;
import org.springframework.cloud.task.batch.configuration.TaskJobLauncherAutoConfiguration;
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.ApplicationContext;
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.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
/**
* @author Glenn Renfro
*/
public class TaskJobLauncherCommandLineRunnerTests {
private ConfigurableApplicationContext applicationContext;
@After
public void tearDown() {
if (this.applicationContext != null) {
this.applicationContext.close();
}
}
@Test
public void testTaskJobLauncherCLRSuccessFail() {
String[] enabledArgs = new String[] { "--spring.cloud.task.batch.commandLineRunnerEnabled=true" };
boolean isExceptionThrown = false;
try {
this.applicationContext = SpringApplication
.run(new Class[] { TaskJobLauncherCommandLineRunnerTests.JobWithFailureConfiguration.class,
PropertyPlaceholderAutoConfiguration.class,
EmbeddedDataSourceConfiguration.class,
BatchAutoConfiguration.class,
TaskBatchAutoConfiguration.class,
TaskJobLauncherAutoConfiguration.class }, enabledArgs);
}
catch (IllegalStateException exception) {
isExceptionThrown = true;
}
assertThat(isExceptionThrown).isTrue();
}
@Test
public void testTaskJobLauncherPickOneJob() {
String[] enabledArgs = new String[] {
"--spring.cloud.task.batch.commandLineRunnerEnabled=true",
"--spring.cloud.task.batch.jobNames=jobSucceed" };
boolean isExceptionThrown = false;
try {
this.applicationContext = SpringApplication
.run(new Class[] { TaskJobLauncherCommandLineRunnerTests.JobWithFailureConfiguration.class,
PropertyPlaceholderAutoConfiguration.class,
EmbeddedDataSourceConfiguration.class,
BatchAutoConfiguration.class,
TaskBatchAutoConfiguration.class,
TaskJobLauncherAutoConfiguration.class }, enabledArgs);
}
catch (IllegalStateException exception) {
isExceptionThrown = true;
}
assertThat(isExceptionThrown).isFalse();
validateContext();
}
@Test
public void testCommandLineRunnerSetToFalse() {
String[] enabledArgs = new String[] { };
this.applicationContext = SpringApplication
.run(new Class[] { TaskJobLauncherCommandLineRunnerTests.JobConfiguration.class,
PropertyPlaceholderAutoConfiguration.class,
EmbeddedDataSourceConfiguration.class,
BatchAutoConfiguration.class,
TaskBatchAutoConfiguration.class,
TaskJobLauncherAutoConfiguration.class }, enabledArgs);
validateContext();
assertThat(applicationContext.getBean(JobLauncherCommandLineRunner.class)).isNotNull();
boolean exceptionThrown = false;
try {
applicationContext.getBean(TaskJobLauncherCommandLineRunner.class);
}
catch (NoSuchBeanDefinitionException exception) {
exceptionThrown = true;
}
assertThat(exceptionThrown).isTrue();
validateContext();
}
private void validateContext() {
TaskExplorer taskExplorer = this.applicationContext.getBean(TaskExplorer.class);
Page<TaskExecution> page = taskExplorer.findTaskExecutionsByName("application", PageRequest.of(0, 1));
Set<Long> jobExecutionIds = taskExplorer
.getJobExecutionIdsByTaskExecutionId(page.iterator().next().getExecutionId());
assertThat(jobExecutionIds.size()).isEqualTo(1);
assertThat(taskExplorer.getTaskExecution(jobExecutionIds.iterator().next()).getExecutionId()).isEqualTo(1);
}
@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 JobWithFailureConfiguration {
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
@Bean
public Job jobFail() {
return jobBuilderFactory.get("jobA")
.start(stepBuilderFactory.get("step1").tasklet(new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution,
ChunkContext chunkContext)
throws Exception {
System.out.println("Executed");
throw new IllegalStateException("WHOOPS");
}
}).build())
.build();
}
@Bean
public Job jobFun() {
return jobBuilderFactory.get("jobSucceed")
.start(stepBuilderFactory.get("step1Succeed").tasklet(new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution,
ChunkContext chunkContext)
throws Exception {
System.out.println("Executed");
return RepeatStatus.FINISHED;
}
}).build())
.build();
}
}
}

View File

@@ -65,7 +65,7 @@ import static org.junit.Assert.assertEquals;
*/
public class TaskBatchExecutionListenerTests {
public static final String[] ARGS = new String[] {"--spring.cloud.task.closecontext_enable=false"};
public static final String[] ARGS = new String[] {};
private ConfigurableApplicationContext applicationContext;

View File

@@ -243,8 +243,15 @@ is covered in detail in the section
As discussed before Spring Cloud Task applications support the ability to record the exit code of a task execution.
However in cases where a user is running a Spring Batch Job within a task, regardless of how the Batch Job
Execution completes the result of the task will always be zero when using default Batch/Boot behavior. Keep in mind
that a task is a boot application and the exit code returned from the task is the same as a boot application. So
to have your task return the exit code based on the result of the batch job execution, you will need to write your
own CommandLineRunner.
Execution completes the result of the task will always be zero when using default Batch/Boot behavior.
Keep in mind that a task is a boot application and the exit code returned from
the task is the same as a boot application.
To override this behavior and allow the task to return an exit code other than zero
(either 1 `the default` or based on the
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-spring-application.html#boot-features-application-exit[ExitCodeGenerator]
specified) upon a batch job returning an
https://docs.spring.io/spring-batch/4.0.x/reference/html/step.html#conditionalFlow[ExitStatus]
of "FAILED", set `spring.cloud.task.batch.commandLineRunnerEnabled` to true.
To have your task return the exit code based on the result of the batch job
execution, you will need to write your own `CommandLineRunner`.