1 | /* |
2 | * Copyright 2006-2013 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; |
17 | |
18 | import org.springframework.util.StringUtils; |
19 | |
20 | import java.io.PrintWriter; |
21 | import java.io.Serializable; |
22 | import java.io.StringWriter; |
23 | |
24 | /** |
25 | * Value object used to carry information about the status of a |
26 | * job or step execution. |
27 | * |
28 | * ExitStatus is immutable and therefore thread-safe. |
29 | * |
30 | * @author Dave Syer |
31 | * |
32 | */ |
33 | @SuppressWarnings("serial") |
34 | public class ExitStatus implements Serializable, Comparable<ExitStatus> { |
35 | |
36 | /** |
37 | * Convenient constant value representing unknown state - assumed not |
38 | * continuable. |
39 | */ |
40 | public static final ExitStatus UNKNOWN = new ExitStatus("UNKNOWN"); |
41 | |
42 | /** |
43 | * Convenient constant value representing continuable state where processing |
44 | * is still taking place, so no further action is required. Used for |
45 | * asynchronous execution scenarios where the processing is happening in |
46 | * another thread or process and the caller is not required to wait for the |
47 | * result. |
48 | */ |
49 | public static final ExitStatus EXECUTING = new ExitStatus("EXECUTING"); |
50 | |
51 | /** |
52 | * Convenient constant value representing finished processing. |
53 | */ |
54 | public static final ExitStatus COMPLETED = new ExitStatus("COMPLETED"); |
55 | |
56 | /** |
57 | * Convenient constant value representing job that did no processing (e.g. |
58 | * because it was already complete). |
59 | */ |
60 | public static final ExitStatus NOOP = new ExitStatus("NOOP"); |
61 | |
62 | /** |
63 | * Convenient constant value representing finished processing with an error. |
64 | */ |
65 | public static final ExitStatus FAILED = new ExitStatus("FAILED"); |
66 | |
67 | /** |
68 | * Convenient constant value representing finished processing with |
69 | * interrupted status. |
70 | */ |
71 | public static final ExitStatus STOPPED = new ExitStatus("STOPPED"); |
72 | |
73 | private final String exitCode; |
74 | |
75 | private final String exitDescription; |
76 | |
77 | public ExitStatus(String exitCode) { |
78 | this(exitCode, ""); |
79 | } |
80 | |
81 | public ExitStatus(String exitCode, String exitDescription) { |
82 | super(); |
83 | this.exitCode = exitCode; |
84 | this.exitDescription = exitDescription == null ? "" : exitDescription; |
85 | } |
86 | |
87 | /** |
88 | * Getter for the exit code (defaults to blank). |
89 | * |
90 | * @return the exit code. |
91 | */ |
92 | public String getExitCode() { |
93 | return exitCode; |
94 | } |
95 | |
96 | /** |
97 | * Getter for the exit description (defaults to blank) |
98 | */ |
99 | public String getExitDescription() { |
100 | return exitDescription; |
101 | } |
102 | |
103 | /** |
104 | * Create a new {@link ExitStatus} with a logical combination of the exit |
105 | * code, and a concatenation of the descriptions. If either value has a |
106 | * higher severity then its exit code will be used in the result. In the |
107 | * case of equal severity, the exit code is replaced if the new value is |
108 | * alphabetically greater.<br/> |
109 | * <br/> |
110 | * |
111 | * Severity is defined by the exit code: |
112 | * <ul> |
113 | * <li>Codes beginning with EXECUTING have severity 1</li> |
114 | * <li>Codes beginning with COMPLETED have severity 2</li> |
115 | * <li>Codes beginning with NOOP have severity 3</li> |
116 | * <li>Codes beginning with STOPPED have severity 4</li> |
117 | * <li>Codes beginning with FAILED have severity 5</li> |
118 | * <li>Codes beginning with UNKNOWN have severity 6</li> |
119 | * </ul> |
120 | * Others have severity 7, so custom exit codes always win.<br/> |
121 | * |
122 | * If the input is null just return this. |
123 | * |
124 | * @param status an {@link ExitStatus} to combine with this one. |
125 | * @return a new {@link ExitStatus} combining the current value and the |
126 | * argument provided. |
127 | */ |
128 | public ExitStatus and(ExitStatus status) { |
129 | if (status == null) { |
130 | return this; |
131 | } |
132 | ExitStatus result = addExitDescription(status.exitDescription); |
133 | if (compareTo(status) < 0) { |
134 | result = result.replaceExitCode(status.exitCode); |
135 | } |
136 | return result; |
137 | } |
138 | |
139 | /** |
140 | * @param status an {@link ExitStatus} to compare |
141 | * @return greater than zero, 0, less than zero according to the severity and exit code |
142 | * @see java.lang.Comparable |
143 | */ |
144 | @Override |
145 | public int compareTo(ExitStatus status) { |
146 | if (severity(status) > severity(this)) { |
147 | return -1; |
148 | } |
149 | if (severity(status) < severity(this)) { |
150 | return 1; |
151 | } |
152 | return this.getExitCode().compareTo(status.getExitCode()); |
153 | } |
154 | |
155 | /** |
156 | * @param status |
157 | * @return |
158 | */ |
159 | private int severity(ExitStatus status) { |
160 | if (status.exitCode.startsWith(EXECUTING.exitCode)) { |
161 | return 1; |
162 | } |
163 | if (status.exitCode.startsWith(COMPLETED.exitCode)) { |
164 | return 2; |
165 | } |
166 | if (status.exitCode.startsWith(NOOP.exitCode)) { |
167 | return 3; |
168 | } |
169 | if (status.exitCode.startsWith(STOPPED.exitCode)) { |
170 | return 4; |
171 | } |
172 | if (status.exitCode.startsWith(FAILED.exitCode)) { |
173 | return 5; |
174 | } |
175 | if (status.exitCode.startsWith(UNKNOWN.exitCode)) { |
176 | return 6; |
177 | } |
178 | return 7; |
179 | } |
180 | |
181 | /* |
182 | * (non-Javadoc) |
183 | * |
184 | * @see java.lang.Object#toString() |
185 | */ |
186 | @Override |
187 | public String toString() { |
188 | return String.format("exitCode=%s;exitDescription=%s", exitCode, exitDescription); |
189 | } |
190 | |
191 | /** |
192 | * Compare the fields one by one. |
193 | * |
194 | * @see java.lang.Object#equals(java.lang.Object) |
195 | */ |
196 | @Override |
197 | public boolean equals(Object obj) { |
198 | if (obj == null) { |
199 | return false; |
200 | } |
201 | return toString().equals(obj.toString()); |
202 | } |
203 | |
204 | /** |
205 | * Compatible with the equals implementation. |
206 | * |
207 | * @see java.lang.Object#hashCode() |
208 | */ |
209 | @Override |
210 | public int hashCode() { |
211 | return toString().hashCode(); |
212 | } |
213 | |
214 | /** |
215 | * Add an exit code to an existing {@link ExitStatus}. If there is already a |
216 | * code present tit will be replaced. |
217 | * |
218 | * @param code the code to add |
219 | * @return a new {@link ExitStatus} with the same properties but a new exit |
220 | * code. |
221 | */ |
222 | public ExitStatus replaceExitCode(String code) { |
223 | return new ExitStatus(code, exitDescription); |
224 | } |
225 | |
226 | /** |
227 | * Check if this status represents a running process. |
228 | * |
229 | * @return true if the exit code is "EXECUTING" or "UNKNOWN" |
230 | */ |
231 | public boolean isRunning() { |
232 | return "EXECUTING".equals(this.exitCode) || "UNKNOWN".equals(this.exitCode); |
233 | } |
234 | |
235 | /** |
236 | * Add an exit description to an existing {@link ExitStatus}. If there is |
237 | * already a description present the two will be concatenated with a |
238 | * semicolon. |
239 | * |
240 | * @param description the description to add |
241 | * @return a new {@link ExitStatus} with the same properties but a new exit |
242 | * description |
243 | */ |
244 | public ExitStatus addExitDescription(String description) { |
245 | StringBuffer buffer = new StringBuffer(); |
246 | boolean changed = StringUtils.hasText(description) && !exitDescription.equals(description); |
247 | if (StringUtils.hasText(exitDescription)) { |
248 | buffer.append(exitDescription); |
249 | if (changed) { |
250 | buffer.append("; "); |
251 | } |
252 | } |
253 | if (changed) { |
254 | buffer.append(description); |
255 | } |
256 | return new ExitStatus(exitCode, buffer.toString()); |
257 | } |
258 | |
259 | /** |
260 | * Extract the stack trace from the throwable provided and append it to |
261 | * the exist description. |
262 | * |
263 | * @param throwable |
264 | * @return a new ExitStatus with the stack trace appended |
265 | */ |
266 | public ExitStatus addExitDescription(Throwable throwable) { |
267 | StringWriter writer = new StringWriter(); |
268 | throwable.printStackTrace(new PrintWriter(writer)); |
269 | String message = writer.toString(); |
270 | return addExitDescription(message); |
271 | } |
272 | |
273 | } |