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