Skip to content

Commit b49eea7

Browse files
fmbenhassinemminella
authored andcommitted
BATCH-2397: fix parameters handling in MethodInvokingTaskletAdapter
When a tasklet is declared with xml using the shortcut version, the MethodInvokingTaskletAdapter that is created automatically does not address passing parameters expected by Tasklet#execute (which is incorrect since the documentation of the schema attribute "method" says the bean should define a method with the same signature). This commit fixes parameters passing when using the shortcut version. Resolves BATCH-2397
1 parent 0c6bdb1 commit b49eea7

File tree

5 files changed

+300
-4
lines changed

5 files changed

+300
-4
lines changed

spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/MethodInvokingTaskletAdapter.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2006-2013 the original author or authors.
2+
* Copyright 2006-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@
3131
* @see AbstractMethodInvokingDelegator
3232
*
3333
* @author Dave Syer
34+
* @author Mahmoud Ben Hassine
3435
*
3536
*/
3637
public class MethodInvokingTaskletAdapter extends AbstractMethodInvokingDelegator<Object> implements Tasklet {
@@ -44,6 +45,9 @@ public class MethodInvokingTaskletAdapter extends AbstractMethodInvokingDelegato
4445
*/
4546
@Override
4647
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
48+
if (getArguments() == null) {
49+
setArguments(new Object[]{contribution, chunkContext});
50+
}
4751
contribution.setExitStatus(mapResult(invokeDelegateMethod()));
4852
return RepeatStatus.FINISHED;
4953
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
/*
2+
* Copyright 2006-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.batch.core.step.tasklet;
17+
18+
import org.junit.Before;
19+
import org.junit.Test;
20+
import org.springframework.batch.core.ExitStatus;
21+
import org.springframework.batch.core.StepContribution;
22+
import org.springframework.batch.core.StepExecution;
23+
import org.springframework.batch.core.scope.context.ChunkContext;
24+
import org.springframework.batch.repeat.RepeatStatus;
25+
26+
import static org.junit.Assert.assertEquals;
27+
import static org.mockito.Mockito.mock;
28+
29+
/**
30+
* @author Mahmoud Ben Hassine
31+
*/
32+
public class MethodInvokingTaskletAdapterTest {
33+
34+
private StepContribution stepContribution;
35+
private ChunkContext chunkContext;
36+
private TestTasklet tasklet;
37+
private MethodInvokingTaskletAdapter adapter;
38+
39+
@Before
40+
public void setUp() throws Exception {
41+
stepContribution = new StepContribution(mock(StepExecution.class));
42+
chunkContext = mock(ChunkContext.class);
43+
tasklet = new TestTasklet();
44+
adapter = new MethodInvokingTaskletAdapter();
45+
adapter.setTargetObject(tasklet);
46+
}
47+
48+
@Test
49+
public void testExactlySameSignature() throws Exception {
50+
adapter.setTargetMethod("execute");
51+
RepeatStatus repeatStatus = adapter.execute(stepContribution, chunkContext);
52+
assertEquals(RepeatStatus.FINISHED, repeatStatus);
53+
assertEquals(tasklet.getStepContribution(), stepContribution);
54+
assertEquals(tasklet.getChunkContext(), chunkContext);
55+
}
56+
57+
@Test
58+
public void testSameSignatureWithDifferentMethodName() throws Exception {
59+
adapter.setTargetMethod("execute1");
60+
RepeatStatus repeatStatus = adapter.execute(stepContribution, chunkContext);
61+
assertEquals(RepeatStatus.FINISHED, repeatStatus);
62+
assertEquals(tasklet.getStepContribution(), stepContribution);
63+
assertEquals(tasklet.getChunkContext(), chunkContext);
64+
}
65+
66+
@Test
67+
public void testDifferentParametersOrder() throws Exception {
68+
adapter.setTargetMethod("execute2");
69+
RepeatStatus repeatStatus = adapter.execute(stepContribution, chunkContext);
70+
assertEquals(RepeatStatus.FINISHED, repeatStatus);
71+
assertEquals(tasklet.getStepContribution(), stepContribution);
72+
assertEquals(tasklet.getChunkContext(), chunkContext);
73+
}
74+
75+
@Test
76+
public void testArgumentSubsetWithOnlyChunkContext() throws Exception {
77+
adapter.setTargetMethod("execute3");
78+
RepeatStatus repeatStatus = adapter.execute(stepContribution, chunkContext);
79+
assertEquals(RepeatStatus.FINISHED, repeatStatus);
80+
assertEquals(tasklet.getChunkContext(), chunkContext);
81+
}
82+
83+
@Test
84+
public void testArgumentSubsetWithOnlyStepContribution() throws Exception {
85+
adapter.setTargetMethod("execute4");
86+
RepeatStatus repeatStatus = adapter.execute(stepContribution, chunkContext);
87+
assertEquals(RepeatStatus.FINISHED, repeatStatus);
88+
assertEquals(tasklet.getStepContribution(), stepContribution);
89+
}
90+
91+
@Test
92+
public void testArgumentSubsetWithoutArguments() throws Exception {
93+
adapter.setTargetMethod("execute5");
94+
RepeatStatus repeatStatus = adapter.execute(stepContribution, chunkContext);
95+
assertEquals(RepeatStatus.FINISHED, repeatStatus);
96+
}
97+
98+
@Test
99+
public void testCompatibleReturnTypeWhenBoolean() throws Exception {
100+
adapter.setTargetMethod("execute6");
101+
RepeatStatus repeatStatus = adapter.execute(stepContribution, chunkContext);
102+
assertEquals(RepeatStatus.FINISHED, repeatStatus);
103+
}
104+
105+
@Test
106+
public void testCompatibleReturnTypeWhenVoid() throws Exception {
107+
adapter.setTargetMethod("execute7");
108+
RepeatStatus repeatStatus = adapter.execute(stepContribution, chunkContext);
109+
assertEquals(RepeatStatus.FINISHED, repeatStatus);
110+
}
111+
112+
@Test
113+
public void testArgumentSubsetWithOnlyStepContributionAndCompatibleReturnTypeBoolean() throws Exception {
114+
adapter.setTargetMethod("execute8");
115+
RepeatStatus repeatStatus = adapter.execute(stepContribution, chunkContext);
116+
assertEquals(RepeatStatus.FINISHED, repeatStatus);
117+
assertEquals(tasklet.getStepContribution(), stepContribution);
118+
}
119+
120+
@Test
121+
public void testArgumentSubsetWithOnlyChunkContextAndCompatibleReturnTypeVoid() throws Exception {
122+
adapter.setTargetMethod("execute9");
123+
RepeatStatus repeatStatus = adapter.execute(stepContribution, chunkContext);
124+
assertEquals(RepeatStatus.FINISHED, repeatStatus);
125+
assertEquals(tasklet.getChunkContext(), chunkContext);
126+
}
127+
128+
@Test(expected = IllegalArgumentException.class)
129+
public void testIncorrectSignatureWithExtraParameter() throws Exception {
130+
adapter.setTargetMethod("execute10");
131+
adapter.execute(stepContribution, chunkContext);
132+
}
133+
134+
@Test
135+
public void testExitStatusReturnType() throws Exception {
136+
adapter.setTargetMethod("execute11");
137+
adapter.execute(stepContribution, chunkContext);
138+
assertEquals(new ExitStatus("DONE"), stepContribution.getExitStatus());
139+
}
140+
141+
@Test
142+
public void testNonExitStatusReturnType() throws Exception {
143+
adapter.setTargetMethod("execute12");
144+
RepeatStatus repeatStatus = adapter.execute(stepContribution, chunkContext);
145+
assertEquals(RepeatStatus.FINISHED, repeatStatus);
146+
assertEquals(ExitStatus.COMPLETED, stepContribution.getExitStatus());
147+
}
148+
149+
/*
150+
<xsd:attribute name="method" type="xsd:string" use="optional">
151+
<xsd:annotation>
152+
<xsd:documentation>
153+
If the tasklet is specified as a bean definition, then a method can be
154+
specified and a POJO will be adapted to the Tasklet interface.
155+
The method suggested should have the same arguments as Tasklet.execute
156+
(or a subset), and have a compatible return type (boolean, void or RepeatStatus).
157+
</xsd:documentation>
158+
</xsd:annotation>
159+
</xsd:attribute>
160+
*/
161+
public static class TestTasklet {
162+
163+
private StepContribution stepContribution;
164+
private ChunkContext chunkContext;
165+
166+
/* exactly same signature */
167+
public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
168+
this.stepContribution = stepContribution;
169+
this.chunkContext = chunkContext;
170+
return RepeatStatus.FINISHED;
171+
}
172+
173+
/* same signature, different method name */
174+
public RepeatStatus execute1(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
175+
this.stepContribution = stepContribution;
176+
this.chunkContext = chunkContext;
177+
return RepeatStatus.FINISHED;
178+
}
179+
180+
/* different parameters order */
181+
public RepeatStatus execute2(ChunkContext chunkContext, StepContribution stepContribution) throws Exception {
182+
this.stepContribution = stepContribution;
183+
this.chunkContext = chunkContext;
184+
return RepeatStatus.FINISHED;
185+
}
186+
187+
/* subset of arguments: only chunk context */
188+
public RepeatStatus execute3(ChunkContext chunkContext) throws Exception {
189+
this.chunkContext = chunkContext;
190+
return RepeatStatus.FINISHED;
191+
}
192+
193+
/* subset of arguments: only step contribution */
194+
public RepeatStatus execute4(StepContribution stepContribution) throws Exception {
195+
this.stepContribution = stepContribution;
196+
return RepeatStatus.FINISHED;
197+
}
198+
199+
/* subset of arguments: no arguments */
200+
public RepeatStatus execute5() throws Exception {
201+
return RepeatStatus.FINISHED;
202+
}
203+
204+
/* compatible return type: boolean */
205+
public boolean execute6(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
206+
this.stepContribution = stepContribution;
207+
this.chunkContext = chunkContext;
208+
return true;
209+
}
210+
211+
/* compatible return type: void */
212+
public void execute7(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
213+
this.stepContribution = stepContribution;
214+
this.chunkContext = chunkContext;
215+
}
216+
217+
/* subset of arguments (only step contribution) and compatible return type (boolean) */
218+
public boolean execute8(StepContribution stepContribution) throws Exception {
219+
this.stepContribution = stepContribution;
220+
return true;
221+
}
222+
223+
/* subset of arguments (only chunk context) and compatible return type (void) */
224+
public void execute9(ChunkContext chunkContext) throws Exception {
225+
this.chunkContext = chunkContext;
226+
}
227+
228+
/* Incorrect signature: extra parameter (ie a superset not a subset as specified) */
229+
public RepeatStatus execute10(StepContribution stepContribution, ChunkContext chunkContext, String string) throws Exception {
230+
this.stepContribution = stepContribution;
231+
this.chunkContext = chunkContext;
232+
return RepeatStatus.FINISHED;
233+
}
234+
235+
/* ExitStatus return type : should be returned as is */
236+
public ExitStatus execute11(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
237+
this.stepContribution = stepContribution;
238+
this.chunkContext = chunkContext;
239+
return new ExitStatus("DONE");
240+
}
241+
242+
/* Non ExitStatus return type : should return ExitStatus.COMPLETED */
243+
public String execute12(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
244+
this.stepContribution = stepContribution;
245+
this.chunkContext = chunkContext;
246+
return "DONE";
247+
}
248+
249+
public StepContribution getStepContribution() {
250+
return stepContribution;
251+
}
252+
253+
public ChunkContext getChunkContext() {
254+
return chunkContext;
255+
}
256+
}
257+
}
258+

spring-batch-infrastructure/src/main/java/org/springframework/batch/item/adapter/AbstractMethodInvokingDelegator.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2006-2013 the original author or authors.
2+
* Copyright 2006-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -38,6 +38,7 @@
3838
* by {@link InvocationTargetThrowableWrapper}.
3939
*
4040
* @author Robert Kasanicky
41+
* @author Mahmoud Ben Hassine
4142
*/
4243
public abstract class AbstractMethodInvokingDelegator<T> implements InitializingBean {
4344

@@ -213,6 +214,14 @@ public void setArguments(Object[] arguments) {
213214
this.arguments = arguments == null ? null : Arrays.asList(arguments).toArray();
214215
}
215216

217+
/**
218+
* Return arguments.
219+
* @return arguments
220+
*/
221+
protected Object[] getArguments() {
222+
return arguments;
223+
}
224+
216225
/**
217226
* Used to wrap a {@link Throwable} (not an {@link Exception}) thrown by a
218227
* reflectively-invoked delegate.

spring-batch-samples/src/main/resources/jobs/taskletJob.xml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,18 @@
1616
p:proxyTargetClass="true" />
1717

1818
<job id="loopJob" xmlns="http://www.springframework.org/schema/batch">
19-
<step id="step1">
19+
<!-- this step tests the usage of MethodInvokingTaskletAdapter declared as a bean -->
20+
<step id="step1" next="step2">
2021
<tasklet ref="adapter">
2122
<transaction-attributes propagation="REQUIRED"/>
2223
</tasklet>
2324
</step>
25+
26+
<!-- this step tests the shortcut version that automatically wraps a bean
27+
in a MethodInvokingTaskletAdapter -->
28+
<step id="step2">
29+
<tasklet ref="task" method="doWork"/>
30+
</step>
2431
</job>
2532

2633
<bean id="adapter"
@@ -41,4 +48,7 @@
4148
scope="step">
4249
<property name="value" value="#{jobParameters[value]}" />
4350
</bean>
51+
52+
<bean id="task" class="org.springframework.batch.sample.TaskletJobFunctionalTests$Task"/>
53+
4454
</beans>

spring-batch-samples/src/test/java/org/springframework/batch/sample/TaskletJobFunctionalTests.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2006-2007 the original author or authors.
2+
* Copyright 2006-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
2323
import org.springframework.batch.core.BatchStatus;
2424
import org.springframework.batch.core.JobExecution;
2525
import org.springframework.batch.core.JobParametersBuilder;
26+
import org.springframework.batch.core.scope.context.ChunkContext;
2627
import org.springframework.batch.test.JobLauncherTestUtils;
2728
import org.springframework.beans.factory.annotation.Autowired;
2829
import org.springframework.test.context.ContextConfiguration;
@@ -41,6 +42,7 @@ public void testLaunchJob() throws Exception {
4142
JobExecution jobExecution = jobLauncherTestUtils.launchJob(new JobParametersBuilder().addString("value", "foo")
4243
.toJobParameters());
4344
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
45+
assertEquals("yes", jobExecution.getExecutionContext().getString("done"));
4446
}
4547

4648
public static class TestBean {
@@ -57,5 +59,18 @@ public void execute(String strValue, Integer integerValue, double doubleValue) {
5759
assertEquals(3.14, doubleValue, 0.01);
5860
}
5961
}
62+
63+
public static class Task {
64+
65+
public boolean doWork(ChunkContext chunkContext) {
66+
chunkContext.
67+
getStepContext().
68+
getStepExecution().
69+
getJobExecution().
70+
getExecutionContext().put("done", "yes");
71+
return true;
72+
}
73+
74+
}
6075

6176
}

0 commit comments

Comments
 (0)