diff --git a/spring-bootstrap-samples/spring-bootstrap-batch-sample/pom.xml b/spring-bootstrap-samples/spring-bootstrap-batch-sample/pom.xml new file mode 100644 index 00000000000..f95d95c4da9 --- /dev/null +++ b/spring-bootstrap-samples/spring-bootstrap-batch-sample/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + org.springframework.bootstrap + spring-bootstrap-samples + 0.0.1-SNAPSHOT + + spring-bootstrap-batch-sample + jar + + ${project.basedir}/../.. + org.springframework.bootstrap.sample.simple.SimpleBootstrapApplication + + + + ${project.groupId} + spring-bootstrap + ${project.version} + + + org.springframework.batch + spring-batch-core + + + org.springframework + spring-jdbc + + + org.hsqldb + hsqldb + + + org.slf4j + slf4j-jdk14 + runtime + + + + + + maven-dependency-plugin + + + maven-assembly-plugin + + + + diff --git a/spring-bootstrap-samples/spring-bootstrap-batch-sample/src/main/java/org/springframework/bootstrap/sample/batch/BatchBootstrapApplication.java b/spring-bootstrap-samples/spring-bootstrap-batch-sample/src/main/java/org/springframework/bootstrap/sample/batch/BatchBootstrapApplication.java new file mode 100644 index 00000000000..137ed1c02d6 --- /dev/null +++ b/spring-bootstrap-samples/spring-bootstrap-batch-sample/src/main/java/org/springframework/bootstrap/sample/batch/BatchBootstrapApplication.java @@ -0,0 +1,73 @@ +/* + * Copyright 2012-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.bootstrap.sample.batch; + +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +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.bootstrap.SpringApplication; +import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableAutoConfiguration +@ComponentScan +@EnableBatchProcessing +public class BatchBootstrapApplication { + + @Autowired + private JobBuilderFactory jobs; + + @Autowired + private StepBuilderFactory steps; + + @Bean + protected Tasklet tasklet() { + return new Tasklet() { + @Override + public RepeatStatus execute(StepContribution contribution, + ChunkContext context) { + return RepeatStatus.FINISHED; + } + }; + } + + @Bean + public Job job() throws Exception { + return this.jobs.get("job").start(step1()).build(); + } + + @Bean + protected Step step1() throws Exception { + return this.steps.get("step1").tasklet(tasklet()).build(); + } + + public static void main(String[] args) throws Exception { + // System.exit is common for Batch applications since the exit code can be used to + // drive a workflow + System.exit(SpringApplication.exit(SpringApplication.run( + BatchBootstrapApplication.class, args))); + } +} diff --git a/spring-bootstrap-samples/spring-bootstrap-batch-sample/src/test/java/org/springframework/bootstrap/sample/batch/BatchBootstrapApplicationTests.java b/spring-bootstrap-samples/spring-bootstrap-batch-sample/src/test/java/org/springframework/bootstrap/sample/batch/BatchBootstrapApplicationTests.java new file mode 100644 index 00000000000..879eb2af0bc --- /dev/null +++ b/spring-bootstrap-samples/spring-bootstrap-batch-sample/src/test/java/org/springframework/bootstrap/sample/batch/BatchBootstrapApplicationTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.bootstrap.sample.batch; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.bootstrap.SpringApplication; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class BatchBootstrapApplicationTests { + + private PrintStream savedOutput; + private ByteArrayOutputStream output; + + @Before + public void init() { + this.savedOutput = System.err; + this.output = new ByteArrayOutputStream(); + // jdk logging goes to syserr by default + System.setErr(new PrintStream(this.output)); + } + + @After + public void after() { + System.setErr(this.savedOutput); + } + + private String getOutput() { + return this.output.toString(); + } + + @Test + public void testDefaultSettings() throws Exception { + assertEquals(0, SpringApplication.exit(SpringApplication + .run(BatchBootstrapApplication.class))); + String output = getOutput(); + assertTrue("Wrong output: " + output, + output.contains("completed with the following parameters")); + } + +} diff --git a/spring-bootstrap-samples/spring-bootstrap-simple-sample/src/test/java/org/springframework/bootstrap/sample/simple/SimpleBootstrapApplicationTests.java b/spring-bootstrap-samples/spring-bootstrap-simple-sample/src/test/java/org/springframework/bootstrap/sample/simple/SimpleBootstrapApplicationTests.java index e0a33f57851..4a3905edf34 100644 --- a/spring-bootstrap-samples/spring-bootstrap-simple-sample/src/test/java/org/springframework/bootstrap/sample/simple/SimpleBootstrapApplicationTests.java +++ b/spring-bootstrap-samples/spring-bootstrap-simple-sample/src/test/java/org/springframework/bootstrap/sample/simple/SimpleBootstrapApplicationTests.java @@ -45,7 +45,7 @@ public class SimpleBootstrapApplicationTests { } @Test - public void testCommandLoneOverrides() throws Exception { + public void testCommandLineOverrides() throws Exception { SimpleBootstrapApplication.main(new String[] { "--name=Gordon" }); String output = getOutput(); assertTrue("Wrong output: " + output, output.contains("Hello Gordon")); diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/ExitCodeGenerator.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/ExitCodeGenerator.java new file mode 100644 index 00000000000..cf142a015b6 --- /dev/null +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/ExitCodeGenerator.java @@ -0,0 +1,26 @@ +/* + * Copyright 2012-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.bootstrap; + +/** + * @author Dave Syer + * + */ +public interface ExitCodeGenerator { + + int getExitCode(); + +} diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/SpringApplication.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/SpringApplication.java index 4d3836341ac..8613e68f73a 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/SpringApplication.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/SpringApplication.java @@ -563,4 +563,49 @@ public class SpringApplication { return new SpringApplication(sources).run(args); } + /** + * Static helper that can be used to exit a {@link SpringApplication} and obtain a + * code indicating success (0) or otherwise. Does not throw exceptions but should + * print stack traces of any encountered. + * @param context the context to close if possible + * @return the outcome (0 if successful) + */ + public static int exit(ApplicationContext context, + ExitCodeGenerator... exitCodeGenerators) { + + int code = 0; + + List exiters = new ArrayList( + Arrays.asList(exitCodeGenerators)); + + try { + + exiters.addAll(context.getBeansOfType(ExitCodeGenerator.class).values()); + + for (ExitCodeGenerator exiter : exiters) { + try { + int value = exiter.getExitCode(); + if (value > code || value < 0 && value < code) { + code = value; + } + } catch (Exception e) { + code = code == 0 ? 1 : code; + e.printStackTrace(); + } + } + + if (context instanceof ConfigurableApplicationContext) { + ConfigurableApplicationContext closable = (ConfigurableApplicationContext) context; + closable.close(); + } + + } catch (Exception e) { + code = code == 0 ? 1 : code; + e.printStackTrace(); + } + + return code; + + } + } diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/batch/BatchAutoConfiguration.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/batch/BatchAutoConfiguration.java index f5634d5bde8..0ca60ea804e 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/batch/BatchAutoConfiguration.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/batch/BatchAutoConfiguration.java @@ -18,6 +18,7 @@ package org.springframework.bootstrap.autoconfigure.batch; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.bootstrap.CommandLineRunner; +import org.springframework.bootstrap.ExitCodeGenerator; import org.springframework.bootstrap.context.annotation.ConditionalOnClass; import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean; import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration; @@ -45,4 +46,10 @@ public class BatchAutoConfiguration { return new JobLauncherCommandLineRunner(); } + @Bean + @ConditionalOnMissingBean({ ExitCodeGenerator.class }) + public ExitCodeGenerator jobExecutionExitCodeGenerator() { + return new JobExecutionExitCodeGenerator(); + } + } diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/batch/JobExecutionEvent.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/batch/JobExecutionEvent.java new file mode 100644 index 00000000000..915210f967b --- /dev/null +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/batch/JobExecutionEvent.java @@ -0,0 +1,44 @@ +/* + * Copyright 2012-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.bootstrap.autoconfigure.batch; + +import org.springframework.batch.core.JobExecution; +import org.springframework.context.ApplicationEvent; + +/** + * @author Dave Syer + * + */ +public class JobExecutionEvent extends ApplicationEvent { + + private JobExecution execution; + + /** + * @param execution + */ + public JobExecutionEvent(JobExecution execution) { + super(execution); + this.execution = execution; + } + + /** + * @return the job execution + */ + public JobExecution getJobExecution() { + return this.execution; + } + +} diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/batch/JobExecutionExitCodeGenerator.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/batch/JobExecutionExitCodeGenerator.java new file mode 100644 index 00000000000..829684dd59b --- /dev/null +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/batch/JobExecutionExitCodeGenerator.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.bootstrap.autoconfigure.batch; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.batch.core.JobExecution; +import org.springframework.bootstrap.ExitCodeGenerator; +import org.springframework.context.ApplicationListener; + +/** + * @author Dave Syer + * + */ +public class JobExecutionExitCodeGenerator implements + ApplicationListener, ExitCodeGenerator { + + private List executions = new ArrayList(); + + @Override + public void onApplicationEvent(JobExecutionEvent event) { + this.executions.add(event.getJobExecution()); + } + + @Override + public int getExitCode() { + for (JobExecution execution : this.executions) { + if (execution.getStatus().isUnsuccessful()) { + return 2; + } + } + return 0; + } + +} diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/batch/JobLauncherCommandLineRunner.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/batch/JobLauncherCommandLineRunner.java index e0262907d98..7c48167cde9 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/batch/JobLauncherCommandLineRunner.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/batch/JobLauncherCommandLineRunner.java @@ -21,18 +21,22 @@ import java.util.Properties; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobExecutionException; import org.springframework.batch.core.converter.DefaultJobParametersConverter; import org.springframework.batch.core.converter.JobParametersConverter; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.bootstrap.CommandLineRunner; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; @Component // FIXME: what to do with more than one Job? -public class JobLauncherCommandLineRunner implements CommandLineRunner { +public class JobLauncherCommandLineRunner implements CommandLineRunner, + ApplicationEventPublisherAware { private static Log logger = LogFactory.getLog(JobLauncherCommandLineRunner.class); @@ -45,18 +49,24 @@ public class JobLauncherCommandLineRunner implements CommandLineRunner { @Autowired private Job job; - public void run(String... args) { - logger.info("Running default command line with: " + Arrays.asList(args)); - launchJobFromProperties(StringUtils.splitArrayElementsIntoProperties(args, - "=")); + private ApplicationEventPublisher publisher; + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { + this.publisher = publisher; } - protected void launchJobFromProperties(Properties properties) { - try { - this.jobLauncher.run(this.job, - this.converter.getJobParameters(properties)); - } catch (JobExecutionException e) { - throw new IllegalStateException("Could not run job", e); + 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 { + JobExecution execution = this.jobLauncher.run(this.job, + this.converter.getJobParameters(properties)); + if (this.publisher != null) { + this.publisher.publishEvent(new JobExecutionEvent(execution)); } } } \ No newline at end of file diff --git a/spring-bootstrap/src/test/java/org/springframework/bootstrap/SpringApplicationTests.java b/spring-bootstrap/src/test/java/org/springframework/bootstrap/SpringApplicationTests.java index 7ea645e6918..1f4e0fb8eaf 100644 --- a/spring-bootstrap/src/test/java/org/springframework/bootstrap/SpringApplicationTests.java +++ b/spring-bootstrap/src/test/java/org/springframework/bootstrap/SpringApplicationTests.java @@ -51,6 +51,7 @@ import org.springframework.web.context.support.StaticWebApplicationContext; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.sameInstance; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -279,6 +280,29 @@ public class SpringApplicationTests { assertNotNull(this.context); } + @Test + public void exit() throws Exception { + SpringApplication application = new SpringApplication(ExampleConfig.class); + application.setWebEnvironment(false); + ApplicationContext context = application.run(); + assertNotNull(context); + assertEquals(0, SpringApplication.exit(context)); + } + + @Test + public void exitWithExplicitCOde() throws Exception { + SpringApplication application = new SpringApplication(ExampleConfig.class); + application.setWebEnvironment(false); + ApplicationContext context = application.run(); + assertNotNull(context); + assertEquals(2, SpringApplication.exit(context, new ExitCodeGenerator() { + @Override + public int getExitCode() { + return 2; + } + })); + } + @Test public void defaultCommandLineArgs() throws Exception { SpringApplication application = new SpringApplication(ExampleConfig.class);