Skip to content

Commit be1dfcc

Browse files
authored
#190: Rework of devon4j-batch module (#191)
1 parent a8f5825 commit be1dfcc

File tree

3 files changed

+100
-71
lines changed

3 files changed

+100
-71
lines changed

documentation/guide-batch-layer.asciidoc

+2-4
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ It is a typical design decision you have to take when designing your specific ba
4545

4646
== Project structure and packaging
4747

48-
Batches will be implememented in a separate maven module to keep the application core free of batch dependencies. The batch module includes a dependency to the application core-module to allow reuse of the use cases, DAOs etc.
48+
Batches will be implemented in a separate Maven module to keep the application core free of batch dependencies. The batch module includes a dependency to the application core-module to allow reuse of the use cases, DAOs etc.
4949
Additionally the batch module has dependencies to the required spring batch jars:
5050

5151
[source,xml]
@@ -197,7 +197,7 @@ E.g. the state will be running, despite the process crashed sometime ago.
197197
For that cases you have to change the status of the execution in the database.
198198

199199
=== CLI-Tool
200-
Devonfw provides a simple cli-tool to manage the executing status of your jobs.
200+
Devonfw provides a easy to use cli-tool to manage the executing status of your jobs.
201201
The tool is implemented in the devonfw module `devon4j-batch-tool`. It will provide a runnable jar, which may be used as follows:
202202

203203
List names of all previous executed jobs::
@@ -212,8 +212,6 @@ Show help::
212212
As you can the each invocation includes the jdbc connection string to your database.
213213
This means that you have to make sure that the corresponding DB driver is in the classpath (the prepared jar only contains H2).
214214

215-
216-
217215
== Tipps & tricks
218216

219217
=== Identifying job parameters

modules/batch/src/main/java/com/devonfw/module/batch/common/base/SpringBootBatchCommandLine.java

+92-65
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44
import java.util.List;
55
import java.util.Set;
66

7+
import javax.inject.Inject;
8+
79
import org.slf4j.Logger;
810
import org.slf4j.LoggerFactory;
911
import org.springframework.batch.core.BatchStatus;
12+
import org.springframework.batch.core.Job;
1013
import org.springframework.batch.core.JobExecution;
1114
import org.springframework.batch.core.JobParameters;
1215
import org.springframework.batch.core.configuration.JobLocator;
@@ -15,36 +18,77 @@
1518
import org.springframework.batch.core.launch.JobExecutionNotRunningException;
1619
import org.springframework.batch.core.launch.JobLauncher;
1720
import org.springframework.batch.core.launch.JobOperator;
21+
import org.springframework.batch.core.launch.NoSuchJobException;
1822
import org.springframework.batch.core.launch.support.CommandLineJobRunner;
19-
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
23+
import org.springframework.beans.factory.annotation.Autowired;
24+
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
2025
import org.springframework.boot.ExitCodeGenerator;
2126
import org.springframework.boot.SpringApplication;
2227
import org.springframework.boot.WebApplicationType;
2328
import org.springframework.context.ConfigurableApplicationContext;
2429
import org.springframework.util.StringUtils;
2530

2631
/**
27-
* Launcher for launching batch jobs from the command line when Spring Boot is used. Similar to the
28-
* {@link CommandLineJobRunner}, which does not work very well with Spring Boot.
29-
* <p>
30-
* Do not use this class if Spring Boot is not used!
31-
* <p>
32-
* It expects the full class name of the Spring Boot configuration class to be used as first argument, the class/XML
33-
* file for configuring the job as second argument and the job name as third.<br>
32+
* Launcher for launching batch jobs from the command line when Spring Boot is used. It is somewhat similar to the
33+
* {@link CommandLineJobRunner}. The main difference is, that this launcher disables the web-app for the spring context.
34+
*
35+
* It expects the full class name of the Spring Boot configuration class to be used as first argument and the jobs
36+
* beanname as the second.<br>
3437
* Moreover parameters can be specified as further arguments (convention: key1=value1 key2=value2 ...).
3538
* <p>
3639
* Example:<br>
37-
* java com.devonfw.module.batch.common.base.SpringBootBatchCommandLine
38-
* com.devonfw.gastronomy.restaurant.SpringBootBatchApp classpath:config/app/batch/beans-productimport.xml
39-
* productImportJob drinks.file=file:import/drinks.csv date(date)=2015/12/20
40+
* {@code java -jar my-app-batch-bootified.jar com.devonfw.application.example.SpringBootApp myJob param=value...}
41+
* </p>
4042
* <p>
4143
* For stopping all running executions of a job, use the -stop option.
42-
* <p>
44+
*
4345
* Example:<br>
44-
* java com.devonfw.module.batch.common.base.SpringBootBatchCommandLine
45-
* com.devonfw.gastronomy.restaurant.SpringBootBatchApp classpath:config/app/batch/beans-productimport.xml
46-
* productImportJob -stop
46+
* {@code java -jar my-app-batch-bootified.jar com.devonfw.application.example.SpringBootApp myJob -stop}
47+
* </p>
48+
* <p>
49+
* To make that work expect that the batchs is deployed in form of a "bootified" jar, whith this class here als the
50+
* start-class. For that you have to add the following snipped to your pom.xml:
51+
*
52+
* <pre>
53+
* {@code
54+
<build>
55+
<plugins>
56+
<plugin>
57+
<groupId>org.apache.maven.plugins</groupId>
58+
<artifactId>maven-jar-plugin</artifactId>
59+
<configuration>
60+
<excludes>
61+
<exclude>config/application.properties</exclude>
62+
</excludes>
63+
</configuration>
64+
</plugin>
65+
<plugin>
66+
<groupId>org.springframework.boot</groupId>
67+
<artifactId>spring-boot-maven-plugin</artifactId>
68+
<configuration>
69+
<mainClass>com.devonfw.module.batch.common.base.SpringBootBatchCommandLine</mainClass>
70+
<classifier>bootified</classifier>
71+
</configuration>
72+
<executions>
73+
<execution>
74+
<goals>
75+
<goal>repackage</goal>
76+
</goals>
77+
</execution>
78+
</executions>
79+
</plugin>
80+
</plugins>
81+
</build>
82+
* }
83+
* </pre>
84+
* </p>
85+
* <p>
86+
*
87+
* @deprecated Since spring batch and spring boot are nicely integrated it is possible to start batches without any
88+
* custom launcher. This is documented in the current devonfw batch documentation. This launcher is no
89+
* longer required and will be removed in one of the next releases.
4790
*/
91+
@Deprecated
4892
public class SpringBootBatchCommandLine {
4993

5094
private static final Logger LOG = LoggerFactory.getLogger(SpringBootBatchCommandLine.class);
@@ -59,12 +103,16 @@ public static enum Operation {
59103
STOP
60104
};
61105

106+
@Inject
62107
private JobLauncher launcher;
63108

109+
@Inject
64110
private JobLocator locator;
65111

66-
private JobParametersConverter parametersConverter;
112+
@Autowired(required = false) // Use Autowired here, since @Inject does not support required = false.
113+
private JobParametersConverter parametersConverter = new DefaultJobParametersConverter();;
67114

115+
@Inject
68116
private JobOperator operator;
69117

70118
/**
@@ -73,22 +121,18 @@ public static enum Operation {
73121
*/
74122
public static void main(String[] args) throws Exception {
75123

76-
if (args.length < 3) {
124+
if (args.length < 2) {
77125

78126
handleIncorrectParameters();
79127
return;
80128
}
81129

82-
List<String> configurations = new ArrayList<>(2);
83-
configurations.add(args[0]);
84-
configurations.add(args[1]);
85-
86130
List<String> parameters = new ArrayList<>();
87131

88132
Operation op = Operation.START;
89-
if (args.length > 3 && args[3].equalsIgnoreCase("-stop")) {
133+
if (args.length > 2 && args[2].equalsIgnoreCase("-stop")) {
90134

91-
if (args.length > 4) {
135+
if (args.length > 3) {
92136

93137
handleIncorrectParameters();
94138
return;
@@ -97,29 +141,25 @@ public static void main(String[] args) throws Exception {
97141
op = Operation.STOP;
98142
} else {
99143

100-
for (int i = 3; i < args.length; i++) {
144+
for (int i = 2; i < args.length; i++) {
101145

102146
parameters.add(args[i]);
103147
}
104148
}
105149

106-
new SpringBootBatchCommandLine().execute(op, configurations, args[2], parameters);
150+
new SpringBootBatchCommandLine().execute(op, args[0], args[1], parameters);
107151
}
108152

109153
private static void handleIncorrectParameters() {
110154

111155
LOG.error("Incorrect parameters.");
112156
LOG.info("Usage:");
113-
LOG.info("java com.devonfw.module.batch.common.base.SpringBootBatchCommandLine"
114-
+ " <SpringBootConfiguration> <BatchJobConfiguration>" + " <JobName> param1=value1 param2=value2 ...");
157+
LOG.info(
158+
"java -jar my-app-batch-bootified.jar <SpringBootConfiguration> <JobName> param1=value1 param2=value2 ...");
115159
LOG.info("For stopping all running executions of a batch job:");
116-
LOG.info("java com.devonfw.module.batch.common.base.BatchCommandLine"
117-
+ " <SpringBootConfiguration> <BatchJobConfiguration>" + " <JobName> -stop");
160+
LOG.info("java -jar my-app-batch-bootified.jar <SpringBootConfiguration> <JobName> -stop");
118161
LOG.info("Example:");
119-
LOG.info("java com.devonfw.module.batch.common.base.SpringBootBatchCommandLine"
120-
+ " com.devonfw.gastronomy.restaurant.SpringBootBatchApp"
121-
+ " classpath:config/app/batch/beans-productimport.xml" + " productImportJob drinks.file=file:import/drinks.csv"
122-
+ " date(date)=2015/12/20");
162+
LOG.info("java com.devonfw.application.example.SpringBootApp exportJob pathToFile=myfile.csv");
123163
}
124164

125165
/**
@@ -134,50 +174,32 @@ protected int getReturnCode(JobExecution jobExecution) {
134174
return 1;
135175
}
136176

137-
private void findBeans(ConfigurableApplicationContext ctx) {
138-
139-
this.launcher = ctx.getBean(JobLauncher.class);
140-
this.locator = ctx.getBean(JobLocator.class); // supertype of JobRegistry
141-
this.operator = ctx.getBean(JobOperator.class);
142-
try {
143-
144-
this.parametersConverter = ctx.getBean(JobParametersConverter.class);
145-
} catch (NoSuchBeanDefinitionException e) {
146-
147-
this.parametersConverter = new DefaultJobParametersConverter();
148-
}
149-
}
150-
151177
/**
152178
* Initialize the application context and execute the operation.
153179
* <p>
154180
* The application context is closed after the operation has finished.
155181
*
156182
* @param operation The operation to start.
157-
* @param configurations The sources of bean configurations (either JavaConfig classes or XML files).
183+
* @param configuration The sources of app context configuration.
158184
* @param jobName The name of the job to launch/stop.
159185
* @param parameters The parameters (key=value).
160186
* @throws Exception in case of an error.
161187
*/
162-
@SuppressWarnings("deprecation")
163-
public void execute(Operation operation, List<String> configurations, String jobName, List<String> parameters)
188+
public void execute(Operation operation, String configuration, String jobName, List<String> parameters)
164189
throws Exception {
165190

166-
// get sources of configuration
167-
Class<?>[] configurationClasses = new Class[configurations.size()];
168-
for (int i = 0; i < configurations.size(); i++) {
169-
170-
configurationClasses[i] = Class.forName(configurations.get(i));
171-
}
172-
173-
SpringApplication app = new SpringApplication(configurationClasses);
191+
SpringApplication app = new SpringApplication(Class.forName(configuration));
174192

175193
// no (web) server needed
176194
app.setWebApplicationType(WebApplicationType.NONE);
177195

178196
// start the application
179197
ConfigurableApplicationContext ctx = app.run(new String[0]);
180198

199+
// start injection for properties of this class (here), by manually invoking autowiring for the new context.
200+
ctx.getAutowireCapableBeanFactory().autowireBeanProperties(this, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE,
201+
false);
202+
181203
switch (operation) {
182204
case START:
183205
startBatch(ctx, jobName, parameters);
@@ -197,15 +219,22 @@ private void startBatch(ConfigurableApplicationContext ctx, String jobName, List
197219
JobExecution jobExecution = null;
198220
try {
199221

200-
findBeans(ctx);
201-
202222
JobParameters params = this.parametersConverter
203223
.getJobParameters(StringUtils.splitArrayElementsIntoProperties(parameters.toArray(new String[] {}), "="));
204224

205225
// execute the batch
206-
// the JobOperator would require special logic for a restart, so we
207-
// are using the JobLauncher directly here
208-
jobExecution = this.launcher.run(this.locator.getJob(jobName), params);
226+
Job job = null;
227+
if (this.locator != null) {
228+
try {
229+
job = this.locator.getJob(jobName);
230+
} catch (NoSuchJobException e) {
231+
}
232+
}
233+
if (job == null) {
234+
job = (Job) ctx.getBean(jobName);
235+
}
236+
237+
jobExecution = this.launcher.run(job, params);
209238

210239
} finally {
211240

@@ -245,8 +274,6 @@ private void stopBatch(ConfigurableApplicationContext ctx, String jobName) throw
245274
int returnCode = 0;
246275
try {
247276

248-
findBeans(ctx);
249-
250277
Set<Long> runningJobExecutionIDs = this.operator.getRunningExecutions(jobName);
251278
if (runningJobExecutionIDs.isEmpty()) {
252279

modules/batch/src/main/java/com/devonfw/module/batch/common/impl/JobLauncherWithAdditionalRestartCapabilities.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.springframework.batch.core.JobParametersIncrementer;
88
import org.springframework.batch.core.JobParametersInvalidException;
99
import org.springframework.batch.core.launch.JobLauncher;
10+
import org.springframework.batch.core.launch.support.RunIdIncrementer;
1011
import org.springframework.batch.core.launch.support.SimpleJobLauncher;
1112
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
1213
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
@@ -21,7 +22,10 @@
2122
* the 'run.id' parameter). It is actually just a convenience functionality so that the one starting batches does not
2223
* have to change the parameters manually.
2324
*
25+
* @deprecated Using springs {@link RunIdIncrementer} is sufficient for most use cases.
26+
*
2427
*/
28+
@Deprecated
2529
public class JobLauncherWithAdditionalRestartCapabilities extends SimpleJobLauncher {
2630

2731
private JobRepository jobRepository;
@@ -39,8 +43,8 @@ public JobExecution run(final Job job, JobParameters jobParameters) throws JobEx
3943
// analogous to SimpleJobRepository#createJobExecution
4044
JobExecution jobExecution = this.jobRepository.getLastJobExecution(job.getName(), jobParameters);
4145
if (jobExecution.isRunning()) {
42-
throw new JobExecutionAlreadyRunningException("A job execution for this job is already running: "
43-
+ jobExecution.getJobInstance());
46+
throw new JobExecutionAlreadyRunningException(
47+
"A job execution for this job is already running: " + jobExecution.getJobInstance());
4448
}
4549
BatchStatus status = jobExecution.getStatus();
4650
if (status == BatchStatus.COMPLETED || status == BatchStatus.ABANDONED) {

0 commit comments

Comments
 (0)