View Javadoc

1   /*
2    * $Id: ExceptionHandler.java 471754 2006-11-06 14:55:09Z husted $
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  package org.apache.struts.action;
22  
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  import org.apache.struts.Globals;
26  import org.apache.struts.config.ExceptionConfig;
27  import org.apache.struts.util.MessageResources;
28  import org.apache.struts.util.ModuleException;
29  
30  import javax.servlet.RequestDispatcher;
31  import javax.servlet.ServletException;
32  import javax.servlet.http.HttpServletRequest;
33  import javax.servlet.http.HttpServletResponse;
34  
35  import java.io.IOException;
36  
37  /**
38   * <p>An <strong>ExceptionHandler</strong> is configured in the Struts
39   * configuration file to handle a specific type of exception thrown by an
40   * <code>Action.execute</code> method.</p>
41   *
42   * @since Struts 1.1
43   */
44  public class ExceptionHandler {
45      /**
46       * <p>The name of a configuration property which can be set to specify an
47       * alternative path which should be used when the HttpServletResponse has
48       * already been committed.</p> <p>To use this, in your
49       * <code>struts-config.xml</code> specify the exception handler like
50       * this:
51       * <pre>
52       *   &lt;exception
53       *       key="GlobalExceptionHandler.default"
54       *       type="java.lang.Exception"
55       *       path="/ErrorPage.jsp"&gt;
56       *       &lt;set-property key="INCLUDE_PATH" value="/error.jsp" /&gt;
57       *   &lt;/exception&gt;
58       *  </pre>
59       * </p> <p>You would want to use this when your normal ExceptionHandler
60       * path is a Tiles definition or otherwise unsuitable for use in an
61       * <code>include</code> context.  If you do not use this, and you do not
62       * specify "SILENT_IF_COMMITTED" then the ExceptionHandler will attempt to
63       * forward to the same path which would be used in normal circumstances,
64       * specified using the "path" attribute in the &lt;exception&gt;
65       * element.</p>
66       *
67       * @since Struts 1.3
68       */
69      public static final String INCLUDE_PATH = "INCLUDE_PATH";
70  
71      /**
72       * <p>The name of a configuration property which indicates that Struts
73       * should do nothing if the response has already been committed.  This
74       * suppresses the default behavior, which is to use an "include" rather
75       * than a "forward" in this case in hopes of providing some meaningful
76       * information to the browser.</p> <p>To use this, in your
77       * <code>struts-config.xml</code> specify the exception handler like
78       * this:
79       * <pre>
80       *   &lt;exception
81       *       key="GlobalExceptionHandler.default"
82       *       type="java.lang.Exception"
83       *       path="/ErrorPage.jsp"&gt;
84       *       &lt;set-property key="SILENT_IF_COMMITTED" value="true" /&gt;
85       *   &lt;/exception&gt;
86       *  </pre>
87       * To be effective, this value must be defined to the literal String
88       * "true". If it is not defined or defined to any other value, the default
89       * behavior will be used. </p> <p>You only need to use this if you do not
90       * want error information displayed in the browser when Struts intercepts
91       * an exception after the response has been committed.</p>
92       *
93       * @since Struts 1.3
94       */
95      public static final String SILENT_IF_COMMITTED = "SILENT_IF_COMMITTED";
96  
97      /**
98       * <p>Commons logging instance.</p>
99       */
100     private static final Log LOG = LogFactory.getLog(ExceptionHandler.class);
101 
102     /**
103      * <p>The message resources for this package.</p>
104      */
105     private static MessageResources messages =
106         MessageResources.getMessageResources(
107             "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.getScope());
156 
157         if (!response.isCommitted()) {
158             return forward;
159         }
160 
161         LOG.debug("Response is already committed, so forwarding will not work."
162             + " Attempt alternate handling.");
163 
164         if (!silent(ae)) {
165             handleCommittedResponse(ex, ae, mapping, formInstance, request,
166                 response, forward);
167         } else {
168             LOG.warn("ExceptionHandler configured with " + SILENT_IF_COMMITTED
169                 + " and response is committed.", ex);
170         }
171 
172         return null;
173     }
174 
175     /**
176      * <p>Attempt to give good information when the response has already been
177      * committed when the exception was thrown. This happens often when Tiles
178      * is used. Base implementation will see if the INCLUDE_PATH property has
179      * been set, or if not, it will attempt to use the same path to which
180      * control would have been forwarded.</p>
181      *
182      * @param ex            The exception to handle
183      * @param config        The ExceptionConfig we are processing
184      * @param mapping       The ActionMapping we are processing
185      * @param formInstance  The ActionForm we are processing
186      * @param request       The servlet request we are processing
187      * @param response      The servlet response we are creating
188      * @param actionForward The ActionForward we are processing
189      * @since Struts 1.3
190      */
191     protected void handleCommittedResponse(Exception ex,
192         ExceptionConfig config, ActionMapping mapping, ActionForm formInstance,
193         HttpServletRequest request, HttpServletResponse response,
194         ActionForward actionForward) {
195         String includePath = determineIncludePath(config, actionForward);
196 
197         if (includePath != null) {
198             if (includePath.startsWith("/")) {
199                 LOG.debug("response committed, "
200                     + "but attempt to include results "
201                     + "of actionForward path");
202 
203                 RequestDispatcher requestDispatcher =
204                     request.getRequestDispatcher(includePath);
205 
206                 try {
207                     requestDispatcher.include(request, response);
208 
209                     return;
210                 } catch (IOException e) {
211                     LOG.error("IOException when trying to include "
212                         + "the error page path " + includePath, e);
213                 } catch (ServletException e) {
214                     LOG.error("ServletException when trying to include "
215                         + "the error page path " + includePath, e);
216                 }
217             } else {
218                 LOG.warn("Suspicious includePath doesn't seem likely to work, "
219                     + "so skipping it: " + includePath
220                     + "; expected path to start with '/'");
221             }
222         }
223 
224         LOG.debug("Include not available or failed; "
225             + "try writing to the response directly.");
226 
227         try {
228             response.getWriter().println("Unexpected error: " + ex);
229             response.getWriter().println("<!-- ");
230             ex.printStackTrace(response.getWriter());
231             response.getWriter().println("-->");
232         } catch (IOException e) {
233             LOG.error("Error giving minimal information about exception", e);
234             LOG.error("Original exception: ", ex);
235         }
236     }
237 
238     /**
239      * <p>Return a path to which an include should be attempted in the case
240      * when the response was committed before the <code>ExceptionHandler</code>
241      * was invoked.  </p> <p>If the <code>ExceptionConfig</code> has the
242      * property <code>INCLUDE_PATH</code> defined, then the value of that
243      * property will be returned. Otherwise, the ActionForward path is
244      * returned. </p>
245      *
246      * @param config        Configuration element
247      * @param actionForward Forward to use on error
248      * @return Path of resource to include
249      * @since Struts 1.3
250      */
251     protected String determineIncludePath(ExceptionConfig config,
252         ActionForward actionForward) {
253         String includePath = config.getProperty("INCLUDE_PATH");
254 
255         if (includePath == null) {
256             includePath = actionForward.getPath();
257         }
258 
259         return includePath;
260     }
261 
262     /**
263      * <p>Logs the <code>Exception</code> using commons-logging.</p>
264      *
265      * @param e The Exception to LOG.
266      * @since Struts 1.2
267      */
268     protected void logException(Exception e) {
269         LOG.debug(messages.getMessage("exception.LOG"), e);
270     }
271 
272     /**
273      * <p>Default implementation for handling an <code>ActionMessage</code>
274      * generated from an <code>Exception</code> during <code>Action</code>
275      * delegation. The default implementation is to set an attribute of the
276      * request or session, as defined by the scope provided (the scope from
277      * the exception mapping). An <code>ActionMessages</code> instance is
278      * created, the error is added to the collection and the collection is set
279      * under the <code>Globals.ERROR_KEY</code>.</p>
280      *
281      * @param request  The request we are handling
282      * @param property The property name to use for this error
283      * @param error    The error generated from the exception mapping
284      * @param forward  The forward generated from the input path (from the
285      *                 form or exception mapping)
286      * @param scope    The scope of the exception mapping.
287      * @since Struts 1.2
288      */
289     protected void storeException(HttpServletRequest request, String property,
290         ActionMessage error, ActionForward forward, String scope) {
291         ActionMessages errors = new ActionMessages();
292 
293         errors.add(property, error);
294 
295         if ("request".equals(scope)) {
296             request.setAttribute(Globals.ERROR_KEY, errors);
297         } else {
298             request.getSession().setAttribute(Globals.ERROR_KEY, errors);
299         }
300     }
301 
302     /**
303      * <p>Indicate whether this Handler has been configured to be silent.  In
304      * the base implementation, this is done by specifying the value
305      * <code>"true"</code> for the property "SILENT_IF_COMMITTED" in the
306      * ExceptionConfig.</p>
307      *
308      * @param config The ExceptionConfiguration we are handling
309      * @return True if Handler is silent
310      * @since Struts 1.3
311      */
312     private boolean silent(ExceptionConfig config) {
313         return "true".equals(config.getProperty(SILENT_IF_COMMITTED));
314     }
315 }