001: /*
002: * $Id: ExceptionHandler.java 471754 2006-11-06 14:55:09Z husted $
003: *
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021: package org.apache.struts.action;
022:
023: import org.apache.commons.logging.Log;
024: import org.apache.commons.logging.LogFactory;
025: import org.apache.struts.Globals;
026: import org.apache.struts.config.ExceptionConfig;
027: import org.apache.struts.util.MessageResources;
028: import org.apache.struts.util.ModuleException;
029:
030: import javax.servlet.RequestDispatcher;
031: import javax.servlet.ServletException;
032: import javax.servlet.http.HttpServletRequest;
033: import javax.servlet.http.HttpServletResponse;
034:
035: import java.io.IOException;
036:
037: /**
038: * <p>An <strong>ExceptionHandler</strong> is configured in the Struts
039: * configuration file to handle a specific type of exception thrown by an
040: * <code>Action.execute</code> method.</p>
041: *
042: * @since Struts 1.1
043: */
044: public class ExceptionHandler {
045: /**
046: * <p>The name of a configuration property which can be set to specify an
047: * alternative path which should be used when the HttpServletResponse has
048: * already been committed.</p> <p>To use this, in your
049: * <code>struts-config.xml</code> specify the exception handler like
050: * this:
051: * <pre>
052: * <exception
053: * key="GlobalExceptionHandler.default"
054: * type="java.lang.Exception"
055: * path="/ErrorPage.jsp">
056: * <set-property key="INCLUDE_PATH" value="/error.jsp" />
057: * </exception>
058: * </pre>
059: * </p> <p>You would want to use this when your normal ExceptionHandler
060: * path is a Tiles definition or otherwise unsuitable for use in an
061: * <code>include</code> context. If you do not use this, and you do not
062: * specify "SILENT_IF_COMMITTED" then the ExceptionHandler will attempt to
063: * forward to the same path which would be used in normal circumstances,
064: * specified using the "path" attribute in the <exception>
065: * element.</p>
066: *
067: * @since Struts 1.3
068: */
069: public static final String INCLUDE_PATH = "INCLUDE_PATH";
070:
071: /**
072: * <p>The name of a configuration property which indicates that Struts
073: * should do nothing if the response has already been committed. This
074: * suppresses the default behavior, which is to use an "include" rather
075: * than a "forward" in this case in hopes of providing some meaningful
076: * information to the browser.</p> <p>To use this, in your
077: * <code>struts-config.xml</code> specify the exception handler like
078: * this:
079: * <pre>
080: * <exception
081: * key="GlobalExceptionHandler.default"
082: * type="java.lang.Exception"
083: * path="/ErrorPage.jsp">
084: * <set-property key="SILENT_IF_COMMITTED" value="true" />
085: * </exception>
086: * </pre>
087: * To be effective, this value must be defined to the literal String
088: * "true". If it is not defined or defined to any other value, the default
089: * behavior will be used. </p> <p>You only need to use this if you do not
090: * want error information displayed in the browser when Struts intercepts
091: * an exception after the response has been committed.</p>
092: *
093: * @since Struts 1.3
094: */
095: public static final String SILENT_IF_COMMITTED = "SILENT_IF_COMMITTED";
096:
097: /**
098: * <p>Commons logging instance.</p>
099: */
100: private static final Log LOG = LogFactory
101: .getLog(ExceptionHandler.class);
102:
103: /**
104: * <p>The message resources for this package.</p>
105: */
106: private static MessageResources messages = MessageResources
107: .getMessageResources("org.apache.struts.action.LocalStrings");
108:
109: /**
110: * <p> Handle the Exception. Return the ActionForward instance (if any)
111: * returned by the called ExceptionHandler. </p>
112: *
113: * @param ex The exception to handle
114: * @param ae The ExceptionConfig corresponding to the exception
115: * @param mapping The ActionMapping we are processing
116: * @param formInstance The ActionForm we are processing
117: * @param request The servlet request we are processing
118: * @param response The servlet response we are creating
119: * @return The <code>ActionForward</code> instance (if any) returned by
120: * the called <code>ExceptionHandler</code>.
121: * @throws ServletException if a servlet exception occurs
122: * @since Struts 1.1
123: */
124: public ActionForward execute(Exception ex, ExceptionConfig ae,
125: ActionMapping mapping, ActionForm formInstance,
126: HttpServletRequest request, HttpServletResponse response)
127: throws ServletException {
128: LOG.debug("ExceptionHandler executing for exception " + ex);
129:
130: ActionForward forward;
131: ActionMessage error;
132: String property;
133:
134: // Build the forward from the exception mapping if it exists
135: // or from the form input
136: if (ae.getPath() != null) {
137: forward = new ActionForward(ae.getPath());
138: } else {
139: forward = mapping.getInputForward();
140: }
141:
142: // Figure out the error
143: if (ex instanceof ModuleException) {
144: error = ((ModuleException) ex).getActionMessage();
145: property = ((ModuleException) ex).getProperty();
146: } else {
147: error = new ActionMessage(ae.getKey(), ex.getMessage());
148: property = error.getKey();
149: }
150:
151: this .logException(ex);
152:
153: // Store the exception
154: request.setAttribute(Globals.EXCEPTION_KEY, ex);
155: this .storeException(request, property, error, forward, ae
156: .getScope());
157:
158: if (!response.isCommitted()) {
159: return forward;
160: }
161:
162: LOG
163: .debug("Response is already committed, so forwarding will not work."
164: + " Attempt alternate handling.");
165:
166: if (!silent(ae)) {
167: handleCommittedResponse(ex, ae, mapping, formInstance,
168: request, response, forward);
169: } else {
170: LOG.warn("ExceptionHandler configured with "
171: + SILENT_IF_COMMITTED
172: + " and response is committed.", ex);
173: }
174:
175: return null;
176: }
177:
178: /**
179: * <p>Attempt to give good information when the response has already been
180: * committed when the exception was thrown. This happens often when Tiles
181: * is used. Base implementation will see if the INCLUDE_PATH property has
182: * been set, or if not, it will attempt to use the same path to which
183: * control would have been forwarded.</p>
184: *
185: * @param ex The exception to handle
186: * @param config The ExceptionConfig we are processing
187: * @param mapping The ActionMapping we are processing
188: * @param formInstance The ActionForm we are processing
189: * @param request The servlet request we are processing
190: * @param response The servlet response we are creating
191: * @param actionForward The ActionForward we are processing
192: * @since Struts 1.3
193: */
194: protected void handleCommittedResponse(Exception ex,
195: ExceptionConfig config, ActionMapping mapping,
196: ActionForm formInstance, HttpServletRequest request,
197: HttpServletResponse response, ActionForward actionForward) {
198: String includePath = determineIncludePath(config, actionForward);
199:
200: if (includePath != null) {
201: if (includePath.startsWith("/")) {
202: LOG.debug("response committed, "
203: + "but attempt to include results "
204: + "of actionForward path");
205:
206: RequestDispatcher requestDispatcher = request
207: .getRequestDispatcher(includePath);
208:
209: try {
210: requestDispatcher.include(request, response);
211:
212: return;
213: } catch (IOException e) {
214: LOG.error("IOException when trying to include "
215: + "the error page path " + includePath, e);
216: } catch (ServletException e) {
217: LOG.error(
218: "ServletException when trying to include "
219: + "the error page path "
220: + includePath, e);
221: }
222: } else {
223: LOG
224: .warn("Suspicious includePath doesn't seem likely to work, "
225: + "so skipping it: "
226: + includePath
227: + "; expected path to start with '/'");
228: }
229: }
230:
231: LOG.debug("Include not available or failed; "
232: + "try writing to the response directly.");
233:
234: try {
235: response.getWriter().println("Unexpected error: " + ex);
236: response.getWriter().println("<!-- ");
237: ex.printStackTrace(response.getWriter());
238: response.getWriter().println("-->");
239: } catch (IOException e) {
240: LOG.error(
241: "Error giving minimal information about exception",
242: e);
243: LOG.error("Original exception: ", ex);
244: }
245: }
246:
247: /**
248: * <p>Return a path to which an include should be attempted in the case
249: * when the response was committed before the <code>ExceptionHandler</code>
250: * was invoked. </p> <p>If the <code>ExceptionConfig</code> has the
251: * property <code>INCLUDE_PATH</code> defined, then the value of that
252: * property will be returned. Otherwise, the ActionForward path is
253: * returned. </p>
254: *
255: * @param config Configuration element
256: * @param actionForward Forward to use on error
257: * @return Path of resource to include
258: * @since Struts 1.3
259: */
260: protected String determineIncludePath(ExceptionConfig config,
261: ActionForward actionForward) {
262: String includePath = config.getProperty("INCLUDE_PATH");
263:
264: if (includePath == null) {
265: includePath = actionForward.getPath();
266: }
267:
268: return includePath;
269: }
270:
271: /**
272: * <p>Logs the <code>Exception</code> using commons-logging.</p>
273: *
274: * @param e The Exception to LOG.
275: * @since Struts 1.2
276: */
277: protected void logException(Exception e) {
278: LOG.debug(messages.getMessage("exception.LOG"), e);
279: }
280:
281: /**
282: * <p>Default implementation for handling an <code>ActionMessage</code>
283: * generated from an <code>Exception</code> during <code>Action</code>
284: * delegation. The default implementation is to set an attribute of the
285: * request or session, as defined by the scope provided (the scope from
286: * the exception mapping). An <code>ActionMessages</code> instance is
287: * created, the error is added to the collection and the collection is set
288: * under the <code>Globals.ERROR_KEY</code>.</p>
289: *
290: * @param request The request we are handling
291: * @param property The property name to use for this error
292: * @param error The error generated from the exception mapping
293: * @param forward The forward generated from the input path (from the
294: * form or exception mapping)
295: * @param scope The scope of the exception mapping.
296: * @since Struts 1.2
297: */
298: protected void storeException(HttpServletRequest request,
299: String property, ActionMessage error,
300: ActionForward forward, String scope) {
301: ActionMessages errors = new ActionMessages();
302:
303: errors.add(property, error);
304:
305: if ("request".equals(scope)) {
306: request.setAttribute(Globals.ERROR_KEY, errors);
307: } else {
308: request.getSession()
309: .setAttribute(Globals.ERROR_KEY, errors);
310: }
311: }
312:
313: /**
314: * <p>Indicate whether this Handler has been configured to be silent. In
315: * the base implementation, this is done by specifying the value
316: * <code>"true"</code> for the property "SILENT_IF_COMMITTED" in the
317: * ExceptionConfig.</p>
318: *
319: * @param config The ExceptionConfiguration we are handling
320: * @return True if Handler is silent
321: * @since Struts 1.3
322: */
323: private boolean silent(ExceptionConfig config) {
324: return "true".equals(config.getProperty(SILENT_IF_COMMITTED));
325: }
326: }
|