/* * $Id: LookupDispatchAction.java 471754 2006-11-06 14:55:09Z husted $ * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.struts.actions; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.struts.Globals; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.apache.struts.config.MessageResourcesConfig; import org.apache.struts.config.ModuleConfig; import org.apache.struts.util.MessageResources; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.HashMap; import java.util.Iterator; import java.util.Locale; import java.util.Map; /** * <p> An abstract <strong>Action</strong> that dispatches to the subclass * mapped <code>execute</code> method. This is useful in cases where an HTML * form has multiple submit buttons with the same name. The button name is * specified by the <code>parameter</code> property of the corresponding * ActionMapping. To configure the use of this action in your * <code>struts-config.xml</code> file, create an entry like this:</p> <pre> * <action path="/test" * type="org.example.MyAction" * name="MyForm" * scope="request" * input="/test.jsp" * parameter="method"/> * </pre> <p> * * which will use the value of the request parameter named "method" to locate * the corresponding key in ApplicationResources. For example, you might have * the following ApplicationResources.properties:</p> <pre> * button.add=Add Record * button.delete=Delete Record * </pre><p> * * And your JSP would have the following format for submit buttons:</p> <pre> * <html:form action="/test"> * <html:submit property="method"> * <bean:message key="button.add"/> * </html:submit> * <html:submit property="method"> * <bean:message key="button.delete"/> * </html:submit> * </html:form> * </pre> <p> * * Your subclass must implement both getKeyMethodMap and the methods defined * in the map. An example of such implementations are:</p> * <pre> * protected Map getKeyMethodMap() { * Map map = new HashMap(); * map.put("button.add", "add"); * map.put("button.delete", "delete"); * return map; * } * * public ActionForward add(ActionMapping mapping, * ActionForm form, * HttpServletRequest request, * HttpServletResponse response) * throws IOException, ServletException { * // do add * return mapping.findForward("success"); * } * * public ActionForward delete(ActionMapping mapping, * ActionForm form, * HttpServletRequest request, * HttpServletResponse response) * throws IOException, ServletException { * // do delete * return mapping.findForward("success"); * } * </pre> * <p> <strong>Notes</strong> - If duplicate values exist for the keys * returned by getKeys, only the first one found will be returned. If no * corresponding key is found then an exception will be thrown. You can * override the method <code>unspecified</code> to provide a custom handler. * If the submit was cancelled (a <code>html:cancel</code> button was * pressed), the custom handler <code>cancelled</code> will be used instead. */ public abstract class LookupDispatchAction extends DispatchAction { /** * Commons Logging instance. */ private static final Log LOG = LogFactory.getLog(LookupDispatchAction.class); /** * Reverse lookup map from resource value to resource key. */ protected Map localeMap = new HashMap(); /** * Resource key to method name lookup. */ protected Map keyMethodMap = null; // ---------------------------------------------------------- Public Methods /** * Process the specified HTTP request, and create the corresponding HTTP * response (or forward to another web component that will create it). * Return an <code>ActionForward</code> instance describing where and how * control should be forwarded, or <code>null</code> if the response has * already been completed. * * @param mapping The ActionMapping used to select this instance * @param request The HTTP request we are processing * @param response The HTTP response we are creating * @param form The optional ActionForm bean for this request (if any) * @return Describes where and how control should be forwarded. * @throws Exception if an error occurs */ public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { return super.execute(mapping, form, request, response); } /** * This is the first time this Locale is used so build the reverse lookup * Map. Search for message keys in all configured MessageResources for the * current module. * * @param request The HTTP request we are processing * @param userLocale The locale for this request * @return The reverse lookup map for the specified locale. */ private Map initLookupMap(HttpServletRequest request, Locale userLocale) { Map lookupMap = new HashMap(); this.keyMethodMap = this.getKeyMethodMap(); ModuleConfig moduleConfig = (ModuleConfig) request.getAttribute(Globals.MODULE_KEY); MessageResourcesConfig[] mrc = moduleConfig.findMessageResourcesConfigs(); // Look through all module's MessageResources for (int i = 0; i < mrc.length; i++) { MessageResources resources = this.getResources(request, mrc[i].getKey()); // Look for key in MessageResources Iterator iter = this.keyMethodMap.keySet().iterator(); while (iter.hasNext()) { String key = (String) iter.next(); String text = resources.getMessage(userLocale, key); // Found key and haven't added to Map yet, so add the text if ((text != null) && !lookupMap.containsKey(text)) { lookupMap.put(text, key); } } } return lookupMap; } /** * Provides the mapping from resource key to method name. * * @return Resource key / method name map. */ protected abstract Map getKeyMethodMap(); /** * Lookup the method name corresponding to the client request's locale. * * @param request The HTTP request we are processing * @param keyName The parameter name to use as the properties key * @param mapping The ActionMapping used to select this instance * @return The method's localized name. * @throws ServletException if keyName cannot be resolved * @since Struts 1.2.0 */ protected String getLookupMapName(HttpServletRequest request, String keyName, ActionMapping mapping) throws ServletException { // Based on this request's Locale get the lookupMap Map lookupMap = null; synchronized (localeMap) { Locale userLocale = this.getLocale(request); lookupMap = (Map) this.localeMap.get(userLocale); if (lookupMap == null) { lookupMap = this.initLookupMap(request, userLocale); this.localeMap.put(userLocale, lookupMap); } } // Find the key for the resource String key = (String) lookupMap.get(keyName); if (key == null) { String message = messages.getMessage("dispatch.resource", mapping.getPath()); LOG.error(message + " '" + keyName + "'"); throw new ServletException(message); } // Find the method name String methodName = (String) keyMethodMap.get(key); if (methodName == null) { String message = messages.getMessage("dispatch.lookup", mapping.getPath(), key); throw new ServletException(message); } return methodName; } /** * Returns the method name, given a parameter's value. * * @param mapping The ActionMapping used to select this instance * @param form The optional ActionForm bean for this request (if * any) * @param request The HTTP request we are processing * @param response The HTTP response we are creating * @param parameter The <code>ActionMapping</code> parameter's name * @return The method's name. * @throws Exception if an error occurs * @since Struts 1.2.0 */ protected String getMethodName(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response, String parameter) throws Exception { // Identify the method name to be dispatched to. // dispatchMethod() will call unspecified() if name is null String keyName = request.getParameter(parameter); if ((keyName == null) || (keyName.length() == 0)) { return null; } String methodName = getLookupMapName(request, keyName, mapping); return methodName; } }