View Javadoc

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