View Javadoc

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