All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.struts2.dispatcher.mapper.DefaultActionMapper Maven / Gradle / Ivy

There is a newer version: 6.4.0
Show newest version
/*
 * 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.struts2.dispatcher.mapper;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.config.Configuration;
import com.opensymphony.xwork2.config.ConfigurationManager;
import com.opensymphony.xwork2.config.entities.PackageConfig;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Inject;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.RequestUtils;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.util.PrefixTrie;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.regex.Pattern;

/**
 * 
 * 

* Default action mapper implementation, using the standard *.[ext] (where ext * usually "action") pattern. The extension is looked up from the Struts * configuration key struts.action.extension. *

* *

* To help with dealing with buttons and other related requirements, this * mapper (and other {@link ActionMapper}s, we hope) has the ability to name a * button with some predefined prefix and have that button name alter the * execution behaviour. The four prefixes are: *

* *
    *
  • Method prefix - method:default
  • *
  • Action prefix - action:dashboard
  • *
* *

* In addition to these four prefixes, this mapper also understands the * action naming pattern of foo!bar in either the extension form (eg: * foo!bar.action) or in the prefix form (eg: action:foo!bar). This syntax tells * this mapper to map to the action named foo and the method bar. *

* * Method Prefix * *

* With method-prefix, instead of calling baz action's execute() method (by * default if it isn't overriden in struts.xml to be something else), the baz * action's anotherMethod() will be called. A very elegant way determine which * button is clicked. Alternatively, one would have submit button set a * particular value on the action when clicked, and the execute() method decides * on what to do with the setted value depending on which button is clicked. *

* * *
 *  <!-- START SNIPPET: method-example -->
 *  <s:form action="baz">
 *      <s:textfield label="Enter your name" name="person.name"/>
 *      <s:submit value="Create person"/>
 *      <s:submit method="anotherMethod" value="Cancel"/>
 *  </s:form>
 *  <!-- END SNIPPET: method-example -->
 * 
* Action prefix * *

* With action-prefix, instead of executing baz action's execute() method (by * default if it isn't overridden in struts.xml to be something else), the * anotherAction action's execute() method (assuming again if it isn't overridden * with something else in struts.xml) will be executed. *

* * *
 *  <!-- START SNIPPET: action-example -->
 *  <s:form action="baz">
 *      <s:textfield label="Enter your name" name="person.name"/>
 *      <s:submit value="Create person"/>
 *      <s:submit action="anotherAction" value="Cancel"/>
 *  </s:form>
 *  <!-- END SNIPPET: action-example -->
 * 
*/ public class DefaultActionMapper implements ActionMapper { private static final Logger LOG = LogManager.getLogger(DefaultActionMapper.class); protected static final String METHOD_PREFIX = "method:"; protected static final String ACTION_PREFIX = "action:"; protected boolean allowDynamicMethodCalls = false; protected boolean allowSlashesInActionNames = false; protected boolean alwaysSelectFullNamespace = false; protected PrefixTrie prefixTrie = null; protected Pattern allowedNamespaceNames = Pattern.compile("[a-zA-Z0-9._/\\-]*"); protected String defaultNamespaceName = "/"; protected Pattern allowedActionNames = Pattern.compile("[a-zA-Z0-9._!/\\-]*"); protected String defaultActionName = "index"; protected Pattern allowedMethodNames = Pattern.compile("[a-zA-Z_]*[0-9]*"); protected String defaultMethodName = "execute"; private boolean allowActionPrefix = false; private boolean allowActionCrossNamespaceAccess = false; protected List extensions = new ArrayList() {{ add("action"); add(""); }}; protected Container container; public DefaultActionMapper() { prefixTrie = new PrefixTrie() { { put(METHOD_PREFIX, new ParameterAction() { public void execute(String key, ActionMapping mapping) { if (allowDynamicMethodCalls) { mapping.setMethod(cleanupMethodName(key.substring(METHOD_PREFIX.length()))); } } }); put(ACTION_PREFIX, new ParameterAction() { public void execute(final String key, ActionMapping mapping) { if (allowActionPrefix) { String name = key.substring(ACTION_PREFIX.length()); if (allowDynamicMethodCalls) { int bang = name.indexOf('!'); if (bang != -1) { String method = cleanupMethodName(name.substring(bang + 1)); mapping.setMethod(method); name = name.substring(0, bang); } } String actionName = cleanupActionName(name); if (allowSlashesInActionNames && !allowActionCrossNamespaceAccess) { if (actionName.startsWith("/")) { actionName = actionName.substring(1); } } if (!allowSlashesInActionNames && !allowActionCrossNamespaceAccess) { if (actionName.lastIndexOf('/') != -1) { actionName = actionName.substring(actionName.lastIndexOf('/') + 1); } } mapping.setName(actionName); } } }); } }; } /** * Adds a parameter action. Should only be called during initialization * * @param prefix The string prefix to trigger the action * @param parameterAction The parameter action to execute * @since 2.1.0 */ protected void addParameterAction(String prefix, ParameterAction parameterAction) { prefixTrie.put(prefix, parameterAction); } @Inject(StrutsConstants.STRUTS_ENABLE_DYNAMIC_METHOD_INVOCATION) public void setAllowDynamicMethodCalls(String enableDynamicMethodCalls) { this.allowDynamicMethodCalls = BooleanUtils.toBoolean(enableDynamicMethodCalls); } @Inject(StrutsConstants.STRUTS_ENABLE_SLASHES_IN_ACTION_NAMES) public void setSlashesInActionNames(String enableSlashesInActionNames) { this.allowSlashesInActionNames = BooleanUtils.toBoolean(enableSlashesInActionNames); } @Inject(StrutsConstants.STRUTS_ALWAYS_SELECT_FULL_NAMESPACE) public void setAlwaysSelectFullNamespace(String alwaysSelectFullNamespace) { this.alwaysSelectFullNamespace = BooleanUtils.toBoolean(alwaysSelectFullNamespace); } @Inject(value = StrutsConstants.STRUTS_ALLOWED_NAMESPACE_NAMES, required = false) public void setAllowedNamespaceNames(String allowedNamespaceNames) { this.allowedNamespaceNames = Pattern.compile(allowedNamespaceNames); } @Inject(value = StrutsConstants.STRUTS_DEFAULT_NAMESPACE_NAME, required = false) public void setDefaultNamespaceName(String defaultNamespaceName) { this.defaultNamespaceName = defaultNamespaceName; } @Inject(value = StrutsConstants.STRUTS_ALLOWED_ACTION_NAMES, required = false) public void setAllowedActionNames(String allowedActionNames) { this.allowedActionNames = Pattern.compile(allowedActionNames); } @Inject(value = StrutsConstants.STRUTS_DEFAULT_ACTION_NAME, required = false) public void setDefaultActionName(String defaultActionName) { this.defaultActionName = defaultActionName; } @Inject(value = StrutsConstants.STRUTS_ALLOWED_METHOD_NAMES, required = false) public void setAllowedMethodNames(String allowedMethodNames) { this.allowedMethodNames = Pattern.compile(allowedMethodNames); } @Inject(value = StrutsConstants.STRUTS_DEFAULT_METHOD_NAME, required = false) public void setDefaultMethodName(String defaultMethodName) { this.defaultMethodName = defaultMethodName; } @Inject(value = StrutsConstants.STRUTS_MAPPER_ACTION_PREFIX_ENABLED) public void setAllowActionPrefix(String allowActionPrefix) { this.allowActionPrefix = BooleanUtils.toBoolean(allowActionPrefix); } @Inject(value = StrutsConstants.STRUTS_MAPPER_ACTION_PREFIX_CROSSNAMESPACES) public void setAllowActionCrossNamespaceAccess(String allowActionCrossNamespaceAccess) { this.allowActionCrossNamespaceAccess = BooleanUtils.toBoolean(allowActionCrossNamespaceAccess); } @Inject public void setContainer(Container container) { this.container = container; } @Inject(StrutsConstants.STRUTS_ACTION_EXTENSION) public void setExtensions(String extensions) { if (StringUtils.isNotEmpty(extensions)) { List list = new ArrayList<>(); String[] tokens = extensions.split(","); Collections.addAll(list, tokens); if (extensions.endsWith(",")) { list.add(""); } this.extensions = Collections.unmodifiableList(list); } else { this.extensions = null; } } public ActionMapping getMappingFromActionName(String actionName) { ActionMapping mapping = new ActionMapping(); mapping.setName(actionName); return parseActionName(mapping); } public boolean isSlashesInActionNames() { return allowSlashesInActionNames; } /* * (non-Javadoc) * * @see org.apache.struts2.dispatcher.mapper.ActionMapper#getMapping(javax.servlet.http.HttpServletRequest) */ public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) { ActionMapping mapping = new ActionMapping(); String uri = RequestUtils.getUri(request); int indexOfSemicolon = uri.indexOf(';'); uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri; uri = dropExtension(uri, mapping); if (uri == null) { return null; } parseNameAndNamespace(uri, mapping, configManager); handleSpecialParameters(request, mapping); return parseActionName(mapping); } protected ActionMapping parseActionName(ActionMapping mapping) { if (mapping.getName() == null) { return null; } if (allowDynamicMethodCalls) { // handle "name!method" convention. String name = mapping.getName(); int exclamation = name.lastIndexOf('!'); if (exclamation != -1) { mapping.setName(name.substring(0, exclamation)); mapping.setMethod(name.substring(exclamation + 1)); } } return mapping; } /** * Special parameters, as described in the class-level comment, are searched * for and handled. * * @param request The request * @param mapping The action mapping */ public void handleSpecialParameters(HttpServletRequest request, ActionMapping mapping) { // handle special parameter prefixes. Set uniqueParameters = new HashSet<>(); Map parameterMap = request.getParameterMap(); for (Object o : parameterMap.keySet()) { String key = (String) o; // Strip off the image button location info, if found if (key.endsWith(".x") || key.endsWith(".y")) { key = key.substring(0, key.length() - 2); } // Ensure a parameter doesn't get processed twice if (!uniqueParameters.contains(key)) { ParameterAction parameterAction = (ParameterAction) prefixTrie.get(key); if (parameterAction != null) { parameterAction.execute(key, mapping); uniqueParameters.add(key); break; } } } } /** * Parses the name and namespace from the uri * * @param uri The uri * @param mapping The action mapping to populate * @param configManager configuration manager */ protected void parseNameAndNamespace(String uri, ActionMapping mapping, ConfigurationManager configManager) { String namespace, name; int lastSlash = uri.lastIndexOf('/'); if (lastSlash == -1) { namespace = ""; name = uri; } else if (lastSlash == 0) { // ww-1046, assume it is the root namespace, it will fallback to // default // namespace anyway if not found in root namespace. namespace = "/"; name = uri.substring(lastSlash + 1); } else if (alwaysSelectFullNamespace) { // Simply select the namespace as everything before the last slash namespace = uri.substring(0, lastSlash); name = uri.substring(lastSlash + 1); } else { // Try to find the namespace in those defined, defaulting to "" Configuration config = configManager.getConfiguration(); String prefix = uri.substring(0, lastSlash); namespace = ""; boolean rootAvailable = false; // Find the longest matching namespace, defaulting to the default for (PackageConfig cfg : config.getPackageConfigs().values()) { String ns = cfg.getNamespace(); if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == '/')) { if (ns.length() > namespace.length()) { namespace = ns; } } if ("/".equals(ns)) { rootAvailable = true; } } name = uri.substring(namespace.length() + 1); // Still none found, use root namespace if found if (rootAvailable && "".equals(namespace)) { namespace = "/"; } } if (!allowSlashesInActionNames) { int pos = name.lastIndexOf('/'); if (pos > -1 && pos < name.length() - 1) { name = name.substring(pos + 1); } } mapping.setNamespace(cleanupNamespaceName(namespace)); mapping.setName(cleanupActionName(name)); } /** * Checks namespace name against allowed pattern if not matched returns default namespace * * @param rawNamespace name extracted from URI * @return safe namespace name */ protected String cleanupNamespaceName(final String rawNamespace) { if (allowedNamespaceNames.matcher(rawNamespace).matches()) { return rawNamespace; } else { LOG.warn( "{} did not match allowed namespace names {} - default namespace {} will be used!", rawNamespace, allowedNamespaceNames, defaultNamespaceName ); return defaultNamespaceName; } } /** * Checks action name against allowed pattern if not matched returns default action name * * @param rawActionName action name extracted from URI * @return safe action name */ protected String cleanupActionName(final String rawActionName) { if (allowedActionNames.matcher(rawActionName).matches()) { return rawActionName; } else { LOG.warn("{} did not match allowed action names {} - default action {} will be used!", rawActionName, allowedActionNames, defaultActionName); return defaultActionName; } } /** * Checks method name (when DMI is enabled) against allowed pattern if not matched returns default action name * * @param rawMethodName method name extracted from URI * @return safe method name */ protected String cleanupMethodName(final String rawMethodName) { if (allowedMethodNames.matcher(rawMethodName).matches()) { return rawMethodName; } else { LOG.warn("{} did not match allowed method names {} - default method {} will be used!", rawMethodName, allowedMethodNames, defaultMethodName); return defaultMethodName; } } /** * Drops the extension from the action name, storing it in the mapping for later use * * @param name The action name * @param mapping The action mapping to store the extension in * @return The action name without its extension */ protected String dropExtension(String name, ActionMapping mapping) { if (extensions == null) { return name; } for (String ext : extensions) { if ("".equals(ext)) { // This should also handle cases such as /foo/bar-1.0/description. It is tricky to // distinquish /foo/bar-1.0 but perhaps adding a numeric check in the future could // work int index = name.lastIndexOf('.'); if (index == -1 || name.indexOf('/', index) >= 0) { return name; } } else { String extension = "." + ext; if (name.endsWith(extension)) { name = name.substring(0, name.length() - extension.length()); mapping.setExtension(ext); return name; } } } return null; } /** * @return null if no extension is specified. */ protected String getDefaultExtension() { if (extensions == null) { return null; } else { return extensions.get(0); } } /* * (non-Javadoc) * * @see org.apache.struts2.dispatcher.mapper.ActionMapper#getUriFromActionMapping(org.apache.struts2.dispatcher.mapper.ActionMapping) */ public String getUriFromActionMapping(ActionMapping mapping) { StringBuilder uri = new StringBuilder(); handleNamespace(mapping, uri); handleName(mapping, uri); handleDynamicMethod(mapping, uri); handleExtension(mapping, uri); handleParams(mapping, uri); return uri.toString(); } protected void handleNamespace(ActionMapping mapping, StringBuilder uri) { if (mapping.getNamespace() != null) { uri.append(mapping.getNamespace()); if (!"/".equals(mapping.getNamespace())) { uri.append("/"); } } } protected void handleName(ActionMapping mapping, StringBuilder uri) { String name = mapping.getName(); if (name.indexOf('?') != -1) { name = name.substring(0, name.indexOf('?')); } uri.append(name); } protected void handleDynamicMethod(ActionMapping mapping, StringBuilder uri) { // See WW-3965 if (StringUtils.isNotEmpty(mapping.getMethod())) { if (allowDynamicMethodCalls) { // handle "name!method" convention. String name = mapping.getName(); if (!name.contains("!")) { // Append the method as no bang found uri.append("!").append(mapping.getMethod()); } } else { uri.append("!").append(mapping.getMethod()); } } } protected void handleExtension(ActionMapping mapping, StringBuilder uri) { String extension = lookupExtension(mapping.getExtension()); if (extension != null) { if (extension.length() == 0 || (extension.length() > 0 && uri.indexOf('.' + extension) == -1)) { if (extension.length() > 0) { uri.append(".").append(extension); } } } } protected String lookupExtension(String extension) { if (extension == null) { // Look for the current extension, if available ActionContext context = ActionContext.getContext(); if (context != null) { ActionMapping orig = (ActionMapping) context.get(ServletActionContext.ACTION_MAPPING); if (orig != null) { extension = orig.getExtension(); } } if (extension == null) { extension = getDefaultExtension(); } } return extension; } protected void handleParams(ActionMapping mapping, StringBuilder uri) { String name = mapping.getName(); String params = ""; if (name.indexOf('?') != -1) { params = name.substring(name.indexOf('?')); } if (params.length() > 0) { uri.append(params); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy