1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 * <exception
50 * key="GlobalExceptionHandler.default"
51 * type="java.lang.Exception"
52 * path="/ErrorPage.jsp">
53 * <set-property key="INCLUDE_PATH" value="/error.jsp" />
54 * </exception>
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 <exception>
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 * <exception
78 * key="GlobalExceptionHandler.default"
79 * type="java.lang.Exception"
80 * path="/ErrorPage.jsp">
81 * <set-property key="SILENT_IF_COMMITTED" value="true" />
82 * </exception>
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
132
133 if (ae.getPath() != null) {
134 forward = new ActionForward(ae.getPath());
135 } else {
136 forward = mapping.getInputForward();
137 }
138
139
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
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 }