Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/lutris/appserver/server/httpPresentation/servlet/HttpPresentationServlet.java


1   /*
2    * Enhydra Java Application Server Project
3    * 
4    * The contents of this file are subject to the Enhydra Public License
5    * Version 1.1 (the "License"); you may not use this file except in
6    * compliance with the License. You may obtain a copy of the License on
7    * the Enhydra web site ( http://www.enhydra.org/ ).
8    * 
9    * Software distributed under the License is distributed on an "AS IS"
10   * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 
11   * the License for the specific terms governing rights and limitations
12   * under the License.
13   * 
14   * The Initial Developer of the Enhydra Application Server is Lutris
15   * Technologies, Inc. The Enhydra Application Server and portions created
16   * by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
17   * All Rights Reserved.
18   * 
19   * Contributor(s):
20   * 
21   * $Id: HttpPresentationServlet.java,v 1.63.2.3.4.3 2001/01/16 23:11:45 shawn Exp $
22   */
23  
24  
25  
26  
27  
28  package com.lutris.appserver.server.httpPresentation.servlet;
29  
30  import java.util.Vector;
31  import java.io.*;
32  import javax.servlet.*;
33  import javax.servlet.http.*;
34  import com.lutris.appserver.server.httpPresentation.*;
35  import com.lutris.appserver.server.session.Session;
36  import com.lutris.appserver.server.*;
37  import com.lutris.classloader.MultiClassLoader;
38  import com.lutris.Lutris;
39  import com.lutris.util.*;
40  import com.lutris.logging.*;
41  import org.enhydra.servlet.servletManager.*;
42  import org.enhydra.servlet.ServletContainer;
43  import org.enhydra.servlet.filter.Filter;
44  import com.lutris.multiServer.MultiServer;
45  
46  
47  /**
48   * Presentation server implemented as a servlet. Translates servlet
49   * requests into HttpPresentationManager requests.  There is a one
50   * to one correspondence between an instance of this servlet and
51   * an Enhydra application.  An instance of the application is created by
52   * the servlet.
53   *
54   * The servlet requires a single init parameter `configFile', that
55   * contains the path to the application's configuration file.  This
56   * file must define the following fields:
57   * <UL>
58   * <LI> <CODE>server.classPath</CODE> - The class path for the application,
59   *      as a comma seperated list.  This should not contain the class path
60   *      for the Java runtime classes or the Lutris classes.
61   * <LI> <CODE>server.appClass</CODE> - The full class name of the application
62   *      class.  This must implement
63   *      <CODE>com.lutris.appserver.server.Application</CODE>.
64   * <LI> <CODE>server.presentationPrefix</CODE> - This is the path that is used as
65   *      a prefixed for all URL references withing the application.  It must use
66   *      a `/' seperator (as with URLs).
67   * <LI> <CODE>server.autoReload</CODE> - A flag to indicate whether or not
68   *      the application should be automatically reloaded when any class or
69   *      jar file changes in the application's classpath.  <B><I>NOTE: THIS IS
70   *      A DEBUGGING OPTION AND MAY SLOW DOWN THE APPLICATION!</I></B>
71   * <LI>
72  
73   * </UL> <P>
74   *
75   * This servlet also implements the <CODE>ListenableServlet</CODE>
76   * interface. This allows other objects to register with this servlet and
77   * be notified every time <CODE>service</CODE> is called. These remote
78   * listeners may ignore the notification, examine the request, or even
79   * replace the request and/or response with extended versions. This is
80   * used by the debugging and monitoring servlets.
81   *
82   *
83   * @version  $Revision: 1.63.2.3.4.3 $
84   * @author  Mark Diekhans
85   * @since  1.3
86   */
87  //FIXME:  Explain the presentationPrefix better; give example.
88  //FIXME: Integrate URLEncodeServlet
89  public class HttpPresentationServlet
90               extends HttpServlet
91               implements FilterableServlet {
92  
93      /**
94       * Log level for DOM statistics.
95       */
96      public static final String XMLC_DOM_STATS_LOG_LEVEL = "XMLC_DOM_STATS";
97  
98      protected ServletContext context;
99  
100     /*
101      * Application configuration definitions.
102      */
103     private static final String APP_CONFIG_INIT_PARAM_NAME =
104       ServletContainer.CONF_FILE;
105     private static final String APP_CLASS_PATH_FIELD =
106       ServletContainer.SERVER + "." + ServletContainer.CLASS_PATH;
107     private static final String APP_APP_CLASS_FIELD =
108       ServletContainer.SERVER + "." + ServletContainer.APP_CLASS;
109     private static final String APP_PRESENTATION_PREFIX_FIELD =
110       ServletContainer.SERVER + "." + ServletContainer.PRESENTATION_PREFIX;
111     private static final String APP_AUTO_RELOAD_FIELD =
112       ServletContainer.SERVER + "." + ServletContainer.AUTO_RELOAD;
113     private static final String APP_LOGFILE =
114             ServletContainer.SERVER + "." + MultiServer.LOG_FILE;
115     private static final String APP_LOG_TO_FILE =
116             ServletContainer.SERVER + "." + MultiServer.LOG_TO_FILE;
117     private static final String APP_LOG_TO_STDERR =
118             ServletContainer.SERVER + "." + MultiServer.LOG_TO_STDERR;
119 
120     /*
121      *  Where to log information to.
122      *  If we are running in the multiserver, the central logger will already
123      *  be created and initialzied. In any other server, we will need to
124      *  instantiate the central logger. The first application (servlet)
125      *  to be instantiated in a given class loader will configure logging;
126      *  any additional applications (servlets) created in the same class
127      *  loader will use the existing logger (ignoring the settings in their
128      *  config file).
129      */
130     private LogChannel logChannel;
131 
132     /*
133      *  If we create the central logger, we save a copy of it for later
134      *  initialization. If we didn't create it, this will be null.
135      */
136     private StandardLogger standardLogger;
137 
138     /*
139      *  If we create the central logger, we will need to initialize it later
140      *  when init() is called.
141      */
142     private boolean needToConfigureLogChannel = false;
143 
144     /*
145      *  Application and its configuration information.
146      */
147     Application application = null;
148     Config appConfig = null;
149     String presentationPrefix;
150     boolean cacheClasses;
151     boolean cacheFiles;
152     boolean autoReload;  // Reload application when its classes are modified?
153 
154     HttpPresentationManager presentationManager = null;
155 
156     /**
157      * If not null, log DOM statistics here after each DOM write.
158      */
159     private PrintWriter domStatsLogWriter;
160 
161     /**
162      * Create a new HttpPresentationServlet.
163      */
164     public HttpPresentationServlet() {
165         super();
166 
167         /**
168          *  If the central logger exists, use it. That means we are
169          *  probably running in the MultiServer. If it does not yet exist,
170          *  then create it (configure it later).
171          */
172         if (Logger.getCentralLogger() == null) {
173             standardLogger = new StandardLogger(true);
174             needToConfigureLogChannel = true;
175         }
176         logChannel = Logger.getCentralLogger().getChannel("Multiserver");
177     }
178 
179     /**
180      * Throw a servlet exceptionm logging the message if possible.
181      */
182     private void throwServletException(String msg, Throwable cause)
183         throws ServletException {
184         if (logChannel != null) {
185             logChannel.write(Logger.ERROR, msg, cause);
186         } else {
187             // Desprite
188             System.err.println("Error: " + msg);
189             cause.printStackTrace();
190         }
191         throw new ServletException(msg + "\ncaused by " + cause.getClass()
192                                    + ": " + cause.getMessage());
193     }
194 
195     /**
196      * Generate an error for a field not being specified in a config file.
197      */
198     private void missingConfField(String field, String file)
199             throws HttpPresentationException {
200         throw new HttpPresentationException("field \"" + field +
201             "\" not specified in application config file \"" + file + "\"");
202     }
203 
204     /**
205      * Open and read the application's configuration file and initialize the
206      * application, but don't start it yet. This is done on in service so it
207      * part of the standard state-change mechanism.
208      */
209     private void initApplication(ServletConfig servletConfig,
210                                  MultiClassLoader appClassLoader)
211             throws ServletException {
212 
213   try {
214       /*
215        * If this servlet is being run by the MultiServer, then it
216        * will be passed a special context object that can be used
217        * to get extra information about the context the servlet is
218        * running in. If it is a special context, use it, but we may
219        * not assume that it is, or depend on it.
220        */
221       context = servletConfig.getServletContext();
222       EnhydraServletContext lbsContext = null;
223       if (context instanceof
224     org.enhydra.servlet.servletManager.EnhydraServletContext)
225     lbsContext = (EnhydraServletContext)context;
226 
227       // Get configuration file and parameters.
228       String configFileName = servletConfig.getInitParameter(APP_CONFIG_INIT_PARAM_NAME);
229       if (configFileName == null) {
230     throw new HttpPresentationException("init parameter \"" +
231                 APP_CONFIG_INIT_PARAM_NAME +
232                 "\" not specified for Enhydra servlet");
233       }
234       File configFile = new File(configFileName);
235       ConfigFile configF = new ConfigFile(configFile);
236       appConfig = configF.getConfig();
237 
238       /*
239        *  First thing is to configure logging (if we created the central
240        *  logger). That way we can log errors asap. This will only happen
241        *  if we are the first application to load and we are not in the
242        *  multiserver.
243        */
244       if (needToConfigureLogChannel) {
245     configureLogChannel(configFileName);
246             }
247 
248       //FIXME: The null checks below are never used, as get  returns an error.
249 
250       String[] appClassPath = appConfig.getStrings(APP_CLASS_PATH_FIELD);
251       if (appClassPath == null) {
252     missingConfField(APP_CLASS_PATH_FIELD, configFileName);
253       }
254       String appClass = appConfig.getString(APP_APP_CLASS_FIELD);
255       if (appClass == null) {
256     missingConfField(APP_APP_CLASS_FIELD, configFileName);
257       }
258       presentationPrefix = appConfig.getString(APP_PRESENTATION_PREFIX_FIELD);
259       if (presentationPrefix == null) {
260     missingConfField(APP_PRESENTATION_PREFIX_FIELD, configFileName);
261       }
262       autoReload = appConfig.getBoolean(APP_AUTO_RELOAD_FIELD, false);
263 
264       // Add application's class path to class loader.
265       appClassLoader.setClassPath(appClassPath);
266 
267       // Config of presentation manager
268       cacheClasses = appConfig.getBoolean("PresentationManager.CacheClasses",
269             true);
270       cacheFiles = appConfig.getBoolean("PresentationManager.CacheFiles",
271                 false);
272 
273       // FIXME: Left in to help with configuration debugging
274       // until logging is available.
275       logChannel.write(Logger.DEBUG, "Loaded Enhydra application: " + appClass);
276       logChannel.write(Logger.DEBUG,
277            "    presentation prefix: " + presentationPrefix);
278       String cp = "    Class path: ";
279       for (int i = 0; i < appClassPath.length; i++) {
280     if (i > 0)
281         cp += ", ";
282     cp += appClassPath[i];
283       }
284       logChannel.write(Logger.DEBUG, cp);
285 
286       // Load actual application object.
287       Class appClassObj = appClassLoader.loadClass(appClass);
288       application = (Application)appClassObj.newInstance();
289 
290       /*
291        *  Register the application object with this thread.
292        *  We do this so that all calls into the Application object
293        *  can count the Enhydra class to sucessfully return the
294        *  Application object.
295        */
296       Enhydra.register(application);
297       try {
298     /*
299      * If we are being run by a ServletManager, then use the extra
300      * context information. Otherwise, use default values.
301      */
302     if (lbsContext != null) {
303         // Being run by a ServletManager (MultiServer).
304         application.setLogChannel(lbsContext.getLogChannel());
305         application.setName(lbsContext.getName());
306     } else {
307         // Not run by ServletManager (MultiServer). Name = class name.
308         String name = appClass;
309         int idx = name.lastIndexOf('.');
310         if (idx >= 0)
311       name = name.substring(idx + 1);
312         application.setLogChannel(
313         Logger.getCentralLogger().getChannel(name));
314         application.setName(name);
315     }
316       } finally {
317     // Be sure the thread is unregistered no matter what.
318     Enhydra.unRegister();
319       }
320       setPresentationManager(context, appClassLoader);
321 
322             // Set XMLC Factory (needs classLoader set).
323             boolean xmlcReload = appConfig.getBoolean("Server.XMLC.AutoReload",
324                                                       false);
325             boolean xmlcRecomp = appConfig.getBoolean("Server.XMLC.AutoRecompilation",
326                                                       false);
327             application.setXMLCFactory(xmlcReload, xmlcRecomp);
328             
329             if (application.getLogChannel().isEnabled(XMLC_DOM_STATS_LOG_LEVEL)) {
330                 domStatsLogWriter = application.getLogChannel().getLogWriter(XMLC_DOM_STATS_LOG_LEVEL);
331             }
332 
333             // Tell the presentation manager about addititonal
334             // extension/mime-type mappings.
335             Config mimeTypes = appConfig.getConfig("Server.MimeType");
336             if (mimeTypes != null) {
337                 String extensions[] = mimeTypes.keys();
338                 for (int idx = 0; idx < extensions.length; idx++) {
339                     presentationManager.addMimeType(mimeTypes.getString(extensions[idx]),
340                                                     extensions[idx]);
341                 }
342             }
343         } catch(Exception except) {
344             throwServletException("Initialization of application failed",
345                                   except);
346         }
347     }
348 
349     /*
350      * Standard servlet init function, creates the presentation manager
351      * we are going to use.
352      *
353      * @param config Object containing the servlet's startup
354      *         configuration and initialization parameters.
355      * @exception ServletException if a servlet exception has occurred.
356      */
357     public synchronized void init(ServletConfig config) throws ServletException {
358         super.init(config);  // You must do this when overriding this method.
359 
360         /*
361          * If this servlet is being run by the MultiServer, then it
362          * will be passed a special context object that can be used
363          * to get extra information about the context the servlet is
364          * running in. If it is a special context, use it, but we may
365          * not assume that it is, or depend on it.
366          */
367         ServletContext context = config.getServletContext();
368         EnhydraServletContext lbsContext = null;
369         if (context instanceof
370                   org.enhydra.servlet.servletManager.EnhydraServletContext) {
371             lbsContext = (EnhydraServletContext) context;
372             // Use the application's log channel, rather than our generic one.
373             logChannel = lbsContext.getLogChannel();
374         }
375 
376         /*
377          * If we can get the class loader used to load us, then do so.
378          * Otherwise, create a new loader. This loader will be used
379          * to load the presentation objects.
380          */
381         MultiClassLoader appClassLoader;
382         if (lbsContext != null)
383             appClassLoader = lbsContext.getClassLoader();
384         else
385       // Not using MultiServer, so must create class loader from scratch
386             appClassLoader = new MultiClassLoader(logChannel);
387 
388   initApplication(config, appClassLoader);
389 
390         // This call is added for the 3.0 release.
391         // in the past ensure app is running was called by the servletmanager
392         // after a cast of the servlet variable.
393         ensureAppIsRunning();
394     }
395 
396     /**
397      * Sets the presentation manager for the application.  First creates a
398      * new presentation manager with the presentationPrefix, application,
399      * cacheClasses and cacheFiles fields (so these parameters must already
400      * be initialized).  Then, the presentation manager is set in the
401      * application.
402      *
403      * @param context The servlet context for this application.
404      * @param appClassLoader The class loader for this application.
405      * @exception ServletException if a servlet exception has occurred.
406      */
407     private void setPresentationManager(ServletContext context,
408                                         ClassLoader appClassLoader)
409       throws ServletException {
410         try {
411             presentationManager = new HttpPresentationManager(presentationPrefix,
412                 application,
413                 appClassLoader,
414                 cacheClasses,
415                 cacheFiles);
416             presentationManager.setServletAndContext(this, context);
417         } catch(Exception except) {
418             throwServletException("Initialization of presentation manager failed",
419                                   except);
420         }
421 
422         /*
423    * Tell application about the presentation manager running it.
424          *
425          *  Register the application object with this thread.
426          *  We do this so that all calls into the Application object
427          *  can count the Enhydra class to sucessfully return the
428          *  Application object.
429          */
430         Enhydra.register(application);
431         try {
432             application.setHttpPresentationManager(presentationManager);
433         } finally {
434             // Be sure the thread is unregistered no matter what.
435             Enhydra.unRegister();
436         }
437     }
438 
439     /**
440      * Attempt to change the application to the running state.
441      */
442     private synchronized void changeToRunningState()
443             throws ApplicationException {
444 
445         // We may or may not be registered here..
446         boolean isRegistered = (Enhydra.getApplication() != null);
447         if (!isRegistered) {
448             Enhydra.register(application);
449         }
450         try {
451             switch (application.getState()) {
452               case Application.STOPPED:
453                 application.startup(appConfig);
454                 break;
455               case Application.RUNNING:
456                 break;
457               case Application.INCOMPLETE:
458                 application.restartup(appConfig);
459                 break;
460               case Application.DEAD:
461                 throw new ApplicationException("The application " +
462                                                application.getName() +
463                                                " is dead");
464               case Application.HALTED:
465                 throw new ApplicationException("The application " +
466                                                application.getName() +
467                                                " is halted");
468 
469               default:
470                 throw new ApplicationException("The application " +
471                                                application.getName() +
472                                                " in invalid state");
473             }
474         } finally {
475             if (!isRegistered) {
476                 Enhydra.unRegister();
477             }
478         }
479     }
480 
481 
482 
483     private void configureLogChannel(String configFile)
484                  throws HttpPresentationException {
485 
486         String defaultLogFile = "/tmp/enhydra.log";
487         String[] defaultToFile = {"EMERGENCY", "ALERT", "CRITICAL", "ERROR",
488                                   "WARNING", "NOTICE", "INFO"};
489         String[] defaultToStderr = {"EMERGENCY", "ALERT", "CRITICAL", "ERROR",
490                                     "WARNING", "NOTICE", "INFO"};
491 
492         String logFile = null;
493         try {
494             logFile = appConfig.getString(APP_LOGFILE, null);
495         } catch (ConfigException e) {
496             System.out.println("Warning: error parsing key " +
497                 APP_LOGFILE + " from log file " +
498                 configFile + ". Using default log options.");
499             logFile = defaultLogFile;
500         }
501         if (logFile == null) {
502             System.out.println("Warning: no key " + APP_LOGFILE +
503                 " was found in config file " + configFile +
504                  ". Using default log options.");
505             logFile = defaultLogFile;
506         }
507 
508         String[] toFile = null;
509         try {
510             toFile = appConfig.getStrings(APP_LOG_TO_FILE, null);
511         } catch (ConfigException e) {
512             System.out.println("Warning: Error parsing key " +
513                 APP_LOG_TO_FILE + " from log file " + configFile +
514                 ". Using default log options.");
515             toFile = defaultToFile;
516         }
517         if (toFile == null) {
518             System.out.println("Warning: no key " +
519                 APP_LOG_TO_FILE + " found in log file " + configFile +
520                 ". Using default log options.");
521             toFile = defaultToFile;
522         }
523 
524         String[] toStderr = null;
525         try {
526             toStderr = appConfig.getStrings(APP_LOG_TO_STDERR, null);
527         } catch (ConfigException e) {
528             System.out.println("Warning: error parsing key " +
529                     APP_LOG_TO_STDERR +
530                     " from log file " + configFile +
531                     ". Using default log options.");
532             toStderr = defaultToStderr;
533         }
534         if (toStderr == null) {
535             System.out.println("Warning: no key " + APP_LOG_TO_STDERR +
536                     " from log file " + configFile +
537                     ". Using default log options.");
538             toStderr = defaultToStderr;
539         }
540 
541         File theLogFile = new File(logFile);
542         try {
543             if (standardLogger != null)
544                 standardLogger.configure(theLogFile, toFile, toStderr);
545         } catch (IOException ioe) {
546             System.out.println(
547                    "Warning: Unable to begin logging to the file " + logFile +
548                    ":\n" + ioe.getMessage());
549             return;
550         }
551 
552         /*
553          * Done bootstrapping. From here on out we may use logging.
554          */
555         logChannel.write(Logger.INFO, "Logging initialized");
556     }
557 
558 
559     /**
560      * This is an HTTP-specific version of the Servlet.service method
561      * that translates that request into a request to a presentation
562      * manager.  This object is method is not synchronized, the thread
563      * is used to handle to entire request. <P>
564      *
565      * As part of implementing the FiterableServlet interface, the real work
566      * is done here in serviceDirect(), while calls to service() result in
567      * filters being applied and called. The end of the filter chain is a
568      * glue servlet that directly calls this method.
569      *
570      * @param req encapsulates the request to the servlet
571      * @param resp encapsulates the response from the servlet
572      * @exception ServletException if the request could not be handled
573      * @exception IOException if detected when handling the request
574      */
575     public void serviceDirect (HttpServletRequest req, HttpServletResponse resp)
576             throws ServletException, IOException {
577 
578         /*
579          * Register the application object with this thread.
580          * We do this so that all the calls into the Application object
581          * can count on the Enhydra class to return the Application object.
582          * This is important not so much for the Application object, but
583          * for any utility classes it may call.
584          */
585         Enhydra.register(application);
586         try {
587             ensureAppIsRunning();
588             presentationManager.Run(new ServletHttpPresentationRequest(req),
589                                     new ServletHttpPresentationResponse(resp, domStatsLogWriter));
590         } catch(HttpPresentationException except) {
591             throwServletException("An unhandled error occured while servicing an application request",
592                                   except);
593         } finally {
594             // Be sure the thread is unregistered no matter what.
595             Enhydra.unRegister();
596         }
597     }
598 
599     /**
600      * This method is public for use by the ServletManager
601      * to run the application startup() method after init().
602      *
603      * @exception ServletException
604      *  If any error occurs on startup.
605      */
606     public void ensureAppIsRunning() throws ServletException {
607   checkAutoReload();
608         // If an application is associated,
609         // then make sure it's in the run state.
610         if ((application != null) &&
611             (application.getState() != Application.RUNNING)) {
612             try {
613                 changeToRunningState();
614                 context.setAttribute(ServletManager.SESSION_MANAGER_KEY,application.getSessionManager());
615             } catch (Throwable except) {
616                 throwServletException("Unable to change application to running state",
617                                       except);
618             }
619         }
620     }
621 
622     /**
623      * Return information specific to this servlet.
624      */
625     public String getServletInfo() {
626         return Lutris.getEnhydraLongName() + "/" + Lutris.getEnhydraVersion();
627     }
628 
629     /**
630      * This method instantiates the HttpPresentationRequest
631      * object that the presentation manager expects when
632      * servicing a request.
633      *
634      * @param req
635      *   The servlet request object.
636      * @param resp
637      *   The servlet response object.
638      * @return The HttpPresentationRequest object used
639      *   by the presentation manager.
640      */
641     protected HttpPresentationRequest getHttpPresentationRequest(
642             HttpServletRequest req,
643             HttpServletResponse resp) {
644         return new ServletHttpPresentationRequest(req);
645     }
646 
647     /**
648      * This method instantiates the HttpPresentationResponse
649      * object that the presentation manager expects when
650      * servicing a request.
651      *
652      * @param req
653      *   The servlet request object.
654      * @param resp
655      *   The servlet response object.
656      * @return The HttpPresentationResponse object used
657      *   by the presentation manager.
658      */
659     protected HttpPresentationResponse getHttpPresentationResponse(
660         HttpServletRequest req,
661         HttpServletResponse resp) {
662         return new ServletHttpPresentationResponse(resp, domStatsLogWriter);
663     }
664 
665     /**
666      * Returns the application being run by this instance of
667      * the servlet.
668      */
669     public Application getApplication() {
670         return application;
671     }
672 
673     /**
674      * Returns the application configuration init parameter.
675      */
676     protected Config getAppConfigInitParam() {
677         return appConfig;
678     }
679 
680     /**
681      * Returns the application configuration init parameter name.
682      */
683     protected String getAppConfigInitParamName() {
684         return APP_CONFIG_INIT_PARAM_NAME;
685     }
686 
687 
688     // ----------------- FilterableServlet methods --------------
689 
690 
691     private Vector filters = new Vector();
692 
693 
694     /**
695      *  Add a filter to the end of the list of filters.
696      *
697      *  @param filter
698      *    The filter to add.
699      */
700     public void addFilter(Filter filter) {
701         synchronized (filters) {
702             filters.addElement(filter);
703         }
704     }
705 
706     /**
707      *  Remove a filter from the list of current filters.
708      *  Silently fails if the filter is not in the list.
709      *
710      *  @param filter
711      *    The filter to remove.
712      */
713     public void removeFilter(Filter filter) {
714         synchronized (filters) {
715             filters.removeElement(filter);
716         }
717     }
718 
719 
720     /**
721      *  Handle a service request. Use the first filter to wrap the request,
722      *  response, and servlet (this) objects. This will return new objects.
723      *  Then use the second filter to wrap those, again returning new
724      *  objects. Then use the fourth filter to wrap those etc....
725      *  Then finally call <CODE>service()</CODE> on the outermost results.
726      *
727      *  @param request
728      *    The original servlet request object.
729      *  @param response
730      *    The original servlet response object.
731      */
732     public void service(HttpServletRequest request,
733                         HttpServletResponse response)
734                 throws ServletException, IOException {
735         /*
736          *  This hook was added to support the debugger. Almost all
737          *  applications will not use this.
738          */
739         if (presentationManager.servletRequestPreprocessor(this,
740                                    context, request, response)) {
741             // The request has been completly processed. Nothing left to do.
742             return;
743   }
744 
745         /*
746          *  Route the request/response through the list of filters (if any)
747          *  that have been applied to this application. Normally there will
748          *  not be any. The debugger, for example, installs a filter to
749          *  implement the request logging and tracing.
750          */
751   HttpPresentationServletGlue glue =
752     new HttpPresentationServletGlue(this);
753         /*
754          *  Technically this should be synchronized on the filters vector,
755          *  so it doesn't change out from under us while we iterate through
756          *  the vector. But that would kill performance. This is a major
757          *  bottleneck area (all requests come through here), so we make the
758          *  tradeoff that a filter might be skipped if a request comes through
759          *  at the same time someone adds or removes a filter (adding and
760          *  removing filters is a very rare occurance).
761          *
762          *  The filters are here to support debugging, and possibly monitoring
763          *  and other applications in the future. Normally the filters
764          *  vector will be empty.
765          */
766         int size = filters.size();
767   HttpServletRequest reqResult = request;
768   HttpServletResponse resResult = response;
769         if (size == 0) {
770             serviceDirect(request, response);
771         } else {
772             for (int i=0; i<size; i++) {
773                 Filter f = null;
774                 try {
775                     f = (Filter) filters.elementAt(i);
776                 } catch (Throwable t) {
777                     // Array might have changed out from under us.
778                 }
779                 if (f != null) {
780                     reqResult = f.wrapRequest(reqResult, resResult);
781                     resResult = f.wrapResponse(reqResult, resResult);
782     }
783             }
784             glue.service(reqResult, resResult);
785         }
786     }
787 
788 
789     /**
790      *  Looks up the session object (if any) that would be used to
791      *  process the request. This is will not used normally, because the
792      *  session is give to the application's preprocessor method and the
793      *  presentation objects. Also, it is not normal to have a raw
794      *  ServletRequest. The debugger uses this to examine the session data
795      *  before and after each request. Consider this an internal use only
796      *  method.
797      *
798      *  @param request
799      *    The (raw) request that would be sent in to this application.
800      *  @return
801      *    The session object that would be associated with the request.
802      *    Returns null if none is found; a new session is not created.
803      */
804     public Session getSession(ServletRequest request) {
805         if (application == null)
806             return null;
807         Enhydra.register(application);
808         Session s = null;
809         try {
810             s = presentationManager.getSession(request);
811         } finally {
812             Enhydra.unRegister();
813         }
814         return s;
815     }
816 
817     /**
818      * Check if the application should be automatically reloaded and, if so,
819      * call <CODE>initApplication</CODE> with a new instance of the class
820      * loader.  The application should be reloaded if the
821      * <CODE>autoReload</CODE> flag is <CODE>true</CODE> and any class has
822      * changed in the application's classpath.
823      *
824      * @exception ServletException if a servlet exception has occurred.
825      */
826     private void checkAutoReload() throws ServletException {
827   if (autoReload) {
828       MultiClassLoader appClassLoader =
829     (MultiClassLoader) application.getClass().getClassLoader();
830       if (appClassLoader.shouldReload()) {
831     application.shutdown();
832     initApplication(getServletConfig(),
833         new MultiClassLoader(logChannel));
834       }
835   }
836     }
837 
838     /**
839      * Destroys the servlet.  Called by the servlet manager when the servlet
840      * is shutdown.
841      */
842     public synchronized void destroy() {
843         application.shutdown();
844     }
845 }
846 
847 
848 
849 // FIX: It appears that throwing an error in a servlet doesn't get
850 //      displayed in the client, we might want to deal with this
851 //      explictly.