View Javadoc

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