001: /*
002: * Copyright (c) 2002-2006 by OpenSymphony
003: * All rights reserved.
004: */
005: package com.opensymphony.webwork.dispatcher;
006:
007: import com.opensymphony.util.ClassLoaderUtil;
008: import com.opensymphony.util.FileManager;
009: import com.opensymphony.webwork.ServletActionContext;
010: import com.opensymphony.webwork.WebWorkStatics;
011: import com.opensymphony.webwork.WebWorkConstants;
012: import com.opensymphony.webwork.views.freemarker.FreemarkerManager;
013: import com.opensymphony.webwork.config.Configuration;
014: import com.opensymphony.webwork.dispatcher.mapper.ActionMapping;
015: import com.opensymphony.webwork.dispatcher.multipart.MultiPartRequest;
016: import com.opensymphony.webwork.dispatcher.multipart.MultiPartRequestWrapper;
017: import com.opensymphony.webwork.util.AttributeMap;
018: import com.opensymphony.webwork.util.ObjectFactoryDestroyable;
019: import com.opensymphony.webwork.util.ObjectFactoryInitializable;
020: import com.opensymphony.xwork.*;
021: import com.opensymphony.xwork.config.ConfigurationException;
022: import com.opensymphony.xwork.config.ConfigurationManager;
023: import com.opensymphony.xwork.interceptor.component.ComponentInterceptor;
024: import com.opensymphony.xwork.interceptor.component.ComponentManager;
025: import com.opensymphony.xwork.util.*;
026: import com.opensymphony.xwork.util.location.Location;
027: import com.opensymphony.xwork.util.location.LocationUtils;
028: import org.apache.commons.logging.Log;
029: import org.apache.commons.logging.LogFactory;
030:
031: import javax.servlet.ServletContext;
032: import javax.servlet.ServletException;
033: import javax.servlet.http.HttpServletRequest;
034: import javax.servlet.http.HttpServletResponse;
035: import java.io.File;
036: import java.io.IOException;
037: import java.util.*;
038:
039: import freemarker.template.Template;
040:
041: /**
042: * A utility class whereby FilterDispatcher delegate most of its tasks to. A static
043: * singleton that gets initlialized upon the call to it's
044: * <code>initalize(ServletContext)</code>
045: * method
046: *
047: * @author patrick
048: * @author Rainer Hermanns
049: * @author tm_jee
050: * @version $Date: 2007-06-11 16:53:47 +0200 (Mon, 11 Jun 2007) $ $Id: DispatcherUtils.java 2920 2007-06-11 14:53:47Z tm_jee $
051: *
052: * @see com.opensymphony.webwork.dispatcher.FilterDispatcher
053: */
054: public class DispatcherUtils {
055: private static final Log LOG = LogFactory
056: .getLog(DispatcherUtils.class);
057:
058: private static DispatcherUtils instance;
059:
060: private static boolean portletSupportActive;
061:
062: public static void initialize(ServletContext servletContext) {
063: synchronized (DispatcherUtils.class) {
064: if (instance == null) {
065: instance = new DispatcherUtils(servletContext);
066: }
067: }
068: }
069:
070: public static DispatcherUtils getInstance() {
071: return instance;
072: }
073:
074: public static void setInstance(DispatcherUtils instance) {
075: DispatcherUtils.instance = instance;
076: }
077:
078: protected boolean devMode = false;
079:
080: // used to get WebLogic to play nice
081: protected boolean paramsWorkaroundEnabled = false;
082:
083: protected DispatcherUtils(ServletContext servletContext) {
084: init(servletContext);
085: }
086:
087: public void cleanup() {
088: ObjectFactory objectFactory = ObjectFactory.getObjectFactory();
089:
090: // inform ShutDownListener, we are shuting down
091: if (Configuration
092: .isSet(WebWorkConstants.WEBWORK_DISPATCHER_SHUTDOWN_LISTENER)) {
093: String[] shutdownListenerClassNames = Configuration
094: .getString(
095: WebWorkConstants.WEBWORK_DISPATCHER_SHUTDOWN_LISTENER)
096: .split(",");
097: for (int a = 0; a < shutdownListenerClassNames.length; a++) {
098: String shutdownListenerClassName = shutdownListenerClassNames[a]
099: .trim();
100: try {
101: ShutDownListener shutDownListener = (ShutDownListener) objectFactory
102: .buildBean(shutdownListenerClassName,
103: Collections.EMPTY_MAP);
104: if (LOG.isDebugEnabled()) {
105: LOG.debug("notifying shutdown listener ["
106: + shutDownListener + "]");
107: }
108: shutDownListener.shutdown();
109: } catch (Exception e) { // we might also get ClassCastException
110: LOG
111: .warn(
112: "shutdown listener ["
113: + shutdownListenerClassName
114: + "] failed to be initialized, it will be ignored",
115: e);
116: }
117: }
118: }
119:
120: // clean up ObjectFactory
121: if (objectFactory == null) {
122: LOG
123: .warn("Object Factory is null, something is seriously wrong, no clean up will be performed");
124: }
125: if (objectFactory instanceof ObjectFactoryDestroyable) {
126: try {
127: ((ObjectFactoryDestroyable) objectFactory).destroy();
128: } catch (Exception e) {
129: // catch any exception that may occured during destroy() and log it
130: LOG.error(
131: "exception occurred while destroying ObjectFactory ["
132: + objectFactory + "]", e);
133: }
134: }
135:
136: // clean up ConfigurationManager
137: ConfigurationManager.destroyConfiguration();
138: }
139:
140: protected void init(ServletContext servletContext) {
141: boolean reloadi18n = Boolean.valueOf(
142: (String) Configuration
143: .get(WebWorkConstants.WEBWORK_I18N_RELOAD))
144: .booleanValue();
145: LocalizedTextUtil.setReloadBundles(reloadi18n);
146:
147: // initialize ObjectFactory
148: ObjectFactory objectFactory = null;
149: if (Configuration.isSet(WebWorkConstants.WEBWORK_OBJECTFACTORY)) {
150: String className = (String) Configuration
151: .get(WebWorkConstants.WEBWORK_OBJECTFACTORY);
152: if (className.equals("spring")) {
153: // note: this class name needs to be in string form so we don't put hard
154: // dependencies on spring, since it isn't technically required.
155: className = "com.opensymphony.webwork.spring.WebWorkSpringObjectFactory";
156: } else if (className.equals("plexus")) {
157: // note: this class name needs to be in string form so we don't put hard
158: // dependencies on spring, since it isn't technically required.
159: className = "com.opensymphony.webwork.plexus.PlexusObjectFactory";
160: }
161:
162: try {
163: Class clazz = ClassLoaderUtil.loadClass(className,
164: DispatcherUtils.class);
165: objectFactory = (ObjectFactory) clazz.newInstance();
166: if (objectFactory instanceof ObjectFactoryInitializable) {
167: ((ObjectFactoryInitializable) objectFactory)
168: .init(servletContext);
169: }
170: ObjectFactory.setObjectFactory(objectFactory);
171: } catch (Exception e) {
172: LOG.error("Could not load ObjectFactory named "
173: + className + ". Using default ObjectFactory.",
174: e);
175: }
176: }
177:
178: // Intialize ObjecTypeDeterminer
179: if (Configuration
180: .isSet(WebWorkConstants.WEBWORK_OBJECTTYPEDETERMINER)) {
181: String className = (String) Configuration
182: .get(WebWorkConstants.WEBWORK_OBJECTTYPEDETERMINER);
183: if (className.equals("tiger")) {
184: // note: this class name needs to be in string form so we don't put hard
185: // dependencies on xwork-tiger, since it isn't technically required.
186: className = "com.opensymphony.xwork.util.GenericsObjectTypeDeterminer";
187: } else if (className.equals("notiger")) {
188: className = "com.opensymphony.xwork.util.DefaultObjectTypeDeterminer";
189: }
190:
191: try {
192: Class clazz = ClassLoaderUtil.loadClass(className,
193: DispatcherUtils.class);
194: ObjectTypeDeterminer objectTypeDeterminer = (ObjectTypeDeterminer) clazz
195: .newInstance();
196: ObjectTypeDeterminerFactory
197: .setInstance(objectTypeDeterminer);
198: } catch (Exception e) {
199: LOG
200: .error(
201: "Could not load ObjectTypeDeterminer named "
202: + className
203: + ". Using default DefaultObjectTypeDeterminer.",
204: e);
205: }
206: }
207:
208: if ("true".equals(Configuration
209: .get(WebWorkConstants.WEBWORK_DEVMODE))) {
210: devMode = true;
211: Configuration.set(WebWorkConstants.WEBWORK_I18N_RELOAD,
212: "true");
213: Configuration.set(
214: WebWorkConstants.WEBWORK_CONFIGURATION_XML_RELOAD,
215: "true");
216: }
217:
218: //check for configuration reloading
219: if (Configuration
220: .isSet(WebWorkConstants.WEBWORK_CONFIGURATION_XML_RELOAD)
221: && "true"
222: .equalsIgnoreCase(Configuration
223: .getString(WebWorkConstants.WEBWORK_CONFIGURATION_XML_RELOAD))) {
224: FileManager.setReloadingConfigs(true);
225: }
226:
227: if (Configuration
228: .isSet(WebWorkConstants.WEBWORK_CONTINUATIONS_PACKAGE)) {
229: String pkg = Configuration
230: .getString(WebWorkConstants.WEBWORK_CONTINUATIONS_PACKAGE);
231: ObjectFactory.setContinuationPackage(pkg);
232: }
233:
234: // test wether param-access workaround needs to be enabled
235: if (servletContext.getServerInfo().indexOf("WebLogic") >= 0) {
236: LOG
237: .info("WebLogic server detected. Enabling WebWork parameter access work-around.");
238: paramsWorkaroundEnabled = true;
239: } else if (Configuration
240: .isSet(WebWorkConstants.WEBWORK_DISPATCHER_PARAMETERSWORKAROUND)) {
241: paramsWorkaroundEnabled = "true"
242: .equals(Configuration
243: .get(WebWorkConstants.WEBWORK_DISPATCHER_PARAMETERSWORKAROUND));
244: } else {
245: LOG.debug("Parameter access work-around disabled.");
246: }
247:
248: // inform startup listeners
249: if (Configuration
250: .isSet(WebWorkConstants.WEBWORK_DISPATCHER_START_UP_LISTENER)) {
251: String[] startupListenerClassNames = Configuration
252: .getString(
253: WebWorkConstants.WEBWORK_DISPATCHER_START_UP_LISTENER)
254: .split(",");
255: for (int a = 0; a < startupListenerClassNames.length; a++) {
256: String startupListenerClassName = startupListenerClassNames[a]
257: .trim();
258: try {
259: StartUpListener startUpListener = (StartUpListener) objectFactory
260: .buildBean(startupListenerClassName,
261: Collections.EMPTY_MAP);
262: if (LOG.isDebugEnabled()) {
263: LOG.debug("notifying start up listener ["
264: + startUpListener + "]");
265: }
266: startUpListener.startup();
267: } catch (Exception e) { // we might also get ClassCastException
268: LOG
269: .warn(
270: "shutdown listener ["
271: + startupListenerClassName
272: + "] failed to be initialized, it will be ignored",
273: e);
274: }
275: }
276: }
277:
278: }
279:
280: /**
281: * Loads the action and executes it. This method first creates the action context from the given
282: * parameters then loads an <tt>ActionProxy</tt> from the given action name and namespace. After that,
283: * the action is executed and output channels throught the response object. Actions not found are
284: * sent back to the user via the {@link DispatcherUtils#sendError} method, using the 404 return code.
285: * All other errors are reported by throwing a ServletException.
286: *
287: * @param request the HttpServletRequest object
288: * @param response the HttpServletResponse object
289: * @param mapping the action mapping object
290: * @throws ServletException when an unknown error occurs (not a 404, but typically something that
291: * would end up as a 5xx by the servlet container)
292: */
293: public void serviceAction(HttpServletRequest request,
294: HttpServletResponse response, ServletContext context,
295: ActionMapping mapping) throws ServletException {
296: Map extraContext = createContextMap(request, response, mapping,
297: context);
298:
299: // If there was a previous value stack, then create a new copy and pass it in to be used by the new Action
300: OgnlValueStack stack = (OgnlValueStack) request
301: .getAttribute(ServletActionContext.WEBWORK_VALUESTACK_KEY);
302: if (stack != null) {
303: extraContext.put(ActionContext.VALUE_STACK,
304: new OgnlValueStack(stack));
305: }
306:
307: try {
308: String namespace = mapping.getNamespace();
309: String name = mapping.getName();
310: String method = mapping.getMethod();
311:
312: String id = request
313: .getParameter(XWorkContinuationConfig.CONTINUE_PARAM);
314: if (id != null) {
315: // remove the continue key from the params - we don't want to bother setting
316: // on the value stack since we know it won't work. Besides, this breaks devMode!
317: Map params = (Map) extraContext
318: .get(ActionContext.PARAMETERS);
319: params.remove(XWorkContinuationConfig.CONTINUE_PARAM);
320:
321: // and now put the key in the context to be picked up later by XWork
322: extraContext.put(XWorkContinuationConfig.CONTINUE_KEY,
323: id);
324: }
325:
326: ActionProxy proxy = ActionProxyFactory.getFactory()
327: .createActionProxy(namespace, name, extraContext,
328: true, false);
329: proxy.setMethod(method);
330: request.setAttribute(
331: ServletActionContext.WEBWORK_VALUESTACK_KEY, proxy
332: .getInvocation().getStack());
333:
334: // if the ActionMapping says to go straight to a result, do it!
335: if (mapping.getResult() != null) {
336: Result result = mapping.getResult();
337: result.execute(proxy.getInvocation());
338: } else {
339: proxy.execute();
340: }
341:
342: // If there was a previous value stack then set it back onto the request
343: if (stack != null) {
344: request.setAttribute(
345: ServletActionContext.WEBWORK_VALUESTACK_KEY,
346: stack);
347: }
348: } catch (ConfigurationException e) {
349: LOG.error("Could not find action", e);
350: sendError(request, response, context,
351: HttpServletResponse.SC_NOT_FOUND, e);
352: } catch (Exception e) {
353: throw new ServletException(e);
354: }
355: }
356:
357: public Map createContextMap(HttpServletRequest request,
358: HttpServletResponse response, ActionMapping mapping,
359: ServletContext context) {
360: // request map wrapping the http request objects
361: Map requestMap = new RequestMap(request);
362:
363: // parameters map wrapping the http paraneters.
364: Map params = null;
365: if (mapping != null) {
366: params = mapping.getParams();
367: }
368: Map requestParams = new HashMap(request.getParameterMap());
369: if (params != null) {
370: params.putAll(requestParams);
371: } else {
372: params = requestParams;
373: }
374:
375: // session map wrapping the http session
376: Map session = new SessionMap(request);
377:
378: // application map wrapping the ServletContext
379: Map application = new ApplicationMap(context);
380:
381: return createContextMap(requestMap, params, session,
382: application, request, response, context);
383: }
384:
385: /**
386: * Merges all application and servlet attributes into a single <tt>HashMap</tt> to represent the entire
387: * <tt>Action</tt> context.
388: *
389: * @param requestMap a Map of all request attributes.
390: * @param parameterMap a Map of all request parameters.
391: * @param sessionMap a Map of all session attributes.
392: * @param applicationMap a Map of all servlet context attributes.
393: * @param request the HttpServletRequest object.
394: * @param response the HttpServletResponse object.
395: * @param servletContext the ServletContext object.
396: * @return a HashMap representing the <tt>Action</tt> context.
397: */
398: public HashMap createContextMap(Map requestMap, Map parameterMap,
399: Map sessionMap, Map applicationMap,
400: HttpServletRequest request, HttpServletResponse response,
401: ServletContext servletContext) {
402: HashMap extraContext = new HashMap();
403: extraContext.put(ActionContext.PARAMETERS, new HashMap(
404: parameterMap));
405: extraContext.put(ActionContext.SESSION, sessionMap);
406: extraContext.put(ActionContext.APPLICATION, applicationMap);
407:
408: Locale locale = null;
409: if (Configuration.isSet(WebWorkConstants.WEBWORK_LOCALE)) {
410: locale = LocalizedTextUtil.localeFromString(Configuration
411: .getString(WebWorkConstants.WEBWORK_LOCALE),
412: request.getLocale());
413: } else {
414: locale = request.getLocale();
415: }
416:
417: extraContext.put(ActionContext.LOCALE, locale);
418: extraContext.put(ActionContext.DEV_MODE, Boolean
419: .valueOf(devMode));
420:
421: extraContext.put(WebWorkStatics.HTTP_REQUEST, request);
422: extraContext.put(WebWorkStatics.HTTP_RESPONSE, response);
423: extraContext
424: .put(WebWorkStatics.SERVLET_CONTEXT, servletContext);
425: extraContext
426: .put(
427: ComponentInterceptor.COMPONENT_MANAGER,
428: request
429: .getAttribute(ComponentManager.COMPONENT_MANAGER_KEY));
430:
431: // helpers to get access to request/session/application scope
432: extraContext.put("request", requestMap);
433: extraContext.put("session", sessionMap);
434: extraContext.put("application", applicationMap);
435: extraContext.put("parameters", parameterMap);
436:
437: AttributeMap attrMap = new AttributeMap(extraContext);
438: extraContext.put("attr", attrMap);
439:
440: return extraContext;
441: }
442:
443: /**
444: * Returns the maximum upload size allowed for multipart requests (this is configurable).
445: *
446: * @return the maximum upload size allowed for multipart requests
447: */
448: public static int getMaxSize() {
449: Integer maxSize = new Integer(Integer.MAX_VALUE);
450: try {
451: String maxSizeStr = Configuration
452: .getString(WebWorkConstants.WEBWORK_MULTIPART_MAXSIZE);
453:
454: if (maxSizeStr != null) {
455: try {
456: maxSize = new Integer(maxSizeStr);
457: } catch (NumberFormatException e) {
458: LOG
459: .warn("Unable to format 'webwork.multipart.maxSize' property setting. Defaulting to Integer.MAX_VALUE");
460: }
461: } else {
462: LOG
463: .warn("Unable to format 'webwork.multipart.maxSize' property setting. Defaulting to Integer.MAX_VALUE");
464: }
465: } catch (IllegalArgumentException e1) {
466: LOG
467: .warn("Unable to format 'webwork.multipart.maxSize' property setting. Defaulting to Integer.MAX_VALUE");
468: }
469:
470: if (LOG.isDebugEnabled()) {
471: LOG.debug("maxSize=" + maxSize);
472: }
473:
474: return maxSize.intValue();
475: }
476:
477: /**
478: * Returns the path to save uploaded files to (this is configurable).
479: *
480: * @return the path to save uploaded files to
481: */
482: public String getSaveDir(ServletContext servletContext) {
483: String saveDir = Configuration.getString(
484: WebWorkConstants.WEBWORK_MULTIPART_SAVEDIR).trim();
485:
486: if (saveDir.equals("")) {
487: File tempdir = (File) servletContext
488: .getAttribute("javax.servlet.context.tempdir");
489: LOG
490: .info("Unable to find 'webwork.multipart.saveDir' property setting. Defaulting to javax.servlet.context.tempdir");
491:
492: if (tempdir != null) {
493: saveDir = tempdir.toString();
494: }
495: } else {
496: File multipartSaveDir = new File(saveDir);
497:
498: if (!multipartSaveDir.exists()) {
499: multipartSaveDir.mkdir();
500: }
501: }
502:
503: if (LOG.isDebugEnabled()) {
504: LOG.debug("saveDir=" + saveDir);
505: }
506:
507: return saveDir;
508: }
509:
510: public void prepare(HttpServletRequest request,
511: HttpServletResponse response) {
512: String encoding = null;
513: if (Configuration.isSet(WebWorkConstants.WEBWORK_I18N_ENCODING)) {
514: encoding = Configuration
515: .getString(WebWorkConstants.WEBWORK_I18N_ENCODING);
516: }
517:
518: Locale locale = null;
519: if (Configuration.isSet(WebWorkConstants.WEBWORK_LOCALE)) {
520: locale = LocalizedTextUtil.localeFromString(Configuration
521: .getString(WebWorkConstants.WEBWORK_LOCALE),
522: request.getLocale());
523: }
524:
525: if (encoding != null) {
526: try {
527: request.setCharacterEncoding(encoding);
528: } catch (Exception e) {
529: LOG.error("Error setting character encoding to '"
530: + encoding + "' - ignoring.", e);
531: }
532: }
533:
534: if (locale != null) {
535: response.setLocale(locale);
536: }
537:
538: if (paramsWorkaroundEnabled) {
539: request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request
540: }
541: }
542:
543: /**
544: * Wraps and returns the given response or returns the original response object. This is used to transparently
545: * handle multipart data as a wrapped class around the given request. Override this method to handle multipart
546: * requests in a special way or to handle other types of requests. Note, {@link com.opensymphony.webwork.dispatcher.multipart.MultiPartRequestWrapper} is
547: * flexible - you should look to that first before overriding this method to handle multipart data.
548: *
549: * @param request the HttpServletRequest object.
550: * @return a wrapped request or original request.
551: * @see com.opensymphony.webwork.dispatcher.multipart.MultiPartRequestWrapper
552: */
553: public HttpServletRequest wrapRequest(HttpServletRequest request,
554: ServletContext servletContext) throws IOException {
555: // don't wrap more than once
556: if (request instanceof WebWorkRequestWrapper) {
557: return request;
558: }
559:
560: if (MultiPartRequest.isMultiPart(request)) {
561: request = new MultiPartRequestWrapper(request,
562: getSaveDir(servletContext), getMaxSize());
563: } else {
564: request = new WebWorkRequestWrapper(request);
565: }
566:
567: return request;
568: }
569:
570: /**
571: * Sends an HTTP error response code.
572: *
573: * @param request the HttpServletRequest object.
574: * @param response the HttpServletResponse object.
575: * @param code the HttpServletResponse error code (see {@link javax.servlet.http.HttpServletResponse} for possible error codes).
576: * @param e the Exception that is reported.
577: */
578: public void sendError(HttpServletRequest request,
579: HttpServletResponse response, ServletContext ctx, int code,
580: Exception e) {
581: if (devMode) {
582: response.setContentType("text/html");
583:
584: try {
585: freemarker.template.Configuration config = FreemarkerManager
586: .getInstance().getConfiguration(ctx);
587: Template template = config
588: .getTemplate("/com/opensymphony/webwork/dispatcher/error.ftl");
589:
590: List chain = new ArrayList();
591: Throwable cur = e;
592: chain.add(cur);
593: while ((cur = cur.getCause()) != null) {
594: chain.add(cur);
595: }
596:
597: HashMap data = new HashMap();
598: data.put("exception", e);
599: data.put("unknown", Location.UNKNOWN);
600: data.put("chain", chain);
601: data.put("locator", new Locator());
602: template.process(data, response.getWriter());
603: response.getWriter().close();
604: } catch (Exception exp) {
605: try {
606: response.sendError(code,
607: "Unable to show problem report: " + exp);
608: } catch (IOException ex) {
609: // we're already sending an error, not much else we can do if more stuff breaks
610: }
611: }
612: } else {
613: try {
614: // send a http error response to use the servlet defined error handler
615: // make the exception availible to the web.xml defined error page
616: request
617: .setAttribute("javax.servlet.error.exception",
618: e);
619:
620: // for compatibility
621: request.setAttribute("javax.servlet.jsp.jspException",
622: e);
623:
624: // send the error response
625: response.sendError(code, e.getMessage());
626: } catch (IOException e1) {
627: // we're already sending an error, not much else we can do if more stuff breaks
628: }
629: }
630: }
631:
632: /**
633: * Returns <tt>true</tt>, if portlet support is active, <tt>false</tt> otherwise.
634: *
635: * @return <tt>true</tt>, if portlet support is active, <tt>false</tt> otherwise.
636: */
637: public static boolean isPortletSupportActive() {
638: return portletSupportActive;
639: }
640:
641: /**
642: * Set the flag that portlet support is active or not.
643: * @param portletSupportActive <tt>true</tt> or <tt>false</tt>
644: */
645: public static void setPortletSupportActive(
646: boolean portletSupportActive) {
647: DispatcherUtils.portletSupportActive = portletSupportActive;
648: }
649:
650: /** Simple accessor for a static method */
651: public class Locator {
652: public Location getLocation(Object obj) {
653: Location loc = LocationUtils.getLocation(obj);
654: if (loc == null) {
655: return Location.UNKNOWN;
656: }
657: return loc;
658: }
659: }
660: }
|