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