View Javadoc

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