package water; import water.util.HttpResponseStatus; import water.util.IcedHashMap; import water.util.IcedHashMapGeneric; import water.util.Log; import java.util.ArrayList; import java.util.Arrays; /** * Class which represents a back-end error which will be returned to the client. Such * errors may be caused by the user (specifying an object which has been removed) or due * to a failure which is out of the user's control. */ public class H2OError extends Iced { /** Milliseconds since the epoch for the time that this H2OError instance was created. Generally this is a short time since the underlying error ocurred. */ public long _timestamp; public String _error_url = null; /** Message intended for the end user (a data scientist). */ public String _msg; /** Potentially more detailed message intended for a developer (e.g. a front end engineer or someone designing a language binding). */ public String _dev_msg; /** HTTP status code for this error. */ public int _http_status; /** Unique ID for this error instance, so that later we can build a dictionary of errors for docs and I18N. public int _error_id;*/ /** Any values that are relevant to reporting or handling this error. Examples are a key name if the error is on a key, or a field name and object name if it's on a specific field. */ public IcedHashMapGeneric.IcedHashMapStringObject _values; /** Exception type, if any. */ public String _exception_type; /** Raw exception message, if any. */ public String _exception_msg; /** Stacktrace, if any. */ public String[] _stacktrace; public H2OError(String error_url, String msg, String dev_msg, int http_status, IcedHashMapGeneric.IcedHashMapStringObject values, Exception e) { this(System.currentTimeMillis(), error_url, msg, dev_msg, http_status, values, e); } public H2OError(long timestamp, String error_url, String msg, String dev_msg, int http_status, IcedHashMapGeneric.IcedHashMapStringObject values, Throwable e) { Log.err(e); this._timestamp = timestamp; this._error_url = error_url; this._msg = msg; this._dev_msg = dev_msg; this._http_status = http_status; this._values = values; if (null == this._msg) { // It's crazy, but some Java exceptions like NullPointerException do not have a message! if (null != e) { this._msg = "Caught exception: " + e.getClass().getCanonicalName(); this._dev_msg = this._msg + " from: " + e.getStackTrace()[0]; } else { this._msg = "Unknown error"; this._dev_msg = this._msg; } } if (null != e) { this._exception_type = e.getClass().getCanonicalName(); this._exception_msg = e.getMessage(); ArrayList<String> arr = new ArrayList<>(); arr.add(e.toString()); StackTraceElement[] trace = e.getStackTrace(); for (StackTraceElement ste : trace) { String s = " " + ste.toString(); arr.add(s); if (s.startsWith("org.eclipse.jetty")) { // Don't need humongous jetty stack traces. break; } } // All distributed exceptions have the real cause while((e = e.getCause()) != null) { arr.add("Caused by:" + e.toString()); trace = e.getStackTrace(); for (StackTraceElement ste : trace) { String s = " " + ste.toString(); arr.add(s); if (s.startsWith("org.eclipse.jetty")) { // Don't need humongous jetty stack traces. break; } } } this._stacktrace = arr.toArray(new String[0]); } // Add a little header to make it error message stand out. Note: don't do this in toString() as we want clients to get this change too. String prefix = "\n\nERROR MESSAGE:\n\n"; String postfix = "\n\n"; _msg = prefix + _msg + postfix; _dev_msg = prefix + _dev_msg + postfix; _exception_msg = prefix + _exception_msg + postfix; } public H2OError(Throwable e, String error_url) { this(System.currentTimeMillis(), error_url, e.getMessage(), e.getMessage(), HttpResponseStatus.INTERNAL_SERVER_ERROR.getCode(), new IcedHashMapGeneric.IcedHashMapStringObject(), e); } static public String httpStatusHeader(int status_code) { switch (status_code) { case 200: return "200 OK"; case 201: return "201 Created"; case 400: return "400 Bad Request"; case 401: return "401 Unauthorized"; case 403: return "403 Forbidden"; case 404: return "404 Not Found"; case 409: return "409 Conflict"; case 410: return "410 Gone"; case 412: return "412 Precondition Failed"; case 500: return "500 Internal Server Error"; case 501: return "501 Not Implemented"; case 503: return "503 Service Unavailable"; default: return status_code + " Unimplemented http status code"; } } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(this._dev_msg != null ? this._dev_msg : this._msg); sb.append("; "); sb.append("Stacktrace: ").append(Arrays.toString(this._stacktrace)); if (!this._values.isEmpty()) { sb.append("; Values: "); sb.append(this._values.toJsonString()); } return sb.toString(); } }