View Javadoc

1   /*
2    * $Id: Dispatcher.java 454856 2006-10-10 18:06:01Z mrdon $
3    *
4    * Copyright 2006 The Apache Software Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.struts2.dispatcher;
19  
20  import java.io.File;
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Locale;
26  import java.util.Map;
27  
28  import javax.servlet.ServletContext;
29  import javax.servlet.ServletException;
30  import javax.servlet.http.HttpServletRequest;
31  import javax.servlet.http.HttpServletResponse;
32  
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  import org.apache.struts2.ServletActionContext;
36  import org.apache.struts2.StrutsConstants;
37  import org.apache.struts2.StrutsStatics;
38  import org.apache.struts2.config.Settings;
39  import org.apache.struts2.config.StrutsXmlConfigurationProvider;
40  import org.apache.struts2.dispatcher.mapper.ActionMapping;
41  import org.apache.struts2.dispatcher.multipart.MultiPartRequest;
42  import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
43  import org.apache.struts2.impl.StrutsActionProxyFactory;
44  import org.apache.struts2.impl.StrutsObjectFactory;
45  import org.apache.struts2.util.AttributeMap;
46  import org.apache.struts2.util.ObjectFactoryDestroyable;
47  import org.apache.struts2.util.ObjectFactoryInitializable;
48  import org.apache.struts2.views.freemarker.FreemarkerManager;
49  
50  import com.opensymphony.xwork2.util.ClassLoaderUtil;
51  import com.opensymphony.xwork2.util.FileManager;
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.ConfigurationException;
58  import com.opensymphony.xwork2.config.ConfigurationManager;
59  import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider;
60  import com.opensymphony.xwork2.util.LocalizedTextUtil;
61  import com.opensymphony.xwork2.util.ObjectTypeDeterminer;
62  import com.opensymphony.xwork2.util.ObjectTypeDeterminerFactory;
63  import com.opensymphony.xwork2.util.ValueStack;
64  import com.opensymphony.xwork2.util.ValueStackFactory;
65  import com.opensymphony.xwork2.util.XWorkContinuationConfig;
66  import com.opensymphony.xwork2.util.location.Location;
67  import com.opensymphony.xwork2.util.location.LocationUtils;
68  import com.opensymphony.xwork2.util.profiling.UtilTimerStack;
69  
70  import freemarker.template.Template;
71  
72  /***
73   * A utility class the actual dispatcher delegates most of its tasks to. Each instance
74   * of the primary dispatcher holds an instance of this dispatcher to be shared for
75   * all requests.
76   *
77   * @see org.apache.struts2.dispatcher.FilterDispatcher
78   * @see org.apache.struts2.portlet.dispatcher.Jsr168Dispatcher
79   */
80  public class Dispatcher {
81  
82      // Set Struts-specific factories.
83      static {
84          ObjectFactory.setObjectFactory(new StrutsObjectFactory());
85          ActionProxyFactory.setFactory(new StrutsActionProxyFactory());
86      }
87  
88      private static final Log LOG = LogFactory.getLog(Dispatcher.class);
89  
90      private static ThreadLocal<Dispatcher> instance = new ThreadLocal<Dispatcher>();
91      private static List<DispatcherListener> dispatcherListeners =
92          new ArrayList<DispatcherListener>();
93  
94      private ConfigurationManager configurationManager;
95      private static boolean portletSupportActive;
96      private boolean devMode = false;
97  
98      // used to get WebLogic to play nice
99      private boolean paramsWorkaroundEnabled = false;
100 
101     /***
102      * Gets the current instance for this thread
103      *
104      * @return The dispatcher instance
105      */
106     public static Dispatcher getInstance() {
107         return (Dispatcher) instance.get();
108     }
109 
110     /***
111      * Sets the dispatcher instance for this thread
112      *
113      * @param instance The instance
114      */
115     public static void setInstance(Dispatcher instance) {
116         Dispatcher.instance.set(instance);
117     }
118 
119     /***
120      * Adds a dispatcher lifecycle listener
121      *
122      * @param l The listener
123      */
124     public static synchronized void addDispatcherListener(DispatcherListener l) {
125         dispatcherListeners.add(l);
126     }
127 
128     /***
129      * Removes a dispatcher lifecycle listener
130      *
131      * @param l The listener
132      */
133     public static synchronized void removeDispatcherListener(DispatcherListener l) {
134         dispatcherListeners.remove(l);
135     }
136 
137     /***
138      * The constructor with its servlet context instance (optional)
139      *
140      * @param servletContext The servlet context
141      */
142     public Dispatcher(ServletContext servletContext) {
143         init(servletContext);
144     }
145 
146     /***
147      * Cleans up thread local variables
148      */
149     public void cleanup() {
150         ObjectFactory objectFactory = ObjectFactory.getObjectFactory();
151         if (objectFactory == null) {
152             LOG.warn("Object Factory is null, something is seriously wrong, no clean up will be performed");
153         }
154         if (objectFactory instanceof ObjectFactoryDestroyable) {
155             try {
156                 ((ObjectFactoryDestroyable)objectFactory).destroy();
157             }
158             catch(Exception e) {
159                 // catch any exception that may occured during destroy() and log it
160                 LOG.error("exception occurred while destroying ObjectFactory ["+objectFactory+"]", e);
161             }
162         }
163         instance.set(null);
164         synchronized(Dispatcher.class) {
165             if (dispatcherListeners.size() > 0) {
166                 for (DispatcherListener l : dispatcherListeners) {
167                     l.dispatcherDestroyed(this);
168                 }
169             }
170         }
171     }
172 
173     /***
174      * Initializes the instance
175      *
176      * @param servletContext The servlet context
177      */
178     private void init(ServletContext servletContext) {
179         boolean reloadi18n = Boolean.valueOf((String) Settings.get(StrutsConstants.STRUTS_I18N_RELOAD)).booleanValue();
180         LocalizedTextUtil.setReloadBundles(reloadi18n);
181 
182         if (Settings.isSet(StrutsConstants.STRUTS_OBJECTFACTORY)) {
183             String className = (String) Settings.get(StrutsConstants.STRUTS_OBJECTFACTORY);
184             if (className.equals("spring")) {
185                 // note: this class name needs to be in string form so we don't put hard
186                 //       dependencies on spring, since it isn't technically required.
187                 className = "org.apache.struts2.spring.StrutsSpringObjectFactory";
188             } else if (className.equals("plexus")) {
189                 className = "org.apache.struts2.plexus.PlexusObjectFactory";
190                 LOG.warn("The 'plexus' shorthand for the Plexus ObjectFactory is deprecated.  Please "
191                         +"use the full class name: "+className);
192             }
193 
194             try {
195                 Class clazz = ClassLoaderUtil.loadClass(className, Dispatcher.class);
196                 ObjectFactory objectFactory = (ObjectFactory) clazz.newInstance();
197                 if (servletContext != null) {
198                     if (objectFactory instanceof ObjectFactoryInitializable) {
199                         ((ObjectFactoryInitializable) objectFactory).init(servletContext);
200                     }
201                 }
202                 ObjectFactory.setObjectFactory(objectFactory);
203             } catch (Exception e) {
204                 LOG.error("Could not load ObjectFactory named " + className + ". Using default ObjectFactory.", e);
205             }
206         }
207 
208         if (Settings.isSet(StrutsConstants.STRUTS_OBJECTTYPEDETERMINER)) {
209             String className = (String) Settings.get(StrutsConstants.STRUTS_OBJECTTYPEDETERMINER);
210             if (className.equals("tiger")) {
211                 // note: this class name needs to be in string form so we don't put hard
212                 //       dependencies on xwork-tiger, since it isn't technically required.
213                 className = "com.opensymphony.xwork2.util.GenericsObjectTypeDeterminer";
214             }
215             else if (className.equals("notiger")) {
216                 className = "com.opensymphony.xwork2.util.DefaultObjectTypeDeterminer";
217             }
218 
219             try {
220                 Class clazz = ClassLoaderUtil.loadClass(className, Dispatcher.class);
221                 ObjectTypeDeterminer objectTypeDeterminer = (ObjectTypeDeterminer) clazz.newInstance();
222                 ObjectTypeDeterminerFactory.setInstance(objectTypeDeterminer);
223             } catch (Exception e) {
224                 LOG.error("Could not load ObjectTypeDeterminer named " + className + ". Using default DefaultObjectTypeDeterminer.", e);
225             }
226         }
227 
228         if ("true".equals(Settings.get(StrutsConstants.STRUTS_DEVMODE))) {
229             devMode = true;
230             Settings.set(StrutsConstants.STRUTS_I18N_RELOAD, "true");
231             Settings.set(StrutsConstants.STRUTS_CONFIGURATION_XML_RELOAD, "true");
232         }
233 
234         //check for configuration reloading
235         if ("true".equalsIgnoreCase(Settings.get(StrutsConstants.STRUTS_CONFIGURATION_XML_RELOAD))) {
236             FileManager.setReloadingConfigs(true);
237         }
238 
239         if (Settings.isSet(StrutsConstants.STRUTS_CONTINUATIONS_PACKAGE)) {
240             String pkg = Settings.get(StrutsConstants.STRUTS_CONTINUATIONS_PACKAGE);
241             ObjectFactory.setContinuationPackage(pkg);
242         }
243 
244         // test wether param-access workaround needs to be enabled
245         if (servletContext != null && servletContext.getServerInfo() != null
246                 && servletContext.getServerInfo().indexOf("WebLogic") >= 0) {
247             LOG.info("WebLogic server detected. Enabling Struts parameter access work-around.");
248             paramsWorkaroundEnabled = true;
249         } else if (Settings.isSet(StrutsConstants.STRUTS_DISPATCHER_PARAMETERSWORKAROUND)) {
250             paramsWorkaroundEnabled = "true".equals(Settings.get(StrutsConstants.STRUTS_DISPATCHER_PARAMETERSWORKAROUND));
251         } else {
252             LOG.debug("Parameter access work-around disabled.");
253         }
254 
255         configurationManager = new ConfigurationManager();
256         String configFiles = null;
257         if (Settings.isSet(StrutsConstants.STRUTS_CONFIGURATION_FILES)) {
258             configFiles = Settings.get(StrutsConstants.STRUTS_CONFIGURATION_FILES);
259         }
260         if (configFiles != null) {
261 	        String[] files = configFiles.split("//s*[,]//s*");
262 	        for (String file : files) {
263 	            if ("xwork.xml".equals(file)) {
264 	                configurationManager.addConfigurationProvider(new XmlConfigurationProvider(file, false));
265 	            } else {
266 	                configurationManager.addConfigurationProvider(new StrutsXmlConfigurationProvider(file, false));
267 	            }
268 	        }
269         }
270 
271         synchronized(Dispatcher.class) {
272             if (dispatcherListeners.size() > 0) {
273                 for (DispatcherListener l : dispatcherListeners) {
274                     l.dispatcherInitialized(this);
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 Dispatcher#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, HttpServletResponse response, ServletContext context, ActionMapping mapping) throws ServletException {
294         Map<String, Object> extraContext = createContextMap(request, response, mapping, context);
295 
296         // If there was a previous value stack, then create a new copy and pass it in to be used by the new Action
297         ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
298         if (stack != null) {
299             extraContext.put(ActionContext.VALUE_STACK, ValueStackFactory.getFactory().createValueStack(stack));
300         }
301 
302         String timerKey = "Handling request from Dispatcher";
303         try {
304             UtilTimerStack.push(timerKey);
305             String namespace = mapping.getNamespace();
306             String name = mapping.getName();
307             String method = mapping.getMethod();
308 
309             String id = request.getParameter(XWorkContinuationConfig.CONTINUE_PARAM);
310             if (id != null) {
311                 // remove the continue key from the params - we don't want to bother setting
312                 // on the value stack since we know it won't work. Besides, this breaks devMode!
313                 Map params = (Map) extraContext.get(ActionContext.PARAMETERS);
314                 params.remove(XWorkContinuationConfig.CONTINUE_PARAM);
315 
316                 // and now put the key in the context to be picked up later by XWork
317                 extraContext.put(XWorkContinuationConfig.CONTINUE_KEY, id);
318             }
319 
320             ActionProxy proxy = ActionProxyFactory.getFactory().createActionProxy(
321                     configurationManager.getConfiguration(), namespace, name, extraContext, true, false);
322             proxy.setMethod(method);
323             request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
324 
325             // if the ActionMapping says to go straight to a result, do it!
326             if (mapping.getResult() != null) {
327                 Result result = mapping.getResult();
328                 result.execute(proxy.getInvocation());
329             } else {
330                 proxy.execute();
331             }
332 
333             // If there was a previous value stack then set it back onto the request
334             if (stack != null) {
335                 request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
336             }
337         } catch (ConfigurationException e) {
338             LOG.error("Could not find action or result", e);
339             sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e);
340         } catch (Exception e) {
341             LOG.error("Could not execute action", e);
342             sendError(request, response, context, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
343         } finally {
344             UtilTimerStack.pop(timerKey);
345         }
346     }
347 
348     /***
349      * Creates a context map containing all the wrapped request objects
350      *
351      * @param request The servlet request
352      * @param response The servlet response
353      * @param mapping The action mapping
354      * @param context The servlet context
355      * @return A map of context objects
356      */
357     public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response,
358             ActionMapping mapping, ServletContext context) {
359         // request map wrapping the http request objects
360         Map requestMap = new RequestMap(request);
361 
362         // parameters map wrapping the http paraneters.
363         Map params = null;
364         if (mapping != null) {
365             params = mapping.getParams();
366         }
367         Map requestParams = new HashMap(request.getParameterMap());
368         if (params != null) {
369             params.putAll(requestParams);
370         } else {
371             params = requestParams;
372         }
373 
374         // session map wrapping the http session
375         Map session = new SessionMap(request);
376 
377         // application map wrapping the ServletContext
378         Map application = new ApplicationMap(context);
379 
380         Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context);
381         extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);
382         return extraContext;
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 ServletContextmapping object.
396      * @return a HashMap representing the <tt>Action</tt> context.
397      */
398     public HashMap<String,Object> createContextMap(Map requestMap,
399                                     Map parameterMap,
400                                     Map sessionMap,
401                                     Map applicationMap,
402                                     HttpServletRequest request,
403                                     HttpServletResponse response,
404                                     ServletContext servletContext) {
405         HashMap<String,Object> extraContext = new HashMap<String,Object>();
406         extraContext.put(ActionContext.PARAMETERS, new HashMap(parameterMap));
407         extraContext.put(ActionContext.SESSION, sessionMap);
408         extraContext.put(ActionContext.APPLICATION, applicationMap);
409 
410         Locale locale = null;
411         if (Settings.isSet(StrutsConstants.STRUTS_LOCALE)) {
412             locale = LocalizedTextUtil.localeFromString(Settings.get(StrutsConstants.STRUTS_LOCALE), request.getLocale());
413         } else {
414             locale = request.getLocale();
415         }
416 
417         extraContext.put(ActionContext.LOCALE, locale);
418         extraContext.put(ActionContext.DEV_MODE, Boolean.valueOf(devMode));
419 
420         extraContext.put(StrutsStatics.HTTP_REQUEST, request);
421         extraContext.put(StrutsStatics.HTTP_RESPONSE, response);
422         extraContext.put(StrutsStatics.SERVLET_CONTEXT, servletContext);
423 
424         // helpers to get access to request/session/application scope
425         extraContext.put("request", requestMap);
426         extraContext.put("session", sessionMap);
427         extraContext.put("application", applicationMap);
428         extraContext.put("parameters", parameterMap);
429 
430         AttributeMap attrMap = new AttributeMap(extraContext);
431         extraContext.put("attr", attrMap);
432 
433         return extraContext;
434     }
435 
436     /***
437      * Returns the maximum upload size allowed for multipart requests (this is configurable).
438      *
439      * @return the maximum upload size allowed for multipart requests
440      */
441     private static int getMaxSize() {
442         Integer maxSize = new Integer(Integer.MAX_VALUE);
443         try {
444             String maxSizeStr = Settings.get(StrutsConstants.STRUTS_MULTIPART_MAXSIZE);
445 
446             if (maxSizeStr != null) {
447                 try {
448                     maxSize = new Integer(maxSizeStr);
449                 } catch (NumberFormatException e) {
450                     LOG.warn("Unable to format 'struts.multipart.maxSize' property setting. Defaulting to Integer.MAX_VALUE");
451                 }
452             } else {
453                 LOG.warn("Unable to format 'struts.multipart.maxSize' property setting. Defaulting to Integer.MAX_VALUE");
454             }
455         } catch (IllegalArgumentException e1) {
456             LOG.warn("Unable to format 'struts.multipart.maxSize' property setting. Defaulting to Integer.MAX_VALUE");
457         }
458 
459         if (LOG.isDebugEnabled()) {
460             LOG.debug("maxSize=" + maxSize);
461         }
462 
463         return maxSize.intValue();
464     }
465 
466     /***
467      * Returns the path to save uploaded files to (this is configurable).
468      *
469      * @return the path to save uploaded files to
470      */
471     private String getSaveDir(ServletContext servletContext) {
472         String saveDir = Settings.get(StrutsConstants.STRUTS_MULTIPART_SAVEDIR).trim();
473 
474         if (saveDir.equals("")) {
475             File tempdir = (File) servletContext.getAttribute("javax.servlet.context.tempdir");
476             LOG.info("Unable to find 'struts.multipart.saveDir' property setting. Defaulting to javax.servlet.context.tempdir");
477 
478             if (tempdir != null) {
479                 saveDir = tempdir.toString();
480             }
481         } else {
482             File multipartSaveDir = new File(saveDir);
483 
484             if (!multipartSaveDir.exists()) {
485                 multipartSaveDir.mkdir();
486             }
487         }
488 
489         if (LOG.isDebugEnabled()) {
490             LOG.debug("saveDir=" + saveDir);
491         }
492 
493         return saveDir;
494     }
495 
496     /***
497      * Prepares a request, including setting the encoding and locale
498      *
499      * @param request The request
500      * @param response The response
501      */
502     public void prepare(HttpServletRequest request, HttpServletResponse response) {
503         String encoding = null;
504         if (Settings.isSet(StrutsConstants.STRUTS_I18N_ENCODING)) {
505             encoding = Settings.get(StrutsConstants.STRUTS_I18N_ENCODING);
506         }
507 
508         Locale locale = null;
509         if (Settings.isSet(StrutsConstants.STRUTS_LOCALE)) {
510             locale = LocalizedTextUtil.localeFromString(Settings.get(StrutsConstants.STRUTS_LOCALE), request.getLocale());
511         }
512 
513         if (encoding != null) {
514             try {
515                 request.setCharacterEncoding(encoding);
516             } catch (Exception e) {
517                 LOG.error("Error setting character encoding to '" + encoding + "' - ignoring.", e);
518             }
519         }
520 
521         if (locale != null) {
522             response.setLocale(locale);
523         }
524 
525         if (paramsWorkaroundEnabled) {
526             request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request
527         }
528     }
529 
530     /***
531      * Wraps and returns the given response or returns the original response object. This is used to transparently
532      * handle multipart data as a wrapped class around the given request. Override this method to handle multipart
533      * requests in a special way or to handle other types of requests. Note, {@link org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper} is
534      * flexible - you should look to that first before overriding this method to handle multipart data.
535      *
536      * @param request the HttpServletRequest object.
537      * @return a wrapped request or original request.
538      * @see org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper
539      */
540     public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException {
541         // don't wrap more than once
542         if (request instanceof StrutsRequestWrapper) {
543             return request;
544         }
545 
546         if (MultiPartRequest.isMultiPart(request)) {
547             request = new MultiPartRequestWrapper(request, getSaveDir(servletContext), getMaxSize());
548         } else {
549             request = new StrutsRequestWrapper(request);
550         }
551 
552         return request;
553     }
554 
555     /***
556      * Sends an HTTP error response code.
557      *
558      * @param request  the HttpServletRequest object.
559      * @param response the HttpServletResponse object.
560      * @param code     the HttpServletResponse error code (see {@link javax.servlet.http.HttpServletResponse} for possible error codes).
561      * @param e        the Exception that is reported.
562      */
563     public void sendError(HttpServletRequest request, HttpServletResponse response,
564             ServletContext ctx, int code, Exception e) {
565         if (devMode) {
566             response.setContentType("text/html");
567 
568             try {
569                 freemarker.template.Configuration config = FreemarkerManager.getInstance().getConfiguration(ctx);
570                 Template template = config.getTemplate("/org/apache/struts2/dispatcher/error.ftl");
571 
572                 List<Throwable> chain = new ArrayList<Throwable>();
573                 Throwable cur = e;
574                 chain.add(cur);
575                 while ((cur = cur.getCause()) != null) {
576                     chain.add(cur);
577                 }
578 
579                 HashMap<String,Object> data = new HashMap<String,Object>();
580                 data.put("exception", e);
581                 data.put("unknown", Location.UNKNOWN);
582                 data.put("chain", chain);
583                 data.put("locator", new Locator());
584                 template.process(data, response.getWriter());
585                 response.getWriter().close();
586             } catch (Exception exp) {
587                 try {
588                     response.sendError(code, "Unable to show problem report: " + exp);
589                 } catch (IOException ex) {
590                     // we're already sending an error, not much else we can do if more stuff breaks
591                 }
592             }
593         } else {
594             try {
595                 // send a http error response to use the servlet defined error handler
596                 // make the exception availible to the web.xml defined error page
597                 request.setAttribute("javax.servlet.error.exception", e);
598 
599                 // for compatibility
600                 request.setAttribute("javax.servlet.jsp.jspException", e);
601 
602                 // send the error response
603                 response.sendError(code, e.getMessage());
604             } catch (IOException e1) {
605                 // we're already sending an error, not much else we can do if more stuff breaks
606             }
607         }
608     }
609 
610     /***
611      * Returns <tt>true</tt>, if portlet support is active, <tt>false</tt> otherwise.
612      *
613      * @return <tt>true</tt>, if portlet support is active, <tt>false</tt> otherwise.
614      */
615     public boolean isPortletSupportActive() {
616         return portletSupportActive;
617     }
618 
619     /***
620      * Set the flag that portlet support is active or not.
621      * @param portletSupportActive <tt>true</tt> or <tt>false</tt>
622      */
623     public static void setPortletSupportActive(boolean portletSupportActive) {
624         Dispatcher.portletSupportActive = portletSupportActive;
625     }
626 
627     /*** Simple accessor for a static method */
628     public class Locator {
629         public Location getLocation(Object obj) {
630             Location loc = LocationUtils.getLocation(obj);
631             if (loc == null) {
632                 return Location.UNKNOWN;
633             }
634             return loc;
635         }
636     }
637 
638     /***
639      * Gets the current configuration manager instance
640      *
641      * @return The instance
642      */
643     public ConfigurationManager getConfigurationManager() {
644         return configurationManager;
645     }
646 
647     /***
648      * Sets the current configuration manager instance
649      *
650      * @param mgr The configuration manager
651      */
652     public void setConfigurationManager(ConfigurationManager mgr) {
653         this.configurationManager = mgr;
654     }
655 
656     /***
657      * @return the devMode
658      */
659     public boolean isDevMode() {
660         return devMode;
661     }
662 
663     /***
664      * @param devMode the devMode to set
665      */
666     public void setDevMode(boolean devMode) {
667         this.devMode = devMode;
668     }
669 
670 }