1 /* 2 * $Id: DispatchAction.java 384089 2006-03-08 01:50:52Z niallp $ 3 * 4 * Copyright 2001-2006 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 19 package org.apache.struts.actions; 20 21 import java.lang.reflect.InvocationTargetException; 22 import java.lang.reflect.Method; 23 import java.util.HashMap; 24 25 import javax.servlet.ServletException; 26 import javax.servlet.http.HttpServletRequest; 27 import javax.servlet.http.HttpServletResponse; 28 29 import org.apache.commons.logging.Log; 30 import org.apache.commons.logging.LogFactory; 31 import org.apache.struts.action.Action; 32 import org.apache.struts.action.ActionForm; 33 import org.apache.struts.action.ActionForward; 34 import org.apache.struts.action.ActionMapping; 35 import org.apache.struts.util.MessageResources; 36 37 /** 38 * <p>An abstract <strong>Action</strong> that dispatches to a public 39 * method that is named by the request parameter whose name is specified 40 * by the <code>parameter</code> property of the corresponding 41 * ActionMapping. This Action is useful for developers who prefer to 42 * combine many similar actions into a single Action class, in order to 43 * simplify their application design.</p> 44 * 45 * <p>To configure the use of this action in your 46 * <code>struts-config.xml</code> file, create an entry like this:</p> 47 * 48 * <code> 49 * <action path="/saveSubscription" 50 * type="org.apache.struts.actions.DispatchAction" 51 * name="subscriptionForm" 52 * scope="request" 53 * input="/subscription.jsp" 54 * parameter="method"/> 55 * </code> 56 * 57 * <p>which will use the value of the request parameter named "method" 58 * to pick the appropriate "execute" method, which must have the same 59 * signature (other than method name) of the standard Action.execute 60 * method. For example, you might have the following three methods in the 61 * same action:</p> 62 * <ul> 63 * <li>public ActionForward delete(ActionMapping mapping, ActionForm form, 64 * HttpServletRequest request, HttpServletResponse response) 65 * throws Exception</li> 66 * <li>public ActionForward insert(ActionMapping mapping, ActionForm form, 67 * HttpServletRequest request, HttpServletResponse response) 68 * throws Exception</li> 69 * <li>public ActionForward update(ActionMapping mapping, ActionForm form, 70 * HttpServletRequest request, HttpServletResponse response) 71 * throws Exception</li> 72 * </ul> 73 * <p>and call one of the methods with a URL like this:</p> 74 * <code> 75 * http://localhost:8080/myapp/saveSubscription.do?method=update 76 * </code> 77 * 78 * <p><strong>NOTE</strong> - All of the other mapping characteristics of 79 * this action must be shared by the various handlers. This places some 80 * constraints over what types of handlers may reasonably be packaged into 81 * the same <code>DispatchAction</code> subclass.</p> 82 * 83 * <p><strong>NOTE</strong> - If the value of the request parameter is empty, 84 * a method named <code>unspecified</code> is called. The default action is 85 * to throw an exception. If the request was cancelled (a <code>html:cancel</code> 86 * button was pressed), the custom handler <code>cancelled</code> will be used instead. 87 * You can also override the <code>getMethodName</code> method to override the action's 88 * default handler selection.</p> 89 * 90 * @version $Rev: 384089 $ $Date: 2006-03-08 01:50:52 +0000 (Wed, 08 Mar 2006) $ 91 */ 92 public abstract class DispatchAction extends Action { 93 94 95 // ----------------------------------------------------- Instance Variables 96 97 98 /** 99 * The Class instance of this <code>DispatchAction</code> class. 100 */ 101 protected Class clazz = this.getClass(); 102 103 104 /** 105 * Commons Logging instance. 106 */ 107 protected static Log log = LogFactory.getLog(DispatchAction.class); 108 109 110 /** 111 * The message resources for this package. 112 */ 113 protected static MessageResources messages = 114 MessageResources.getMessageResources 115 ("org.apache.struts.actions.LocalStrings"); 116 117 118 /** 119 * The set of Method objects we have introspected for this class, 120 * keyed by method name. This collection is populated as different 121 * methods are called, so that introspection needs to occur only 122 * once per method name. 123 */ 124 protected HashMap methods = new HashMap(); 125 126 127 /** 128 * The set of argument type classes for the reflected method call. These 129 * are the same for all calls, so calculate them only once. 130 */ 131 protected Class[] types = 132 { 133 ActionMapping.class, 134 ActionForm.class, 135 HttpServletRequest.class, 136 HttpServletResponse.class}; 137 138 139 140 // --------------------------------------------------------- Public Methods 141 142 143 /** 144 * Process the specified HTTP request, and create the corresponding HTTP 145 * response (or forward to another web component that will create it). 146 * Return an <code>ActionForward</code> instance describing where and how 147 * control should be forwarded, or <code>null</code> if the response has 148 * already been completed. 149 * 150 * @param mapping The ActionMapping used to select this instance 151 * @param form The optional ActionForm bean for this request (if any) 152 * @param request The HTTP request we are processing 153 * @param response The HTTP response we are creating 154 * 155 * @exception Exception if an exception occurs 156 */ 157 public ActionForward execute(ActionMapping mapping, 158 ActionForm form, 159 HttpServletRequest request, 160 HttpServletResponse response) 161 throws Exception { 162 if (isCancelled(request)) { 163 ActionForward af = cancelled(mapping, form, request, response); 164 if (af != null) { 165 return af; 166 } 167 } 168 169 // Get the parameter. This could be overridden in subclasses. 170 String parameter = getParameter(mapping, form, request, response); 171 172 // Get the method's name. This could be overridden in subclasses. 173 String name = getMethodName(mapping, form, request, response, parameter); 174 175 176 // Prevent recursive calls 177 if ("execute".equals(name) || "perform".equals(name)){ 178 String message = 179 messages.getMessage("dispatch.recursive", mapping.getPath()); 180 181 log.error(message); 182 throw new ServletException(message); 183 } 184 185 186 // Invoke the named method, and return the result 187 return dispatchMethod(mapping, form, request, response, name); 188 189 } 190 191 192 193 194 /** 195 * Method which is dispatched to when there is no value for specified 196 * request parameter included in the request. Subclasses of 197 * <code>DispatchAction</code> should override this method if they wish 198 * to provide default behavior different than throwing a ServletException. 199 */ 200 protected ActionForward unspecified( 201 ActionMapping mapping, 202 ActionForm form, 203 HttpServletRequest request, 204 HttpServletResponse response) 205 throws Exception { 206 207 String message = 208 messages.getMessage( 209 "dispatch.parameter", 210 mapping.getPath(), 211 mapping.getParameter()); 212 213 log.error(message); 214 215 throw new ServletException(message); 216 } 217 218 /** 219 * Method which is dispatched to when the request is a cancel button submit. 220 * Subclasses of <code>DispatchAction</code> should override this method if 221 * they wish to provide default behavior different than returning null. 222 * @since Struts 1.2.0 223 */ 224 protected ActionForward cancelled(ActionMapping mapping, 225 ActionForm form, 226 HttpServletRequest request, 227 HttpServletResponse response) 228 throws Exception { 229 230 return null; 231 } 232 233 // ----------------------------------------------------- Protected Methods 234 235 236 /** 237 * Dispatch to the specified method. 238 * @since Struts 1.1 239 */ 240 protected ActionForward dispatchMethod(ActionMapping mapping, 241 ActionForm form, 242 HttpServletRequest request, 243 HttpServletResponse response, 244 String name) throws Exception { 245 246 // Make sure we have a valid method name to call. 247 // This may be null if the user hacks the query string. 248 if (name == null) { 249 return this.unspecified(mapping, form, request, response); 250 } 251 252 // Identify the method object to be dispatched to 253 Method method = null; 254 try { 255 method = getMethod(name); 256 257 } catch(NoSuchMethodException e) { 258 String message = 259 messages.getMessage("dispatch.method", mapping.getPath(), name); 260 log.error(message, e); 261 262 String userMsg = 263 messages.getMessage("dispatch.method.user", mapping.getPath()); 264 throw new NoSuchMethodException(userMsg); 265 } 266 267 ActionForward forward = null; 268 try { 269 Object args[] = {mapping, form, request, response}; 270 forward = (ActionForward) method.invoke(this, args); 271 272 } catch(ClassCastException e) { 273 String message = 274 messages.getMessage("dispatch.return", mapping.getPath(), name); 275 log.error(message, e); 276 throw e; 277 278 } catch(IllegalAccessException e) { 279 String message = 280 messages.getMessage("dispatch.error", mapping.getPath(), name); 281 log.error(message, e); 282 throw e; 283 284 } catch(InvocationTargetException e) { 285 // Rethrow the target exception if possible so that the 286 // exception handling machinery can deal with it 287 Throwable t = e.getTargetException(); 288 if (t instanceof Exception) { 289 throw ((Exception) t); 290 } else { 291 String message = 292 messages.getMessage("dispatch.error", mapping.getPath(), name); 293 log.error(message, e); 294 throw new ServletException(t); 295 } 296 } 297 298 // Return the returned ActionForward instance 299 return (forward); 300 } 301 302 /** 303 * <p>Returns the parameter value.</p> 304 * 305 * @param mapping The ActionMapping used to select this instance 306 * @param form The optional ActionForm bean for this request (if any) 307 * @param request The HTTP request we are processing 308 * @param response The HTTP response we are creating 309 * @return The <code>ActionMapping</code> parameter's value 310 * @throws Exception if the parameter is missing. 311 */ 312 protected String getParameter(ActionMapping mapping, ActionForm form, 313 HttpServletRequest request, HttpServletResponse response) 314 throws Exception { 315 316 // Identify the request parameter containing the method name 317 String parameter = mapping.getParameter(); 318 319 if (parameter == null) { 320 String message = 321 messages.getMessage("dispatch.handler", mapping.getPath()); 322 323 log.error(message); 324 325 throw new ServletException(message); 326 } 327 328 329 return parameter; 330 } 331 332 /** 333 * Introspect the current class to identify a method of the specified 334 * name that accepts the same parameter types as the <code>execute</code> 335 * method does. 336 * 337 * @param name Name of the method to be introspected 338 * 339 * @exception NoSuchMethodException if no such method can be found 340 */ 341 protected Method getMethod(String name) 342 throws NoSuchMethodException { 343 344 synchronized(methods) { 345 Method method = (Method) methods.get(name); 346 if (method == null) { 347 method = clazz.getMethod(name, types); 348 methods.put(name, method); 349 } 350 return (method); 351 } 352 353 } 354 355 /** 356 * Returns the method name, given a parameter's value. 357 * 358 * @param mapping The ActionMapping used to select this instance 359 * @param form The optional ActionForm bean for this request (if any) 360 * @param request The HTTP request we are processing 361 * @param response The HTTP response we are creating 362 * @param parameter The <code>ActionMapping</code> parameter's name 363 * 364 * @return The method's name. 365 * @since Struts 1.2.0 366 */ 367 protected String getMethodName(ActionMapping mapping, 368 ActionForm form, 369 HttpServletRequest request, 370 HttpServletResponse response, 371 String parameter) 372 throws Exception { 373 374 // Identify the method name to be dispatched to. 375 // dispatchMethod() will call unspecified() if name is null 376 return request.getParameter(parameter); 377 } 378 379 }