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

Quick Search    Search Deep

Source code: org/enhydra/servlet/servletManager/ServletManager.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: ServletManager.java,v 1.18.2.18.2.2 2000/12/12 01:35:13 peterj Exp $
22   */
23  
24  package org.enhydra.servlet.servletManager;
25  
26  import java.util.Enumeration;
27  import java.util.Hashtable;
28  import java.util.Properties;
29  import java.util.StringTokenizer;
30  import java.util.Date;
31  import java.util.Vector;
32  
33  import java.net.*;
34  
35  import javax.servlet.Servlet;
36  import javax.servlet.ServletException;
37  import javax.servlet.http.HttpServlet;
38  import com.lutris.logging.Logger;
39  import com.lutris.logging.LogChannel;
40  import com.lutris.http.MimeType;
41  import com.lutris.classloader.MultiClassLoader;
42  import com.lutris.util.Config;
43  import com.lutris.util.ExceptionUtils;
44  import com.lutris.appserver.server.httpPresentation.servlet.HttpPresentationServlet;
45  import org.enhydra.servlet.connectionMethods.*;
46  import com.lutris.multiServer.MultiServer;
47  import org.enhydra.servlet.ServletContainer;
48  import org.enhydra.servlet.filter.FilterManager;
49  import org.enhydra.servlet.filter.Filter;
50  import com.lutris.appserver.server.session.*;
51  import org.enhydra.servlet.tomcat.*;
52  import org.enhydra.i18n.ResManager;
53  
54  import org.apache.tomcat.core.*;
55  import org.apache.tomcat.util.URLUtil;
56  
57  
58  /**
59   *   This class loads Servlet classes, creates instances of Servlets,
60   *  caches the Servlets, and returns pointers to them. The Servlets
61   *  are refered to by an identifer string (a symbolic name). <P>
62   *
63   *  First Servlets are registered via <CODE>add()</CODE>. Then they are
64   *  started via <CODE>start()</CODE>. Then they are available for
65   *  use via <CODE>get()</CODE>. <P>
66   *
67   *  The ServletManager will call <CODE>init()</CODE> on the Servlet.
68   *  It will also call <CODE>destoroy()</CODE>.
69   *  Other classes which use Servlets returned by <CODE>get()</CODE> must
70   *  not call these two methods. When <CODE>get()</CODE> returns a Servlet,
71   *  it is ready to accept <CODE>service()</CODE> calls. <P>
72   *
73   *  @see javax.servlet.Servlet
74   *  @see org.enhydra.servlet.servletManager.ServletStatus
75   *  @author Andy John
76   */
77  public class ServletManager implements Runnable {
78  
79  
80      /**
81       * Attribute key to store session manager in context.
82       */
83  
84      public static final String  SESSION_MANAGER_KEY = "org.enhydra.session.manager";
85  
86      /*
87       *  How much statistics to store.
88       */
89      static private final int RPMBufferSize = 60 * 24; // One day's worth.
90  
91      /*
92       *  The server named exposed to the user
93       */
94      static private final String SERVER_INFO_DISPLAY = "Enhydra Multiserver";
95  
96      /*
97       * The mapping of contexts stored by servletId and the urp prefix
98       */
99      private Hashtable contextByKey = new Hashtable();
100 
101     /*
102      * Mapping of servlet id to filter ids.
103      */
104     private Hashtable filters = new Hashtable();
105 
106     // All the data about a particular Servlet.
107     class TableEntry {
108         String          className;      // Name of Servlet class to instantiate
109         String[]        classPath;      // Diretories, .zip, or .jar files.
110         String          docRoot;        // Where it's files on disk are.
111         Properties      initArgs;       // Servlet initialization string
112         String          description;    // Human readable, for admin app
113         int             refCount;       // How many times it was asked for.
114         LogChannel      logChannel;     // The Servlet logs to this.
115         Servlet         servlet;        // The actual Servlet, or null.
116         MultiClassLoader classLoader;   // The loader for this Servlet.
117         Date            createTime;     // When it was instantiated, or null.
118 
119         int             RPM_counter;    // How gets in current minute.
120         long            RPM_startTime;  // When current minute started.
121         double          RPM_current;    // The req/min for last minute.
122         double          RPM_max;        // The maximum req/min so far.
123         Date            RPM_maxTime;    // When the max req/min happened.
124         /*
125          *  Keep a circular buffer of RPM values. The current series is
126          *  stored in the range historyStart..(historyStart+historyNum-1).
127          *  The next free slot to add to is historyStart+historyNum.
128          *  Since this is a circular buffer, remember to wrap around to the
129          *  beginning if you go off the end of the array.
130          */
131         double          historyRPM[];   // The request-per-minute values.
132         long            historyWhen[];  // The time when each value applied.
133         int             historyStart;   // The start of the series.
134         int             historyNum;     // The number of stats int the buffer.
135 
136         boolean         isWar;          // The context is a war
137         Vector          paths;          // The url prefix's of the context.
138 
139         boolean         started;        // The context is Running
140         boolean         runOnInit;      // The context should be started at init time.
141 
142         // The following are used only by a WAR
143 
144         String          defaultSessionTimeout;
145         String          isWARExpanded;
146         String          isWARValidated;
147         String          isInvokerEnabled;
148         String          isWorkDirPersistent;
149 
150         // used to record the config info for the session manager.
151         Config          sessionConfig;
152     }
153 
154 
155     //
156     class SingleContextServer implements Server {
157 
158         SingleContextServer() {
159         }
160 
161         private Context context;
162 
163         void setContext(Context context) {
164             this.context = context;
165         }
166 
167         // The server interface can find a context by path
168         // Tomcat assumes that the context manager will only have
169         // contexts with unique url prefixes. If Enhydra has multiple
170         // channels with the same url this function can fail to find the
171         // expected context. However if the servlet is asking for a url
172         // from withing the same context it will work.
173         //
174         // N.B. this function will not always return the expected context.
175         //
176         public Context getContextByPath(String startPath) {
177             Context foundContext = null;
178             if (startPath.equals("/")) {
179                 startPath = "";
180             }
181             String path = startPath;
182             // first try the current context.
183             boolean done = false;
184             while (!done && foundContext == null) {
185                 if (context.getPath().equals(path)) {
186                     foundContext = context;
187                 }
188                 else {
189                     // didn't find it strip off the end and start again.
190                     int i = path.lastIndexOf('/');
191                     if (i > -1 ) {
192                         path = path.substring(0,i);
193                     }
194                     done = (path.length() <= 1) || (i == -1);
195                 }
196             }
197             if (foundContext == null) {
198                 // This is the part that may not return the right context
199                 // if there is more than one that match the url.
200                 // So warn the user!
201 
202                 String resKey = "Looking for context by path \"{0}\"";
203                 String msg = rez.format(resKey,startPath);
204 
205                 msg = rez.format("Looking for context by path \"{0}\"",startPath);
206                 context.getLogChannel().write(Logger.WARNING, msg);
207                 foundContext = lookuptContextByPath(startPath);
208                 if (foundContext == null) {
209                     resKey = "No Context found that matches path \"{0}\"";
210                     msg = rez.format(resKey,startPath);
211                     context.getLogChannel().write(Logger.WARNING, msg);
212                 }
213                 else {
214                     // found a context but warn that it may not be correct.
215                     resKey = "Found context path is \"{0}\"";
216                     msg = rez.format(resKey,context.getPath());
217                     context.getLogChannel().write(Logger.WARNING, msg);
218                 }
219             }
220             return foundContext;
221         }
222 
223 
224         // The serverinfo string passed down to the ServletContext
225         // This string is visible to the servlet via the ServletContext
226         public String getServerInfo() {
227             return getServerInfoString();
228         }
229 
230     }
231 
232     // The logging channel for the ServletMananger write to.
233     private LogChannel logChannel;
234 
235     // The set of all Servlets contained by this object.
236     private Hashtable myTable;
237 
238     // This is used by the ServletContext object indirectly passed to
239     // the Servlet's init() method.
240     private MimeType myMime = new MimeType();
241 
242     // The thread that monitors the request per minute stats every
243     // sixty seconds.
244     private  Thread RPM_thread;
245 
246     // A flag used to tell the above thread when to die.
247     private boolean RPM_stayAlive;
248 
249 
250     // some default values taken from the context
251     private static String defaultSessionTimeOutDefault;
252     private static String isWARExpandedDefault;
253     private static String isWARValidatedDefault;
254     private static String isInvokerEnabledDefault;
255     private static String isWorkDirPersistentDefault;
256 
257     // The directory that the jsp work files will be built in.
258     private String workDir;
259 
260     public void setWorkDir(String workDir) {
261         this.workDir = workDir;
262         String msg = rez.format("Work dir base set to \"{0}\"",workDir);
263         logChannel.write(Logger.DEBUG, msg);
264     }
265 
266     //
267     ResManager rez;
268 
269 
270     /**
271      *    Create a new ServletManager, initially containing no Servlets.
272      */
273     public ServletManager () {
274 
275         myTable = new Hashtable();
276         /*
277          *    Where to log messages to.
278          */
279         logChannel = Logger.getCentralLogger().getChannel("Multiserver");
280         /*
281          *   Start up the monitoring thread. It will do a little work
282          *   once a minute.
283          */
284         RPM_thread = new Thread(this);
285         RPM_thread.setDaemon(true); // VM won't wait to die.
286         RPM_stayAlive = true;
287         RPM_thread.start();
288 
289         // Find the default values for these settings
290         // They are set within the context and might not be specified in the
291         // conf file.
292         //
293 
294         contextManager = new ContextManager();
295 
296   String docRoot = "/";
297   URL docRootURL = getURLFromPath(docRoot);
298 
299   defaultContext = contextManager.addContext(docRoot, docRootURL);
300   contextManager.setDefaultContext(defaultContext);
301         defaultContext.setLogChannel(logChannel);
302   //defaultContext.init();
303 
304         defaultSessionTimeOutDefault = "" + defaultContext.getSessionTimeOut();
305         isWARExpandedDefault         = "" + defaultContext.isWARExpanded();
306         isWARValidatedDefault        = "" + defaultContext.isWARValidated();
307         isInvokerEnabledDefault      = "" + defaultContext.isInvokerEnabled();
308         isWorkDirPersistentDefault   = "" + defaultContext.isWorkDirPersistent();
309 
310         // Gets a string  manager wrapped around org.enhydra.servlet.Res
311         rez = ResManager.getResManager(this.getClass());
312 
313     }
314 
315 
316     /**
317      * The Tomcat ContextManager used to handle servlet requests.
318      */
319      private ContextManager contextManager;
320 
321     /**
322      * The default context
323      */
324 
325      private Context defaultContext;
326 
327     /**
328      * Shut down the ServletManager. This stops all the Servlets, then
329      * deletes all the Servlets, then kills the statistics gathering
330      * thread. There is no way to restart the thread.
331      */
332     public synchronized void destroy() {
333         /*
334          * First stop all the Servlets. This will call their
335          * destroy() methods, and give them a chance to shut down
336          * gracefully.
337          */
338         Enumeration e = myTable.keys();
339         while (e.hasMoreElements()) {
340             String id = (String) e.nextElement();
341             try {
342                 TableEntry t = (TableEntry) myTable.get(id);
343                 if (t != null && t.started) {
344                    stop(id);
345                 }
346             } catch (ServletException se) {
347                 String msg = rez.format("Failed to stop {0}.",id);
348                 logChannel.write(Logger.DEBUG, msg, se);
349             }
350         }
351         /*
352          *  Now delete all the Servlets. This will remove them from the
353          *  table, and prevent them from being restarted.
354          */
355         e = myTable.keys();
356         while (e.hasMoreElements()) {
357             String id = (String) e.nextElement();
358             try {
359                 delete(id);
360             } catch (ServletException se) {
361                 String msg = rez.format("Failed to delete {0}.",  id);
362                 logChannel.write(Logger.DEBUG, msg, se);
363             }
364         }
365         /*
366          *  Now stop the request per minute monitoring thread.
367          *  If we leave this thread alive, the virtual machine will never
368          *  exit.
369          */
370         RPM_stayAlive = false;
371         RPM_thread.interrupt();
372     }
373 
374 
375     /**
376      *  This function is for internal use only. Do not call this method.
377      *  When the request per minute monitoring thread starts, this is
378      *  the code it starts executing. The thread sleeps, waking up once
379      *  a minute to compute the current request per minute stats, and the
380      *  maximum request per minute stats.
381      */
382     public void run() {
383         // Keep sleeping till this many milliseconds have gone by.
384         int snooze = 60000;
385         // logChannel.write(Logger.DEBUG, "Starting request/min thread.");
386         while (RPM_stayAlive) {
387             long start = System.currentTimeMillis();
388             while (RPM_stayAlive) {
389                 try {
390                     Thread.sleep(snooze);
391                 } catch (InterruptedException ie) {
392                 }
393                 if (!RPM_stayAlive)
394                     return;
395                 long now = System.currentTimeMillis();
396                 if ((now - start) >= snooze)
397                     break;
398              }
399              // logChannel.write(Logger.DEBUG, "Updating request/min stats.");
400              synchronized(this) {
401                  long now = System.currentTimeMillis();
402                  Enumeration e = myTable.keys();
403                  while (e.hasMoreElements()) {
404                      String servletID = (String) e.nextElement();
405                      TableEntry t = (TableEntry) myTable.get(servletID);
406                      double minutes = (double)(now - t.RPM_startTime) / 60000.0;
407                      t.RPM_current = (double)t.RPM_counter / minutes;
408                      if (t.RPM_current > t.RPM_max) {
409                          t.RPM_max = t.RPM_current;
410                          t.RPM_maxTime = new Date();
411                      }
412                      t.RPM_counter = 0;
413                      t.RPM_startTime = now;
414                      /*
415                       *  Add the current RPM to the circular buffer.
416                       */
417                      int nextFree = t.historyStart + t.historyNum;
418                      if (nextFree >= RPMBufferSize)
419                          nextFree -= RPMBufferSize;
420                      t.historyRPM[nextFree] = t.RPM_current;
421                      t.historyWhen[nextFree] = now;
422                      t.historyNum++;
423                      if (t.historyNum > RPMBufferSize) {
424                          // Overflow; just stomped on an old value.
425                          t.historyStart++;
426                          if (t.historyStart >= RPMBufferSize)
427                              t.historyStart = 0;
428                          t.historyNum = RPMBufferSize;
429                      }
430                  }
431              }
432          }
433     }
434 
435 
436 
437 
438     /**
439      *  Register a new Servlet with the ServletManager. You pass in
440      *  everything needed to find and create the Servlet, but it is not
441      *  created until <CODE>start()</CODE> is called on it.
442      *  Requests for the Servlet
443      *  inbetween the time it is added and started will return null.
444      *  After this call, the name passed in as servletID should be used
445      *  to refer to the Servlet.<P>
446      *
447      *  Each element of classPath specifies a source of classes, either
448      *  a .jar file, a .zip file, or a directory tree. Each of these sources
449      *  has a set of class masks associated with it, initially null.
450      *  If a source's set of class masks
451      *  is null (the normal condition), then classes are loaded normally.
452      *  If the set is not null, then it contains a set of class masks.
453      *  Each class mask is a string.
454      *  Before a source is searched to see if it contains
455      *  a given class, the name of the class is compared with the class masks.
456      *  Only if the requested class name begins with one of the class masks
457      *  will it be loaded from the source. This lets you specify, for example,
458      *  a jar file, but limit access to a specific class, or a set of classes
459      *  that start with a common string. The syntax for specifing class
460      *  masks is (on Unix): <CODE>pathname:mask,mask,...,mask</CODE>.
461      *  If no masks are specified (the normal usage), then any class found
462      *  via the pathname is used. Be sure to use
463      *  <CODE>java.io.File.pathSeparatorChar</CODE> between the pathname
464      *  and the mask list (this will be : on Unix, and ; on Windows and
465      *  Macintosh). If the class mask does not specifiy a full class name,
466      *  you may want to end it with a period. For example, if your mask was
467      *  <CODE>com.lutris</CODE> it would allow access to
468      *  <CODE>com.lutris.TheClass</CODE> as well as
469      *  <CODE>com.lutrisMisc.UnwatedClass</CODE>. If the mask was
470      *  <CODE>com.lutris.</CODE> the class <CODE>UnwantedClass</CODE>
471      *  would not be loaded from this source.
472      *
473      *  @param servletID The identifier string used to refer to this
474      *  Servlet.
475      *  @param className The name of the class to instantiate. It must
476      *  implement the Servlet interface.
477      *  @param classPath An array of directories, .zip files or .jar files.
478      *  The Servlet will be loaded by it's own class loader, which will
479      *  look in these locations first. If a class is not found, the system
480      *  class loader will be used (which uses the CLASSPATH Java started
481      *  the MultiServer with).
482      *  @param docRoot The root of the Servlet's filesystem on disk?
483      *  @param initArgs The initial arguments to the Servlet. These will be
484      *  made available to the Servlet's <CODE>init()</CODE> method.
485      *  These names and values are accessed
486      *  via the ServletConfig object passed to the Servlet's
487      *  <CODE>init()</CODE> method.
488      *  @param description A human readable description of this Servlet.
489      *  @exception ServletException If there is an error.
490      *  @see javax.servlet.Servlet
491      */
492     public synchronized void add(String servletID, String className,
493                   String[] classPath, String docRoot, Properties initArgs,
494                   boolean runOnInit, String description,boolean isWar) throws ServletException {
495         TableEntry t = new TableEntry();
496 
497         // may not be set by the admin app
498         if (docRoot == null) {
499             docRoot = "/";
500         }
501         t.className = className;
502         t.classPath = classPath;
503         t.docRoot = docRoot;
504         t.initArgs = initArgs;
505         t.description = description;
506         t.refCount = 0;
507         t.servlet = null;
508         t.classLoader = null;
509         t.createTime = null;
510         t.RPM_counter = 0;
511         t.RPM_startTime = System.currentTimeMillis();
512         t.RPM_current = -1.0;
513         t.RPM_max = -1.0;;
514         t.RPM_maxTime = null;
515         t.historyRPM = new double[RPMBufferSize];
516         t.historyWhen = new long[RPMBufferSize];
517         t.historyStart = 0; // The start of the series.
518         t.historyNum = 0;   // The buffer starts off empty.
519         t.logChannel = Logger.getCentralLogger().getChannel(servletID);
520         t.isWar = isWar;
521         t.paths = new Vector();
522         t.started = false;
523         t.runOnInit = runOnInit;
524 
525         t.defaultSessionTimeout=null;
526         t.isWARExpanded=null;
527         t.isWARValidated=null;
528         t.isInvokerEnabled=null;
529         t.isWorkDirPersistent=null;
530 
531         myTable.put(servletID, t);
532     }
533 
534 
535 
536     /**
537      * Build a key out of the servletid and the path if the path is null
538      * this is the base context that will be stored by the servletid only.
539      * the separator between the id and the path is some string that can never
540      * be in the id. e.g. a space.
541      *
542      */
543     private String buildKey(String id,String path) {
544        if (path == null) {
545             // this is the base context set the path to something that will never be
546             // in a url we will be able to lookup the base context by just the
547             // servlet id and this string.
548             path = " ? "; // something that can never be a url
549        }
550        else if (path.equals("/")) {
551             // contexts at "/" are stored as ""
552             path = "";
553        }
554        StringBuffer sb = new StringBuffer(80);
555        sb.append(id);
556        sb.append(" - ");  // something that can never be in an id
557        sb.append(path);
558        return sb.toString();
559     }
560 
561     private void putContext(String id,String path,Context context) {
562         if (path != null) {
563             // notify the user about the new context
564             Context baseContext = getContext(id,null);
565             String msg;
566             if (baseContext == null || path.equals(baseContext.getPath())) {
567                 msg = rez.format("Application {0} has url prefix \"{1}\".",id,path);
568           logChannel.write(Logger.INFO,msg);
569             }
570             else {
571                 msg = rez.format("Application {0} has an extra url prefix \"{1}\".",id,path);
572              logChannel.write(Logger.WARNING,msg);
573             }
574 
575         }
576         contextByKey.put(buildKey(id,path),context);
577 
578     }
579 
580 
581     private void removeContext(String id,String path) {
582         if (path == null) {
583             Enumeration enum = contextByKey.keys();
584             while (enum.hasMoreElements()) {
585                 String key = (String) enum.nextElement();
586                 if (key.startsWith(id)) {
587                     contextByKey.remove(key);
588                 }
589             }
590         }
591         else {
592             contextByKey.remove(buildKey(id,path));
593         }
594     }
595 
596     private Context getContext(String id,String path) {
597         return (Context) contextByKey.get(buildKey(id,path));
598     }
599 
600 
601 
602     /**
603      * Record the url prefix that will be used by the context
604      * The url path for the context is set when the channel is defined.
605      * This is after the application is defined in the .conf file.
606      *
607      *  @param servletID  The name of the context
608      *  @param urlPrefix  The start of the URL that leads to this context
609      *
610      */
611     public void  recordContextPath(String servletID,String urlPrefix)
612                      throws ConnectionMethodException  {
613 
614         urlPrefix = urlPrefix.trim();
615 
616         TableEntry t = (TableEntry) myTable.get(servletID);
617         if (t != null) {
618             // check if the prefix is already there if it is we can reuse it
619             int i=0;
620             while (i != t.paths.size()) {
621                 String path = (String) t.paths.elementAt(i);
622                 if (path.equalsIgnoreCase(urlPrefix)) {
623                     return;
624                 }
625                 else {
626                     if (t.isWar) {
627                         String msg = "Web Application {0} already has a URL prefix of {1}. Cannot add new Prefix {2}.";
628                         throw new ConnectionMethodException(rez.format(msg,servletID,path,urlPrefix));
629                     }
630                 }
631                 i++;
632             }
633             t.paths.addElement(urlPrefix);
634             if (t.started) {
635 
636                 // need to add the extra context.
637                 Context baseContext = getContext(servletID,null);
638                 Context context = createContext(servletID,urlPrefix);
639                 context.copyContext(baseContext);
640                 putContext(servletID,urlPrefix,context);
641             }
642         }
643      }
644 
645 
646     /**
647      * A channel has been closed remove the context.
648      *
649      *  @param servletID  The name of the context
650      *  @param urlPrefix  The start of the URL that leads to this context
651      *
652      */
653     public void  removeContextPath(String servletID,String urlPrefix) {
654         urlPrefix = urlPrefix.trim();
655 
656         TableEntry t = (TableEntry) myTable.get(servletID);
657         if (t != null) {
658             int i=0;
659             while (i < t.paths.size()) {
660                 String path = (String) t.paths.elementAt(i);
661                 if (path.equalsIgnoreCase(urlPrefix)) {
662                     t.paths.removeElementAt(i);
663                 }
664                 i++;
665             }
666             if (t.started) {
667                 if (t.isWar) {
668                     try {
669                         stop(servletID);
670                     }
671                     catch (Exception ex) {
672                         logChannel.write(Logger.ERROR,
673                             rez.format("Error removing channel for app {0}.",
674                             servletID,ex));
675                     }
676                 }
677                 else {
678                     // what ? Context context = getContext(servletID,urlPrefix);
679                 }
680             }
681 
682         }
683     }
684 
685 
686     /**
687      * These are values that only apply to a WAR
688      *
689      *  @param servletID  The id of the Context
690      *  @param defaultSessionTimeout
691      *  @param isWARExpanded
692      *  @param isWARValidated
693      *  @param isInvokerEnabled
694      *  @param isWorkDirPersistent
695      *
696      */
697     public void  recordWarOnlySettings(String servletID,
698                                        String defaultSessionTimeout,
699                                        String isWARExpanded,
700                                        String isWARValidated,
701                                        String isInvokerEnabled,
702                                        String isWorkDirPersistent ) {
703 
704         // These defaults are discovered at init time they may
705         // not be set in the conf file.
706         if (defaultSessionTimeout == null || defaultSessionTimeout.length()==0)
707           defaultSessionTimeout = defaultSessionTimeOutDefault;
708 
709         if (isWARExpanded == null || isWARExpanded.length()==0)
710           isWARExpanded = isWARExpandedDefault;
711 
712         if (isWARValidated == null || isWARValidated.length()==0)
713           isWARValidated = isWARValidatedDefault;
714 
715         if (isInvokerEnabled == null || isInvokerEnabled.length()==0)
716           isInvokerEnabled = isInvokerEnabledDefault;
717 
718         if (isWorkDirPersistent == null || isWorkDirPersistent.length()==0)
719           isWorkDirPersistent = isWorkDirPersistentDefault;
720 
721         TableEntry t = (TableEntry) myTable.get(servletID);
722         if (t != null) {
723           t.defaultSessionTimeout=defaultSessionTimeout;
724           t.isWARExpanded=isWARExpanded;
725           t.isWARValidated=isWARValidated;
726           t.isInvokerEnabled=isInvokerEnabled;
727           t.isWorkDirPersistent=isWorkDirPersistent;
728         }
729      }
730 
731 
732     /**
733      *  The context may have configuration information relating to the
734      * sessionManager
735      *
736      *  @param servletID The id of the context.
737      *
738      */
739     public void  recordSessionConfig(String servletID,
740                                      Config sessionConfig) {
741         TableEntry t = (TableEntry) myTable.get(servletID);
742         if (t != null) {
743           t.sessionConfig = sessionConfig;
744         }
745     }
746 
747 
748 
749     /**
750      * After all the configuration information has been collected
751      * it's safe to start all contexts that have been marked as runOnInit
752      * in the multiserver configuration file
753      *
754      *  @exception Exception If there is an error.
755      */
756     public void startAllContexts() throws ServletException {
757 
758         String[] servletIds = null;
759         int      i=0;
760         try {
761           servletIds = getServletIDs();
762           while ( i != servletIds.length) {
763             ServletStatus ss = getStatus(servletIds[i]);
764             if (ss.runOnInit) {
765                 // if one application fails keep trying the others
766                 // If the admin starts up the user may be able to fix
767                 // the problem with GUI rather than editing the conf file.
768                 try {
769                     start(servletIds[i]);
770                 }
771                 catch (ServletException se) {
772                     String resKey = "Failed to start context {0}.";
773                     String msg = rez.format(resKey,  servletIds[i] );
774                     logChannel.write(Logger.ERROR, msg);
775                 }
776             }
777             i++;
778           }
779         } catch (Exception e) {
780             String resKey = "Error starting Context {0}.";
781             String msg = rez.format(resKey,  servletIds[i] );
782             logChannel.write(Logger.DEBUG, msg, e);
783             throw new ServletException(msg,e);
784         }
785     }
786 
787 
788 
789 
790     // Given the servletID build the context. This method is called when the
791     // context is started. If the context is a WAR the context is allowed to
792     // read the web.xml file.
793     private Context buildContext(String servletID) throws ServletException {
794 
795   //logChannel.write(Logger.DEBUG, "func: buildContext(\"" + servletID + "\")");
796 
797         ServletStatus servletStatus = getStatus(servletID);
798     URL url = null;
799         try {
800             url = URLUtil.resolve(servletStatus.docRoot);
801         } catch (MalformedURLException mue) {
802             String resKey = "Failed to resolve docRoot {0}";
803             String msg = rez.format(resKey,  servletStatus.docRoot);
804       logChannel.write(Logger.ERROR,msg,mue);
805             throw new ServletException(msg);
806   }
807 
808         if (servletStatus.paths.size() == 0 ) {
809             String resKey = "No path set for context {0}. Make sure there is a valid connection for the app.";
810             String msg = rez.format(resKey,  servletID);
811       logChannel.write(Logger.ERROR,msg);
812             throw new ServletException(msg);
813         }
814 
815         Context baseContext = null;
816         int i=0;
817         while (i != servletStatus.paths.size()) {
818             String path = (String) servletStatus.paths.elementAt(i);
819             // If the path is "/" or nothing set in to default "".
820             // For example if the context path is "/context", a url of "/context/path"
821             // can be broken into "/context" and "/path" by removing the context path.
822             // If the context path is "/" and we have a url of "/path"
823             // it must be stored as "" so that the same operation results in "" and "/path"
824             if (path.equals("") || path.equals("/")) {
825                 path = org.apache.tomcat.core.Constants.Context.Default.Path;
826             }
827             Context context = createContext(servletID, path);
828             initContext(context,baseContext,path,url,servletID);
829             if (i==0) {
830                 baseContext = context;
831             }
832             i++;
833         }
834         applyFilters(servletID);
835         return baseContext;
836     }
837 
838     /**
839      * Create the context. (duh?)
840      */
841     private Context createContext(String servletID,String path) {
842         SingleContextServer server = new SingleContextServer();
843         Context context = new Context(server,path);
844         server.setContext(context);
845         context.setLogChannel(Logger.getCentralLogger().getChannel(servletID));
846         return context;
847     }
848 
849 
850     private void applyFilters(String servletID) {
851   // apply any filters
852 
853   ServletContainer sc = (ServletContainer)
854     MultiServer.getService("ServletContainer");
855   FilterManager fm = sc.getFilterManager();
856   Vector fv = (Vector) filters.get(servletID);
857   if (fv != null) {
858       Enumeration fe = fv.elements();
859       while (fe.hasMoreElements()) {
860     String filterID = (String) fe.nextElement();
861     Filter f = fm.get(filterID);
862     f.registerInterceptors(servletID);
863       }
864   }
865 
866     }
867 
868     private void initContext(Context context,Context baseContext,
869                             String path,URL url,String servletID ) {
870 
871          logChannel.write(Logger.DEBUG,
872              rez.format("initContext() servletID={0} context={1} path={2} url={3}.",
873              servletID ,context.toString(), path, url));
874 
875         String resKey;
876         String msg;
877 
878         ServletStatus servletStatus = getStatus(servletID);
879         putContext(servletID,path,context);
880 
881         if (baseContext == null) {
882             // This is the baseContext
883             putContext(servletID,null,context);
884             context.setDocumentBase(url);
885         }
886         else {
887             // copy the internal info from the baseContext
888             context.copyContext(baseContext);
889         }
890 
891         // Now init the context. If it is a war we can call init() which will
892         // load and read the web.xml file. Otherwise we need to read the config
893         // information from the enhydra app and feed it into the context.
894 
895         if (servletStatus.isWar) {
896             // The work dir name needs to come from the config file.
897             context.setWorkDir(workDir + java.io.File.separator + servletID,
898                         servletStatus.IsWorkDirPersistent());
899             context.setInvokerEnabled(servletStatus.IsInvokerEnabled());
900             context.setIsWARExpanded(servletStatus.IsWARExpanded());
901             context.setIsWARValidated(servletStatus.IsWARValidated());
902 
903             context.init();
904             int timeOut=0;
905             try {
906                timeOut = Integer.parseInt(servletStatus.defaultSessionTimeOut);
907             }
908             catch (NumberFormatException nfe) {
909                 logChannel.write(Logger.ERROR,nfe.getLocalizedMessage(),nfe);
910             }
911             logChannel.write(Logger.DEBUG,
912                 rez.format("Context time out is ",new Integer(timeOut)));
913             context.setSessionTimeOut(timeOut);
914         }
915         else {
916 
917             // where on the disk we do da work.
918             context.setDocumentBase(url);
919 
920             // This context will have only one servlet, mapping it to "/" sets it
921             // up as the 'default' servlet. Every request that is directed to the
922             // context will invoke this servlet.
923             context.getContainer().addServlet(servletID,  servletStatus.className, null);
924             context.getContainer().addMapping(servletID,"/");
925 
926             // Now we have to set the initParameters of the context
927             // copy these out of the property names of the property file  we have
928             // now
929 
930             Hashtable initParams = new Hashtable();
931             Enumeration argNames = servletStatus.initArgs.propertyNames();
932             while (argNames.hasMoreElements()) {
933                 String name = (String) argNames.nextElement();
934                 String value = servletStatus.initArgs.getProperty(name);
935                 initParams.put(name,value);
936             }
937             context.getContainer().setServletInitParams(servletID, initParams) ;
938 
939             // a war might have a diffirent base and doc url we assume thay are the same
940             // set the servlet base to root.
941 
942       URL docRootUrl = getURLFromPath("/");
943             context.getContainer().setServletBase(docRootUrl);
944 
945             // For a standalone servlet put the classpaths from the config file
946             // into the context container.
947             if (!servletStatus.className.equals(ServletContainer.ENYHDRA_APP_CLASSNAME)) {
948                 int len = servletStatus.classPath.length;
949                 int i=0;
950                 while (i!=len) {
951                     context.getContainer().addClassPath(servletStatus.classPath[i]);
952                     i++;
953                 }
954             }
955 
956          }
957     }
958 
959     /**
960      * Adds the given filter to the list of filters to be applied
961      * to the given servlet when the Context is built.
962      *
963      * @param servletID a String id for the Context (Servlet).
964      * @param filterID a String id for the Filter to apply.
965      */
966     public void addFilter(String servletID, String filterID) {
967   Vector fv = (Vector) filters.get(servletID);
968   if (fv == null) {
969       Vector tmpv = new Vector();
970       filters.put(servletID, tmpv);
971       fv = tmpv;
972   }
973         if (fv.indexOf(filterID) == -1) {
974       fv.addElement(filterID);
975       // check to see if the context has already been built
976       if (getContext(servletID,null) != null) {
977           ServletContainer sc = (ServletContainer)
978             MultiServer.getService("ServletContainer");
979           FilterManager fm = sc.getFilterManager();
980           Filter f = fm.get(filterID);
981           f.registerInterceptors(servletID);
982       }
983         }
984     }
985 
986     public void removeFilter(String servletID, String filterID) {
987   Vector fv = (Vector) filters.get(servletID);
988   if (fv != null) {
989       fv.removeElement(filterID);
990       // check to see if the context has already been built
991             if (getContext(servletID,null) != null) {
992           ServletContainer sc = (ServletContainer)
993             MultiServer.getService("ServletContainer");
994           FilterManager fm = sc.getFilterManager();
995           Filter f = fm.get(filterID);
996           f.unregisterInterceptors(servletID);
997       }
998   }
999     }
1000
1001
1002
1003    /**
1004     * Service the request. The servletManager is taking over the role that the
1005     * ContextManager plays in Tomcat. So this routine is a close copy of the
1006     * ContextManager's <CODE>service()</CODE>. The main diffirence is that
1007     * contexts can be found ny name rather than by path. Which allows us to
1008     * have two contexts with the same urlprefix but diffirent channel's
1009     * e.g. diffirent ports.
1010     *
1011     *  @param channel  The channel that called us
1012     *  @param rrequest
1013     *  @param rresponse
1014     */
1015    public void service(Channel channel,Request rrequest, Response rresponse )
1016        throws Exception {
1017       // Get the context using the servletId
1018        String servletId = channel.getServletID();
1019        String urlPrefix = channel.getURLPrefix();
1020        Context context = getContext(servletId,urlPrefix);
1021
1022        if (context == null) {
1023           String resKey = "Failed to find context for {0}.";
1024           String msg = rez.format(resKey,  servletId);
1025           logChannel.write(Logger.ERROR, msg);
1026
1027           resKey = "The application {0} on path \"{1}\" has not been started.";
1028           msg = rez.format(resKey,servletId,urlPrefix);
1029           logChannel.write(Logger.ERROR, msg);
1030
1031           throw new Exception(msg);
1032        }
1033        TableEntry t = (TableEntry)myTable.get(servletId);
1034        t.refCount++;
1035        t.RPM_counter++;
1036
1037  // resolve the server that we are for
1038  String path = rrequest.getRequestURI();
1039
1040  // final fix on response & request
1041  //    rresponse.setServerHeader(server.getServerHeader());
1042
1043        if (urlPrefix.equals("/")) {
1044            urlPrefix = "";
1045        }
1046        String servletPath = path.substring(0, urlPrefix.length());
1047  String pathInfo = path.substring(urlPrefix.length(),path.length());
1048
1049        // set the pathInfo with the full path param. At this point the
1050        // pathInfo may hold the sessionID. Later the pathInfo will be
1051        // recalculated from the requestURI by the Context
1052        rrequest.setPathInfo(pathInfo);
1053
1054        rrequest.setRequestURI(path);
1055
1056
1057        // dont like this cast can we always assume that the request will be an EnhydraRequest?
1058        // add a ref to Context to point to the sessionmanager
1059        if (rrequest instanceof EnhydraRequest) {
1060            EnhydraRequest er = (EnhydraRequest) rrequest;
1061
1062            SessionManager sessionManager = (SessionManager)
1063                context.getAttribute(SESSION_MANAGER_KEY);
1064            er.setSessionManager(sessionManager);
1065
1066            er.obtainSessionId();
1067        }
1068        else {
1069            String resKey = "Could not set session manager unexpected request type for {0}.";
1070            String msg = rez.format(resKey,  servletId);
1071            logChannel.write(Logger.ERROR, msg);
1072            return;
1073        }
1074
1075  try {
1076      rrequest.setResponse(rresponse);
1077      rresponse.setRequest(rrequest);
1078
1079      // XXX
1080      //    return if an error was detected in processing the
1081      //    request line
1082      if (rresponse.getStatus() >= 400) {
1083    rresponse.finish();
1084    rrequest.recycle();
1085    rresponse.recycle();
1086    return;
1087      }
1088
1089
1090      //    don't do headers if request protocol is http/0.9
1091      if (rrequest.getProtocol() == null) {
1092    rresponse.setOmitHeaders(true);
1093      }
1094
1095      // do it
1096      context.handleRequest(rrequest, rresponse);
1097
1098      // finish and clean up
1099      rresponse.finish();
1100
1101      // protocol notification
1102      rresponse.endResponse();
1103
1104  } catch (org.enhydra.servlet.ServletIOException sioe) {
1105            // Connection reset by peer.
1106         //   sioe.printStackTrace();
1107   } catch (Exception e) {
1108            String resKey = "Exception while handeling request \"{0}\" for servlet {1}.";
1109            String msg = rez.format(resKey,  rrequest.getRequestURI(),servletId);
1110            logChannel.write(Logger.ERROR, msg,e);
1111  }
1112    }
1113
1114
1115
1116    /**
1117     *  Force the instantiation of a Servlet. It will be created, and it's
1118     *  <CODE>init()</CODE> method will be called.
1119     *
1120     *  @param servletID Which Servlet to start.
1121     *  @exception ServletException If there is an error.
1122     */
1123    public synchronized void start(String servletID) throws ServletException {
1124        String resKey;
1125        String msg = rez.format("Starting servlet/application {0} ",servletID);
1126        logChannel.write(Logger.INFO, msg );
1127
1128        TableEntry t = (TableEntry) myTable.get(servletID);
1129        if (t == null) {
1130            resKey = "Attempt to start illegal servlet id {0}.";
1131            msg = rez.format(resKey, servletID);
1132            logChannel.write(Logger.WARNING, msg );
1133
1134            resKey = "Unable to start. Servlet id {0} not found";
1135            msg = rez.format(resKey, servletID );
1136            throw new ServletException(msg);
1137        }
1138
1139        if (t.started) {
1140            resKey = "Attempt to start already started servlet id {0}.";
1141            msg = rez.format(resKey, servletID );
1142            logChannel.write(Logger.WARNING, msg);
1143
1144            resKey = "Unable to start. Servlet ID {0} not found";
1145            msg = rez.format(resKey,   servletID );
1146            throw new ServletException(msg);
1147        }
1148        // The context can't be built untill the URL path has been provided.
1149        if (t.isWar && t.paths.size() == 0) {
1150            resKey = "Channel must be created before starting Web Application: {0}.";
1151            msg = rez.format(resKey,   servletID );
1152
1153            logChannel.write(Logger.WARNING, msg);
1154            throw new ServletException(msg);
1155        }
1156
1157        Context tContext = buildContext(servletID);
1158        t.started = true;
1159        t.servlet = tContext.getContainer().getDefaultServlet();
1160        if (!t.isWar) {
1161            tContext.setupLoadableServlet(servletID);
1162            t.servlet = tContext.getContainer().getDefaultServlet();
1163            // If it's an enhydra force it up.
1164            try {
1165                startEnhydraApp(servletID,t);
1166            } catch (ServletException se) {
1167                t.started = false;
1168                removeContext(servletID,null);
1169                throw se;
1170            }
1171        }
1172
1173        SessionManager sessionManager = null;
1174        // ensureappisrunning is now called by the init() of the servlet.
1175
1176
1177        if (t.servlet instanceof HttpPresentationServlet) {
1178      HttpPresentationServlet hps = (HttpPresentationServlet) t.servlet;
1179            sessionManager = hps.getApplication().getSessionManager();
1180  }
1181
1182
1183        if (sessionManager == null) {
1184           // create new one ???
1185            Config   sessionConfig = t.sessionConfig;
1186            if (sessionConfig == null) {
1187                sessionConfig = new Config();
1188            }
1189            // If the class loader was not set by the enhyda app use the
1190            // context classLoader
1191            ClassLoader classLoader = (ClassLoader) t.classLoader;
1192            if (classLoader == null) {
1193                classLoader =  (ClassLoader) tContext.getAttribute("org.apache.tomcat.classloader");
1194            }
1195            try {
1196                StandardSessionManager sm =
1197                    new StandardSessionManager(classLoader, sessionConfig,
1198                            tContext.getLogChannel());
1199                // the context timeout is in minutes
1200                int timeout = tContext.getSessionTimeOut();
1201//                if (timeout != -1) {
1202                    timeout*=60;
1203//                }
1204                sm.setMaxSessionIdleTime(timeout);
1205
1206                msg = rez.format("Session timeout set to {0} ",
1207                    ""+sm.getMaxSessionIdleTime());
1208                logChannel.write(Logger.DEBUG, msg);
1209                sessionManager = sm;
1210            }
1211            catch (Exception ex) {
1212                resKey = "Unable to start servlet {0}. SessionManager failed";
1213                msg = rez.format(resKey,  servletID);
1214                logChannel.write(Logger.ERROR, msg,ex);
1215                t.started = false;
1216            }
1217        }
1218
1219        if (sessionManager != null) {
1220            tContext.setAttribute(SESSION_MANAGER_KEY,sessionManager);
1221        }
1222
1223    }
1224
1225
1226    private void startEnhydraApp(String servletID, TableEntry t) throws ServletException {
1227            String resKey;
1228            String msg;
1229            if (t.classLoader == null) {
1230                t.classLoader = new MultiClassLoader(t.classPath, t.logChannel);
1231            }
1232
1233            Class c = null;
1234            try {
1235                c = t.classLoader.loadClass(t.className);
1236            } catch (ClassNotFoundException e) {
1237                resKey = "Unable to start servlet {0}. Class {1} not found.";
1238                msg = rez.format(resKey,    servletID, t.className);
1239                logChannel.write(Logger.ERROR, msg);
1240                t.classLoader = null;
1241
1242                resKey = "Unable to load class {0}.";
1243                msg = rez.format(resKey,   t.className);
1244                throw new ServletException(msg);
1245            }
1246
1247
1248            /*
1249            Object o = null;
1250            try {
1251                o = c.newInstance();
1252            } catch (Exception e) {
1253                resKey = "Unable to start servlet {0}. Unable to create instance {1}.";
1254                msg = rez.format(resKey,   servletID, e.toString());
1255                logChannel.write(Logger.ERROR, msg);
1256                t.classLoader = null;
1257
1258                resKey = "Unable to instantiate class {0}.";
1259                msg = rez.format(resKey,  servletID);
1260                throw new ServletException(msg);
1261            }
1262            if (c.!(o instanceof javax.servlet.http.HttpServlet)) {
1263                resKey = "Unable to start servlet {0}. Class {1} is not an HttpServlet.";
1264                msg = rez.format(resKey, servletID, t.className);
1265                logChannel.write(Logger.ERROR, msg);
1266                t.classLoader = null;
1267                throw new ServletException(msg);
1268            }
1269
1270            EnhydraServletContext context =
1271                             new EnhydraServletContext(servletID,
1272                                                       t.logChannel,
1273                                                       this,
1274                                                       t.initArgs,
1275                                                       t.docRoot,
1276                                                       t.classLoader);
1277            EnhydraServletConfig config =  new EnhydraServletConfig(t.initArgs,
1278                    context);
1279            config.setServletName(servletID);
1280
1281            t.servlet = (Servlet) o;
1282            try {
1283                t.servlet.init(config);
1284            } catch (ServletException e) {
1285                resKey = "Unable to start servlet {0}. Error in init {1}";
1286                msg = rez.format(resKey,  servletID,e.toString());
1287                logChannel.write(Logger.ERROR, msg,e);
1288
1289                try {
1290                    t.servlet.destroy();
1291          } finally {
1292                    t.classLoader = null;
1293                    t.servlet = null;
1294                    throw(e);
1295          }
1296            } catch (RuntimeException re) {
1297                resKey = "Unable to start servlet {0}. Error in init {1}";
1298                msg = rez.format(resKey,  servletID,  re.toString());
1299                logChannel.write(Logger.ERROR, msg);
1300
1301          try {
1302                    t.servlet.destroy();
1303          } finally {
1304                    t.classLoader = null;
1305                    t.servlet = null;
1306                    throw(re);
1307          }
1308            }
1309*/
1310          // X22
1311            t.refCount = 0;
1312            t.createTime = new Date();
1313            t.RPM_counter = 0;
1314            t.RPM_startTime = System.currentTimeMillis();
1315            t.RPM_current = -1.0;
1316            t.RPM_max = -1.0;;
1317            t.RPM_maxTime = null;
1318            t.started = true;
1319
1320    }
1321
1322    /**
1323     *  Stop a Servlet. This calls the Servlet's <CODE>destroy()</CODE> method,
1324     *  then discards the Servlet (making it available for garbage
1325     *  collection). After calling this, you may call <CODE>start()</CODE> to
1326     *  cause a new instance of the Servlet to be created.
1327     *
1328     *  @param servletID Which Servlet to stop.
1329     *  @exception ServletException If there is an error.
1330     */
1331    public synchronized void stop(String servletID) throws ServletException {
1332
1333        String resKey = "Stopping servlet/application {0}";
1334        String msg = rez.format(resKey,  servletID);
1335        logChannel.write(Logger.INFO, msg);
1336
1337        TableEntry t = (TableEntry) myTable.get(servletID);
1338        if (t == null)