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.