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 0000000000..f95d95c4da
--- /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 0000000000..137ed1c02d
--- /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 0000000000..879eb2af0b
--- /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 e0a33f5785..4a3905edf3 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 0000000000..cf142a015b
--- /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 4d3836341a..8613e68f73 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 f5634d5bde..0ca60ea804 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 0000000000..915210f967
--- /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 0000000000..829684dd59
--- /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 e0262907d9..7c48167cde 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) {
+ private ApplicationEventPublisher publisher;
+
+ @Override
+ public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
+ this.publisher = publisher;
+ }
+
+ public void run(String... args) throws JobExecutionException {
logger.info("Running default command line with: " + Arrays.asList(args));
- launchJobFromProperties(StringUtils.splitArrayElementsIntoProperties(args,
- "="));
+ launchJobFromProperties(StringUtils.splitArrayElementsIntoProperties(args, "="));
}
- 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);
+ 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 7ea645e691..1f4e0fb8ea 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);