View Javadoc

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 java.io.PrintWriter;
19  import java.io.Serializable;
20  import java.io.StringWriter;
21  
22  import org.springframework.util.StringUtils;
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 1,0,-1 according to the severity and exit code
142 	 */
143 	@Override
144 	public int compareTo(ExitStatus status) {
145 		if (severity(status) > severity(this)) {
146 			return -1;
147 		}
148 		if (severity(status) < severity(this)) {
149 			return 1;
150 		}
151 		return this.getExitCode().compareTo(status.getExitCode());
152 	}
153 
154 	/**
155 	 * @param status
156 	 * @return
157 	 */
158 	private int severity(ExitStatus status) {
159 		if (status.exitCode.startsWith(EXECUTING.exitCode)) {
160 			return 1;
161 		}
162 		if (status.exitCode.startsWith(COMPLETED.exitCode)) {
163 			return 2;
164 		}
165 		if (status.exitCode.startsWith(NOOP.exitCode)) {
166 			return 3;
167 		}
168 		if (status.exitCode.startsWith(STOPPED.exitCode)) {
169 			return 4;
170 		}
171 		if (status.exitCode.startsWith(FAILED.exitCode)) {
172 			return 5;
173 		}
174 		if (status.exitCode.startsWith(UNKNOWN.exitCode)) {
175 			return 6;
176 		}
177 		return 7;
178 	}
179 
180 	/*
181 	 * (non-Javadoc)
182 	 *
183 	 * @see java.lang.Object#toString()
184 	 */
185 	@Override
186 	public String toString() {
187 		return String.format("exitCode=%s;exitDescription=%s", exitCode, exitDescription);
188 	}
189 
190 	/**
191 	 * Compare the fields one by one.
192 	 *
193 	 * @see java.lang.Object#equals(java.lang.Object)
194 	 */
195 	@Override
196 	public boolean equals(Object obj) {
197 		if (obj == null) {
198 			return false;
199 		}
200 		return toString().equals(obj.toString());
201 	}
202 
203 	/**
204 	 * Compatible with the equals implementation.
205 	 *
206 	 * @see java.lang.Object#hashCode()
207 	 */
208 	@Override
209 	public int hashCode() {
210 		return toString().hashCode();
211 	}
212 
213 	/**
214 	 * Add an exit code to an existing {@link ExitStatus}. If there is already a
215 	 * code present tit will be replaced.
216 	 *
217 	 * @param code the code to add
218 	 * @return a new {@link ExitStatus} with the same properties but a new exit
219 	 * code.
220 	 */
221 	public ExitStatus replaceExitCode(String code) {
222 		return new ExitStatus(code, exitDescription);
223 	}
224 
225 	/**
226 	 * Check if this status represents a running process.
227 	 *
228 	 * @return true if the exit code is "RUNNING" or "UNKNOWN"
229 	 */
230 	public boolean isRunning() {
231 		return "RUNNING".equals(this.exitCode) || "UNKNOWN".equals(this.exitCode);
232 	}
233 
234 	/**
235 	 * Add an exit description to an existing {@link ExitStatus}. If there is
236 	 * already a description present the two will be concatenated with a
237 	 * semicolon.
238 	 *
239 	 * @param description the description to add
240 	 * @return a new {@link ExitStatus} with the same properties but a new exit
241 	 * description
242 	 */
243 	public ExitStatus addExitDescription(String description) {
244 		StringBuffer buffer = new StringBuffer();
245 		boolean changed = StringUtils.hasText(description) && !exitDescription.equals(description);
246 		if (StringUtils.hasText(exitDescription)) {
247 			buffer.append(exitDescription);
248 			if (changed) {
249 				buffer.append("; ");
250 			}
251 		}
252 		if (changed) {
253 			buffer.append(description);
254 		}
255 		return new ExitStatus(exitCode, buffer.toString());
256 	}
257 
258 	/**
259 	 * Extract the stack trace from the throwable provided and append it to
260 	 * the exist description.
261 	 *
262 	 * @param throwable
263 	 * @return a new ExitStatus with the stack trace appended
264 	 */
265 	public ExitStatus addExitDescription(Throwable throwable) {
266 		StringWriter writer = new StringWriter();
267 		throwable.printStackTrace(new PrintWriter(writer));
268 		String message = writer.toString();
269 		return addExitDescription(message);
270 	}
271 
272 }