diff --git a/src/helma/framework/ResponseBean.java b/src/helma/framework/ResponseBean.java index be7de967..c8c244a7 100644 --- a/src/helma/framework/ResponseBean.java +++ b/src/helma/framework/ResponseBean.java @@ -17,9 +17,12 @@ package helma.framework; import helma.objectmodel.db.Transactor; +import helma.scripting.ScriptingException; import javax.servlet.http.HttpServletResponse; import java.io.Serializable; +import java.io.StringWriter; +import java.io.PrintWriter; import java.util.Date; import java.util.Map; @@ -373,9 +376,43 @@ public class ResponseBean implements Serializable { * @return the error message */ public String getError() { + return res.getErrorMessage(); + } + + /** + * Get the uncaught exception for the response, if any + * @return the uncaught exception + */ + public Throwable getException() { return res.getError(); } + /** + * Return the Javascript stack trace of an uncought exception. + * @return the script stack trace of any uncaught exception or null. + */ + public String getScriptStack() { + Throwable t = res.getError(); + if (t instanceof ScriptingException) + return ((ScriptingException) t).getScriptStackTrace(); + return null; + } + + /** + * Get the Java stack trace of an uncaught exception. + * @return the java stack trace of an uncaught exception or null. + */ + public String getJavaStack() { + Throwable t = res.getError(); + if (t == null) + return null; + else if (t instanceof ScriptingException) + return ((ScriptingException) t).getJavaStackTrace(); + StringWriter w = new StringWriter(); + t.printStackTrace(new PrintWriter(w)); + return w.toString(); + } + /** * Get the current message for the response, if set * diff --git a/src/helma/framework/ResponseTrans.java b/src/helma/framework/ResponseTrans.java index bf7e108b..8e8a322a 100644 --- a/src/helma/framework/ResponseTrans.java +++ b/src/helma/framework/ResponseTrans.java @@ -19,6 +19,7 @@ package helma.framework; import helma.framework.core.Skin; import helma.framework.core.Application; import helma.util.*; +import helma.scripting.ScriptingException; import javax.servlet.http.HttpServletResponse; import java.io.*; @@ -95,8 +96,8 @@ public final class ResponseTrans extends Writer implements Serializable { // field for generic message to be displayed private transient String message; - // field for error message - private transient String error; + // field for error + private transient Throwable error; // the res.data map of form and cookie data private transient Map values = new SystemMap(); @@ -190,7 +191,8 @@ public final class ResponseTrans extends Writer implements Serializable { buffers = null; response = null; cacheable = true; - redir = forward = message = error = null; + redir = forward = message = null; + error = null; etag = realm = charset = null; contentType = "text/html"; values.clear(); @@ -546,21 +548,59 @@ public final class ResponseTrans extends Writer implements Serializable { * Write a vanilla error report. Callers should make sure the ResponeTrans is * new or has been reset. * - * @param appName the application name - * @param message the error message + * @param throwable the error */ - public void reportError(String appName, String message) { + public void reportError(Throwable throwable) { + if (throwable == null) { + // just to be safe + reportError("Unspecified error"); + return; + } if (reqtrans.isXmlRpc()) { - writeXmlRpcError(new RuntimeException(message)); + writeXmlRpcError(new RuntimeException(throwable)); } else { status = 500; if (!"true".equalsIgnoreCase(app.getProperty("suppressErrorPage"))) { - write("
"); + encode(getErrorMessage(throwable)); + writeln("
"); + if (app.debug()) { + if (throwable instanceof ScriptingException) { + ScriptingException scriptx = (ScriptingException) throwable; + writeln("" + scriptx.getScriptStackTrace() + ""); + writeln("
" + scriptx.getJavaStackTrace() + ""); + } else { + writeln("
"); + throwable.printStackTrace(new PrintWriter(this)); + writeln(""); + } + } + writeln(""); + } + } + } + + /** + * Write a vanilla error report. Callers should make sure the ResponeTrans is + * new or has been reset. + * @param errorMessage the error message + */ + public void reportError(String errorMessage) { + if (reqtrans.isXmlRpc()) { + writeXmlRpcError(new RuntimeException(errorMessage)); + } else { + status = 500; + if (!"true".equalsIgnoreCase(app.getProperty("suppressErrorPage"))) { + write("
"); + encode(errorMessage); + writeln("
"); } } } @@ -1010,7 +1050,7 @@ public final class ResponseTrans extends Writer implements Serializable { * Get the error message to display to the user, if any. * @return the error message */ - public String getError() { + public Throwable getError() { return error; } @@ -1018,10 +1058,25 @@ public final class ResponseTrans extends Writer implements Serializable { * Set a message to display to the user. * @param error the error message */ - public void setError(String error) { + public void setError(Throwable error) { this.error = error; } + public String getErrorMessage() { + if (error == null) + return null; + return getErrorMessage(error); + } + + private static String getErrorMessage(Throwable t) { + String msg = t.getMessage(); + if (msg == null || msg.length() == 0) + msg = t.toString(); + if (msg == null || msg.length() == 0) + return "Unspecified Error: " + t.getClass().getName(); + return msg; + } + /** * Get debug messages to append to the response, if any. * @return the response's debug buffer diff --git a/src/helma/framework/core/Application.java b/src/helma/framework/core/Application.java index 098117ac..18b995c9 100644 --- a/src/helma/framework/core/Application.java +++ b/src/helma/framework/core/Application.java @@ -701,7 +701,7 @@ public final class Application implements Runnable { } catch (Exception x) { errorCount += 1; res = new ResponseTrans(this, req); - res.reportError(name, x.getMessage()); + res.reportError(x); } finally { if (primaryRequest) { activeRequests.remove(req); diff --git a/src/helma/framework/core/RequestEvaluator.java b/src/helma/framework/core/RequestEvaluator.java index a1956dee..d9eeb391 100644 --- a/src/helma/framework/core/RequestEvaluator.java +++ b/src/helma/framework/core/RequestEvaluator.java @@ -149,7 +149,7 @@ public final class RequestEvaluator implements Runnable { int tries = 0; boolean done = false; - String error = null; + Throwable error = null; String functionName = function instanceof String ? (String) function : null; @@ -543,7 +543,7 @@ public final class RequestEvaluator implements Runnable { Thread.sleep((long) (base + (Math.random() * base * 2))); } catch (InterruptedException interrupt) { // we got interrrupted, create minimal error message - res.reportError(app.getName(), error); + res.reportError(interrupt); done = true; // and release resources and thread rtx = null; @@ -555,11 +555,8 @@ public final class RequestEvaluator implements Runnable { } abortTransaction(); - if (error == null) - error = "Application too busy, please try again later"; - // error in error action. use traditional minimal error message - res.reportError(app.getName(), error); + res.reportError("Application too busy, please try again later"); done = true; } } catch (Throwable x) { @@ -587,15 +584,7 @@ public final class RequestEvaluator implements Runnable { // set done to false so that the error will be processed done = false; - error = x.getMessage(); - - if ((error == null) || (error.length() == 0)) { - error = x.toString(); - } - - if (error == null) { - error = "Unspecified error"; - } + error = x; app.logError(txname + ": " + error, x); @@ -610,7 +599,7 @@ public final class RequestEvaluator implements Runnable { } } else { // error in error action. use traditional minimal error message - res.reportError(app.getName(), error); + res.reportError(error); done = true; } } finally { @@ -755,7 +744,7 @@ public final class RequestEvaluator implements Runnable { if (reqtype != NONE && stopTransactor()) { res.reset(); - res.reportError(app.getName(), "Request timed out"); + res.reportError("Request timed out"); } session.commit(this); diff --git a/src/helma/framework/core/Skin.java b/src/helma/framework/core/Skin.java index 7fa46f5d..61b3712e 100644 --- a/src/helma/framework/core/Skin.java +++ b/src/helma/framework/core/Skin.java @@ -688,7 +688,7 @@ public final class Skin { if ("message".equals(propName)) value = cx.reval.getResponse().getMessage(); else if ("error".equals(propName)) - value = cx.reval.getResponse().getError(); + value = cx.reval.getResponse().getErrorMessage(); if (value != null) return filter(value, cx); } diff --git a/src/helma/scripting/ScriptingException.java b/src/helma/scripting/ScriptingException.java index c7f3a3d6..0d682cb2 100644 --- a/src/helma/scripting/ScriptingException.java +++ b/src/helma/scripting/ScriptingException.java @@ -18,10 +18,7 @@ package helma.scripting; import org.mozilla.javascript.RhinoException; -import java.io.PrintStream; -import java.io.PrintWriter; -import java.io.FilenameFilter; -import java.io.File; +import java.io.*; /** * The base class for wrapped exceptions thrown by invocation of the scripting engine. @@ -60,9 +57,26 @@ public class ScriptingException extends Exception { } } + /** + * Get the script stack, or null if none is available + * @return the script stack trace + */ + public String getScriptStackTrace() { + return scriptStack; + } + + /** + * Get the java stack trace. + * @return the java stack trace + */ + public String getJavaStackTrace() { + StringWriter w = new StringWriter(); + getCause().printStackTrace(new PrintWriter(w)); + return w.toString(); + } /* - * Adaption from Throwable.printStackTrace() to only print Script file stack elements. + * Adaption from Throwable.printStackTrace() to also print Script file stack elements. */ public void printStackTrace(PrintStream s) { synchronized (s) { @@ -77,7 +91,7 @@ public class ScriptingException extends Exception { /* - * Adaption from Throwable.printStackTrace() to only print Script file stack elements. + * Adaption from Throwable.printStackTrace() to also print Script file stack elements. */ public void printStackTrace(PrintWriter s) { synchronized (s) {