View Javadoc

1   /*
2    * $Id: Dispatcher.java 729675 2008-12-27 21:13:04Z musachy $
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.Collection;
28  import java.util.HashMap;
29  import java.util.HashSet;
30  import java.util.List;
31  import java.util.Locale;
32  import java.util.Map;
33  import java.util.Set;
34  import java.util.concurrent.CopyOnWriteArrayList;
35  
36  import javax.servlet.ServletContext;
37  import javax.servlet.ServletException;
38  import javax.servlet.http.HttpServletRequest;
39  import javax.servlet.http.HttpServletResponse;
40  
41  import org.apache.struts2.ServletActionContext;
42  import org.apache.struts2.StrutsConstants;
43  import org.apache.struts2.StrutsStatics;
44  import org.apache.struts2.config.BeanSelectionProvider;
45  import org.apache.struts2.config.DefaultPropertiesProvider;
46  import org.apache.struts2.config.LegacyPropertiesConfigurationProvider;
47  import org.apache.struts2.config.StrutsXmlConfigurationProvider;
48  import org.apache.struts2.dispatcher.mapper.ActionMapping;
49  import org.apache.struts2.dispatcher.multipart.MultiPartRequest;
50  import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
51  import org.apache.struts2.util.AttributeMap;
52  import org.apache.struts2.util.ClassLoaderUtils;
53  import org.apache.struts2.util.ObjectFactoryDestroyable;
54  import org.apache.struts2.views.freemarker.FreemarkerManager;
55  
56  import com.opensymphony.xwork2.ActionContext;
57  import com.opensymphony.xwork2.ActionProxy;
58  import com.opensymphony.xwork2.ActionProxyFactory;
59  import com.opensymphony.xwork2.ObjectFactory;
60  import com.opensymphony.xwork2.Result;
61  import com.opensymphony.xwork2.config.Configuration;
62  import com.opensymphony.xwork2.config.ConfigurationException;
63  import com.opensymphony.xwork2.config.ConfigurationManager;
64  import com.opensymphony.xwork2.config.ConfigurationProvider;
65  import com.opensymphony.xwork2.config.entities.InterceptorMapping;
66  import com.opensymphony.xwork2.config.entities.InterceptorStackConfig;
67  import com.opensymphony.xwork2.config.entities.PackageConfig;
68  import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider;
69  import com.opensymphony.xwork2.inject.Container;
70  import com.opensymphony.xwork2.inject.ContainerBuilder;
71  import com.opensymphony.xwork2.inject.Inject;
72  import com.opensymphony.xwork2.interceptor.Interceptor;
73  import com.opensymphony.xwork2.util.FileManager;
74  import com.opensymphony.xwork2.util.LocalizedTextUtil;
75  import com.opensymphony.xwork2.util.ValueStack;
76  import com.opensymphony.xwork2.util.ValueStackFactory;
77  import com.opensymphony.xwork2.util.location.LocatableProperties;
78  import com.opensymphony.xwork2.util.location.Location;
79  import com.opensymphony.xwork2.util.location.LocationUtils;
80  import com.opensymphony.xwork2.util.logging.Logger;
81  import com.opensymphony.xwork2.util.logging.LoggerFactory;
82  import com.opensymphony.xwork2.util.profiling.UtilTimerStack;
83  
84  import freemarker.template.Template;
85  
86  /***
87   * A utility class the actual dispatcher delegates most of its tasks to. Each instance
88   * of the primary dispatcher holds an instance of this dispatcher to be shared for
89   * all requests.
90   *
91   * @see org.apache.struts2.dispatcher.FilterDispatcher
92   */
93  public class Dispatcher {
94  
95      /***
96       * Provide a logging instance.
97       */
98      private static final Logger LOG = LoggerFactory.getLogger(Dispatcher.class);
99  
100     /***
101      * Provide a thread local instance.
102      */
103     private static ThreadLocal<Dispatcher> instance = new ThreadLocal<Dispatcher>();
104 
105     /***
106      * Store list of DispatcherListeners.
107      */
108     private static List<DispatcherListener> dispatcherListeners =
109         new CopyOnWriteArrayList<DispatcherListener>();
110 
111     /***
112      * Store ConfigurationManager instance, set on init.
113      */
114     private ConfigurationManager configurationManager;
115 
116     /***
117      * Store state of  StrutsConstants.STRUTS_DEVMODE setting.
118      */
119     private boolean devMode;
120 
121     /***
122      * Store state of StrutsConstants.STRUTS_I18N_ENCODING setting.
123      */
124     private String defaultEncoding;
125 
126     /***
127      * Store state of StrutsConstants.STRUTS_LOCALE setting.
128      */
129     private String defaultLocale;
130 
131     /***
132      * Store state of StrutsConstants.STRUTS_MULTIPART_SAVEDIR setting.
133      */
134     private String multipartSaveDir;
135 
136     /***
137      * Provide list of default configuration files.
138      */
139     private static final String DEFAULT_CONFIGURATION_PATHS = "struts-default.xml,struts-plugin.xml,struts.xml";
140 
141     /***
142      * Store state of STRUTS_DISPATCHER_PARAMETERSWORKAROUND.
143      * <p/>
144      * The workaround is for WebLogic.
145      * We try to autodect WebLogic on Dispatcher init.
146      * The workaround can also be enabled manually.
147      */
148     private boolean paramsWorkaroundEnabled = false;
149 
150     /***
151      * Provide the dispatcher instance for the current thread.
152      *
153      * @return The dispatcher instance
154      */
155     public static Dispatcher getInstance() {
156         return instance.get();
157     }
158 
159     /***
160      * Store the dispatcher instance for this thread.
161      *
162      * @param instance The instance
163      */
164     public static void setInstance(Dispatcher instance) {
165         Dispatcher.instance.set(instance);
166     }
167 
168     /***
169      * Add a dispatcher lifecycle listener.
170      *
171      * @param listener The listener to add
172      */
173     public static void addDispatcherListener(DispatcherListener listener) {
174         dispatcherListeners.add(listener);
175     }
176 
177     /***
178      * Remove a specific dispatcher lifecycle listener.
179      *
180      * @param listener The listener
181      */
182     public static void removeDispatcherListener(DispatcherListener listener) {
183         dispatcherListeners.remove(listener);
184     }
185 
186     private ServletContext servletContext;
187     private Map<String, String> initParams;
188 
189     private ValueStackFactory valueStackFactory;
190 
191 
192     /***
193      * Create the Dispatcher instance for a given ServletContext and set of initialization parameters.
194      *
195      * @param servletContext Our servlet context
196      * @param initParams The set of initialization parameters
197      */
198     public Dispatcher(ServletContext servletContext, Map<String, String> initParams) {
199         this.servletContext = servletContext;
200         this.initParams = initParams;
201     }
202 
203     /***
204      * Modify state of StrutsConstants.STRUTS_DEVMODE setting.
205      * @param mode New setting
206      */
207     @Inject(StrutsConstants.STRUTS_DEVMODE)
208     public void setDevMode(String mode) {
209         devMode = "true".equals(mode);
210     }
211 
212     /***
213      * Modify state of StrutsConstants.STRUTS_LOCALE setting.
214      * @param val New setting
215      */
216     @Inject(value=StrutsConstants.STRUTS_LOCALE, required=false)
217     public void setDefaultLocale(String val) {
218         defaultLocale = val;
219     }
220 
221     /***
222      * Modify state of StrutsConstants.STRUTS_I18N_ENCODING setting.
223      * @param val New setting
224      */
225     @Inject(StrutsConstants.STRUTS_I18N_ENCODING)
226     public void setDefaultEncoding(String val) {
227         defaultEncoding = val;
228     }
229 
230     /***
231      * Modify state of StrutsConstants.STRUTS_MULTIPART_SAVEDIR setting.
232      * @param val New setting
233      */
234     @Inject(StrutsConstants.STRUTS_MULTIPART_SAVEDIR)
235     public void setMultipartSaveDir(String val) {
236         multipartSaveDir = val;
237     }
238 
239     @Inject
240     public void setValueStackFactory(ValueStackFactory valueStackFactory) {
241         this.valueStackFactory = valueStackFactory;
242     }
243 
244     /***
245      * Releases all instances bound to this dispatcher instance.
246      */
247     public void cleanup() {
248 
249     	// clean up ObjectFactory
250         ObjectFactory objectFactory = getContainer().getInstance(ObjectFactory.class);
251         if (objectFactory == null) {
252             LOG.warn("Object Factory is null, something is seriously wrong, no clean up will be performed");
253         }
254         if (objectFactory instanceof ObjectFactoryDestroyable) {
255             try {
256                 ((ObjectFactoryDestroyable)objectFactory).destroy();
257             }
258             catch(Exception e) {
259                 // catch any exception that may occured during destroy() and log it
260                 LOG.error("exception occurred while destroying ObjectFactory ["+objectFactory+"]", e);
261             }
262         }
263 
264         // clean up Dispatcher itself for this thread
265         instance.set(null);
266 
267         // clean up DispatcherListeners
268         if (!dispatcherListeners.isEmpty()) {
269             for (DispatcherListener l : dispatcherListeners) {
270                 l.dispatcherDestroyed(this);
271             }
272         }
273 
274         // clean up all interceptors by calling their destroy() method
275         Set<Interceptor> interceptors = new HashSet<Interceptor>();
276         Collection<PackageConfig> packageConfigs = configurationManager.getConfiguration().getPackageConfigs().values();
277         for (PackageConfig packageConfig : packageConfigs) {
278             for (Object config : packageConfig.getAllInterceptorConfigs().values()) {
279                 if (config instanceof InterceptorStackConfig) {
280                     for (InterceptorMapping interceptorMapping : ((InterceptorStackConfig) config).getInterceptors()) {
281                 	    interceptors.add(interceptorMapping.getInterceptor());
282                     }
283                 }
284             }
285         }
286         for (Interceptor interceptor : interceptors) {
287         	interceptor.destroy();
288         }
289 
290         // clean up configuration
291     	configurationManager.destroyConfiguration();
292     	configurationManager = null;
293     }
294 
295     private void init_DefaultProperties() {
296         configurationManager.addConfigurationProvider(new DefaultPropertiesProvider());
297     }
298     
299     private void init_LegacyStrutsProperties() {
300         configurationManager.addConfigurationProvider(new LegacyPropertiesConfigurationProvider());
301     }
302 
303     private void init_TraditionalXmlConfigurations() {
304         String configPaths = initParams.get("config");
305         if (configPaths == null) {
306             configPaths = DEFAULT_CONFIGURATION_PATHS;
307         }
308         String[] files = configPaths.split("//s*[,]//s*");
309         for (String file : files) {
310             if (file.endsWith(".xml")) {
311                 if ("xwork.xml".equals(file)) {
312                     configurationManager.addConfigurationProvider(new XmlConfigurationProvider(file, false));
313                 } else {
314                     configurationManager.addConfigurationProvider(new StrutsXmlConfigurationProvider(file, false, servletContext));
315                 }
316             } else {
317                 throw new IllegalArgumentException("Invalid configuration file name");
318             }
319         }
320     }
321 
322     private void init_CustomConfigurationProviders() {
323         String configProvs = initParams.get("configProviders");
324         if (configProvs != null) {
325             String[] classes = configProvs.split("//s*[,]//s*");
326             for (String cname : classes) {
327                 try {
328                     Class cls = ClassLoaderUtils.loadClass(cname, this.getClass());
329                     ConfigurationProvider prov = (ConfigurationProvider)cls.newInstance();
330                     configurationManager.addConfigurationProvider(prov);
331                 } catch (InstantiationException e) {
332                     throw new ConfigurationException("Unable to instantiate provider: "+cname, e);
333                 } catch (IllegalAccessException e) {
334                     throw new ConfigurationException("Unable to access provider: "+cname, e);
335                 } catch (ClassNotFoundException e) {
336                     throw new ConfigurationException("Unable to locate provider class: "+cname, e);
337                 }
338             }
339         }
340     }
341 
342     private void init_FilterInitParameters() {
343         configurationManager.addConfigurationProvider(new ConfigurationProvider() {
344             public void destroy() {}
345             public void init(Configuration configuration) throws ConfigurationException {}
346             public void loadPackages() throws ConfigurationException {}
347             public boolean needsReload() { return false; }
348 
349             public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException {
350                 props.putAll(initParams);
351             }
352         });
353     }
354 
355     private void init_AliasStandardObjects() {
356         configurationManager.addConfigurationProvider(new BeanSelectionProvider());
357     }
358 
359     private Container init_PreloadConfiguration() {
360         Configuration config = configurationManager.getConfiguration();
361         Container container = config.getContainer();
362 
363         boolean reloadi18n = Boolean.valueOf(container.getInstance(String.class, StrutsConstants.STRUTS_I18N_RELOAD));
364         LocalizedTextUtil.setReloadBundles(reloadi18n);
365 
366         return container;
367     }
368 
369     private void init_CheckConfigurationReloading(Container container) {
370         FileManager.setReloadingConfigs("true".equals(container.getInstance(String.class,
371                 StrutsConstants.STRUTS_CONFIGURATION_XML_RELOAD)));
372     }
373 
374     private void init_CheckWebLogicWorkaround(Container container) {
375         // test whether param-access workaround needs to be enabled
376         if (servletContext != null && servletContext.getServerInfo() != null
377                 && servletContext.getServerInfo().indexOf("WebLogic") >= 0) {
378             LOG.info("WebLogic server detected. Enabling Struts parameter access work-around.");
379             paramsWorkaroundEnabled = true;
380         } else {
381             paramsWorkaroundEnabled = "true".equals(container.getInstance(String.class,
382                     StrutsConstants.STRUTS_DISPATCHER_PARAMETERSWORKAROUND));
383         }
384     }
385 
386     /***
387      * Load configurations, including both XML and zero-configuration strategies,
388      * and update optional settings, including whether to reload configurations and resource files.
389      */
390     public void init() {
391 
392     	if (configurationManager == null) {
393     		configurationManager = new ConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME);
394     	}
395 
396     	init_DefaultProperties(); // [1]
397         init_TraditionalXmlConfigurations(); // [2]
398         init_LegacyStrutsProperties(); // [3]
399         init_CustomConfigurationProviders(); // [5]
400         init_FilterInitParameters() ; // [6]
401         init_AliasStandardObjects() ; // [7]
402 
403         Container container = init_PreloadConfiguration();
404         container.inject(this);
405         init_CheckConfigurationReloading(container);
406         init_CheckWebLogicWorkaround(container);
407 
408         if (!dispatcherListeners.isEmpty()) {
409             for (DispatcherListener l : dispatcherListeners) {
410                 l.dispatcherInitialized(this);
411             }
412         }
413     }
414 
415     /***
416      * Load Action class for mapping and invoke the appropriate Action method, or go directly to the Result.
417      * <p/>
418      * This method first creates the action context from the given parameters,
419      * and then loads an <tt>ActionProxy</tt> from the given action name and namespace.
420      * After that, the Action method is executed and output channels through the response object.
421      * Actions not found are sent back to the user via the {@link Dispatcher#sendError} method,
422      * using the 404 return code.
423      * All other errors are reported by throwing a ServletException.
424      *
425      * @param request  the HttpServletRequest object
426      * @param response the HttpServletResponse object
427      * @param mapping  the action mapping object
428      * @throws ServletException when an unknown error occurs (not a 404, but typically something that
429      *                          would end up as a 5xx by the servlet container)
430      * @param context Our ServletContext object
431      */
432     public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,
433                               ActionMapping mapping) throws ServletException {
434 
435         Map<String, Object> extraContext = createContextMap(request, response, mapping, context);
436 
437         // If there was a previous value stack, then create a new copy and pass it in to be used by the new Action
438         ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
439         boolean nullStack = stack == null;
440         if (nullStack) {
441             ActionContext ctx = ActionContext.getContext();
442             if (ctx != null) {
443                 stack = ctx.getValueStack();
444             }
445         }
446         if (stack != null) {
447             extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack));
448         }
449 
450         String timerKey = "Handling request from Dispatcher";
451         try {
452             UtilTimerStack.push(timerKey);
453             String namespace = mapping.getNamespace();
454             String name = mapping.getName();
455             String method = mapping.getMethod();
456 
457             Configuration config = configurationManager.getConfiguration();
458             ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
459                     namespace, name, method, extraContext, true, false);
460 
461             request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
462 
463             // if the ActionMapping says to go straight to a result, do it!
464             if (mapping.getResult() != null) {
465                 Result result = mapping.getResult();
466                 result.execute(proxy.getInvocation());
467             } else {
468                 proxy.execute();
469             }
470 
471             // If there was a previous value stack then set it back onto the request
472             if (!nullStack) {
473                 request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
474             }
475         } catch (ConfigurationException e) {
476         	// WW-2874 Only log error if in devMode
477         	if(devMode) {
478         		LOG.error("Could not find action or result", e);
479         	}
480         	else {
481         		LOG.warn("Could not find action or result", e);
482         	}
483             sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e);
484         } catch (Exception e) {
485             sendError(request, response, context, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
486         } finally {
487             UtilTimerStack.pop(timerKey);
488         }
489     }
490 
491     /***
492      * Create a context map containing all the wrapped request objects
493      *
494      * @param request The servlet request
495      * @param response The servlet response
496      * @param mapping The action mapping
497      * @param context The servlet context
498      * @return A map of context objects
499      */
500     public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response,
501             ActionMapping mapping, ServletContext context) {
502 
503         // request map wrapping the http request objects
504         Map requestMap = new RequestMap(request);
505 
506         // parameters map wrapping the http parameters.  ActionMapping parameters are now handled and applied separately
507         Map params = new HashMap(request.getParameterMap());
508 
509         // session map wrapping the http session
510         Map session = new SessionMap(request);
511 
512         // application map wrapping the ServletContext
513         Map application = new ApplicationMap(context);
514 
515         Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context);
516 
517         if (mapping != null) {
518             extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);
519         }
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 }