View Javadoc

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