View Javadoc

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