View Javadoc

1   /*
2    * $Id: Dispatcher.java 498215 2007-01-20 23:59:10Z mrdon $
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             LOG.error("Could not execute action", e);
519             sendError(request, response, context, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
520         } finally {
521             UtilTimerStack.pop(timerKey);
522         }
523     }
524 
525     /***
526      * Create a context map containing all the wrapped request objects
527      *
528      * @param request The servlet request
529      * @param response The servlet response
530      * @param mapping The action mapping
531      * @param context The servlet context
532      * @return A map of context objects
533      */
534     public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response,
535             ActionMapping mapping, ServletContext context) {
536 
537         // request map wrapping the http request objects
538         Map requestMap = new RequestMap(request);
539 
540         // parameters map wrapping the http paraneters.
541         Map params = null;
542         if (mapping != null) {
543             params = mapping.getParams();
544         }
545         Map requestParams = new HashMap(request.getParameterMap());
546         if (params != null) {
547             params.putAll(requestParams);
548         } else {
549             params = requestParams;
550         }
551 
552         // session map wrapping the http session
553         Map session = new SessionMap(request);
554 
555         // application map wrapping the ServletContext
556         Map application = new ApplicationMap(context);
557 
558         Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context);
559         extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);
560         return extraContext;
561     }
562 
563     /***
564      * Merge all application and servlet attributes into a single <tt>HashMap</tt> to represent the entire
565      * <tt>Action</tt> context.
566      *
567      * @param requestMap     a Map of all request attributes.
568      * @param parameterMap   a Map of all request parameters.
569      * @param sessionMap     a Map of all session attributes.
570      * @param applicationMap a Map of all servlet context attributes.
571      * @param request        the HttpServletRequest object.
572      * @param response       the HttpServletResponse object.
573      * @param servletContext the ServletContextmapping object.
574      * @return a HashMap representing the <tt>Action</tt> context.
575      */
576     public HashMap<String,Object> createContextMap(Map requestMap,
577                                     Map parameterMap,
578                                     Map sessionMap,
579                                     Map applicationMap,
580                                     HttpServletRequest request,
581                                     HttpServletResponse response,
582                                     ServletContext servletContext) {
583         HashMap<String,Object> extraContext = new HashMap<String,Object>();
584         extraContext.put(ActionContext.PARAMETERS, new HashMap(parameterMap));
585         extraContext.put(ActionContext.SESSION, sessionMap);
586         extraContext.put(ActionContext.APPLICATION, applicationMap);
587 
588         Locale locale;
589         if (defaultLocale != null) {
590             locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
591         } else {
592             locale = request.getLocale();
593         }
594 
595         extraContext.put(ActionContext.LOCALE, locale);
596         //extraContext.put(ActionContext.DEV_MODE, Boolean.valueOf(devMode));
597 
598         extraContext.put(StrutsStatics.HTTP_REQUEST, request);
599         extraContext.put(StrutsStatics.HTTP_RESPONSE, response);
600         extraContext.put(StrutsStatics.SERVLET_CONTEXT, servletContext);
601 
602         // helpers to get access to request/session/application scope
603         extraContext.put("request", requestMap);
604         extraContext.put("session", sessionMap);
605         extraContext.put("application", applicationMap);
606         extraContext.put("parameters", parameterMap);
607 
608         AttributeMap attrMap = new AttributeMap(extraContext);
609         extraContext.put("attr", attrMap);
610 
611         return extraContext;
612     }
613 
614     /***
615      * Return the path to save uploaded files to (this is configurable).
616      *
617      * @return the path to save uploaded files to
618      * @param servletContext Our ServletContext
619      */
620     private String getSaveDir(ServletContext servletContext) {
621         String saveDir = multipartSaveDir.trim();
622 
623         if (saveDir.equals("")) {
624             File tempdir = (File) servletContext.getAttribute("javax.servlet.context.tempdir");
625             LOG.info("Unable to find 'struts.multipart.saveDir' property setting. Defaulting to javax.servlet.context.tempdir");
626 
627             if (tempdir != null) {
628                 saveDir = tempdir.toString();
629             }
630         } else {
631             File multipartSaveDir = new File(saveDir);
632 
633             if (!multipartSaveDir.exists()) {
634                 multipartSaveDir.mkdir();
635             }
636         }
637 
638         if (LOG.isDebugEnabled()) {
639             LOG.debug("saveDir=" + saveDir);
640         }
641 
642         return saveDir;
643     }
644 
645     /***
646      * Prepare a request, including setting the encoding and locale.
647      *
648      * @param request The request
649      * @param response The response
650      */
651     public void prepare(HttpServletRequest request, HttpServletResponse response) {
652         String encoding = null;
653         if (defaultEncoding != null) {
654             encoding = defaultEncoding;
655         }
656 
657         Locale locale = null;
658         if (defaultLocale != null) {
659             locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
660         }
661 
662         if (encoding != null) {
663             try {
664                 request.setCharacterEncoding(encoding);
665             } catch (Exception e) {
666                 LOG.error("Error setting character encoding to '" + encoding + "' - ignoring.", e);
667             }
668         }
669 
670         if (locale != null) {
671             response.setLocale(locale);
672         }
673 
674         if (paramsWorkaroundEnabled) {
675             request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request
676         }
677     }
678 
679     /***
680      * Wrap and return the given request or return the original request object.
681      * </p>
682      * This method transparently handles multipart data as a wrapped class around the given request.
683      * Override this method to handle multipart requests in a special way or to handle other types of requests.
684      * Note, {@link org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper} is
685      * flexible - look first to that object before overriding this method to handle multipart data.
686      *
687      * @param request the HttpServletRequest object.
688      * @param servletContext Our ServletContext object
689      * @return a wrapped request or original request.
690      * @see org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper
691      * @throws java.io.IOException on any error.
692      */
693     public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException {
694         // don't wrap more than once
695         if (request instanceof StrutsRequestWrapper) {
696             return request;
697         }
698 
699         String content_type = request.getContentType();
700         if (content_type != null && content_type.indexOf("multipart/form-data") != -1) {
701             MultiPartRequest multi = getContainer().getInstance(MultiPartRequest.class);
702             request = new MultiPartRequestWrapper(multi, request, getSaveDir(servletContext));
703         } else {
704             request = new StrutsRequestWrapper(request);
705         }
706 
707         return request;
708     }
709 
710     /***
711      * Send an HTTP error response code.
712      *
713      * @param request  the HttpServletRequest object.
714      * @param response the HttpServletResponse object.
715      * @param code     the HttpServletResponse error code (see {@link javax.servlet.http.HttpServletResponse} for possible error codes).
716      * @param e        the Exception that is reported.
717      * @param ctx      the ServletContext object.
718      */
719     public void sendError(HttpServletRequest request, HttpServletResponse response,
720             ServletContext ctx, int code, Exception e) {
721         if (devMode) {
722             response.setContentType("text/html");
723 
724             try {
725                 FreemarkerManager mgr = getContainer().getInstance(FreemarkerManager.class);
726 
727                 freemarker.template.Configuration config = mgr.getConfiguration(ctx);
728                 Template template = config.getTemplate("/org/apache/struts2/dispatcher/error.ftl");
729 
730                 List<Throwable> chain = new ArrayList<Throwable>();
731                 Throwable cur = e;
732                 chain.add(cur);
733                 while ((cur = cur.getCause()) != null) {
734                     chain.add(cur);
735                 }
736 
737                 HashMap<String,Object> data = new HashMap<String,Object>();
738                 data.put("exception", e);
739                 data.put("unknown", Location.UNKNOWN);
740                 data.put("chain", chain);
741                 data.put("locator", new Locator());
742                 template.process(data, response.getWriter());
743                 response.getWriter().close();
744             } catch (Exception exp) {
745                 try {
746                     response.sendError(code, "Unable to show problem report: " + exp);
747                 } catch (IOException ex) {
748                     // we're already sending an error, not much else we can do if more stuff breaks
749                 }
750             }
751         } else {
752             try {
753                 // send a http error response to use the servlet defined error handler
754                 // make the exception availible to the web.xml defined error page
755                 request.setAttribute("javax.servlet.error.exception", e);
756 
757                 // for compatibility
758                 request.setAttribute("javax.servlet.jsp.jspException", e);
759 
760                 // send the error response
761                 response.sendError(code, e.getMessage());
762             } catch (IOException e1) {
763                 // we're already sending an error, not much else we can do if more stuff breaks
764             }
765         }
766     }
767 
768     /***
769      * Return <tt>true</tt>, if portlet support is active, <tt>false</tt> otherwise.
770      *
771      * @return <tt>true</tt>, if portlet support is active, <tt>false</tt> otherwise.
772      */
773     public boolean isPortletSupportActive() {
774         return portletSupportActive;
775     }
776 
777     /***
778      * Modify the portlet support mode.
779      * @param portletSupportActive <tt>true</tt> or <tt>false</tt>
780      */
781     public static void setPortletSupportActive(boolean portletSupportActive) {
782         Dispatcher.portletSupportActive = portletSupportActive;
783     }
784 
785     /***
786      * Search classpath for a page.
787      */
788     private final class ServletContextPageLocator implements PageLocator {
789         private final ServletContext context;
790         private ClasspathPageLocator classpathPageLocator = new ClasspathPageLocator();
791 
792         private ServletContextPageLocator(ServletContext context) {
793             this.context = context;
794         }
795 
796         public URL locate(String path) {
797             URL url = null;
798             try {
799                 url = context.getResource(path);
800                 if (url == null) {
801                     url = classpathPageLocator.locate(path);
802                 }
803             } catch (MalformedURLException e) {
804                 if (LOG.isDebugEnabled()) {
805                     LOG.debug("Unable to resolve path "+path+" against the servlet context");
806                 }
807             }
808             return url;
809         }
810     }
811 
812     /***
813      * Provide an accessor class for static XWork utility.
814      */
815     public class Locator {
816         public Location getLocation(Object obj) {
817             Location loc = LocationUtils.getLocation(obj);
818             if (loc == null) {
819                 return Location.UNKNOWN;
820             }
821             return loc;
822         }
823     }
824 
825     /***
826      * Expose the ConfigurationManager instance.
827      *
828      * @return The instance
829      */
830     public ConfigurationManager getConfigurationManager() {
831         return configurationManager;
832     }
833 
834     /***
835      * Modify the ConfigurationManager instance
836      *
837      * @param mgr The configuration manager
838      */
839     public void setConfigurationManager(ConfigurationManager mgr) {
840         this.configurationManager = mgr;
841     }
842 
843     /***
844      * Expose the dependency injection container.
845      * @return Our dependency injection container
846      */
847     public Container getContainer() {
848         ConfigurationManager mgr = getConfigurationManager();
849         if (mgr == null) {
850             throw new IllegalStateException("The configuration manager shouldn't be null");
851         } else {
852             Configuration config = mgr.getConfiguration();
853             if (config == null) {
854                 throw new IllegalStateException("Unable to load configuration");
855             } else {
856                 return config.getContainer();
857             }
858         }
859     }
860 }