1 /* 2 * $Id$ 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 java.io.IOException; 24 25 import jakarta.servlet.RequestDispatcher; 26 import jakarta.servlet.ServletException; 27 import jakarta.servlet.http.HttpServletRequest; 28 import jakarta.servlet.http.HttpServletResponse; 29 30 import org.apache.struts.Globals; 31 import org.apache.struts.config.ExceptionConfig; 32 import org.apache.struts.util.MessageResources; 33 import org.apache.struts.util.ModuleException; 34 import org.slf4j.Logger; 35 import org.slf4j.LoggerFactory; 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 * <exception 53 * key="GlobalExceptionHandler.default" 54 * type="java.lang.Exception" 55 * path="/ErrorPage.jsp"> 56 * <set-property key="INCLUDE_PATH" value="/error.jsp" /> 57 * </exception> 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 <exception> 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 * <exception 81 * key="GlobalExceptionHandler.default" 82 * type="java.lang.Exception" 83 * path="/ErrorPage.jsp"> 84 * <set-property key="SILENT_IF_COMMITTED" value="true" /> 85 * </exception> 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 * The {@code Log} instance for this class. 99 */ 100 private final Logger log = 101 LoggerFactory.getLogger(ExceptionHandler.class); 102 103 /** 104 * <p>The message resources for this package.</p> 105 */ 106 private static MessageResources messages = 107 MessageResources.getMessageResources( 108 "org.apache.struts.action.LocalStrings"); 109 110 /** 111 * <p> Handle the Exception. Return the ActionForward instance (if any) 112 * returned by the called ExceptionHandler. </p> 113 * 114 * @param ex The exception to handle 115 * @param ae The ExceptionConfig corresponding to the exception 116 * @param mapping The ActionMapping we are processing 117 * @param formInstance The ActionForm we are processing 118 * @param request The servlet request we are processing 119 * @param response The servlet response we are creating 120 * @return The <code>ActionForward</code> instance (if any) returned by 121 * the called <code>ExceptionHandler</code>. 122 * @throws ServletException if a servlet exception occurs 123 * @since Struts 1.1 124 */ 125 public ActionForward execute(Exception ex, ExceptionConfig ae, 126 ActionMapping mapping, ActionForm formInstance, 127 HttpServletRequest request, HttpServletResponse response) 128 throws ServletException { 129 log.debug("ExceptionHandler executing for exception ", ex); 130 131 ActionForward forward; 132 ActionMessage error; 133 String property; 134 135 // Build the forward from the exception mapping if it exists 136 // or from the form input 137 if (ae.getPath() != null) { 138 forward = new ActionForward(ae.getPath()); 139 } else { 140 forward = mapping.getInputForward(); 141 } 142 143 // Figure out the error 144 if (ex instanceof ModuleException) { 145 error = ((ModuleException) ex).getActionMessage(); 146 property = ((ModuleException) ex).getProperty(); 147 } else { 148 // STR-2924 149 if (ae.getKey() != null) { 150 error = new ActionMessage(ae.getKey(), ex.getMessage()); 151 property = error.getKey(); 152 } else { 153 error = null; 154 property = null; 155 } 156 } 157 158 this.logException(ex); 159 160 // Store the exception 161 request.setAttribute(Globals.EXCEPTION_KEY, ex); 162 this.storeException(request, property, error, forward, ae.getScope()); 163 164 if (!response.isCommitted()) { 165 return forward; 166 } 167 168 log.debug("Response is already committed, so forwarding will not work." 169 + " Attempt alternate handling."); 170 171 if (!silent(ae)) { 172 handleCommittedResponse(ex, ae, mapping, formInstance, request, 173 response, forward); 174 } else { 175 log.warn("ExceptionHandler configured with {}" 176 + " and response is committed.", SILENT_IF_COMMITTED, ex); 177 } 178 179 return null; 180 } 181 182 /** 183 * <p>Attempt to give good information when the response has already been 184 * committed when the exception was thrown. This happens often when Tiles 185 * is used. Base implementation will see if the INCLUDE_PATH property has 186 * been set, or if not, it will attempt to use the same path to which 187 * control would have been forwarded.</p> 188 * 189 * @param ex The exception to handle 190 * @param config The ExceptionConfig we are processing 191 * @param mapping The ActionMapping we are processing 192 * @param formInstance The ActionForm we are processing 193 * @param request The servlet request we are processing 194 * @param response The servlet response we are creating 195 * @param actionForward The ActionForward we are processing 196 * @since Struts 1.3 197 */ 198 protected void handleCommittedResponse(Exception ex, 199 ExceptionConfig config, ActionMapping mapping, ActionForm formInstance, 200 HttpServletRequest request, HttpServletResponse response, 201 ActionForward actionForward) { 202 String includePath = determineIncludePath(config, actionForward); 203 204 if (includePath != null) { 205 if (includePath.startsWith("/")) { 206 log.debug("response committed, " 207 + "but attempt to include results " 208 + "of actionForward path"); 209 210 RequestDispatcher requestDispatcher = 211 request.getRequestDispatcher(includePath); 212 213 try { 214 requestDispatcher.include(request, response); 215 216 return; 217 } catch (IOException e) { 218 log.error("IOException when trying to include " 219 + "the error page path {}", includePath, e); 220 } catch (ServletException e) { 221 log.error("ServletException when trying to include " 222 + "the error page path {}", includePath, e); 223 } 224 } else { 225 log.warn("Suspicious includePath doesn't seem likely to work, " 226 + "so skipping it: {}" 227 + "; expected path to start with '/'", includePath); 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("Error giving minimal information about exception", e); 241 log.error("Original exception: ", ex); 242 } 243 } 244 245 /** 246 * <p>Return a path to which an include should be attempted in the case 247 * when the response was committed before the <code>ExceptionHandler</code> 248 * was invoked. </p> <p>If the <code>ExceptionConfig</code> has the 249 * property <code>INCLUDE_PATH</code> defined, then the value of that 250 * property will be returned. Otherwise, the ActionForward path is 251 * returned. </p> 252 * 253 * @param config Configuration element 254 * @param actionForward Forward to use on error 255 * @return Path of resource to include 256 * @since Struts 1.3 257 */ 258 protected String determineIncludePath(ExceptionConfig config, 259 ActionForward actionForward) { 260 String includePath = config.getProperty("INCLUDE_PATH"); 261 262 if (includePath == null) { 263 includePath = actionForward.getPath(); 264 } 265 266 return includePath; 267 } 268 269 /** 270 * <p>Logs the <code>Exception</code> using commons-logging.</p> 271 * 272 * @param e The Exception to log. 273 * @since Struts 1.2 274 */ 275 protected void logException(Exception e) { 276 log.atDebug() 277 .setMessage(() -> messages.getMessage("exception.LOG")) 278 .setCause(e).log(); 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), if <code>error</code> is not <code>null</code>. 287 * Otherwise, an <code>ActionMessages</code> instance is created, the error 288 * is added to the collection and the collection is set 289 * under the <code>Globals.ERROR_KEY</code>.</p> 290 * 291 * @param request The request we are handling 292 * @param property The property name to use for this error 293 * @param error The error generated from the exception mapping 294 * @param forward The forward generated from the input path (from the 295 * form or exception mapping) 296 * @param scope The scope of the exception mapping. 297 * @since Struts 1.2 298 */ 299 protected void storeException(HttpServletRequest request, String property, 300 ActionMessage error, ActionForward forward, String scope) { 301 302 if (error != null) { 303 ActionMessages errors = new ActionMessages(); 304 errors.add(property, error); 305 306 if ("request".equals(scope)) { 307 request.setAttribute(Globals.ERROR_KEY, errors); 308 } else { 309 request.getSession().setAttribute(Globals.ERROR_KEY, errors); 310 } 311 } 312 } 313 314 /** 315 * <p>Indicate whether this Handler has been configured to be silent. In 316 * the base implementation, this is done by specifying the value 317 * <code>"true"</code> for the property "SILENT_IF_COMMITTED" in the 318 * ExceptionConfig.</p> 319 * 320 * @param config The ExceptionConfiguration we are handling 321 * @return True if Handler is silent 322 * @since Struts 1.3 323 */ 324 private boolean silent(ExceptionConfig config) { 325 return "true".equals(config.getProperty(SILENT_IF_COMMITTED)); 326 } 327 }