blob: b2951ad5891bea99f4c2ce155e139cb762822f4e [file] [log] [blame]
/*
* 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;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.ActionProxyFactory;
import com.opensymphony.xwork2.FileManager;
import com.opensymphony.xwork2.FileManagerFactory;
import com.opensymphony.xwork2.LocaleProviderFactory;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.Result;
import com.opensymphony.xwork2.config.Configuration;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.config.ConfigurationManager;
import com.opensymphony.xwork2.config.ConfigurationProvider;
import com.opensymphony.xwork2.config.FileManagerFactoryProvider;
import com.opensymphony.xwork2.config.FileManagerProvider;
import com.opensymphony.xwork2.config.ServletContextAwareConfigurationProvider;
import com.opensymphony.xwork2.config.entities.InterceptorMapping;
import com.opensymphony.xwork2.config.entities.InterceptorStackConfig;
import com.opensymphony.xwork2.config.entities.PackageConfig;
import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.ContainerBuilder;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.interceptor.Interceptor;
import com.opensymphony.xwork2.util.ClassLoaderUtil;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.ValueStackFactory;
import com.opensymphony.xwork2.util.location.LocatableProperties;
import com.opensymphony.xwork2.util.location.Location;
import com.opensymphony.xwork2.util.location.LocationUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.LocaleUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.StrutsException;
import org.apache.struts2.StrutsStatics;
import org.apache.struts2.config.DefaultPropertiesProvider;
import org.apache.struts2.config.PropertiesConfigurationProvider;
import org.apache.struts2.config.StrutsBeanSelectionProvider;
import org.apache.struts2.config.StrutsJavaConfiguration;
import org.apache.struts2.config.StrutsJavaConfigurationProvider;
import org.apache.struts2.config.StrutsXmlConfigurationProvider;
import org.apache.struts2.dispatcher.mapper.ActionMapping;
import org.apache.struts2.dispatcher.multipart.MultiPartRequest;
import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
import org.apache.struts2.util.AttributeMap;
import org.apache.struts2.util.ObjectFactoryDestroyable;
import org.apache.struts2.util.fs.JBossFileManager;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Pattern;
/**
* A utility class the actual dispatcher delegates most of its tasks to. Each instance
* of the primary dispatcher holds an instance of this dispatcher to be shared for
* all requests.
*
* @see InitOperations
*/
public class Dispatcher {
/**
* Provide a logging instance.
*/
private static final Logger LOG = LogManager.getLogger(Dispatcher.class);
/**
* {@link HttpServletRequest#getMethod()}
*/
public static final String REQUEST_POST_METHOD = "POST";
public static final String MULTIPART_FORM_DATA_REGEX = "^multipart/form-data(?:\\s*;\\s*boundary=[0-9a-zA-Z'()+_,\\-./:=?]{1,70})?(?:\\s*;\\s*charset=[a-zA-Z\\-0-9]{3,14})?";
/**
* Provide a thread local instance.
*/
private static ThreadLocal<Dispatcher> instance = new ThreadLocal<>();
/**
* Store list of DispatcherListeners.
*/
private static List<DispatcherListener> dispatcherListeners = new CopyOnWriteArrayList<>();
/**
* Store state of StrutsConstants.STRUTS_DEVMODE setting.
*/
private boolean devMode;
/**
* Store state of StrutsConstants.DISABLE_REQUEST_ATTRIBUTE_VALUE_STACK_LOOKUP setting.
*/
private boolean disableRequestAttributeValueStackLookup;
/**
* Store state of StrutsConstants.STRUTS_I18N_ENCODING setting.
*/
private String defaultEncoding;
/**
* Store state of StrutsConstants.STRUTS_LOCALE setting.
*/
private String defaultLocale;
/**
* Store state of StrutsConstants.STRUTS_MULTIPART_SAVEDIR setting.
*/
private String multipartSaveDir;
/**
* Stores the value of {@link StrutsConstants#STRUTS_MULTIPART_PARSER} setting
*/
private String multipartHandlerName;
/**
* Stores the value of {@link StrutsConstants#STRUTS_MULTIPART_ENABLED}
*/
private boolean multipartSupportEnabled = true;
/**
* A regular expression used to validate if request is a multipart/form-data request
*/
private Pattern multipartValidationPattern = Pattern.compile(MULTIPART_FORM_DATA_REGEX);
/**
* Provide list of default configuration files.
*/
private static final String DEFAULT_CONFIGURATION_PATHS = "struts-default.xml,struts-plugin.xml,struts.xml";
/**
* <p>
* Store state of STRUTS_DISPATCHER_PARAMETERSWORKAROUND.
* </p>
*
* <p>
* The workaround is for WebLogic.
* We try to autodetect WebLogic on Dispatcher init.
* The workaround can also be enabled manually.
* </p>
*/
private boolean paramsWorkaroundEnabled = false;
/**
* Indicates if Dispatcher should handle exception and call sendError()
* Introduced to allow integration with other frameworks like Spring Security
*/
private boolean handleException;
/**
* Interface used to handle internal errors or missing resources
*/
private DispatcherErrorHandler errorHandler;
/**
* Store ConfigurationManager instance, set on init.
*/
protected ConfigurationManager configurationManager;
/**
* Provide the dispatcher instance for the current thread.
*
* @return The dispatcher instance
*/
public static Dispatcher getInstance() {
return instance.get();
}
/**
* Store the dispatcher instance for this thread.
*
* @param instance The instance
*/
public static void setInstance(Dispatcher instance) {
Dispatcher.instance.set(instance);
}
/**
* Add a dispatcher lifecycle listener.
*
* @param listener The listener to add
*/
public static void addDispatcherListener(DispatcherListener listener) {
dispatcherListeners.add(listener);
}
/**
* Remove a specific dispatcher lifecycle listener.
*
* @param listener The listener
*/
public static void removeDispatcherListener(DispatcherListener listener) {
dispatcherListeners.remove(listener);
}
private ValueStackFactory valueStackFactory;
/**
* Keeps current reference to external world and must be protected to support class inheritance
*/
protected ServletContext servletContext;
protected Map<String, String> initParams;
/**
* Create the Dispatcher instance for a given ServletContext and set of initialization parameters.
*
* @param servletContext Our servlet context
* @param initParams The set of initialization parameters
*/
public Dispatcher(ServletContext servletContext, Map<String, String> initParams) {
this.servletContext = servletContext;
this.initParams = initParams;
}
public static Dispatcher getInstance(ServletContext servletContext) {
return (Dispatcher) servletContext.getAttribute(StrutsStatics.SERVLET_DISPATCHER);
}
/**
* Modify state of StrutsConstants.STRUTS_DEVMODE setting.
*
* @param mode New setting
*/
@Inject(StrutsConstants.STRUTS_DEVMODE)
public void setDevMode(String mode) {
devMode = Boolean.parseBoolean(mode);
}
public boolean isDevMode() {
return devMode;
}
/**
* Modify state of StrutsConstants.DISABLE_REQUEST_ATTRIBUTE_VALUE_STACK_LOOKUP setting.
*
* @param disableRequestAttributeValueStackLookup New setting
*/
@Inject(value = StrutsConstants.STRUTS_DISABLE_REQUEST_ATTRIBUTE_VALUE_STACK_LOOKUP, required = false)
public void setDisableRequestAttributeValueStackLookup(String disableRequestAttributeValueStackLookup) {
this.disableRequestAttributeValueStackLookup = BooleanUtils.toBoolean(disableRequestAttributeValueStackLookup);
}
/**
* Modify state of StrutsConstants.STRUTS_LOCALE setting.
*
* @param val New setting
*/
@Inject(value = StrutsConstants.STRUTS_LOCALE, required = false)
public void setDefaultLocale(String val) {
defaultLocale = val;
}
/**
* Modify state of StrutsConstants.STRUTS_I18N_ENCODING setting.
*
* @param val New setting
*/
@Inject(StrutsConstants.STRUTS_I18N_ENCODING)
public void setDefaultEncoding(String val) {
defaultEncoding = val;
}
/**
* Modify state of StrutsConstants.STRUTS_MULTIPART_SAVEDIR setting.
*
* @param val New setting
*/
@Inject(StrutsConstants.STRUTS_MULTIPART_SAVEDIR)
public void setMultipartSaveDir(String val) {
multipartSaveDir = val;
}
@Inject(StrutsConstants.STRUTS_MULTIPART_PARSER)
public void setMultipartHandler(String val) {
multipartHandlerName = val;
}
@Inject(value = StrutsConstants.STRUTS_MULTIPART_ENABLED, required = false)
public void setMultipartSupportEnabled(String multipartSupportEnabled) {
this.multipartSupportEnabled = Boolean.parseBoolean(multipartSupportEnabled);
}
@Inject(value = StrutsConstants.STRUTS_MULTIPART_VALIDATION_REGEX, required = false)
public void setMultipartValidationRegex(String multipartValidationRegex) {
this.multipartValidationPattern = Pattern.compile(multipartValidationRegex);
}
@Inject
public void setValueStackFactory(ValueStackFactory valueStackFactory) {
this.valueStackFactory = valueStackFactory;
}
@Inject(StrutsConstants.STRUTS_HANDLE_EXCEPTION)
public void setHandleException(String handleException) {
this.handleException = Boolean.parseBoolean(handleException);
}
public boolean isHandleException() {
return handleException;
}
@Inject
public void setDispatcherErrorHandler(DispatcherErrorHandler errorHandler) {
this.errorHandler = errorHandler;
}
/**
* Releases all instances bound to this dispatcher instance.
*/
public void cleanup() {
// clean up ObjectFactory
ObjectFactory objectFactory = getContainer().getInstance(ObjectFactory.class);
if (objectFactory == null) {
LOG.warn("Object Factory is null, something is seriously wrong, no clean up will be performed");
}
if (objectFactory instanceof ObjectFactoryDestroyable) {
try {
((ObjectFactoryDestroyable) objectFactory).destroy();
} catch (Exception e) {
// catch any exception that may occurred during destroy() and log it
LOG.error("Exception occurred while destroying ObjectFactory [{}]", objectFactory.toString(), e);
}
}
// clean up Dispatcher itself for this thread
instance.set(null);
servletContext.setAttribute(StrutsStatics.SERVLET_DISPATCHER, null);
// clean up DispatcherListeners
if (!dispatcherListeners.isEmpty()) {
for (DispatcherListener l : dispatcherListeners) {
l.dispatcherDestroyed(this);
}
}
// clean up all interceptors by calling their destroy() method
Set<Interceptor> interceptors = new HashSet<>();
Collection<PackageConfig> packageConfigs = configurationManager.getConfiguration().getPackageConfigs().values();
for (PackageConfig packageConfig : packageConfigs) {
for (Object config : packageConfig.getAllInterceptorConfigs().values()) {
if (config instanceof InterceptorStackConfig) {
for (InterceptorMapping interceptorMapping : ((InterceptorStackConfig) config).getInterceptors()) {
interceptors.add(interceptorMapping.getInterceptor());
}
}
}
}
for (Interceptor interceptor : interceptors) {
interceptor.destroy();
}
// Clear container holder when application is unloaded / server shutdown
ContainerHolder.clear();
//cleanup action context
ActionContext.clear();
// clean up configuration
configurationManager.destroyConfiguration();
configurationManager = null;
}
private void init_FileManager() throws ClassNotFoundException {
if (initParams.containsKey(StrutsConstants.STRUTS_FILE_MANAGER)) {
final String fileManagerClassName = initParams.get(StrutsConstants.STRUTS_FILE_MANAGER);
final Class<FileManager> fileManagerClass = (Class<FileManager>) Class.forName(fileManagerClassName);
LOG.info("Custom FileManager specified: {}", fileManagerClassName);
configurationManager.addContainerProvider(new FileManagerProvider(fileManagerClass, fileManagerClass.getSimpleName()));
} else {
// add any other Struts 2 provided implementations of FileManager
configurationManager.addContainerProvider(new FileManagerProvider(JBossFileManager.class, "jboss"));
}
if (initParams.containsKey(StrutsConstants.STRUTS_FILE_MANAGER_FACTORY)) {
final String fileManagerFactoryClassName = initParams.get(StrutsConstants.STRUTS_FILE_MANAGER_FACTORY);
final Class<FileManagerFactory> fileManagerFactoryClass = (Class<FileManagerFactory>) Class.forName(fileManagerFactoryClassName);
LOG.info("Custom FileManagerFactory specified: {}", fileManagerFactoryClassName);
configurationManager.addContainerProvider(new FileManagerFactoryProvider(fileManagerFactoryClass));
}
}
private void init_DefaultProperties() {
configurationManager.addContainerProvider(new DefaultPropertiesProvider());
}
private void init_LegacyStrutsProperties() {
configurationManager.addContainerProvider(new PropertiesConfigurationProvider());
}
private void init_TraditionalXmlConfigurations() {
String configPaths = initParams.get("config");
if (configPaths == null) {
configPaths = DEFAULT_CONFIGURATION_PATHS;
}
String[] files = configPaths.split("\\s*[,]\\s*");
for (String file : files) {
if (file.endsWith(".xml")) {
configurationManager.addContainerProvider(createStrutsXmlConfigurationProvider(file, false, servletContext));
} else {
throw new IllegalArgumentException("Invalid configuration file name");
}
}
}
protected XmlConfigurationProvider createStrutsXmlConfigurationProvider(String filename, boolean errorIfMissing, ServletContext ctx) {
return new StrutsXmlConfigurationProvider(filename, errorIfMissing, ctx);
}
private void init_JavaConfigurations() {
String configClasses = initParams.get("javaConfigClasses");
if (configClasses != null) {
String[] classes = configClasses.split("\\s*[,]\\s*");
for (String cname : classes) {
try {
Class cls = ClassLoaderUtil.loadClass(cname, this.getClass());
StrutsJavaConfiguration config = (StrutsJavaConfiguration) cls.newInstance();
configurationManager.addContainerProvider(createJavaConfigurationProvider(config));
} catch (InstantiationException e) {
throw new ConfigurationException("Unable to instantiate java configuration: " + cname, e);
} catch (IllegalAccessException e) {
throw new ConfigurationException("Unable to access java configuration: " + cname, e);
} catch (ClassNotFoundException e) {
throw new ConfigurationException("Unable to locate java configuration class: " + cname, e);
}
}
}
}
protected StrutsJavaConfigurationProvider createJavaConfigurationProvider(StrutsJavaConfiguration config) {
return new StrutsJavaConfigurationProvider(config);
}
private void init_CustomConfigurationProviders() {
String configProvs = initParams.get("configProviders");
if (configProvs != null) {
String[] classes = configProvs.split("\\s*[,]\\s*");
for (String cname : classes) {
try {
Class cls = ClassLoaderUtil.loadClass(cname, this.getClass());
ConfigurationProvider prov = (ConfigurationProvider) cls.newInstance();
if (prov instanceof ServletContextAwareConfigurationProvider) {
((ServletContextAwareConfigurationProvider) prov).initWithContext(servletContext);
}
configurationManager.addContainerProvider(prov);
} catch (InstantiationException e) {
throw new ConfigurationException("Unable to instantiate provider: " + cname, e);
} catch (IllegalAccessException e) {
throw new ConfigurationException("Unable to access provider: " + cname, e);
} catch (ClassNotFoundException e) {
throw new ConfigurationException("Unable to locate provider class: " + cname, e);
}
}
}
}
private void init_FilterInitParameters() {
configurationManager.addContainerProvider(new ConfigurationProvider() {
public void destroy() {
}
public void init(Configuration configuration) throws ConfigurationException {
}
public void loadPackages() throws ConfigurationException {
}
public boolean needsReload() {
return false;
}
public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException {
props.putAll(initParams);
}
});
}
private void init_AliasStandardObjects() {
configurationManager.addContainerProvider(new StrutsBeanSelectionProvider());
}
private Container init_PreloadConfiguration() {
return getContainer();
}
private void init_CheckWebLogicWorkaround(Container container) {
// test whether param-access workaround needs to be enabled
if (servletContext != null && StringUtils.contains(servletContext.getServerInfo(), "WebLogic")) {
LOG.info("WebLogic server detected. Enabling Struts parameter access work-around.");
paramsWorkaroundEnabled = true;
} else {
paramsWorkaroundEnabled = "true".equals(container.getInstance(String.class,
StrutsConstants.STRUTS_DISPATCHER_PARAMETERSWORKAROUND));
}
}
/**
* Load configurations, including both XML and zero-configuration strategies,
* and update optional settings, including whether to reload configurations and resource files.
*/
public void init() {
if (configurationManager == null) {
configurationManager = createConfigurationManager(Container.DEFAULT_NAME);
}
try {
init_FileManager();
init_DefaultProperties(); // [1]
init_TraditionalXmlConfigurations(); // [2]
init_JavaConfigurations();
init_LegacyStrutsProperties(); // [3]
init_CustomConfigurationProviders(); // [5]
init_FilterInitParameters(); // [6]
init_AliasStandardObjects(); // [7]
Container container = init_PreloadConfiguration();
container.inject(this);
init_CheckWebLogicWorkaround(container);
if (!dispatcherListeners.isEmpty()) {
for (DispatcherListener l : dispatcherListeners) {
l.dispatcherInitialized(this);
}
}
errorHandler.init(servletContext);
if (servletContext.getAttribute(StrutsStatics.SERVLET_DISPATCHER) == null) {
servletContext.setAttribute(StrutsStatics.SERVLET_DISPATCHER, this);
}
} catch (Exception ex) {
LOG.error("Dispatcher initialization failed", ex);
throw new StrutsException(ex);
}
}
protected ConfigurationManager createConfigurationManager(String name) {
return new ConfigurationManager(name);
}
/**
* <p>
* Load Action class for mapping and invoke the appropriate Action method, or go directly to the Result.
* </p>
*
* <p>
* This method first creates the action context from the given parameters,
* and then loads an <tt>ActionProxy</tt> from the given action name and namespace.
* After that, the Action method is executed and output channels through the response object.
* Actions not found are sent back to the user via the {@link Dispatcher#sendError} method,
* using the 404 return code.
* All other errors are reported by throwing a ServletException.
* </p>
*
* @param request the HttpServletRequest object
* @param response the HttpServletResponse object
* @param mapping the action mapping object
* @throws ServletException when an unknown error occurs (not a 404, but typically something that
* would end up as a 5xx by the servlet container)
* @since 2.3.17
*/
public void serviceAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping)
throws ServletException {
Map<String, Object> extraContext = createContextMap(request, response, mapping);
// If there was a previous value stack, then create a new copy and pass it in to be used by the new Action
ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
boolean nullStack = stack == null;
if (nullStack) {
ActionContext ctx = ActionContext.getContext();
if (ctx != null) {
stack = ctx.getValueStack();
}
}
if (stack != null) {
extraContext = ActionContext.of(extraContext)
.withValueStack(valueStackFactory.createValueStack(stack))
.getContextMap();
}
try {
String namespace = mapping.getNamespace();
String name = mapping.getName();
String method = mapping.getMethod();
ActionProxy proxy;
//check if we are probably in an async resuming
ActionInvocation invocation = ActionContext.getContext().getActionInvocation();
if (invocation == null || invocation.isExecuted()) {
proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy(namespace, name, method,
extraContext, true, false);
} else {
proxy = invocation.getProxy();
}
request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
// if the ActionMapping says to go straight to a result, do it!
if (mapping.getResult() != null) {
Result result = mapping.getResult();
result.execute(proxy.getInvocation());
} else {
proxy.execute();
}
// If there was a previous value stack then set it back onto the request
if (!nullStack) {
request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
}
} catch (ConfigurationException e) {
logConfigurationException(request, e);
sendError(request, response, HttpServletResponse.SC_NOT_FOUND, e);
} catch (Exception e) {
if (handleException || devMode) {
if (devMode) {
LOG.debug("Dispatcher serviceAction failed", e);
}
sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
} else {
throw new ServletException(e);
}
}
}
/**
* Performs logging of missing action/result configuration exception
*
* @param request current {@link HttpServletRequest}
* @param e {@link ConfigurationException} that occurred
*/
protected void logConfigurationException(HttpServletRequest request, ConfigurationException e) {
// WW-2874 Only log error if in devMode
String uri = request.getRequestURI();
if (request.getQueryString() != null) {
uri = uri + "?" + request.getQueryString();
}
if (devMode) {
LOG.error("Could not find action or result: {}", uri, e);
} else if (LOG.isWarnEnabled()) {
LOG.warn("Could not find action or result: {}", uri, e);
}
}
/**
* Create a context map containing all the wrapped request objects
*
* @param request The servlet request
* @param response The servlet response
* @param mapping The action mapping
* @return A map of context objects
* @since 2.3.17
*/
public Map<String, Object> createContextMap(HttpServletRequest request, HttpServletResponse response,
ActionMapping mapping) {
// request map wrapping the http request objects
Map requestMap = new RequestMap(request);
// parameters map wrapping the http parameters. ActionMapping parameters are now handled and applied separately
HttpParameters params = HttpParameters.create(request.getParameterMap()).build();
// session map wrapping the http session
Map session = new SessionMap(request);
// application map wrapping the ServletContext
Map application = new ApplicationMap(servletContext);
Map<String, Object> extraContext = createContextMap(requestMap, params, session, application, request, response);
if (mapping != null) {
extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);
}
return extraContext;
}
/**
* Merge all application and servlet attributes into a single <tt>HashMap</tt> to represent the entire
* <tt>Action</tt> context.
*
* @param requestMap a Map of all request attributes.
* @param parameters an Object of all request parameters.
* @param sessionMap a Map of all session attributes.
* @param applicationMap a Map of all servlet context attributes.
* @param request the HttpServletRequest object.
* @param response the HttpServletResponse object.
* @return a HashMap representing the <tt>Action</tt> context.
* @since 2.3.17
*/
public Map<String, Object> createContextMap(Map<String, Object> requestMap,
HttpParameters parameters,
Map<String, Object> sessionMap,
Map<String, Object> applicationMap,
HttpServletRequest request,
HttpServletResponse response) {
Map<String, Object> extraContext = ActionContext.of(new HashMap<>())
.withParameters(parameters)
.withSession(sessionMap)
.withApplication(applicationMap)
.withLocale(getLocale(request))
.withServletRequest(request)
.withServletResponse(response)
.withServletContext(servletContext)
// helpers to get access to request/session/application scope
.with("request", requestMap)
.with("session", sessionMap)
.with("application", applicationMap)
.with("parameters", parameters)
.getContextMap();
AttributeMap attrMap = new AttributeMap(extraContext);
extraContext.put("attr", attrMap);
return extraContext;
}
protected Locale getLocale(HttpServletRequest request) {
Locale locale;
if (defaultLocale != null) {
try {
locale = LocaleUtils.toLocale(defaultLocale);
} catch (IllegalArgumentException e) {
LOG.warn(new ParameterizedMessage("Cannot convert 'struts.locale' = [{}] to proper locale, defaulting to request locale [{}]",
defaultLocale, request.getLocale()), e);
locale = request.getLocale();
}
} else {
locale = request.getLocale();
}
return locale;
}
/**
* Return the path to save uploaded files to (this is configurable).
*
* @return the path to save uploaded files to
*/
protected String getSaveDir() {
String saveDir = multipartSaveDir.trim();
if (saveDir.equals("")) {
File tempdir = (File) servletContext.getAttribute("javax.servlet.context.tempdir");
LOG.info("Unable to find 'struts.multipart.saveDir' property setting. Defaulting to javax.servlet.context.tempdir");
if (tempdir != null) {
saveDir = tempdir.toString();
setMultipartSaveDir(saveDir);
}
} else {
File multipartSaveDir = new File(saveDir);
if (!multipartSaveDir.exists()) {
if (!multipartSaveDir.mkdirs()) {
String logMessage;
try {
logMessage = "Could not find create multipart save directory '" + multipartSaveDir.getCanonicalPath() + "'.";
} catch (IOException e) {
logMessage = "Could not find create multipart save directory '" + multipartSaveDir.toString() + "'.";
}
if (devMode) {
LOG.error(logMessage);
} else {
LOG.warn(logMessage);
}
}
}
}
LOG.debug("saveDir={}", saveDir);
return saveDir;
}
/**
* Prepare a request, including setting the encoding and locale.
*
* @param request The request
* @param response The response
*/
public void prepare(HttpServletRequest request, HttpServletResponse response) {
String encoding = null;
if (defaultEncoding != null) {
encoding = defaultEncoding;
}
// check for Ajax request to use UTF-8 encoding strictly http://www.w3.org/TR/XMLHttpRequest/#the-send-method
if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
encoding = "UTF-8";
}
Locale locale = getLocale(request);
if (encoding != null) {
applyEncoding(request, encoding);
}
if (locale != null) {
response.setLocale(locale);
}
if (paramsWorkaroundEnabled) {
request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request
}
}
private void applyEncoding(HttpServletRequest request, String encoding) {
try {
if (!encoding.equals(request.getCharacterEncoding())) {
// if the encoding is already correctly set and the parameters have been already read
// do not try to set encoding because it is useless and will cause an error
request.setCharacterEncoding(encoding);
}
} catch (Exception e) {
LOG.error("Error setting character encoding to '{}' - ignoring.", encoding, e);
}
}
/**
* <p>
* Wrap and return the given request or return the original request object.
* </p>
*
* <p>
* This method transparently handles multipart data as a wrapped class around the given request.
* Override this method to handle multipart requests in a special way or to handle other types of requests.
* Note, {@link org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper} is
* flexible - look first to that object before overriding this method to handle multipart data.
* </p>
*
* @param request the HttpServletRequest object.
* @return a wrapped request or original request.
* @throws java.io.IOException on any error.
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper
* @since 2.3.17
*/
public HttpServletRequest wrapRequest(HttpServletRequest request) throws IOException {
// don't wrap more than once
if (request instanceof StrutsRequestWrapper) {
return request;
}
if (isMultipartSupportEnabled(request) && isMultipartRequest(request)) {
MultiPartRequest multiPartRequest = getMultiPartRequest();
LocaleProviderFactory localeProviderFactory = getContainer().getInstance(LocaleProviderFactory.class);
request = new MultiPartRequestWrapper(
multiPartRequest,
request,
getSaveDir(),
localeProviderFactory.createLocaleProvider(),
disableRequestAttributeValueStackLookup
);
} else {
request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup);
}
return request;
}
/**
* Checks if support to parse multipart requests is enabled
*
* @param request current servlet request
* @return false if disabled
* @since 2.5.11
*/
protected boolean isMultipartSupportEnabled(HttpServletRequest request) {
return multipartSupportEnabled;
}
/**
* Checks if request is a multipart request (a file upload request)
*
* @param request current servlet request
* @return true if it is a multipart request
* @since 2.5.11
*/
protected boolean isMultipartRequest(HttpServletRequest request) {
String httpMethod = request.getMethod();
String contentType = request.getContentType();
return REQUEST_POST_METHOD.equalsIgnoreCase(httpMethod) &&
contentType != null &&
multipartValidationPattern.matcher(contentType.toLowerCase(Locale.ENGLISH)).matches();
}
/**
* On each request it must return a new instance as implementation could be not thread safe
* and thus ensure of resource clean up
*
* @return a multi part request object
*/
protected MultiPartRequest getMultiPartRequest() {
MultiPartRequest mpr = null;
//check for alternate implementations of MultiPartRequest
Set<String> multiNames = getContainer().getInstanceNames(MultiPartRequest.class);
for (String multiName : multiNames) {
if (multiName.equals(multipartHandlerName)) {
mpr = getContainer().getInstance(MultiPartRequest.class, multiName);
}
}
if (mpr == null) {
mpr = getContainer().getInstance(MultiPartRequest.class);
}
return mpr;
}
/**
* Removes all the files created by MultiPartRequestWrapper.
*
* @param request the HttpServletRequest object.
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper
*/
public void cleanUpRequest(HttpServletRequest request) {
ContainerHolder.clear();
if (!(request instanceof MultiPartRequestWrapper)) {
return;
}
MultiPartRequestWrapper multiWrapper = (MultiPartRequestWrapper) request;
multiWrapper.cleanUp();
}
/**
* Send an HTTP error response code.
*
* @param request the HttpServletRequest object.
* @param response the HttpServletResponse object.
* @param code the HttpServletResponse error code (see {@link javax.servlet.http.HttpServletResponse} for possible error codes).
* @param e the Exception that is reported.
* @since 2.3.17
*/
public void sendError(HttpServletRequest request, HttpServletResponse response, int code, Exception e) {
errorHandler.handleError(request, response, code, e);
}
/**
* Cleanup any resources used to initialise Dispatcher
*/
public void cleanUpAfterInit() {
if (LOG.isDebugEnabled()) {
LOG.debug("Cleaning up resources used to init Dispatcher");
}
ContainerHolder.clear();
}
/**
* Provide an accessor class for static XWork utility.
*/
public static class Locator {
public Location getLocation(Object obj) {
Location loc = LocationUtils.getLocation(obj);
if (loc == null) {
return Location.UNKNOWN;
}
return loc;
}
}
/**
* Expose the ConfigurationManager instance.
*
* @return The instance
*/
public ConfigurationManager getConfigurationManager() {
return configurationManager;
}
/**
* Expose the dependency injection container.
*
* @return Our dependency injection container
*/
public Container getContainer() {
if (ContainerHolder.get() != null) {
return ContainerHolder.get();
}
ConfigurationManager mgr = getConfigurationManager();
if (mgr == null) {
throw new IllegalStateException("The configuration manager shouldn't be null");
} else {
Configuration config = mgr.getConfiguration();
if (config == null) {
throw new IllegalStateException("Unable to load configuration");
} else {
Container container = config.getContainer();
ContainerHolder.store(container);
return container;
}
}
}
}