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.HashMap;
24 import java.util.Locale;
25 import java.util.Map;
26
27 import jakarta.servlet.ServletException;
28 import jakarta.servlet.http.HttpServletRequest;
29 import jakarta.servlet.http.HttpServletResponse;
30
31 import org.apache.struts.Globals;
32 import org.apache.struts.action.ActionForm;
33 import org.apache.struts.action.ActionForward;
34 import org.apache.struts.action.ActionMapping;
35 import org.apache.struts.config.MessageResourcesConfig;
36 import org.apache.struts.config.ModuleConfig;
37 import org.apache.struts.util.MessageResources;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 /**
42 * <p> An abstract <strong>Action</strong> that dispatches to the subclass
43 * mapped <code>execute</code> method. This is useful in cases where an HTML
44 * form has multiple submit buttons with the same name. The button name is
45 * specified by the <code>parameter</code> property of the corresponding
46 * ActionMapping. To configure the use of this action in your
47 * <code>struts-config.xml</code> file, create an entry like this:</p> <pre>
48 * <action path="/test"
49 * type="org.example.MyAction"
50 * name="MyForm"
51 * scope="request"
52 * input="/test.jsp"
53 * parameter="method"/>
54 * </pre> <p>
55 *
56 * which will use the value of the request parameter named "method" to locate
57 * the corresponding key in ApplicationResources. For example, you might have
58 * the following ApplicationResources.properties:</p> <pre>
59 * button.add=Add Record
60 * button.delete=Delete Record
61 * </pre><p>
62 *
63 * And your JSP would have the following format for submit buttons:</p> <pre>
64 * <html:form action="/test">
65 * <html:submit property="method">
66 * <bean:message key="button.add"/>
67 * </html:submit>
68 * <html:submit property="method">
69 * <bean:message key="button.delete"/>
70 * </html:submit>
71 * </html:form>
72 * </pre> <p>
73 *
74 * Your subclass must implement both getKeyMethodMap and the methods defined
75 * in the map. An example of such implementations are:</p>
76 * <pre>
77 * protected Map getKeyMethodMap() {
78 * Map map = new HashMap();
79 * map.put("button.add", "add");
80 * map.put("button.delete", "delete");
81 * return map;
82 * }
83 *
84 * public ActionForward add(ActionMapping mapping,
85 * ActionForm form,
86 * HttpServletRequest request,
87 * HttpServletResponse response)
88 * throws IOException, ServletException {
89 * // do add
90 * return mapping.findForward("success");
91 * }
92 *
93 * public ActionForward delete(ActionMapping mapping,
94 * ActionForm form,
95 * HttpServletRequest request,
96 * HttpServletResponse response)
97 * throws IOException, ServletException {
98 * // do delete
99 * return mapping.findForward("success");
100 * }
101 * </pre>
102 * <p> <strong>Notes</strong> - If duplicate values exist for the keys
103 * returned by getKeys, only the first one found will be returned. If no
104 * corresponding key is found then an exception will be thrown. You can
105 * override the method <code>unspecified</code> to provide a custom handler.
106 * If the submit was cancelled (a <code>html:cancel</code> button was
107 * pressed), the custom handler <code>cancelled</code> will be used instead.
108 */
109 public abstract class LookupDispatchAction extends DispatchAction {
110 private static final long serialVersionUID = 8608624951935780151L;
111
112 /**
113 * The {@code Log} instance for this class.
114 */
115 private transient final Logger log =
116 LoggerFactory.getLogger(LookupDispatchAction.class);
117
118 /**
119 * Reverse lookup map from resource value to resource key.
120 */
121 protected HashMap<Locale, Map<String, String>> localeMap = new HashMap<>();
122
123 /**
124 * Resource key to method name lookup.
125 */
126 protected Map<String, String> keyMethodMap = null;
127
128 // ---------------------------------------------------------- Public Methods
129
130 /**
131 * Process the specified HTTP request, and create the corresponding HTTP
132 * response (or forward to another web component that will create it).
133 * Return an <code>ActionForward</code> instance describing where and how
134 * control should be forwarded, or <code>null</code> if the response has
135 * already been completed.
136 *
137 * @param mapping The ActionMapping used to select this instance
138 * @param request The HTTP request we are processing
139 * @param response The HTTP response we are creating
140 * @param form The optional ActionForm bean for this request (if any)
141 * @return Describes where and how control should be forwarded.
142 * @throws Exception if an error occurs
143 */
144 public ActionForward execute(ActionMapping mapping, ActionForm form,
145 HttpServletRequest request, HttpServletResponse response)
146 throws Exception {
147 return super.execute(mapping, form, request, response);
148 }
149
150 /**
151 * This is the first time this Locale is used so build the reverse lookup
152 * Map. Search for message keys in all configured MessageResources for the
153 * current module.
154 *
155 * @param request The HTTP request we are processing
156 * @param userLocale The locale for this request
157 * @return The reverse lookup map for the specified locale.
158 */
159 private Map<String, String> initLookupMap(HttpServletRequest request, Locale userLocale) {
160 Map<String, String> lookupMap = new HashMap<>();
161
162 this.keyMethodMap = this.getKeyMethodMap();
163
164 ModuleConfig moduleConfig =
165 (ModuleConfig) request.getAttribute(Globals.MODULE_KEY);
166
167 MessageResourcesConfig[] mrc =
168 moduleConfig.findMessageResourcesConfigs();
169
170 // Look through all module's MessageResources
171 for (int i = 0; i < mrc.length; i++) {
172 MessageResources resources =
173 this.getResources(request, mrc[i].getKey());
174
175 // Look for key in MessageResources
176 for (String key : this.keyMethodMap.keySet()) {
177 String text = resources.getMessage(userLocale, key);
178
179 // Found key and haven't added to Map yet, so add the text
180 if ((text != null) && !lookupMap.containsKey(text)) {
181 lookupMap.put(text, key);
182 }
183 }
184 }
185
186 return lookupMap;
187 }
188
189 /**
190 * Provides the mapping from resource key to method name.
191 *
192 * @return Resource key / method name map.
193 */
194 protected abstract Map<String, String> getKeyMethodMap();
195
196 /**
197 * Lookup the method name corresponding to the client request's locale.
198 *
199 * @param request The HTTP request we are processing
200 * @param keyName The parameter name to use as the properties key
201 * @param mapping The ActionMapping used to select this instance
202 * @return The method's localized name.
203 * @throws ServletException if keyName cannot be resolved
204 * @since Struts 1.2.0
205 */
206 protected String getLookupMapName(HttpServletRequest request,
207 String keyName, ActionMapping mapping)
208 throws ServletException {
209 // Based on this request's Locale get the lookupMap
210 Map<String, String> lookupMap = null;
211
212 synchronized (localeMap) {
213 Locale userLocale = this.getLocale(request);
214
215 lookupMap = this.localeMap.get(userLocale);
216
217 if (lookupMap == null) {
218 lookupMap = this.initLookupMap(request, userLocale);
219 this.localeMap.put(userLocale, lookupMap);
220 }
221 }
222
223 // Find the key for the resource
224 String key = lookupMap.get(keyName);
225
226 if (key == null) {
227 String message =
228 messages.getMessage("dispatch.resource", mapping.getPath());
229 log.error("{} '{}'", message, keyName);
230 throw new ServletException(message);
231 }
232
233 // Find the method name
234 String methodName = keyMethodMap.get(key);
235
236 if (methodName == null) {
237 String message =
238 messages.getMessage("dispatch.lookup", mapping.getPath(), key);
239
240 throw new ServletException(message);
241 }
242
243 return methodName;
244 }
245
246 /**
247 * Returns the method name, given a parameter's value.
248 *
249 * @param mapping The ActionMapping used to select this instance
250 * @param form The optional ActionForm bean for this request (if
251 * any)
252 * @param request The HTTP request we are processing
253 * @param response The HTTP response we are creating
254 * @param parameter The <code>ActionMapping</code> parameter's name
255 * @return The method's name.
256 * @throws Exception if an error occurs
257 * @since Struts 1.2.0
258 */
259 protected String getMethodName(ActionMapping mapping, ActionForm form,
260 HttpServletRequest request, HttpServletResponse response,
261 String parameter) throws Exception {
262 // Identify the method name to be dispatched to.
263 // dispatchMethod() will call unspecified() if name is null
264 String keyName = request.getParameter(parameter);
265
266 if ((keyName == null) || (keyName.length() == 0)) {
267 return null;
268 }
269
270 String methodName = getLookupMapName(request, keyName, mapping);
271
272 return methodName;
273 }
274 }