1 /*
2 * $Id: LookupDispatchAction.java 384089 2006-03-08 01:50:52Z niallp $
3 *
4 * Copyright 2001-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.HashMap;
22 import java.util.Iterator;
23 import java.util.Locale;
24 import java.util.Map;
25
26 import javax.servlet.ServletException;
27 import javax.servlet.http.HttpServletRequest;
28 import javax.servlet.http.HttpServletResponse;
29
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogFactory;
32 import org.apache.struts.Globals;
33 import org.apache.struts.action.ActionForm;
34 import org.apache.struts.action.ActionForward;
35 import org.apache.struts.action.ActionMapping;
36 import org.apache.struts.config.MessageResourcesConfig;
37 import org.apache.struts.config.ModuleConfig;
38 import org.apache.struts.util.MessageResources;
39
40 /**
41 * <p>
42 * An abstract <strong>Action</strong> that dispatches to the subclass mapped
43 * <code>execute</code> method. This is useful in
44 * cases where an HTML form has multiple submit buttons with the same name. The
45 * button name is specified by the <code>parameter</code> property of the
46 * corresponding 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
75 * methods defined 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 * <p>
102 *
103 * <strong>Notes</strong> - If duplicate values exist for the keys returned by
104 * getKeys, only the first one found will be returned. If no corresponding key
105 * is found then an exception will be thrown. You can override the
106 * method <code>unspecified</code> to provide a custom handler. If the submit
107 * was cancelled (a <code>html:cancel</code> button was pressed), the custom
108 * handler <code>cancelled</code> will be used instead.
109 *
110 */
111 public abstract class LookupDispatchAction extends DispatchAction {
112
113 /**
114 * Commons Logging instance.
115 */
116 private static final Log LOG = LogFactory.getLog(LookupDispatchAction.class);
117
118 /**
119 * Reverse lookup map from resource value to resource key.
120 */
121 protected Map localeMap = new HashMap();
122
123 /**
124 * Resource key to method name lookup.
125 */
126 protected Map 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 * @exception Exception if an error occurs
143 */
144 public ActionForward execute(
145 ActionMapping mapping,
146 ActionForm form,
147 HttpServletRequest request,
148 HttpServletResponse response)
149 throws Exception {
150 return super.execute(mapping, form, request, response);
151 }
152
153 /**
154 * This is the first time this Locale is used so build the reverse lookup Map.
155 * Search for message keys in all configured MessageResources for
156 * the current module.
157 */
158 private Map initLookupMap(HttpServletRequest request, Locale userLocale) {
159 Map lookupMap = new HashMap();
160 this.keyMethodMap = this.getKeyMethodMap();
161
162 ModuleConfig moduleConfig =
163 (ModuleConfig) request.getAttribute(Globals.MODULE_KEY);
164
165 MessageResourcesConfig[] mrc = moduleConfig.findMessageResourcesConfigs();
166
167 // Look through all module's MessageResources
168 for (int i = 0; i < mrc.length; i++) {
169 MessageResources resources = this.getResources(request, mrc[i].getKey());
170
171 // Look for key in MessageResources
172 Iterator iter = this.keyMethodMap.keySet().iterator();
173 while (iter.hasNext()) {
174 String key = (String) iter.next();
175 String text = resources.getMessage(userLocale, key);
176
177 // Found key and haven't added to Map yet, so add the text
178 if ((text != null) && !lookupMap.containsKey(text)) {
179 lookupMap.put(text, key);
180 }
181 }
182 }
183
184 return lookupMap;
185 }
186
187 /**
188 * Provides the mapping from resource key to method name.
189 *
190 * @return Resource key / method name map.
191 */
192 protected abstract Map getKeyMethodMap();
193
194 /**
195 * Lookup the method name corresponding to the client request's locale.
196 *
197 * @param request The HTTP request we are processing
198 * @param keyName The parameter name to use as the properties key
199 * @param mapping The ActionMapping used to select this instance
200 *
201 * @return The method's localized name.
202 * @throws ServletException if keyName cannot be resolved
203 * @since Struts 1.2.0
204 */
205 protected String getLookupMapName(
206 HttpServletRequest request,
207 String keyName,
208 ActionMapping mapping)
209 throws ServletException {
210
211 // Based on this request's Locale get the lookupMap
212 Map lookupMap = null;
213
214 synchronized(localeMap) {
215 Locale userLocale = this.getLocale(request);
216 lookupMap = (Map) this.localeMap.get(userLocale);
217
218 if (lookupMap == null) {
219 lookupMap = this.initLookupMap(request, userLocale);
220 this.localeMap.put(userLocale, lookupMap);
221 }
222 }
223
224 // Find the key for the resource
225 String key = (String) lookupMap.get(keyName);
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 = (String) keyMethodMap.get(key);
235 if (methodName == null) {
236 String message = messages.getMessage(
237 "dispatch.lookup", mapping.getPath(), key);
238 throw new ServletException(message);
239 }
240
241 return methodName;
242 }
243
244 /**
245 * Returns the method name, given a parameter's value.
246 *
247 * @param mapping The ActionMapping used to select this instance
248 * @param form The optional ActionForm bean for this request (if any)
249 * @param request The HTTP request we are processing
250 * @param response The HTTP response we are creating
251 * @param parameter The <code>ActionMapping</code> parameter's name
252 *
253 * @return The method's name.
254 * @since Struts 1.2.0
255 */
256 protected String getMethodName(
257 ActionMapping mapping,
258 ActionForm form,
259 HttpServletRequest request,
260 HttpServletResponse response,
261 String parameter)
262 throws Exception {
263
264 // Identify the method name to be dispatched to.
265 // dispatchMethod() will call unspecified() if name is null
266 String keyName = request.getParameter(parameter);
267 if (keyName == null || keyName.length() == 0) {
268 return null;
269 }
270
271 String methodName = getLookupMapName(request, keyName, mapping);
272
273 return methodName;
274 }
275
276
277 }