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 * <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"/>
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 }