View Javadoc

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