001/* 002 * Copyright 2006-2014 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.springframework.batch.core; 017 018import org.springframework.util.StringUtils; 019 020import java.io.PrintWriter; 021import java.io.Serializable; 022import java.io.StringWriter; 023 024/** 025 * Value object used to carry information about the status of a 026 * job or step execution. 027 * 028 * ExitStatus is immutable and therefore thread-safe. 029 * 030 * @author Dave Syer 031 * 032 */ 033@SuppressWarnings("serial") 034public class ExitStatus implements Serializable, Comparable<ExitStatus> { 035 036 /** 037 * Convenient constant value representing unknown state - assumed not 038 * continuable. 039 */ 040 public static final ExitStatus UNKNOWN = new ExitStatus("UNKNOWN"); 041 042 /** 043 * Convenient constant value representing continuable state where processing 044 * is still taking place, so no further action is required. Used for 045 * asynchronous execution scenarios where the processing is happening in 046 * another thread or process and the caller is not required to wait for the 047 * result. 048 */ 049 public static final ExitStatus EXECUTING = new ExitStatus("EXECUTING"); 050 051 /** 052 * Convenient constant value representing finished processing. 053 */ 054 public static final ExitStatus COMPLETED = new ExitStatus("COMPLETED"); 055 056 /** 057 * Convenient constant value representing job that did no processing (e.g. 058 * because it was already complete). 059 */ 060 public static final ExitStatus NOOP = new ExitStatus("NOOP"); 061 062 /** 063 * Convenient constant value representing finished processing with an error. 064 */ 065 public static final ExitStatus FAILED = new ExitStatus("FAILED"); 066 067 /** 068 * Convenient constant value representing finished processing with 069 * interrupted status. 070 */ 071 public static final ExitStatus STOPPED = new ExitStatus("STOPPED"); 072 073 private final String exitCode; 074 075 private final String exitDescription; 076 077 public ExitStatus(String exitCode) { 078 this(exitCode, ""); 079 } 080 081 public ExitStatus(String exitCode, String exitDescription) { 082 super(); 083 this.exitCode = exitCode; 084 this.exitDescription = exitDescription == null ? "" : exitDescription; 085 } 086 087 /** 088 * Getter for the exit code (defaults to blank). 089 * 090 * @return the exit code. 091 */ 092 public String getExitCode() { 093 return exitCode; 094 } 095 096 /** 097 * Getter for the exit description (defaults to blank) 098 * 099 * @return {@link String} containing the exit description. 100 */ 101 public String getExitDescription() { 102 return exitDescription; 103 } 104 105 /** 106 * Create a new {@link ExitStatus} with a logical combination of the exit 107 * code, and a concatenation of the descriptions. If either value has a 108 * higher severity then its exit code will be used in the result. In the 109 * case of equal severity, the exit code is replaced if the new value is 110 * alphabetically greater.<br> 111 * <br> 112 * 113 * Severity is defined by the exit code: 114 * <ul> 115 * <li>Codes beginning with EXECUTING have severity 1</li> 116 * <li>Codes beginning with COMPLETED have severity 2</li> 117 * <li>Codes beginning with NOOP have severity 3</li> 118 * <li>Codes beginning with STOPPED have severity 4</li> 119 * <li>Codes beginning with FAILED have severity 5</li> 120 * <li>Codes beginning with UNKNOWN have severity 6</li> 121 * </ul> 122 * Others have severity 7, so custom exit codes always win.<br> 123 * 124 * If the input is null just return this. 125 * 126 * @param status an {@link ExitStatus} to combine with this one. 127 * @return a new {@link ExitStatus} combining the current value and the 128 * argument provided. 129 */ 130 public ExitStatus and(ExitStatus status) { 131 if (status == null) { 132 return this; 133 } 134 ExitStatus result = addExitDescription(status.exitDescription); 135 if (compareTo(status) < 0) { 136 result = result.replaceExitCode(status.exitCode); 137 } 138 return result; 139 } 140 141 /** 142 * @param status an {@link ExitStatus} to compare 143 * @return greater than zero, 0, less than zero according to the severity and exit code 144 * @see java.lang.Comparable 145 */ 146 @Override 147 public int compareTo(ExitStatus status) { 148 if (severity(status) > severity(this)) { 149 return -1; 150 } 151 if (severity(status) < severity(this)) { 152 return 1; 153 } 154 return this.getExitCode().compareTo(status.getExitCode()); 155 } 156 157 /** 158 * @param status 159 * @return 160 */ 161 private int severity(ExitStatus status) { 162 if (status.exitCode.startsWith(EXECUTING.exitCode)) { 163 return 1; 164 } 165 if (status.exitCode.startsWith(COMPLETED.exitCode)) { 166 return 2; 167 } 168 if (status.exitCode.startsWith(NOOP.exitCode)) { 169 return 3; 170 } 171 if (status.exitCode.startsWith(STOPPED.exitCode)) { 172 return 4; 173 } 174 if (status.exitCode.startsWith(FAILED.exitCode)) { 175 return 5; 176 } 177 if (status.exitCode.startsWith(UNKNOWN.exitCode)) { 178 return 6; 179 } 180 return 7; 181 } 182 183 /* 184 * (non-Javadoc) 185 * 186 * @see java.lang.Object#toString() 187 */ 188 @Override 189 public String toString() { 190 return String.format("exitCode=%s;exitDescription=%s", exitCode, exitDescription); 191 } 192 193 /** 194 * Compare the fields one by one. 195 * 196 * @see java.lang.Object#equals(java.lang.Object) 197 */ 198 @Override 199 public boolean equals(Object obj) { 200 if (obj == null) { 201 return false; 202 } 203 return toString().equals(obj.toString()); 204 } 205 206 /** 207 * Compatible with the equals implementation. 208 * 209 * @see java.lang.Object#hashCode() 210 */ 211 @Override 212 public int hashCode() { 213 return toString().hashCode(); 214 } 215 216 /** 217 * Add an exit code to an existing {@link ExitStatus}. If there is already a 218 * code present tit will be replaced. 219 * 220 * @param code the code to add 221 * @return a new {@link ExitStatus} with the same properties but a new exit 222 * code. 223 */ 224 public ExitStatus replaceExitCode(String code) { 225 return new ExitStatus(code, exitDescription); 226 } 227 228 /** 229 * Check if this status represents a running process. 230 * 231 * @return true if the exit code is "EXECUTING" or "UNKNOWN" 232 */ 233 public boolean isRunning() { 234 return "EXECUTING".equals(this.exitCode) || "UNKNOWN".equals(this.exitCode); 235 } 236 237 /** 238 * Add an exit description to an existing {@link ExitStatus}. If there is 239 * already a description present the two will be concatenated with a 240 * semicolon. 241 * 242 * @param description the description to add 243 * @return a new {@link ExitStatus} with the same properties but a new exit 244 * description 245 */ 246 public ExitStatus addExitDescription(String description) { 247 StringBuilder buffer = new StringBuilder(); 248 boolean changed = StringUtils.hasText(description) && !exitDescription.equals(description); 249 if (StringUtils.hasText(exitDescription)) { 250 buffer.append(exitDescription); 251 if (changed) { 252 buffer.append("; "); 253 } 254 } 255 if (changed) { 256 buffer.append(description); 257 } 258 return new ExitStatus(exitCode, buffer.toString()); 259 } 260 261 /** 262 * Extract the stack trace from the throwable provided and append it to 263 * the exist description. 264 * 265 * @param throwable {@link Throwable} instance containing the stack trace. 266 * @return a new ExitStatus with the stack trace appended 267 */ 268 public ExitStatus addExitDescription(Throwable throwable) { 269 StringWriter writer = new StringWriter(); 270 throwable.printStackTrace(new PrintWriter(writer)); 271 String message = writer.toString(); 272 return addExitDescription(message); 273 } 274 275 /** 276 * @param status the exit code to be evaluated 277 * @return true if the value matches a known exit code 278 */ 279 public static boolean isNonDefaultExitStatus(ExitStatus status) { 280 return status == null || status.getExitCode() == null || 281 status.getExitCode().equals(ExitStatus.COMPLETED.getExitCode()) || 282 status.getExitCode().equals(ExitStatus.EXECUTING.getExitCode()) || 283 status.getExitCode().equals(ExitStatus.FAILED.getExitCode()) || 284 status.getExitCode().equals(ExitStatus.NOOP.getExitCode()) || 285 status.getExitCode().equals(ExitStatus.STOPPED.getExitCode()) || 286 status.getExitCode().equals(ExitStatus.UNKNOWN.getExitCode()); 287 } 288}