View Javadoc

1   /*
2    * $Id: EventActionDispatcher.java 384133 2006-03-08 06:42:48Z niallp $
3    *
4    * Copyright 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.util.StringTokenizer;
22  import java.lang.reflect.Method;
23  
24  import javax.servlet.ServletException;
25  import javax.servlet.http.HttpServletRequest;
26  import javax.servlet.http.HttpServletResponse;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.apache.struts.action.Action;
31  import org.apache.struts.action.ActionForm;
32  import org.apache.struts.action.ActionMapping;
33  import org.apache.struts.action.ActionForward;
34  
35  /***
36   * <p>An Action helper class that dispatches to to one of the public methods
37   * that are named in the <code>parameter</code> attribute of the corresponding
38   * ActionMapping and matches a submission parameter. This is useful for
39   * developers who prefer to use many submit buttons, images, or submit links
40   * on a single form and whose related actions exist in a single Action class.</p>
41   *
42   * <p>The method(s) in the associated <code>Action</code> must have the same
43   * signature (other than method name) of the standard Action.execute method.</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   * <pre><code>
49   *   &lt;action path="/saveSubscription"
50   *           type="org.example.SubscriptionAction"
51   *           name="subscriptionForm"
52   *          scope="request"
53   *          input="/subscription.jsp"
54   *      parameter="save,back,recalc=recalculate,default=save"/&gt;
55   * </code></pre>
56   *
57   * <p>where <code>parameter</code> contains three possible methods and one
58   * default method if nothing matches (such as the user pressing the enter key).</p>
59   *
60   * <p>For utility purposes, you can use the <code>key=value</code> notation to
61   * alias methods so that they are exposed as different form element names, in the
62   * event of a naming conflict or otherwise. In this example, the <em>recalc</em>
63   * button (via a request parameter) will invoke the <code>recalculate</code>
64   * method. The security-minded person may find this feature valuable to
65   * obfuscate and not expose the methods.</p>
66   *
67   * <p>The <em>default</em> key is purely optional. If this is not specified
68   * and no parameters match the list of method keys, <code>null</code> is
69   * returned which means the <code>unspecified</code> method will be invoked.</p>
70   *
71   * <p>The order of the parameters are guaranteed to be iterated in the order
72   * specified. If multiple buttons were accidently submitted, the first match in
73   * the list will be dispatched.</p>
74   *
75   * <p>To implement this <i>dispatch</i> behaviour in an <code>Action</code>,
76   * class create your custom Action as follows, along with the methods you require
77   * (and optionally "cancelled" and "unspecified" methods):</p> <p/>
78   * <pre>
79   *   public class MyCustomAction extends Action {
80   *
81   *       protected ActionDispatcher dispatcher = new EventActionDispatcher(this);
82   *
83   *       public ActionForward execute(ActionMapping mapping,
84   *                                    ActionForm form,
85   *                                    HttpServletRequest request,
86   *                                    HttpServletResponse response)
87   *                           throws Exception {
88   *           return dispatcher.execute(mapping, form, request, response);
89   *       }
90   *   }
91   * </pre>
92   * <p/>
93   *
94   * @since Struts 1.2.9
95   */
96  public class EventActionDispatcher extends ActionDispatcher {
97  
98      /***
99       * Commons Logging instance.
100      */
101     private static final Log LOG = LogFactory.getLog(EventActionDispatcher.class);
102 
103     /***
104      * The method key, if present, to use if other specified method keys
105      * do not match a request parameter.
106      */
107     private static final String DEFAULT_METHOD_KEY = "default";
108 
109     /***
110      * Constructs a new object for the specified action.
111      * @param action the action
112      */
113     public EventActionDispatcher(Action action) {
114         // N.B. MAPPING_FLAVOR causes the getParameter() method
115         //      in ActionDispatcher to throw an exception if the
116         //      parameter is missing
117         super(action, ActionDispatcher.MAPPING_FLAVOR);
118     }
119 
120     /***
121      * <p>Dispatches to the target class' <code>unspecified</code> method, if
122      * present, otherwise throws a ServletException. Classes utilizing
123      * <code>EventActionDispatcher</code> should provide an <code>unspecified</code>
124      * method if they wish to provide behavior different than throwing a
125      * ServletException.</p>
126      *
127      * @param mapping  The ActionMapping used to select this instance
128      * @param form     The optional ActionForm bean for this request (if any)
129      * @param request  The non-HTTP request we are processing
130      * @param response The non-HTTP response we are creating
131      * @return The forward to which control should be transferred, or
132      *         <code>null</code> if the response has been completed.
133      * @throws Exception if the application business logic throws an
134      *                   exception.
135      */
136     protected ActionForward unspecified(ActionMapping mapping, ActionForm form,
137         HttpServletRequest request, HttpServletResponse response)
138         throws Exception {
139         // Identify if there is an "unspecified" method to be dispatched to
140         String name = "unspecified";
141         Method method = null;
142 
143         try {
144             method = getMethod(name);
145         } catch (NoSuchMethodException e) {
146             String message =
147                 messages.getMessage("event.parameter", mapping.getPath());
148 
149             LOG.error(message + " " + mapping.getParameter());
150 
151             throw new ServletException(message);
152         }
153 
154         return dispatchMethod(mapping, form, request, response, name, method);
155     }
156 
157     /***
158      * Returns the method name, given a parameter's value.
159      *
160      * @param mapping   The ActionMapping used to select this instance
161      * @param form      The optional ActionForm bean for this request (if
162      *                  any)
163      * @param request   The HTTP request we are processing
164      * @param response  The HTTP response we are creating
165      * @param parameter The <code>ActionMapping</code> parameter's name
166      * @return The method's name.
167      * @throws Exception if an error occurs.
168      */
169     protected String getMethodName(ActionMapping mapping, ActionForm form,
170             HttpServletRequest request, HttpServletResponse response,
171             String parameter) throws Exception {
172 
173         StringTokenizer st = new StringTokenizer(parameter, ",");
174         String defaultMethodName = null;
175 
176         while (st.hasMoreTokens()) {
177             String methodKey = st.nextToken().trim();
178             String methodName = methodKey;
179 
180             // The key can either be a direct method name or an alias
181             // to a method as indicated by a "key=value" signature
182             int equals = methodKey.indexOf('=');
183             if (equals > -1) {
184                 methodName = methodKey.substring(equals + 1).trim();
185                 methodKey = methodKey.substring(0, equals).trim();
186             }
187 
188             // Set the default if it passes by
189             if (methodKey.equals(DEFAULT_METHOD_KEY)) {
190                 defaultMethodName = methodName;
191             }
192 
193             // If the method key exists as a standalone parameter or with
194             // the image suffixes (.x/.y), the method name has been found.
195             if ((request.getParameter(methodKey) != null)
196                   || (request.getParameter(methodKey + ".x") != null)) {
197                 return methodName;
198             }
199         }
200 
201         return defaultMethodName;
202     }
203 }