View Javadoc

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