View Javadoc

    1   /*
    2    * $Id: Dispatcher.java 508882 2007-02-18 06:42:39Z tschneider $
    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.struts2.dispatcher;
    22
    23  import java.io.File;
    24  import java.io.IOException;
    25  import java.net.MalformedURLException;
    26  import java.net.URL;
    27  import java.util.ArrayList;
    28  import java.util.HashMap;
    29  import java.util.List;
    30  import java.util.Locale;
    31  import java.util.Map;
    32
    33  import javax.servlet.ServletContext;
    34  import javax.servlet.ServletException;
    35  import javax.servlet.http.HttpServletRequest;
    36  import javax.servlet.http.HttpServletResponse;
    37
    38  import org.apache.commons.logging.Log;
    39  import org.apache.commons.logging.LogFactory;
    40  import org.apache.struts2.ServletActionContext;
    41  import org.apache.struts2.StrutsConstants;
    42  import org.apache.struts2.StrutsStatics;
    43  import org.apache.struts2.config.*;
    44  import org.apache.struts2.config.ClasspathConfigurationProvider.ClasspathPageLocator;
    45  import org.apache.struts2.config.ClasspathConfigurationProvider.PageLocator;
    46  import org.apache.struts2.dispatcher.mapper.ActionMapping;
    47  import org.apache.struts2.dispatcher.multipart.MultiPartRequest;
    48  import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
    49  import org.apache.struts2.util.AttributeMap;
    50  import org.apache.struts2.util.ClassLoaderUtils;
    51  import org.apache.struts2.util.ObjectFactoryDestroyable;
    52  import org.apache.struts2.views.freemarker.FreemarkerManager;
    53
    54  import com.opensymphony.xwork2.util.FileManager;
    55  import com.opensymphony.xwork2.ActionContext;
    56  import com.opensymphony.xwork2.ActionProxy;
    57  import com.opensymphony.xwork2.ActionProxyFactory;
    58  import com.opensymphony.xwork2.ObjectFactory;
    59  import com.opensymphony.xwork2.Result;
    60  import com.opensymphony.xwork2.config.Configuration;
    61  import com.opensymphony.xwork2.config.ConfigurationException;
    62  import com.opensymphony.xwork2.config.ConfigurationManager;
    63  import com.opensymphony.xwork2.config.ConfigurationProvider;
    64  import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider;
    65  import com.opensymphony.xwork2.inject.Container;
    66  import com.opensymphony.xwork2.inject.ContainerBuilder;
    67  import com.opensymphony.xwork2.inject.Inject;
    68  import com.opensymphony.xwork2.util.LocalizedTextUtil;
    69  import com.opensymphony.xwork2.util.ObjectTypeDeterminer;
    70  import com.opensymphony.xwork2.util.ObjectTypeDeterminerFactory;
    71  import com.opensymphony.xwork2.util.ValueStack;
    72  import com.opensymphony.xwork2.util.ValueStackFactory;
    73  import com.opensymphony.xwork2.util.location.Location;
    74  import com.opensymphony.xwork2.util.location.LocationUtils;
    75  import com.opensymphony.xwork2.util.location.LocatableProperties;
    76  import com.opensymphony.xwork2.util.profiling.UtilTimerStack;
    77
    78  import freemarker.template.Template;
    79
    80  /***
    81   * A utility class the actual dispatcher delegates most of its tasks to. Each instance
    82   * of the primary dispatcher holds an instance of this dispatcher to be shared for
    83   * all requests.
    84   *
    85   * @see org.apache.struts2.dispatcher.FilterDispatcher
    86   * @see org.apache.struts2.portlet.dispatcher.Jsr168Dispatcher
    87   */
    88  public class Dispatcher {
    89
    90      /***
    91       * Provide a logging instance.
    92       */
    93      private static final Log LOG = LogFactory.getLog(Dispatcher.class);
    94
    95      /***
    96       * Provide a thread local instance.
    97       */
    98      private static ThreadLocal<Dispatcher> instance = new ThreadLocal<Dispatcher>();
    99
    100     /***
    101      * Store list of DispatcherListeners.
    102      */
    103     private static List<DispatcherListener> dispatcherListeners =
    104         new ArrayList<DispatcherListener>();
    105
    106     /***
    107      * Store ConfigurationManager instance, set on init.
    108      */
    109     private ConfigurationManager configurationManager;
    110
    111     /***
    112      * Store whether portlet support is active
    113      * (set to true by Jsr168Dispatcher).
    114      */
    115     private static boolean portletSupportActive;
    116
    117     /***
    118      * Store state of  StrutsConstants.STRUTS_DEVMODE setting.
    119      */
    120     private static boolean devMode;
    121
    122     /***
    123      * Store state of StrutsConstants.STRUTS_I18N_ENCODING setting.
    124      */
    125     private static String defaultEncoding;
    126
    127     /***
    128      * Store state of StrutsConstants.STRUTS_LOCALE setting.
    129      */
    130     private static String defaultLocale;
    131
    132     /***
    133      * Store state of StrutsConstants.STRUTS_MULTIPART_SAVEDIR setting.
    134      */
    135     private static String multipartSaveDir;
    136
    137     /***
    138      * Provide list of default configuration files.
    139      */
    140     private static final String DEFAULT_CONFIGURATION_PATHS = "struts-default.xml,struts-plugin.xml,struts.xml";
    141
    142     /***
    143      * Store state of STRUTS_DISPATCHER_PARAMETERSWORKAROUND.
    144      * <p/>
    145      * The workaround is for WebLogic.
    146      * We try to autodect WebLogic on Dispatcher init.
    147      * The workaround can also be enabled manually.
    148      */
    149     private boolean paramsWorkaroundEnabled = false;
    150
    151     /***
    152      * Provide the dispatcher instance for the current thread.
    153      *
    154      * @return The dispatcher instance
    155      */
    156     public static Dispatcher getInstance() {
    157         return instance.get();
    158     }
    159
    160     /***
    161      * Store the dispatcher instance for this thread.
    162      *
    163      * @param instance The instance
    164      */
    165     public static void setInstance(Dispatcher instance) {
    166         Dispatcher.instance.set(instance);
    167
    168         // Tie the ObjectFactory threadlocal instance to this Dispatcher instance
    169         if (instance != null) {
    170             Container cont = instance.getContainer();
    171             if (cont != null) {
    172                 ObjectFactory.setObjectFactory(cont.getInstance(ObjectFactory.class));
    173             } else {
    174                 LOG.warn("This dispatcher instance doesn't have a container, so the object factory won't be set.");
    175             }
    176         } else {
    177             ObjectFactory.setObjectFactory(null);
    178         }
    179     }
    180
    181     /***
    182      * Add a dispatcher lifecycle listener.
    183      *
    184      * @param listener The listener to add
    185      */
    186     public static synchronized void addDispatcherListener(DispatcherListener listener) {
    187         dispatcherListeners.add(listener);
    188     }
    189
    190     /***
    191      * Remove a specific dispatcher lifecycle listener.
    192      *
    193      * @param listener The listener
    194      */
    195     public static synchronized void removeDispatcherListener(DispatcherListener listener) {
    196         dispatcherListeners.remove(listener);
    197     }
    198
    199     private ServletContext servletContext;
    200     private Map<String, String> initParams;
    201
    202
    203     /***
    204      * Create the Dispatcher instance for a given ServletContext and set of initialization parameters.
    205      *
    206      * @param servletContext Our servlet context
    207      * @param initParams The set of initialization parameters
    208      */
    209     public  Dispatcher(ServletContext servletContext, Map<String, String> initParams) {
    210         this.servletContext = servletContext;
    211         this.initParams = initParams;
    212     }
    213
    214     /***
    215      * Modify state of StrutsConstants.STRUTS_DEVMODE setting.
    216      * @param mode New setting
    217      */
    218     @Inject(StrutsConstants.STRUTS_DEVMODE)
    219     public static void setDevMode(String mode) {
    220         devMode = "true".equals(mode);
    221     }
    222
    223     /***
    224      * Modify state of StrutsConstants.STRUTS_LOCALE setting.
    225      * @param val New setting
    226      */
    227     @Inject(value=StrutsConstants.STRUTS_LOCALE, required=false)
    228     public static void setDefaultLocale(String val) {
    229         defaultLocale = val;
    230     }
    231
    232     /***
    233      * Modify state of StrutsConstants.STRUTS_I18N_ENCODING setting.
    234      * @param val New setting
    235      */
    236     @Inject(StrutsConstants.STRUTS_I18N_ENCODING)
    237     public static void setDefaultEncoding(String val) {
    238         defaultEncoding = val;
    239     }
    240
    241     /***
    242      * Modify state of StrutsConstants.STRUTS_MULTIPART_SAVEDIR setting.
    243      * @param val New setting
    244      */
    245     @Inject(StrutsConstants.STRUTS_MULTIPART_SAVEDIR)
    246     public static void setMultipartSaveDir(String val) {
    247         multipartSaveDir = val;
    248     }
    249
    250     /***
    251      * Releases all instances bound to this dispatcher instance.
    252      */
    253     public void cleanup() {
    254
    255     	// clean up ObjectFactory
    256         ObjectFactory objectFactory = getContainer().getInstance(ObjectFactory.class);
    257         if (objectFactory == null) {
    258             LOG.warn("Object Factory is null, something is seriously wrong, no clean up will be performed");
    259         }
    260         if (objectFactory instanceof ObjectFactoryDestroyable) {
    261             try {
    262                 ((ObjectFactoryDestroyable)objectFactory).destroy();
    263             }
    264             catch(Exception e) {
    265                 // catch any exception that may occured during destroy() and log it
    266                 LOG.error("exception occurred while destroying ObjectFactory ["+objectFactory+"]", e);
    267             }
    268         }
    269
    270         // clean up Dispatcher itself for this thread
    271         instance.set(null);
    272
    273         // clean up DispatcherListeners
    274         synchronized(Dispatcher.class) {
    275             if (dispatcherListeners.size() > 0) {
    276                 for (DispatcherListener l : dispatcherListeners) {
    277                     l.dispatcherDestroyed(this);
    278                 }
    279             }
    280         }
    281
    282         // clean up configuration
    283     	configurationManager.destroyConfiguration();
    284     	configurationManager = null;
    285     }
    286
    287     private void init_DefaultProperties() {
    288         configurationManager.addConfigurationProvider(new DefaultPropertiesProvider());
    289     }
    290
    291     private void init_LegacyStrutsProperties() {
    292         configurationManager.addConfigurationProvider(new LegacyPropertiesConfigurationProvider());
    293     }
    294
    295     private void init_TraditionalXmlConfigurations() {
    296         String configPaths = initParams.get("config");
    297         if (configPaths == null) {
    298             configPaths = DEFAULT_CONFIGURATION_PATHS;
    299         }
    300         String[] files = configPaths.split("//s*[,]//s*");
    301         for (String file : files) {
    302             if (file.endsWith(".xml")) {
    303                 if ("xwork.xml".equals(file)) {
    304                     configurationManager.addConfigurationProvider(new XmlConfigurationProvider(file, false));
    305                 } else {
    306                     configurationManager.addConfigurationProvider(new StrutsXmlConfigurationProvider(file, false, servletContext));
    307                 }
    308             } else {
    309                 throw new IllegalArgumentException("Invalid configuration file name");
    310             }
    311         }
    312     }
    313
    314     private void init_ZeroConfiguration() {
    315         String packages = initParams.get("actionPackages");
    316         if (packages != null) {
    317             String[] names = packages.split("//s*[,]//s*");
    318             // Initialize the classloader scanner with the configured packages
    319             if (names.length > 0) {
    320                 ClasspathConfigurationProvider provider = new ClasspathConfigurationProvider(names);
    321                 provider.setPageLocator(new ServletContextPageLocator(servletContext));
    322                 configurationManager.addConfigurationProvider(provider);
    323             }
    324         }
    325     }
    326
    327     private void init_CustomConfigurationProviders() {
    328         String configProvs = initParams.get("configProviders");
    329         if (configProvs != null) {
    330             String[] classes = configProvs.split("//s*[,]//s*");
    331             for (String cname : classes) {
    332                 try {
    333                     Class cls = ClassLoaderUtils.loadClass(cname, this.getClass());
    334                     ConfigurationProvider prov = (ConfigurationProvider)cls.newInstance();
    335                     configurationManager.addConfigurationProvider(prov);
    336                 } catch (InstantiationException e) {
    337                     throw new ConfigurationException("Unable to instantiate provider: "+cname, e);
    338                 } catch (IllegalAccessException e) {
    339                     throw new ConfigurationException("Unable to access provider: "+cname, e);
    340                 } catch (ClassNotFoundException e) {
    341                     throw new ConfigurationException("Unable to locate provider class: "+cname, e);
    342                 }
    343             }
    344         }
    345     }
    346
    347     private void init_MethodConfigurationProvider() {
    348         // See https://issues.apache.org/struts/browse/WW-1522
    349     /*
    350     com.opensymphony.xwork2.inject.DependencyException: com.opensymphony.xwork2.inject.ContainerImpl$MissingDependencyException: No mapping found for dependency [type=org.apache.struts2.dispatcher.mapper.ActionMapper, name='default'] in public static void org.apache.struts2.dispatcher.FilterDispatcher.setActionMapper(org.apache.struts2.dispatcher.mapper.ActionMapper).
    351 	at com.opensymphony.xwork2.inject.ContainerImpl.addInjectorsForMembers(ContainerImpl.java:135)
    352 	at com.opensymphony.xwork2.inject.ContainerImpl.addInjectorsForMethods(ContainerImpl.java:104)
    353 	at com.opensymphony.xwork2.inject.ContainerImpl.injectStatics(ContainerImpl.java:89)
    354 	at com.opensymphony.xwork2.inject.ContainerBuilder.create(ContainerBuilder.java:494)
    355 	at com.opensymphony.xwork2.config.impl.DefaultConfiguration.reload(DefaultConfiguration.java:140)
    356 	at com.opensymphony.xwork2.config.ConfigurationManager.getConfiguration(ConfigurationManager.java:52)
    357 	at org.apache.struts2.dispatcher.Dispatcher.init_MethodConfigurationProvider(Dispatcher.java:347)
    358 	at org.apache.struts2.dispatcher.Dispatcher.init(Dispatcher.java:421)
    359 	at org.apache.struts2.config.MethodConfigurationProviderTest.setUp(MethodConfigurationProviderTest.java:68)
    360 	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:40)
    361 	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    362 	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    363 	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    364 	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:90)
    365 Caused by: com.opensymphony.xwork2.inject.ContainerImpl$MissingDependencyException: No mapping found for dependency [type=org.apache.struts2.dispatcher.mapper.ActionMapper, name='default'] in public static void org.apache.struts2.dispatcher.FilterDispatcher.setActionMapper(org.apache.struts2.dispatcher.mapper.ActionMapper).
    366 	at com.opensymphony.xwork2.inject.ContainerImpl.createParameterInjector(ContainerImpl.java:217)
    367 	at com.opensymphony.xwork2.inject.ContainerImpl.getParametersInjectors(ContainerImpl.java:207)
    368 	at com.opensymphony.xwork2.inject.ContainerImpl$MethodInjector.<init>(ContainerImpl.java:260)
    369 	at com.opensymphony.xwork2.inject.ContainerImpl$3.create(ContainerImpl.java:108)
    370 	at com.opensymphony.xwork2.inject.ContainerImpl$3.create(ContainerImpl.java:106)
    371 	at com.opensymphony.xwork2.inject.ContainerImpl.addInjectorsForMembers(ContainerImpl.java:132)
    372 	... 26 more
    373
    374         MethodConfigurationProvider provider = new MethodConfigurationProvider();
    375         provider.init(configurationManager.getConfiguration());
    376         provider.loadPackages();
    377    */
    378     }
    379
    380     private void init_FilterInitParameters() {
    381         configurationManager.addConfigurationProvider(new ConfigurationProvider() {
    382             public void destroy() {}
    383             public void init(Configuration configuration) throws ConfigurationException {}
    384             public void loadPackages() throws ConfigurationException {}
    385             public boolean needsReload() { return false; }
    386
    387             public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException {
    388                 props.putAll(initParams);
    389             }
    390         });
    391     }
    392
    393     private void init_AliasStandardObjects() {
    394         configurationManager.addConfigurationProvider(new BeanSelectionProvider());
    395     }
    396
    397     private Container init_PreloadConfiguration() {
    398         Configuration config = configurationManager.getConfiguration();
    399         Container container = config.getContainer();
    400
    401         boolean reloadi18n = Boolean.valueOf(container.getInstance(String.class, StrutsConstants.STRUTS_I18N_RELOAD));
    402         LocalizedTextUtil.setReloadBundles(reloadi18n);
    403
    404         ObjectTypeDeterminer objectTypeDeterminer = container.getInstance(ObjectTypeDeterminer.class);
    405         ObjectTypeDeterminerFactory.setInstance(objectTypeDeterminer);
    406
    407         return container;
    408     }
    409
    410     private void init_CheckConfigurationReloading(Container container) {
    411         FileManager.setReloadingConfigs("true".equals(container.getInstance(String.class,
    412                 StrutsConstants.STRUTS_CONFIGURATION_XML_RELOAD)));
    413     }
    414
    415     private void init_CheckWebLogicWorkaround(Container container) {
    416         // test whether param-access workaround needs to be enabled
    417         if (servletContext != null && servletContext.getServerInfo() != null
    418                 && servletContext.getServerInfo().indexOf("WebLogic") >= 0) {
    419             LOG.info("WebLogic server detected. Enabling Struts parameter access work-around.");
    420             paramsWorkaroundEnabled = true;
    421         } else {
    422             paramsWorkaroundEnabled = "true".equals(container.getInstance(String.class,
    423                     StrutsConstants.STRUTS_DISPATCHER_PARAMETERSWORKAROUND));
    424         }
    425
    426         synchronized(Dispatcher.class) {
    427             if (dispatcherListeners.size() > 0) {
    428                 for (DispatcherListener l : dispatcherListeners) {
    429                     l.dispatcherInitialized(this);
    430                 }
    431             }
    432         }
    433
    434     }
    435
    436     /***
    437      * Load configurations, including both XML and zero-configuration strategies,
    438      * and update optional settings, including whether to reload configurations and resource files.
    439      */
    440     public void init() {
    441
    442     	if (configurationManager == null) {
    443     		configurationManager = new ConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME);
    444     	}
    445
    446     	init_DefaultProperties(); // [1]
    447         init_TraditionalXmlConfigurations(); // [2]
    448         init_LegacyStrutsProperties(); // [3]
    449         init_ZeroConfiguration(); // [4]
    450         init_CustomConfigurationProviders(); // [5]
    451         init_MethodConfigurationProvider();
    452         init_FilterInitParameters() ; // [6]
    453         init_AliasStandardObjects() ; // [7]
    454
    455         Container container = init_PreloadConfiguration();
    456         init_CheckConfigurationReloading(container);
    457         init_CheckWebLogicWorkaround(container);
    458
    459     }
    460
    461     /***
    462      * Load Action class for mapping and invoke the appropriate Action method, or go directly to the Result.
    463      * <p/>
    464      * This method first creates the action context from the given parameters,
    465      * and then loads an <tt>ActionProxy</tt> from the given action name and namespace.
    466      * After that, the Action method is executed and output channels through the response object.
    467      * Actions not found are sent back to the user via the {@link Dispatcher#sendError} method,
    468      * using the 404 return code.
    469      * All other errors are reported by throwing a ServletException.
    470      *
    471      * @param request  the HttpServletRequest object
    472      * @param response the HttpServletResponse object
    473      * @param mapping  the action mapping object
    474      * @throws ServletException when an unknown error occurs (not a 404, but typically something that
    475      *                          would end up as a 5xx by the servlet container)
    476      * @param context Our ServletContext object
    477      */
    478     public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,
    479                               ActionMapping mapping) throws ServletException {
    480
    481         Map<String, Object> extraContext = createContextMap(request, response, mapping, context);
    482
    483         // If there was a previous value stack, then create a new copy and pass it in to be used by the new Action
    484         ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
    485         if (stack != null) {
    486             extraContext.put(ActionContext.VALUE_STACK, ValueStackFactory.getFactory().createValueStack(stack));
    487         }
    488
    489         String timerKey = "Handling request from Dispatcher";
    490         try {
    491             UtilTimerStack.push(timerKey);
    492             String namespace = mapping.getNamespace();
    493             String name = mapping.getName();
    494             String method = mapping.getMethod();
    495
    496             Configuration config = configurationManager.getConfiguration();
    497             ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
    498                     namespace, name, extraContext, true, false);
    499             proxy.setMethod(method);
    500             request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
    501
    502             // if the ActionMapping says to go straight to a result, do it!
    503             if (mapping.getResult() != null) {
    504                 Result result = mapping.getResult();
    505                 result.execute(proxy.getInvocation());
    506             } else {
    507                 proxy.execute();
    508             }
    509
    510             // If there was a previous value stack then set it back onto the request
    511             if (stack != null) {
    512                 request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
    513             }
    514         } catch (ConfigurationException e) {
    515             LOG.error("Could not find action or result", e);
    516             sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e);
    517         } catch (Exception e) {
    518             throw new ServletException(e);
    519         } finally {
    520             UtilTimerStack.pop(timerKey);
    521         }
    522     }
    523
    524     /***
    525      * Create a context map containing all the wrapped request objects
    526      *
    527      * @param request The servlet request
    528      * @param response The servlet response
    529      * @param mapping The action mapping
    530      * @param context The servlet context
    531      * @return A map of context objects
    532      */
    533     public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response,
    534             ActionMapping mapping, ServletContext context) {
    535
    536         // request map wrapping the http request objects
    537         Map requestMap = new RequestMap(request);
    538
    539         // parameters map wrapping the http paraneters.
    540         Map params = null;
    541         if (mapping != null) {
    542             params = mapping.getParams();
    543         }
    544         Map requestParams = new HashMap(request.getParameterMap());
    545         if (params != null) {
    546             params.putAll(requestParams);
    547         } else {
    548             params = requestParams;
    549         }
    550
    551         // session map wrapping the http session
    552         Map session = new SessionMap(request);
    553
    554         // application map wrapping the ServletContext
    555         Map application = new ApplicationMap(context);
    556
    557         Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context);
    558         extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);
    559         return extraContext;
    560     }
    561
    562     /***
    563      * Merge all application and servlet attributes into a single <tt>HashMap</tt> to represent the entire
    564      * <tt>Action</tt> context.
    565      *
    566      * @param requestMap     a Map of all request attributes.
    567      * @param parameterMap   a Map of all request parameters.
    568      * @param sessionMap     a Map of all session attributes.
    569      * @param applicationMap a Map of all servlet context attributes.
    570      * @param request        the HttpServletRequest object.
    571      * @param response       the HttpServletResponse object.
    572      * @param servletContext the ServletContextmapping object.
    573      * @return a HashMap representing the <tt>Action</tt> context.
    574      */
    575     public HashMap<String,Object> createContextMap(Map requestMap,
    576                                     Map parameterMap,
    577                                     Map sessionMap,
    578                                     Map applicationMap,
    579                                     HttpServletRequest request,
    580                                     HttpServletResponse response,
    581                                     ServletContext servletContext) {
    582         HashMap<String,Object> extraContext = new HashMap<String,Object>();
    583         extraContext.put(ActionContext.PARAMETERS, new HashMap(parameterMap));
    584         extraContext.put(ActionContext.SESSION, sessionMap);
    585         extraContext.put(ActionContext.APPLICATION, applicationMap);
    586
    587         Locale locale;
    588         if (defaultLocale != null) {
    589             locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
    590         } else {
    591             locale = request.getLocale();
    592         }
    593
    594         extraContext.put(ActionContext.LOCALE, locale);
    595         //extraContext.put(ActionContext.DEV_MODE, Boolean.valueOf(devMode));
    596
    597         extraContext.put(StrutsStatics.HTTP_REQUEST, request);
    598         extraContext.put(StrutsStatics.HTTP_RESPONSE, response);
    599         extraContext.put(StrutsStatics.SERVLET_CONTEXT, servletContext);
    600
    601         // helpers to get access to request/session/application scope
    602         extraContext.put("request", requestMap);
    603         extraContext.put("session", sessionMap);
    604         extraContext.put("application", applicationMap);
    605         extraContext.put("parameters", parameterMap);
    606
    607         AttributeMap attrMap = new AttributeMap(extraContext);
    608         extraContext.put("attr", attrMap);
    609
    610         return extraContext;
    611     }
    612
    613     /***
    614      * Return the path to save uploaded files to (this is configurable).
    615      *
    616      * @return the path to save uploaded files to
    617      * @param servletContext Our ServletContext
    618      */
    619     private String getSaveDir(ServletContext servletContext) {
    620         String saveDir = multipartSaveDir.trim();
    621
    622         if (saveDir.equals("")) {
    623             File tempdir = (File) servletContext.getAttribute("javax.servlet.context.tempdir");
    624             LOG.info("Unable to find 'struts.multipart.saveDir' property setting. Defaulting to javax.servlet.context.tempdir");
    625
    626             if (tempdir != null) {
    627                 saveDir = tempdir.toString();
    628             }
    629         } else {
    630             File multipartSaveDir = new File(saveDir);
    631
    632             if (!multipartSaveDir.exists()) {
    633                 multipartSaveDir.mkdir();
    634             }
    635         }
    636
    637         if (LOG.isDebugEnabled()) {
    638             LOG.debug("saveDir=" + saveDir);
    639         }
    640
    641         return saveDir;
    642     }
    643
    644     /***
    645      * Prepare a request, including setting the encoding and locale.
    646      *
    647      * @param request The request
    648      * @param response The response
    649      */
    650     public void prepare(HttpServletRequest request, HttpServletResponse response) {
    651         String encoding = null;
    652         if (defaultEncoding != null) {
    653             encoding = defaultEncoding;
    654         }
    655
    656         Locale locale = null;
    657         if (defaultLocale != null) {
    658             locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
    659         }
    660
    661         if (encoding != null) {
    662             try {
    663                 request.setCharacterEncoding(encoding);
    664             } catch (Exception e) {
    665                 LOG.error("Error setting character encoding to '" + encoding + "' - ignoring.", e);
    666             }
    667         }
    668
    669         if (locale != null) {
    670             response.setLocale(locale);
    671         }
    672
    673         if (paramsWorkaroundEnabled) {
    674             request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request
    675         }
    676     }
    677
    678     /***
    679      * Wrap and return the given request or return the original request object.
    680      * </p>
    681      * This method transparently handles multipart data as a wrapped class around the given request.
    682      * Override this method to handle multipart requests in a special way or to handle other types of requests.
    683      * Note, {@link org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper} is
    684      * flexible - look first to that object before overriding this method to handle multipart data.
    685      *
    686      * @param request the HttpServletRequest object.
    687      * @param servletContext Our ServletContext object
    688      * @return a wrapped request or original request.
    689      * @see org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper
    690      * @throws java.io.IOException on any error.
    691      */
    692     public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException {
    693         // don't wrap more than once
    694         if (request instanceof StrutsRequestWrapper) {
    695             return request;
    696         }
    697
    698         String content_type = request.getContentType();
    699         if (content_type != null && content_type.indexOf("multipart/form-data") != -1) {
    700             MultiPartRequest multi = getContainer().getInstance(MultiPartRequest.class);
    701             request = new MultiPartRequestWrapper(multi, request, getSaveDir(servletContext));
    702         } else {
    703             request = new StrutsRequestWrapper(request);
    704         }
    705
    706         return request;
    707     }
    708
    709     /***
    710      * Send an HTTP error response code.
    711      *
    712      * @param request  the HttpServletRequest object.
    713      * @param response the HttpServletResponse object.
    714      * @param code     the HttpServletResponse error code (see {@link javax.servlet.http.HttpServletResponse} for possible error codes).
    715      * @param e        the Exception that is reported.
    716      * @param ctx      the ServletContext object.
    717      */
    718     public void sendError(HttpServletRequest request, HttpServletResponse response,
    719             ServletContext ctx, int code, Exception e) {
    720         if (devMode) {
    721             response.setContentType("text/html");
    722
    723             try {
    724                 FreemarkerManager mgr = getContainer().getInstance(FreemarkerManager.class);
    725
    726                 freemarker.template.Configuration config = mgr.getConfiguration(ctx);
    727                 Template template = config.getTemplate("/org/apache/struts2/dispatcher/error.ftl");
    728
    729                 List<Throwable> chain = new ArrayList<Throwable>();
    730                 Throwable cur = e;
    731                 chain.add(cur);
    732                 while ((cur = cur.getCause()) != null) {
    733                     chain.add(cur);
    734                 }
    735
    736                 HashMap<String,Object> data = new HashMap<String,Object>();
    737                 data.put("exception", e);
    738                 data.put("unknown", Location.UNKNOWN);
    739                 data.put("chain", chain);
    740                 data.put("locator", new Locator());
    741                 template.process(data, response.getWriter());
    742                 response.getWriter().close();
    743             } catch (Exception exp) {
    744                 try {
    745                     response.sendError(code, "Unable to show problem report: " + exp);
    746                 } catch (IOException ex) {
    747                     // we're already sending an error, not much else we can do if more stuff breaks
    748                 }
    749             }
    750         } else {
    751             try {
    752                 // send a http error response to use the servlet defined error handler
    753                 // make the exception availible to the web.xml defined error page
    754                 request.setAttribute("javax.servlet.error.exception", e);
    755
    756                 // for compatibility
    757                 request.setAttribute("javax.servlet.jsp.jspException", e);
    758
    759                 // send the error response
    760                 response.sendError(code, e.getMessage());
    761             } catch (IOException e1) {
    762                 // we're already sending an error, not much else we can do if more stuff breaks
    763             }
    764         }
    765     }
    766
    767     /***
    768      * Return <tt>true</tt>, if portlet support is active, <tt>false</tt> otherwise.
    769      *
    770      * @return <tt>true</tt>, if portlet support is active, <tt>false</tt> otherwise.
    771      */
    772     public boolean isPortletSupportActive() {
    773         return portletSupportActive;
    774     }
    775
    776     /***
    777      * Modify the portlet support mode.
    778      * @param portletSupportActive <tt>true</tt> or <tt>false</tt>
    779      */
    780     public static void setPortletSupportActive(boolean portletSupportActive) {
    781         Dispatcher.portletSupportActive = portletSupportActive;
    782     }
    783
    784     /***
    785      * Search classpath for a page.
    786      */
    787     private final class ServletContextPageLocator implements PageLocator {
    788         private final ServletContext context;
    789         private ClasspathPageLocator classpathPageLocator = new ClasspathPageLocator();
    790
    791         private ServletContextPageLocator(ServletContext context) {
    792             this.context = context;
    793         }
    794
    795         public URL locate(String path) {
    796             URL url = null;
    797             try {
    798                 url = context.getResource(path);
    799                 if (url == null) {
    800                     url = classpathPageLocator.locate(path);
    801                 }
    802             } catch (MalformedURLException e) {
    803                 if (LOG.isDebugEnabled()) {
    804                     LOG.debug("Unable to resolve path "+path+" against the servlet context");
    805                 }
    806             }
    807             return url;
    808         }
    809     }
    810
    811     /***
    812      * Provide an accessor class for static XWork utility.
    813      */
    814     public class Locator {
    815         public Location getLocation(Object obj) {
    816             Location loc = LocationUtils.getLocation(obj);
    817             if (loc == null) {
    818                 return Location.UNKNOWN;
    819             }
    820             return loc;
    821         }
    822     }
    823
    824     /***
    825      * Expose the ConfigurationManager instance.
    826      *
    827      * @return The instance
    828      */
    829     public ConfigurationManager getConfigurationManager() {
    830         return configurationManager;
    831     }
    832
    833     /***
    834      * Modify the ConfigurationManager instance
    835      *
    836      * @param mgr The configuration manager
    837      */
    838     public void setConfigurationManager(ConfigurationManager mgr) {
    839         this.configurationManager = mgr;
    840     }
    841
    842     /***
    843      * Expose the dependency injection container.
    844      * @return Our dependency injection container
    845      */
    846     public Container getContainer() {
    847         ConfigurationManager mgr = getConfigurationManager();
    848         if (mgr == null) {
    849             throw new IllegalStateException("The configuration manager shouldn't be null");
    850         } else {
    851             Configuration config = mgr.getConfiguration();
    852             if (config == null) {
    853                 throw new IllegalStateException("Unable to load configuration");
    854             } else {
    855                 return config.getContainer();
    856             }
    857         }
    858     }
    859 }