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 }