Skip to content

Commit 263d1d6

Browse files
committed
Fix that ExitStatus#setExitException breaks immutability contract
Close GH-5366 Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
1 parent d8632a3 commit 263d1d6

3 files changed

Lines changed: 28 additions & 25 deletions

File tree

spring-batch-core/src/main/java/org/springframework/batch/core/ExitStatus.java

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.io.PrintWriter;
2222
import java.io.Serializable;
2323
import java.io.StringWriter;
24+
import java.util.Objects;
2425

2526
/**
2627
* Value object used to carry information about the status of a job or step execution.
@@ -30,6 +31,7 @@
3031
* @author Dave Syer
3132
* @author Mahmoud Ben Hassine
3233
* @author JiWon Seo
34+
* @author Yanming Zhou
3335
*
3436
*/
3537
public class ExitStatus implements Serializable, Comparable<ExitStatus> {
@@ -73,7 +75,7 @@ public class ExitStatus implements Serializable, Comparable<ExitStatus> {
7375

7476
private final String exitDescription;
7577

76-
@Nullable private Throwable exitException;
78+
private final @Nullable Throwable exitException;
7779

7880
/**
7981
* Constructor that accepts the exit code and sets the exit description to an empty
@@ -91,9 +93,7 @@ public ExitStatus(String exitCode) {
9193
* @param exitDescription The exit description to be used for the {@link ExitStatus}.
9294
*/
9395
public ExitStatus(String exitCode, String exitDescription) {
94-
super();
95-
this.exitCode = exitCode;
96-
this.exitDescription = exitDescription == null ? "" : exitDescription;
96+
this(exitCode, exitDescription, null);
9797
}
9898

9999
/**
@@ -104,8 +104,9 @@ public ExitStatus(String exitCode, String exitDescription) {
104104
* @param exitException The exit exception to the {@link ExitStatus}.
105105
* @since 6.0.3
106106
*/
107-
public ExitStatus(String exitCode, String exitDescription, Throwable exitException) {
108-
this(exitCode, exitDescription);
107+
public ExitStatus(String exitCode, @Nullable String exitDescription, @Nullable Throwable exitException) {
108+
this.exitCode = exitCode;
109+
this.exitDescription = exitDescription == null ? "" : exitDescription;
109110
this.exitException = exitException;
110111
}
111112

@@ -157,14 +158,14 @@ public String getExitDescription() {
157158
* @return a new {@link ExitStatus} combining the current value and the argument
158159
* provided.
159160
*/
160-
public ExitStatus and(ExitStatus status) {
161+
public ExitStatus and(@Nullable ExitStatus status) {
161162
if (status == null) {
162163
return this;
163164
}
164165
ExitStatus result = addExitDescription(status.exitDescription);
165166
Throwable exitException = status.exitException;
166167
if (exitException != null) {
167-
result.setExitException(exitException);
168+
result = result.withExitException(exitException);
168169
}
169170
if (compareTo(status) < 0) {
170171
result = result.replaceExitCode(status.exitCode);
@@ -219,7 +220,8 @@ private int severity(ExitStatus status) {
219220

220221
@Override
221222
public String toString() {
222-
return String.format("exitCode=%s;exitDescription=%s", exitCode, exitDescription);
223+
return String.format("exitCode=%s;exitDescription=%s;exitException=%s", exitCode, exitDescription,
224+
exitException);
223225
}
224226

225227
/**
@@ -228,11 +230,13 @@ public String toString() {
228230
* @see java.lang.Object#equals(java.lang.Object)
229231
*/
230232
@Override
231-
public boolean equals(@Nullable Object obj) {
232-
if (obj == null) {
233+
public boolean equals(Object o) {
234+
if (o == null || getClass() != o.getClass()) {
233235
return false;
234236
}
235-
return toString().equals(obj.toString());
237+
ExitStatus that = (ExitStatus) o;
238+
return Objects.equals(exitCode, that.exitCode) && Objects.equals(exitDescription, that.exitDescription)
239+
&& Objects.equals(exitException, that.exitException);
236240
}
237241

238242
/**
@@ -242,7 +246,7 @@ public boolean equals(@Nullable Object obj) {
242246
*/
243247
@Override
244248
public int hashCode() {
245-
return toString().hashCode();
249+
return Objects.hash(exitCode, exitDescription, exitException);
246250
}
247251

248252
/**
@@ -287,13 +291,12 @@ public ExitStatus addExitDescription(String description) {
287291
}
288292

289293
/**
290-
* Public setter for the exit exception.
294+
* Create new instance with the exit exception.
291295
* @param exitException the last exception that caused the step to exit
292-
* @since 6.0.3
296+
* @since 6.0.4
293297
*/
294-
public ExitStatus setExitException(Throwable exitException) {
295-
this.exitException = exitException;
296-
return this;
298+
public ExitStatus withExitException(Throwable exitException) {
299+
return new ExitStatus(this.exitCode, this.exitDescription, exitException);
297300
}
298301

299302
/**
@@ -314,9 +317,8 @@ public ExitStatus addExitDescription(Throwable throwable) {
314317
* evaluated.
315318
* @return {@code true} if the value matches a known exit code.
316319
*/
317-
public static boolean isNonDefaultExitStatus(ExitStatus status) {
318-
return status == null || status.getExitCode() == null
319-
|| status.getExitCode().equals(ExitStatus.COMPLETED.getExitCode())
320+
public static boolean isNonDefaultExitStatus(@Nullable ExitStatus status) {
321+
return status == null || status.getExitCode().equals(ExitStatus.COMPLETED.getExitCode())
320322
|| status.getExitCode().equals(ExitStatus.EXECUTING.getExitCode())
321323
|| status.getExitCode().equals(ExitStatus.FAILED.getExitCode())
322324
|| status.getExitCode().equals(ExitStatus.NOOP.getExitCode())

spring-batch-core/src/test/java/org/springframework/batch/core/ExitStatusTests.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
* @author Dave Syer
3535
* @author Mahmoud Ben Hassine
3636
* @author JiWon Seo
37+
* @author Yanming Zhou
3738
*
3839
*/
3940
class ExitStatusTests {
@@ -216,9 +217,9 @@ public void testIsNonDefaultExitStatusShouldReturnFalse(ExitStatus status) {
216217
}
217218

218219
private static Stream<Arguments> provideKnownExitStatuses() {
219-
return Stream.of(Arguments.of((ExitStatus) null), Arguments.of(new ExitStatus(null)),
220-
Arguments.of(ExitStatus.COMPLETED), Arguments.of(ExitStatus.EXECUTING), Arguments.of(ExitStatus.FAILED),
221-
Arguments.of(ExitStatus.NOOP), Arguments.of(ExitStatus.STOPPED), Arguments.of(ExitStatus.UNKNOWN));
220+
return Stream.of(Arguments.of((ExitStatus) null), Arguments.of(ExitStatus.COMPLETED),
221+
Arguments.of(ExitStatus.EXECUTING), Arguments.of(ExitStatus.FAILED), Arguments.of(ExitStatus.NOOP),
222+
Arguments.of(ExitStatus.STOPPED), Arguments.of(ExitStatus.UNKNOWN));
222223
}
223224

224225
private static Stream<Arguments> provideCustomExitStatuses() {

spring-batch-samples/src/main/java/org/springframework/batch/samples/chunking/local/LocalChunkingJobConfiguration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public ChunkProcessor<Vet> chunkProcessor(DataSource dataSource, TransactionTemp
105105
catch (Exception e) {
106106
transactionStatus.setRollbackOnly();
107107
contribution.incrementWriteSkipCount(chunk.size());
108-
contribution.setExitStatus(ExitStatus.FAILED.setExitException(e));
108+
contribution.setExitStatus(ExitStatus.FAILED.withExitException(e));
109109
}
110110
});
111111
}

0 commit comments

Comments
 (0)