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 package org.apache.struts.extras.actions;
22
23 import java.util.StringTokenizer;
24
25 import jakarta.servlet.ServletException;
26 import jakarta.servlet.http.HttpServletRequest;
27 import jakarta.servlet.http.HttpServletResponse;
28
29 import org.apache.struts.action.ActionForm;
30 import org.apache.struts.action.ActionForward;
31 import org.apache.struts.action.ActionMapping;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 /**
36 * <p>An <strong>Action</strong> 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) must have the same signature (other than method name) of the
43 * 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 * <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"/>
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 * @since Struts 1.2.9
76 */
77 public class EventDispatchAction extends DispatchAction {
78 private static final long serialVersionUID = -5067513904785952541L;
79
80 /**
81 * The {@code Log} instance for this class.
82 */
83 private transient final Logger log =
84 LoggerFactory.getLogger(EventDispatchAction.class);
85
86 /**
87 * The method key, if present, to use if other specified method keys
88 * do not match a request parameter.
89 */
90 private static final String DEFAULT_METHOD_KEY = "default";
91
92 // --------------------------------------------------------- Protected Methods
93
94 /**
95 * Method which is dispatched to when there is no value for specified
96 * request parameter included in the request. Subclasses of
97 * <code>DispatchAction</code> should override this method if they wish to
98 * provide default behavior different than throwing a ServletException.
99 *
100 * @param mapping The ActionMapping used to select this instance
101 * @param form The optional ActionForm bean for this request (if any)
102 * @param request The non-HTTP request we are processing
103 * @param response The non-HTTP response we are creating
104 * @return The forward to which control should be transferred, or
105 * <code>null</code> if the response has been completed.
106 * @throws Exception if the application business logic throws an
107 * exception.
108 */
109 protected ActionForward unspecified(ActionMapping mapping, ActionForm form,
110 HttpServletRequest request, HttpServletResponse response)
111 throws Exception {
112 String message =
113 messages.getMessage("event.parameter", mapping.getPath(),
114 mapping.getParameter());
115
116 log.error("{} {}", message, mapping.getParameter());
117
118 throw new ServletException(message);
119 }
120
121 /**
122 * Returns the method name, given a parameter's value.
123 *
124 * @param mapping The ActionMapping used to select this instance
125 * @param form The optional ActionForm bean for this request (if
126 * any)
127 * @param request The HTTP request we are processing
128 * @param response The HTTP response we are creating
129 * @param parameter The <code>ActionMapping</code> parameter's name
130 * @return The method's name.
131 * @throws Exception if an error occurs.
132 */
133 protected String getMethodName(ActionMapping mapping, ActionForm form,
134 HttpServletRequest request, HttpServletResponse response,
135 String parameter) throws Exception {
136
137 StringTokenizer st = new StringTokenizer(parameter, ",");
138 String defaultMethodName = null;
139
140 while (st.hasMoreTokens()) {
141 String methodKey = st.nextToken().trim();
142 String methodName = methodKey;
143
144 // The key can either be a direct method name or an alias
145 // to a method as indicated by a "key=value" signature
146 int equals = methodKey.indexOf('=');
147 if (equals > -1) {
148 methodName = methodKey.substring(equals + 1).trim();
149 methodKey = methodKey.substring(0, equals).trim();
150 }
151
152 // Set the default if it passes by
153 if (methodKey.equals(DEFAULT_METHOD_KEY)) {
154 defaultMethodName = methodName;
155 }
156
157 // If the method key exists as a standalone parameter or with
158 // the image suffixes (.x/.y), the method name has been found.
159 if ((request.getParameter(methodKey) != null)
160 || (request.getParameter(methodKey + ".x") != null)) {
161 return methodName;
162 }
163 }
164
165 return defaultMethodName;
166 }
167 }