001/* 002 * Copyright 2002-2014 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.servlet.mvc.multiaction; 018 019import java.util.Properties; 020import javax.servlet.http.HttpServletRequest; 021 022import org.apache.commons.logging.Log; 023import org.apache.commons.logging.LogFactory; 024 025import org.springframework.util.Assert; 026import org.springframework.util.StringUtils; 027import org.springframework.web.util.WebUtils; 028 029/** 030 * Implementation of {@link MethodNameResolver} which supports several strategies 031 * for mapping parameter values to the names of methods to invoke. 032 * 033 * <p>The simplest strategy looks for a specific named parameter, whose value is 034 * considered the name of the method to invoke. The name of the parameter may be 035 * specified as a JavaBean property, if the default {@code action} is not 036 * acceptable. 037 * 038 * <p>The alternative strategy uses the very existence of a request parameter ( 039 * i.e. a request parameter with a certain name is found) as an indication that a 040 * method with the same name should be dispatched to. In this case, the actual 041 * request parameter value is ignored. The list of parameter/method names may 042 * be set via the {@code methodParamNames} JavaBean property. 043 * 044 * <p>The second resolution strategy is primarily expected to be used with web 045 * pages containing multiple submit buttons. The 'name' attribute of each 046 * button should be set to the mapped method name, while the 'value' attribute 047 * is normally displayed as the button label by the browser, and will be 048 * ignored by the resolver. 049 * 050 * <p>Note that the second strategy also supports the use of submit buttons of 051 * type 'image'. That is, an image submit button named 'reset' will normally be 052 * submitted by the browser as two request parameters called 'reset.x', and 053 * 'reset.y'. When checking for the existence of a parameter from the 054 * {@code methodParamNames} list, to indicate that a specific method should 055 * be called, the code will look for a request parameter in the "reset" form 056 * (exactly as specified in the list), and in the "reset.x" form ('.x' appended 057 * to the name in the list). In this way it can handle both normal and image 058 * submit buttons. The actual method name resolved, if there is a match, will 059 * always be the bare form without the ".x". 060 * 061 * <p><b>Note:</b> If both strategies are configured, i.e. both "paramName" 062 * and "methodParamNames" are specified, then both will be checked for any given 063 * request. A match for an explicit request parameter in the "methodParamNames" 064 * list always wins over a value specified for a "paramName" action parameter. 065 * 066 * <p>For use with either strategy, the name of a default handler method to use 067 * when there is no match, can be specified as a JavaBean property. 068 * 069 * <p>For both resolution strategies, the method name is of course coming from 070 * some sort of view code, (such as a JSP page). While this may be acceptable, 071 * it is sometimes desirable to treat this only as a 'logical' method name, 072 * with a further mapping to a 'real' method name. As such, an optional 073 * 'logical' mapping may be specified for this purpose. 074 * 075 * @author Rod Johnson 076 * @author Juergen Hoeller 077 * @author Colin Sampaleanu 078 * @see #setParamName 079 * @see #setMethodParamNames 080 * @see #setLogicalMappings 081 * @see #setDefaultMethodName 082 * @deprecated as of 4.3, in favor of annotation-driven handler methods 083 */ 084@Deprecated 085public class ParameterMethodNameResolver implements MethodNameResolver { 086 087 /** 088 * Default name for the parameter whose value identifies the method to invoke: 089 * "action". 090 */ 091 public static final String DEFAULT_PARAM_NAME = "action"; 092 093 094 protected final Log logger = LogFactory.getLog(getClass()); 095 096 private String paramName = DEFAULT_PARAM_NAME; 097 098 private String[] methodParamNames; 099 100 private Properties logicalMappings; 101 102 private String defaultMethodName; 103 104 105 /** 106 * Set the name of the parameter whose <i>value</i> identifies the name of 107 * the method to invoke. Default is "action". 108 * <p>Alternatively, specify parameter names where the very existence of each 109 * parameter means that a method of the same name should be invoked, via 110 * the "methodParamNames" property. 111 * @see #setMethodParamNames 112 */ 113 public void setParamName(String paramName) { 114 if (paramName != null) { 115 Assert.hasText(paramName, "'paramName' must not be empty"); 116 } 117 this.paramName = paramName; 118 } 119 120 /** 121 * Set a String array of parameter names, where the <i>very existence of a 122 * parameter</i> in the list (with value ignored) means that a method of the 123 * same name should be invoked. This target method name may then be optionally 124 * further mapped via the {@link #logicalMappings} property, in which case it 125 * can be considered a logical name only. 126 * @see #setParamName 127 */ 128 public void setMethodParamNames(String... methodParamNames) { 129 this.methodParamNames = methodParamNames; 130 } 131 132 /** 133 * Specifies a set of optional logical method name mappings. For both resolution 134 * strategies, the method name initially comes in from the view layer. If that needs 135 * to be treated as a 'logical' method name, and mapped to a 'real' method name, then 136 * a name/value pair for that purpose should be added to this Properties instance. 137 * Any method name not found in this mapping will be considered to already be the 138 * real method name. 139 * <p>Note that in the case of no match, where the {@link #defaultMethodName} property 140 * is used if available, that method name is considered to already be the real method 141 * name, and is not run through the logical mapping. 142 * @param logicalMappings a Properties object mapping logical method names to real 143 * method names 144 */ 145 public void setLogicalMappings(Properties logicalMappings) { 146 this.logicalMappings = logicalMappings; 147 } 148 149 /** 150 * Set the name of the default handler method that should be 151 * used when no parameter was found in the request 152 */ 153 public void setDefaultMethodName(String defaultMethodName) { 154 if (defaultMethodName != null) { 155 Assert.hasText(defaultMethodName, "'defaultMethodName' must not be empty"); 156 } 157 this.defaultMethodName = defaultMethodName; 158 } 159 160 161 @Override 162 public String getHandlerMethodName(HttpServletRequest request) throws NoSuchRequestHandlingMethodException { 163 String methodName = null; 164 165 // Check parameter names where the very existence of each parameter 166 // means that a method of the same name should be invoked, if any. 167 if (this.methodParamNames != null) { 168 for (String candidate : this.methodParamNames) { 169 if (WebUtils.hasSubmitParameter(request, candidate)) { 170 methodName = candidate; 171 if (logger.isDebugEnabled()) { 172 logger.debug("Determined handler method '" + methodName + 173 "' based on existence of explicit request parameter of same name"); 174 } 175 break; 176 } 177 } 178 } 179 180 // Check parameter whose value identifies the method to invoke, if any. 181 if (methodName == null && this.paramName != null) { 182 methodName = request.getParameter(this.paramName); 183 if (methodName != null) { 184 if (logger.isDebugEnabled()) { 185 logger.debug("Determined handler method '" + methodName + 186 "' based on value of request parameter '" + this.paramName + "'"); 187 } 188 } 189 } 190 191 if (methodName != null && this.logicalMappings != null) { 192 // Resolve logical name into real method name, if appropriate. 193 String originalName = methodName; 194 methodName = this.logicalMappings.getProperty(methodName, methodName); 195 if (logger.isDebugEnabled()) { 196 logger.debug("Resolved method name '" + originalName + "' to handler method '" + methodName + "'"); 197 } 198 } 199 200 if (methodName != null && !StringUtils.hasText(methodName)) { 201 if (logger.isDebugEnabled()) { 202 logger.debug("Method name '" + methodName + "' is empty: treating it as no method name found"); 203 } 204 methodName = null; 205 } 206 207 if (methodName == null) { 208 if (this.defaultMethodName != null) { 209 // No specific method resolved: use default method. 210 methodName = this.defaultMethodName; 211 if (logger.isDebugEnabled()) { 212 logger.debug("Falling back to default handler method '" + this.defaultMethodName + "'"); 213 } 214 } 215 else { 216 // If resolution failed completely, throw an exception. 217 throw new NoSuchRequestHandlingMethodException(request); 218 } 219 } 220 221 return methodName; 222 } 223 224}