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

Quick Search    Search Deep

Source code: org/enhydra/servlet/ServletContainer.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: ServletContainer.java,v 1.7.2.4.2.2 2000/10/19 17:59:09 jasona Exp $
22   */
23  
24  package org.enhydra.servlet;
25  
26  import java.io.*;
27  import java.net.*;
28  import java.rmi.RemoteException;
29  import java.util.*;
30  import java.text.DateFormat;
31  import javax.servlet.ServletException;
32  //import java.text.MessageFormat;
33  
34  import org.enhydra.servlet.connectionMethods.*;
35  import org.enhydra.servlet.filter.*;
36  import org.enhydra.servlet.servletManager.*;
37  import com.lutris.util.*;
38  import com.lutris.appserver.server.session.*;
39  
40  import java.lang.reflect.Constructor;
41  import java.lang.reflect.InvocationTargetException;
42  
43  import com.lutris.logging.*;
44  import com.lutris.multiServer.Service;
45  
46  import org.apache.tomcat.core.*;
47  import org.apache.tomcat.util.URLUtil;
48  import org.enhydra.i18n.ResManager;
49  
50  
51  /**
52   * Handles requests for Servlets. Multiple connection methods may be
53   * routed to one Servlet, or one connection method may direct requests
54   * to multiple Servlets (based on URL). <P>
55   *
56   * Servlets or Enhydra applications (which are a special kind of servlet)
57   * or Servlet 2.2 Web applications may be served. <P>
58   *
59   * This class uses a ServletManager to hold a set of Servlets,
60   * a ConnectionMethodManager to hold a set of ConnectionMethods,
61   * and a FilterManager to hold a set of Filters. These
62   * are available to other classes (for example the
63   * admin application). <P>
64   *
65   * At startup time a configuration file is read, and it describes what
66   * ConnectionMethods to create, what Servlets to register, and the connections
67   * to make from the ConnectionMethods to the Servlets. It also describes
68   * the filters to create and what channels to install them in. These filters
69   * are all descendants of the MultServerFilter class. <P>
70   *
71   * After this config file is read, and the servlet container is set up, no
72   * further action is taken. The thread that ran through start() exits.
73   * The ConnectionMethods must create threads, and they are responsible for
74   * receiving requests from the outside world and passing them on to the
75   * handleRequest() methods of the ServletContainer. <P>
76   *
77   * Normally one of the Servlets will be an administration application. This
78   * admin app knows about this class, and so it knows how to talk to the
79   * current ConnectionMethods and Servlets. It may change the server setup
80   * on the fly. For example, it can create and add a new ConnectionMethod.
81   * Or it could add a new channel to an already existing ConnectionMethod.
82   * Or register a new Servlet. Or add a logging filter to a channel.
83   * Or monitor performance. <P>
84   *
85   * @see org.enhydra.servlet.connectionMethod.ConnectionMethod
86   * @see org.enhydra.servlet.connectionMethod.ConnectionMethodManager
87   * @see org.enhydra.servlet.filter.FilterManager
88   * @see org.enhydra.servlet.servletManager.ServletManager
89   * @see org.enhydra.servlet.filter.Filter
90   * @see javax.servlet.Servlet
91   * @author Shawn McMurdo
92   * @author Andy John
93   */
94  public class ServletContainer implements Service {
95  
96      /**
97       * The manager for all the ConnectionMethods in use by the server.
98       */
99      private ConnectionMethodManager connectionMethodManager;
100 
101     /**
102      * The manager for all the Servlets in use by the server.
103      */
104     private ServletManager servletManager;
105 
106     /**
107      * The manager for all the Filters (which will all be
108      * descendants of MultiServerFilter) in use by the server.
109      */
110     private FilterManager filterManager;
111 
112     /**
113      * Has the servlet container been initialized and started?
114      */
115     private boolean started = false;
116 
117     /**
118      * The directory where Enhydra application config files
119      * are expected to reside. Only the admin application is expected to need
120      * to use this.
121      */
122     private String configDirectory;
123 
124     /*
125      * The default config file to use if none is specified.
126      */
127     private static final String defaultConfigFileName = "./multiserver.conf";
128 
129     /*
130      * The config file that was actually read.
131      */
132     private String configFileName;
133 
134     /*
135      *   This is the file we read to start up the server. We need to keep
136      *   it around so that we can save the current settings back to the same
137      *   file. The Config object will preserve comments.
138      */
139     private ConfigFile configFile;
140 
141     /*
142      *   This is the keyword value table that was generated by the Config
143      *   object above. When writing the config file, this is used as the
144      *   starting point.
145      */
146     public Config config;
147 
148     private LogChannel logChannel;
149 
150     // Strings used in config file names
151     public static final String CONF_DIR = "ConfDir";
152     public static final String SERVER = "Server";
153 
154     public static final String APPLICATION = "Application";
155     public static final String CONF_FILE = "ConfFile";
156     public static final String SERVLET = "Servlet";
157     public static final String CLASS_NAME = "ClassName";
158     public static final String DOC_ROOT = "DocRoot";
159     public static final String DESCRIPTION = "Description";
160     public static final String RUNNING = "Running";
161     public static final String CLASS_PATH = "ClassPath";
162     public static final String INIT_ARGS = "InitArgs";
163     public static final String URL = "Url";
164     public static final String CONNECTION = "Connection";
165     public static final String FILTERS = "Filters";
166     public static final String CHANNEL = "Channel";
167     public static final String FILTER = "Filter";
168     public static final String ENABLED = "Enabled";
169     public static final String APP_CLASS = "AppClass";
170     public static final String PRESENTATION_PREFIX = "PresentationPrefix";
171     public static final String AUTO_RELOAD = "AutoReload";
172     public static final String SESSION_MANAGER = "SessionManager";
173 
174     // config info for a wab application
175     public static final String DEFAULT_SESSION_TIMEOUT = "defaultSessionTimeOut";
176     public static final String IS_WAR_EXPANDED         = "isWARExpanded";
177     public static final String IS_WAR_VALIDATED        = "isWARValidated";
178     public static final String IS_INVOKER_ENABLED      = "isInvokerEnabled";
179     public static final String IS_WORKDIR_PERSISTENT   = "isWorkDirPersistent";
180 
181     //https stuff
182     public static final String SSL = "SSL";
183     public static final String SERVER_CERTIFICATE = "ServerCertificate";
184     public static final String SECURE_RANDOM_ALGORITHM ="SecureRandomAlgorithm";
185     public static final String SECURE_RANDOM_PROVIDER ="SecureRandomProvider";
186     public static final String SSL_CONTEXT_PROVIDER = "SSLContextProvider";
187     public static final String SSL_CONTEXT_PROTOCOL = "SSLContextProtocol";
188     public static final String KEY_STORE_LOCATION ="KeyStoreLocation";
189     public static final String KEY_STORE_PROVIDER = "KeyStoreProvider";
190     public static final String KEY_MANAGER_ALGORITHM ="KeyManagerAlgorithm";
191     public static final String KEY_MANAGER_PROVIDER = "KeyManagerProvider";
192     public static final String TRUST_MANAGER = "TrustManager";
193     public static final String PASSWORD = "Password";
194     public static final String CLIENT_AUTHENTICATION = "clientAuthentication";
195 
196     // Strings used in config file values
197     public static final String HTTP = "http";
198     public static final String HTTPS= "https";
199     public static final String CGI = "cgi";
200     public static final String RMI = "rmi";
201     public static final String WAI = "wai";
202     public static final String APACHE = "apache";
203     public static final String FULL = "full";
204     public static final String NONE = "none";
205     public static final String YES = "yes";
206     public static final String NO = "no";
207 
208     // Class name of an enhydra app
209     public static final String ENYHDRA_APP_CLASSNAME =
210       "com.lutris.appserver.server.httpPresentation.servlet.HttpPresentationServlet";
211 
212 
213     private StandardSessionManager  standardSessionManager = null;
214     private String yesOrNo = YES+","+NO;
215 
216     private ResManager rez;
217 
218 
219     /**
220      * Default constructor.
221      */
222     public ServletContainer() {
223         // Gets a string  manager wrapped around "org.enhydra.servlet.Res"
224         rez = ResManager.getResManager(this.getClass());
225     }
226 
227     /**
228      * Default initialization.  Uses the existing config file if previously
229      * set, otherwise uses the default config file.
230      */
231     public void init() {
232   if (configFileName == null) {
233       init(defaultConfigFileName);
234   } else {
235       init(configFileName);
236   }
237     }
238 
239     /**
240      * Initialization uses the passed in config file.
241      *
242      * @param configFileName
243      *    a String filename to be read for configuration.
244      */
245     public void init(String configFileName) {
246   this.configFileName = configFileName;
247   logChannel = Logger.getCentralLogger().getChannel("Multiserver");
248     }
249 
250     /**
251      * Start the servlet container.
252      */
253     public void start() {
254         // Get the server ready to read it's config file.
255         initialize();
256 
257   // X22
258   started = true;
259 
260         // Read the config file.
261         readConfigFile(configFileName);
262 
263   // X22 Read the server.xml file
264   // readServerConfig(new String[0]);
265     }
266 
267     /**
268      *   Get the server ready to read it's config file. All the
269      *   manager objects start off empty. There are no "built in"
270      *   servlets, connection methods, channels, or filters.
271      *   They must all be specified in the config file. This
272      *   gives more control to the administrator.
273      */
274     private void initialize() {
275         connectionMethodManager = new ConnectionMethodManager();
276         servletManager = new ServletManager();
277         filterManager = new FilterManager();
278 
279     }
280 
281     /**
282      *  Stop the servlet container.
283      *
284      *  This fucnction disables all the channels in all the
285      *  connection methods, then destroys all the connection
286      *  methods. This causes them to kill all their threads.
287      *  When the last thread exits, the java application will
288      *  exit.
289      */
290     public void stop() {
291   started = false;
292 
293         String[] cmIDs = connectionMethodManager.getIDs();
294 
295         // Disable all channels first.
296         for (int i=0; i<cmIDs.length; i++) {
297             ConnectionMethod cm = connectionMethodManager.get(cmIDs[i]);
298             if (cm == null)
299                 continue;
300             String[] chIDs = cm.getChannelIDs();
301             for (int j=0; j<chIDs.length; j++) {
302                 try {
303                     cm.disableChannel(chIDs[j]);
304                 } catch (ConnectionMethodException cme) {
305                 }
306             }
307         }
308 
309         // Now destroy all ConnectionMethods.
310         for (int i=0; i<cmIDs.length; i++) {
311             ConnectionMethod cm = connectionMethodManager.get(cmIDs[i]);
312             if (cm == null)
313                 continue;
314             try {
315                 cm.destroy();
316             } catch (ConnectionMethodException cme) {
317             }
318         }
319 
320         /*
321          *   Shut down the servlet manager. It will shut down all the
322          *   servlets, and stop it's trans/min monitoring thread.
323          */
324          if (servletManager != null) {
325             servletManager.destroy();
326             servletManager = null;
327         }
328     }
329 
330     /**
331      * Handle a servlet request.
332      *
333      * @param request
334      *    a Tomcat Request.
335      * @param response
336      *    a Tomcat Response.
337      */
338      /*
339     public void handleRequest(Request request, Response response) {
340   if (!started) {
341       init();
342       start();
343   }
344   contextManager.service(request, response);
345     }
346     */
347 
348     /**
349      * Get the connection manager for the ServletContainer.
350      *
351      * @return the ConnectionMethodManager
352      */
353     public ConnectionMethodManager getConnectionMethodManager() {
354   if (!started) {
355       init();
356       start();
357   }
358   return connectionMethodManager;
359     }
360 
361     /**
362      * Get the servlet manager for the ServletContainer.
363      *
364      * @return the ServletManager
365      */
366     public ServletManager getServletManager() {
367   return servletManager;
368     }
369 
370 
371     /**
372      * Get the servlet manager for the ServletContainer.
373      *
374      * @return the ServletManager
375      */
376     public SessionManager getSessionManager() {
377   return standardSessionManager;
378     }
379 
380 
381 
382     /**
383      * Get the filter manager for the ServletContainer.
384      *
385      * @return the FilterManager
386      */
387     public FilterManager getFilterManager() {
388     // This can cause recursive calls to init() and start()
389     // after an error has shut down the ServletContainer
390     //
391     /*
392   if (!started) {
393       init();
394       start();
395   }
396      */
397   return filterManager;
398     }
399 
400 
401     /**
402      * Get the config directory used by this ServletContainer to find apps.
403      * X22 This seems like it should be in the MultiServer Admin rather
404      * X22 than here.
405      *
406      * @return a String config directory.
407      */
408     public String getConfigDirectory() {
409   return configDirectory;
410     }
411 
412     /**
413      * Read the given config file.
414      *
415      * @param configFileName
416      *    a String config file name.
417      */
418     private void readConfigFile(String configFileName) {
419 
420         try {
421       FileInputStream configFIS = new FileInputStream(configFileName);
422       configFile = new ConfigFile(configFIS);
423             config = configFile.getConfig();
424             parseServerSection(myGetSection(config, SERVER, false));
425             configFIS.close();
426         } catch (Throwable t) {
427             // the stack trace (if any) will already be reported.
428             String msg = rez.format("Error encountered while reading config file {0}.",configFileName);
429             logChannel.write(Logger.ERROR, msg);
430             return;
431         }
432 
433   KeywordValueTable applications =
434       myGetSection(config, APPLICATION, false);
435   KeywordValueTable servlets =
436       myGetSection(config, SERVLET, false);
437   KeywordValueTable channels =
438       myGetSection(config, CHANNEL, false);
439   KeywordValueTable filters =
440       myGetSection(config, FILTER, false);
441 
442 
443   // FIX error handling and reporting.
444         try {
445             if (filters != null) {
446                 String[] keys = filters.keys();
447                 for (int i=0; i<keys.length && started; i++)
448                     addFilter((Config)filters.getSection(keys[i]), keys[i]);
449             }
450             if (applications != null && started) {
451                 String[] keys = applications.keys();
452                 for (int i=0; i<keys.length && started; i++)
453                     addApplication((Config)applications.getSection(keys[i]),
454                                    keys[i]);
455             }
456             if (servlets != null && started) {
457                 String[] keys = servlets.keys();
458                 for (int i=0; i<keys.length && started; i++)
459                     addServlet((Config)servlets.getSection(keys[i]),
460                                keys[i]);
461             }
462 
463       try {
464                 if (started)
465                    connectionMethodManager.initConnectionMethods(config);
466       } catch (ConnectionMethodException e) {
467                 // stack trace is already reported.
468                 String msg = rez.format("Exiting the server.");
469     logChannel.write(Logger.ERROR, msg);
470     System.err.println(msg);
471     Runtime vm = Runtime.getRuntime();
472                 // This is deprecated in JDK 1.2
473     Runtime.runFinalizersOnExit(true);
474     vm.exit(1);
475       }
476 
477             if (channels != null && started) {
478                 String[] keys = channels.keys();
479                 for (int i=0; i<keys.length && started; i++)
480                     addChannels((Config)channels.getSection(keys[i]), keys[i]);
481              }
482         } catch (Throwable e) {
483             String msg = rez.format("Error encountered while reading config file {0}.",configFileName);
484             logChannel.write(Logger.ERROR, msg, e);
485             return;
486         }
487 
488         String workDir = "work";
489         int i =  configDirectory.lastIndexOf('/');
490         if (i >0) {
491             workDir = configDirectory.substring(0,i) +
492                         File.separator + "work";
493         }
494         getServletManager().setWorkDir(workDir);
495 
496         if (started) {
497             try {
498                 getServletManager().startAllContexts();
499             } catch (ServletException se) {
500                 throw new ServletError(se);
501             }
502         }
503     }
504 
505     /**
506      * Retrieve the server parameters from the config file and
507      * establish the logging facility.
508      *
509      * @param server
510      *   the config object for the server section.
511      */
512     private void parseServerSection(Config server) {
513         configDirectory = myGetString(server, CONF_DIR, SERVER);
514     }
515 
516 
517     private void addApplication(Config info, String id) {
518 
519         if (info == null)
520             return;
521 
522         String running = myGetString(info, RUNNING, APPLICATION + "." + id);
523 
524         if (!running.equalsIgnoreCase(YES) && !running.equalsIgnoreCase(NO)) {
525             String msg = rez.format("Illegal running entry {0} for application {1}. Expected one of {2}.",
526                     running,id,yesOrNo);
527             fatalErr(msg);
528             return;
529         }
530 
531         String conf = myGetString(info, CONF_FILE, APPLICATION + "." + id,false);
532         boolean bRunning = running.equalsIgnoreCase(YES);
533         if (conf != null)  {
534             addEnhydraApplication(info,id,bRunning,conf);
535         }
536         else {
537             addWarApplication(info,id,bRunning);
538         }
539 
540     }
541 
542     private void addEnhydraApplication(Config info, String id,boolean runOnInit,
543                                        String conf) {
544 
545         String description = myGetString(info, DESCRIPTION, APPLICATION + "." + id);
546         /*
547          *    Read the application's config file.
548          */
549   String confFilename = conf;
550   if (!confFilename.startsWith(File.separator)) {
551       confFilename = configDirectory + File.separator + conf;
552   }
553 
554         File confFile = new File(confFilename);
555 
556         if (!confFile.exists()) {
557             fatalErr(rez.format("No conf file {0}" ,confFilename));
558             return;
559         }
560 
561         if (!confFile.isFile()) {
562             fatalErr(rez.format("Not a file: {0}",confFilename));
563             return;
564         }
565 
566         Config cfg = null;
567         try {
568       FileInputStream cfgFIS = new FileInputStream(confFilename);
569       ConfigFile cfgF = new ConfigFile(cfgFIS);
570             cfgFIS.close();
571             cfg = cfgF.getConfig();
572         } catch (IOException ioe) {
573             fatalErr("IO error reading " + confFilename +
574                      ":\n" + ioe.getMessage(), ioe);
575             return;
576         } catch (ConfigException ce) {
577             fatalErr("Bad file format reading " + confFilename +
578                      ":\n" + ce.getMessage(),ce);
579             return;
580         }
581 
582         /*
583          *   Get the classpath from the config file. This is an optional
584          *   field.
585          */
586         String[] classPath = new String[0];
587         if (cfg.containsKey(SERVER + "." + CLASS_PATH)) {
588             try {
589                 classPath = cfg.getStrings(SERVER + "." + CLASS_PATH);
590             } catch (ConfigException ce) {
591                 fatalErr("Invalid config file " +
592                       confFilename + ". No key " + SERVER + "." + CLASS_PATH);
593                 return;
594             }
595         }
596 
597         /*
598          *    Get the docRoot from the config file.
599          *    FOR NOW ENHYDRA APPS DO NOT NEED THIS.
600          */
601         String docRoot = "/";   // docRoot will be fed to the context it should not be null
602   // X22 ?
603         if (cfg.containsKey(SERVER + "." + DOC_ROOT)) {
604             try {
605                 docRoot = cfg.getString(SERVER + "." + DOC_ROOT);
606             } catch (ConfigException ce) {
607                 fatalErr("Error getting " + SERVER + "." + DOC_ROOT, ce);
608                 return;
609             }
610         }
611 
612         /*
613          *    Create the initial arguments to the servlet.
614          */
615         Properties initArgs = new Properties();
616         initArgs.put(CONF_FILE, confFilename);
617 
618         /*
619          *    Go ahead and register the new servlet.
620          */
621   logChannel.write(Logger.DEBUG, "ADDING APPLICATION:");
622   logChannel.write(Logger.DEBUG, "ID = " + id);
623   for (int z=0; z<classPath.length; z++)
624       logChannel.write(Logger.DEBUG, "CP [" + z + "] = " + classPath[z]);
625   logChannel.write(Logger.DEBUG, "initArgs = " + initArgs);
626   logChannel.write(Logger.DEBUG, "desc = " + description);
627 
628         try {
629             servletManager.add(id,
630                 "com.lutris.appserver.server.httpPresentation." +
631                 "servlet.HttpPresentationServlet",
632                 classPath, docRoot,
633                 initArgs, runOnInit,description,false);
634         } catch (ServletException sme) {
635             fatalErr("Error adding application" + id + " to server:\n" +
636                      sme.getMessage(),sme);
637         }
638 
639     }
640 
641     // The application did not define a confFile so we assume it is a WAR
642     // There must be a docRoot
643     private void addWarApplication(Config info, String id,boolean runOnInit)
644             {
645 
646 
647 
648         Config sessionConfig = null;
649         try {
650             sessionConfig = (Config)info.getSection(SESSION_MANAGER);
651         }
652         catch (KeywordValueException e) {
653             fatalErr(rez.format("Unable to read config file."),e);
654         }
655 
656         String app = APPLICATION + "." + id;
657         String description = myGetString(info, DESCRIPTION,app);
658         String docRoot = myGetString(info, DOC_ROOT, APPLICATION + "." + id,false);
659 
660         if (docRoot == null) {
661             String msg = rez.format("DocRoot required for Web Application {0}.",id);
662             logChannel.write(Logger.ERROR, msg);
663             return;
664         }
665 
666         File docRootFile = new File(docRoot);
667         if (!docRootFile.exists()) {
668             String msg;
669             msg = rez.format("DocRoot \"{0}\" not found for Web Application {1}.",docRoot,id);
670             logChannel.write(Logger.ERROR, msg);
671             if (runOnInit) {
672                 msg = rez.format("Web Application {0} will not be started.",id);
673                 logChannel.write(Logger.INFO, msg);
674                 runOnInit = false;
675             }
676         }
677         else if (!docRootFile.isDirectory()) {
678             // untill tomcat expands the wars.
679             String rezKey = "DocRoot \"{0}\" for apllication {1} is not a directory .";
680             String msg = rez.format(rezKey,docRoot,id);
681             logChannel.write(Logger.WARNING, msg);
682         }
683 
684 
685         // all of these are optional.
686         String defaultSessionTimeout = myGetString(info, DEFAULT_SESSION_TIMEOUT, app, false);
687         String isWARExpanded         = myGetString(info, IS_WAR_EXPANDED, app, false);
688         String isWARValidated        = myGetString(info, IS_WAR_VALIDATED, app, false);
689         String isInvokerEnabled      = myGetString(info, IS_INVOKER_ENABLED, app, false);
690         String isWorkDirPersistent   = myGetString(info, IS_WORKDIR_PERSISTENT, app, false);
691 
692         Properties initArgs = new Properties();
693         String[] classPath = new String[0];
694 
695         try {
696             servletManager.add(id,
697                 "", // className
698                 classPath,
699                 docRoot, initArgs, runOnInit,
700                 description,true);
701         } catch (ServletException sme) {
702             String rezKey = "Error adding application {0} to server.";
703             String msg = rez.format(rezKey,id);
704             logChannel.write(Logger.ERROR, msg,sme);
705             return;
706         }
707 
708         // now add in the optional settings
709         servletManager.recordWarOnlySettings(id, defaultSessionTimeout,
710                                                  isWARExpanded,
711                                                  isWARValidated,
712                                                  isInvokerEnabled,
713                                                  isWorkDirPersistent );
714 
715         servletManager.recordSessionConfig(id,sessionConfig);
716 
717     }
718 
719     private void addServlet(Config info, String id) {
720 
721         if (info == null)
722             return;
723 
724         String className   = myGetString(info, CLASS_NAME, SERVLET + "." + id);
725         String root        = myGetString(info, DOC_ROOT,  SERVLET + "." + id);
726         String description = myGetString(info, DESCRIPTION, SERVLET, false);
727 
728         if (description == null)
729             description = "";
730 
731         String running = myGetString(info, RUNNING, SERVLET + "." + id);
732 
733         if (!running.equalsIgnoreCase(YES) && !running.equalsIgnoreCase(NO)) {
734 
735             String rezKey = "Illegal running entry {0} for servlet {1}. Expected one of {2}.";
736             String msg = rez.format(rezKey,running,id,yesOrNo);
737             fatalErr(msg);
738             return;
739         }
740 
741 
742         /*
743          *   ClassPath (optional) is a comma-separated list.
744          */
745         String[] classPath = new String[0];
746         if (info.containsKey(CLASS_PATH)) {
747             try {
748                 classPath = info.getStrings(CLASS_PATH);
749             } catch (ConfigException ce) {
750                 // resource bundle this string
751                 String rezKey = "Illegal class path specified for servlet {0}.";
752                 String msg = rez.format(rezKey,id);
753                 fatalErr(msg,ce);
754                 return;
755             }
756         }
757         /*
758          *  Init (optional) is a section.
759          */
760         Config init = null;
761         if (info.containsKey(INIT_ARGS)) {
762             try {
763                 init = (Config) info.getSection(INIT_ARGS);
764             } catch (KeywordValueException e) {
765                 fatalErr("Error getting " + INIT_ARGS, e);
766                 return;
767             }
768         } else {
769             init = new Config();
770         }
771         Properties initArgs = new Properties();
772         String[] keys = init.keys();
773         for (int i=0; i<keys.length; i++) {
774             String val = null;
775             try {
776                 val = init.getString(keys[i]);
777             } catch (ConfigException e) {}
778             if (val == null)
779                 val = "";
780             initArgs.put(keys[i], val);
781         }
782         logChannel.write(Logger.DEBUG, "ADDING SERVLET:");
783         logChannel.write(Logger.DEBUG, "ID = " + id);
784         logChannel.write(Logger.DEBUG, "CN = " + className);
785         for (int z=0; z<classPath.length; z++)
786             logChannel.write(Logger.DEBUG, "CP [" + z + "] = " + classPath[z]);
787         logChannel.write(Logger.DEBUG, "root = " + root);
788         logChannel.write(Logger.DEBUG, "initArgs = " + initArgs);
789         logChannel.write(Logger.DEBUG, "desc = " + description);
790         try {
791             servletManager.add(id, className,
792                                classPath, root, initArgs,
793                                running.equalsIgnoreCase(YES),
794                                description,false);
795         } catch (ServletException e) {
796             String rezKey = "Unable to add servlet {0}.";
797             String msg = rez.format(rezKey,id);
798             fatalErr(msg,e);
799             return;
800 
801         }
802     }
803 
804 
805 
806     private void addChannels(Config info, String cmID) {
807         logChannel.write(Logger.DEBUG, "ADD CHANNEL : " + cmID);
808         if (info == null)
809             return;
810         ConnectionMethod cm = connectionMethodManager.get(cmID);
811         if (cm == null) {
812             String rezKey = "Unable to add channel: illegal connection method id {0}.";
813             fatalErr(rez.format(rezKey,cmID));
814         }
815         String[] chanIDs = info.keys();
816         for (int i=0; i<chanIDs.length; i++) {
817             Config props = null;
818             try {
819                 props = (Config) info.getSection(chanIDs[i]);
820             } catch (ConfigException e) {
821             } catch (KeywordValueException e) {
822             }
823             if (props == null) {
824                 String rezKey = "Error reading channel id {0} for connection method {1}.";
825                 String msg = rez.format(rezKey,chanIDs[i],cmID);
826                 fatalErr(msg);
827             }
828 
829             String servletID = myGetString(props, SERVLET,
830                 CHANNEL + "." + cmID + "." + chanIDs[i]);
831             String url = myGetString(props, URL,
832                 CHANNEL + "." + cmID + "." + chanIDs[i]);
833             String enabled = myGetString(props, ENABLED,
834                 CHANNEL + "." + cmID + "." + chanIDs[i]);
835             if (!enabled.equalsIgnoreCase(YES) &&
836                 !enabled.equalsIgnoreCase(NO)) {
837                 String rezKey = "Illegal enabled value {0} for channel {1} in connection method {2}. Expected one of {3}.";
838                 String msg = rez.format(rezKey,enabled,chanIDs[i],cmID,yesOrNo);
839                 fatalErr(msg);
840             }
841             String filters = myGetString(props, FILTERS, CONNECTION, false);
842             if (filters == null)
843                 filters = "";
844             try {
845             //  contextFromConfig(props,cmID,cm);
846                 cm.addChannel(chanIDs[i], url, servletID);
847             } catch (ConnectionMethodException e) {
848                 String rezKey = "Error adding channel {0} to connection method {1}.";
849                 String msg = rez.format(rezKey,chanIDs[i],cmID);
850                 fatalErr(msg,e);
851             }
852             /*
853              *    Now try to add all the requested filters (if any).
854              */
855             StringTokenizer stok = new StringTokenizer(filters, ",");
856             while (stok.hasMoreTokens()) {
857                 String id = trim(stok.nextToken());
858                 Filter tf = filterManager.get(id);
859                 if (tf == null) {
860                     String rezKey = "Channel {0} in connection method {1} has illegal filter id {2}.";
861                     String msg = rez.format(rezKey,chanIDs[i],cmID,id);
862                     fatalErr(msg);
863                 }
864                 try {
865                     cm.addTransactionFilter(chanIDs[i], id);
866                 } catch (ConnectionMethodException cme) {
867                     String rezKey = "Error adding filter {0} to channel {1} in connection method {2}.";
868                     String msg = rez.format(rezKey,id,chanIDs[i],cmID);
869                     fatalErr(msg, cme);
870                 }
871             }
872 
873             /*
874              *    Maybe enable the channel.
875              */
876             if (enabled.equalsIgnoreCase(YES)) {
877                 try {
878                     cm.enableChannel(chanIDs[i]);
879                 } catch (ConnectionMethodException e) {
880                     String rezKey = "Unable to enable channel {0} in connection method {1}.";
881                     String msg = rez.format(rezKey,chanIDs[i],cmID);
882                     fatalErr(msg, e);
883                 }
884             }
885         }
886     }
887 
888     private void addFilter(Config info, String id) {
889         if (info == null)
890             return;
891         String classname = myGetString(info, CLASS_NAME, FILTER);
892         String description = myGetString(info, DESCRIPTION, FILTER);
893         /*
894          *   Init (optional) is a section.
895          */
896         KeywordValueTable init = null;
897         try {
898             init = info.getSection(INIT_ARGS);
899         } catch (KeywordValueException e) {
900         }
901         /*
902          *   Try to create the filter. It must
903          *   have a constructor that takes a String and a
904          *   KeywordValueTable.
905          */
906         Class c = null;
907         try {
908             c = Class.forName(classname);
909         } catch (ClassNotFoundException cnfe) {
910             String rezKey = "Unable to find filter class {0}.";
911             String msg = rez.format(rezKey,classname);
912             fatalErr(msg, cnfe);
913         }
914         try {
915             Class tf = Class.forName("org.enhydra.servlet.filter.Filter");
916             if (!(tf.isAssignableFrom(c))) {
917                 String rezKey = "Filter class {0} does not extend Filter.";
918                 String msg = rez.format(rezKey,classname);
919                 fatalErr(msg);
920             }
921         } catch (ClassNotFoundException cnfe) {
922             fatalErr(rez.format("Error loading filter {0}.",id),cnfe);
923         }
924         Constructor cnst = null;
925         try {
926             Class[] types = new Class[2];
927             types[0] = null;
928             types[1] = null;
929             try {
930                 types[0] = Class.forName("java.lang.String");
931                 types[1] = Class.forName("com.lutris.util.KeywordValueTable");
932             } catch (ClassNotFoundException cnfe) {
933                 fatalErr(rez.format("Error loading filter {0}.",id),cnfe);
934             }
935             cnst = c.getConstructor(types);
936         } catch (NoSuchMethodException nsme) {
937             String rezKey = "Filter class {0} does not support the correct constructor. See class org.enhydra.servlet.filter.MultiServerFilter.";
938             String msg = rez.format(rezKey,classname)            ;
939             fatalErr(msg, nsme);
940         } catch (SecurityException se) {
941             String rezKey = "Filter class {0} caused a security exception.";
942             String msg = rez.format(rezKey,classname)            ;
943             fatalErr(msg,se);
944         }
945         Object o = null;
946         try {
947             Object[] args = new Object[2];
948             args[0] = description;
949             args[1] = init;
950             o = cnst.newInstance(args);
951         } catch (InstantiationException ie) {
952             String rezKey = "Filter {0} could not be instantiated.";
953             String msg = rez.format(rezKey,id);
954             fatalErr(msg, ie);
955         } catch (IllegalAccessException iae) {
956             String rezKey = "Filter {0} could not be accessed. (is it a public class?).";
957             String msg = rez.format(rezKey,id);
958             fatalErr(msg, iae);
959         } catch (IllegalArgumentException iae2) {
960             fatalErr("Filter " + id + " could not be instantiated", iae2);
961         } catch (InvocationTargetException ite) {
962             String rezKey = "Filter {0} threw an expection";
963             String msg = rez.format(rezKey,id);
964             fatalErr(msg,ite.getTargetException());
965         }
966         if (o == null) {
967             String rezKey = "Filter {0} could not be instantiated.";
968             String msg = rez.format(rezKey,id);
969             fatalErr(msg);
970         }
971         Filter tf = null;
972         try {
973             tf = (Filter) o;
974         } catch (Throwable t) {
975             String rezKey = "Filter class {0} is not castable to Filter.";
976             fatalErr("Filter class " + classname +
977                      " is not castable to Filter.", t);
978         }
979         logChannel.write(Logger.DEBUG, "ADDING FILTER: " + id);
980         filterManager.add(id, tf, description);
981     }
982 
983     private String myGetString(Config cfg, String key) {
984         return myGetString(cfg, key, null, true);
985     }
986 
987     private String myGetString(Config cfg, String key, String prefix) {
988         return myGetString(cfg, key, prefix, true);
989     }
990 
991     private String[] myGetStrings(Config t,
992                    String key, String prefix) {
993         return myGetStrings(t, key, prefix, true);
994     }
995 
996     private String myGetString(Config cfg, String key,
997                                       String prefix, boolean required) {
998         String s = null;
999         try {
1000            s = cfg.getString(key);
1001        } catch (ConfigException e) {
1002        }
1003        if ((s == null) && required) {
1004            if (prefix != null)
1005                prefix += ".";
1006            String msg = rez.format("Error reading config file. Key {0} not found",(prefix+key));
1007            fatalErr(msg);
1008        }
1009        return s;
1010    }
1011
1012    private String[] myGetStrings(Config t,
1013                             String key, String prefix, boolean required) {
1014        String[] s = null;
1015        try {
1016            s = t.getStrings(key);
1017        } catch (ConfigException e) {
1018            // Handled below..
1019        }
1020        if ((s == null) && required) {
1021            if (prefix != null)
1022                prefix += ".";
1023            String msg = rez.format("Error reading config file. Key {0} not found",(prefix+key));
1024            fatalErr(msg);
1025        }
1026        return s;
1027    }
1028
1029    private Config myGetSection(Config cfg, String key) {
1030        return myGetSection(cfg, key, true);
1031    }
1032
1033    private Config myGetSection(Config cfg,
1034                                       String key, boolean required) {
1035        Config cfg2 = null;
1036        try {
1037            cfg2 = (Config) cfg.getSection(key);
1038        } catch (ConfigException e) {
1039            // Do nothing.
1040        } catch (Exception e) {
1041            String rezKey = "Internal error parsing config file (section {0}).";
1042            String msg = rez.format(rezKey,key);
1043            fatalErr(msg, e);
1044        }
1045        if ((cfg2 == null) && required) {
1046            String rezKey = "Illegal config file. Section {0} not found.";
1047            String msg = rez.format(rezKey,key);
1048            fatalErr(msg);
1049        }
1050        return cfg2;
1051    }
1052
1053    private String trim(String s) {
1054        if (s == null)
1055            return null;
1056        int len = s.length();
1057        while(len > 0) {
1058            char c = s.charAt(0);
1059            if ((c == ' ') || (c == '\t')) {
1060                s = s.substring(1);
1061                len--;
1062            } else
1063                break;
1064        }
1065        while(len > 0) {
1066            char c = s.charAt(len - 1);
1067            if ((c == ' ') || (c == '\t')) {
1068                s = s.substring(0, len - 1);
1069                len--;
1070            } else
1071                break;
1072        }
1073        return s;
1074    }
1075
1076    /**
1077     *  This function examines the current state of the server and
1078     *  writes it out to the given config file.
1079     *
1080     * @param filename The filename of the config file to write out.
1081     * If null, the same file that was read at startup time will be
1082     * rewritten.
1083     */
1084    public String writeConfigFile(String filename) {
1085        if (filename == null)
1086            filename = configFileName;
1087  File realFile = null;
1088        String tempName = filename + ".tmp";
1089        File tempFile = null;
1090        FileOutputStream tempOut = null;
1091        try {
1092            realFile = new File(filename);
1093      tempFile = new File(tempName);
1094            tempOut = new FileOutputStream(tempFile);
1095        } catch (IOException e) {
1096            String err = rez.format("IO error writing to {0}.",tempName);
1097            logChannel.write(Logger.ERROR, err, e);
1098            return err;
1099        }
1100        try {
1101            try {
1102                createApplicationSection();
1103                createServletSection();
1104    config.remove(CONNECTION);
1105                connectionMethodManager.createConnectionSection(config);
1106                createChannelSection();
1107            } catch (ConfigException e) {
1108                String err = rez.format("Internal error recreating config file.");
1109                logChannel.write(Logger.ERROR, err, e);
1110                return err;
1111            } catch (KeywordValueException e) {
1112                String err = rez.format("Internal error recreating config file.");
1113                logChannel.write(Logger.ERROR, err, e);
1114                return err;
1115            }
1116            try {
1117                configFile.write(tempOut);
1118            } catch (Error e) {
1119                String err = rez.format("Unable to write config file to: {0}.",tempOut);
1120                logChannel.write(Logger.ERROR, err, e);
1121                return err;
1122            }
1123        } finally {
1124            /*
1125             * Be sure we close the output stream no matter what.
1126             */
1127            try {
1128                tempOut.close();
1129            } catch (IOException e) {
1130                // Nothing we can do about it.
1131            }
1132        }
1133        /*
1134         * When running under windows, we need to close the output streams
1135         * and delete the target file or the rename will fail.
1136         */
1137        if (!realFile.delete()) {
1138            String err = rez.format("Unable to delete {0}.",filename);
1139            logChannel.write(Logger.ERROR, err);
1140            return err;
1141        }
1142        if (!tempFile.renameTo(realFile)) {
1143            String rezKey = "Unable to rename {0} to {1}.";
1144            String err = rez.format(rezKey,tempName,filename);
1145            logChannel.write(Logger.ERROR, err);
1146            return err;
1147        }
1148        return null;
1149    }
1150
1151    private void createApplicationSection()
1152                            throws ConfigException, KeywordValueException {
1153        // Delete the old application section.
1154        config.remove(APPLICATION);
1155        // Recreate it from the current state of server.
1156        String[] ids = servletManager.getServletIDs();
1157        for (int i=0; i<ids.length; i++) {
1158            ServletStatus status = servletManager.getStatus(ids[i]);
1159            if (status == null)
1160                continue;
1161            // sjip if it's not an Enhydra App or a War.
1162            if (!status.isWar && !status.className.equals(ENYHDRA_APP_CLASSNAME))
1163                continue;
1164            String base = APPLICATION + "." + ids[i] + ".";
1165            if (!status.isWar) {
1166              // It's an Enhydra app
1167              String confName = status.initArgs.getProperty(CONF_FILE);
1168              if (confName == null) {
1169                  String msg = rez.format(
1170                        "Unable to determine config file for application {0}",
1171                        ids[i]);
1172                  throw new ConfigException(msg);
1173              }
1174              StringTokenizer stok = new StringTokenizer(confName,
1175                                                         File.separator);
1176              while (stok.hasMoreTokens())
1177                  confName = stok.nextToken();
1178              config.set(base + CONF_FILE, confName);
1179            }
1180            else {
1181              // It's a Web app
1182              config.set(base + DOC_ROOT, status.docRoot);
1183              config.set(base + DEFAULT_SESSION_TIMEOUT, status.defaultSessionTimeOut);
1184              config.set(base + IS_WAR_EXPANDED, status.isWARExpanded);
1185              config.set(base + IS_WAR_VALIDATED, status.isWARValidated);
1186              config.set(base + IS_INVOKER_ENABLED, status.isInvokerEnabled);
1187              config.set(base + IS_WORKDIR_PERSISTENT, status.isWorkDirPersistent);
1188            }
1189
1190            if (status.running)
1191                config.set(base + RUNNING, YES);
1192            else
1193                config.set(base + RUNNING, NO);
1194            config.set(base + DESCRIPTION, status.description);
1195        }
1196    }
1197
1198    private void createServletSection()
1199                       throws ConfigException, KeywordValueException {
1200        // Delete the old servlet section.
1201        config.remove(SERVLET);
1202        // Recreate it from the current state of server.
1203        String[] ids = servletManager.getServletIDs();
1204        for (int i=0; i<ids.length; i++) {
1205            ServletStatus status = servletManager.getStatus(ids[i]);
1206            if (status == null)
1207                continue;
1208            if (status.isWar || status.className.equals(ENYHDRA_APP_CLASSNAME))
1209                continue;
1210            String base = SERVLET + "." + ids[i] + ".";
1211            config.set(base + CLASS_NAME, status.className);
1212            if (status.running)
1213                config.set(base + RUNNING, YES);
1214            else
1215                config.set(base + RUNNING, NO);
1216            config.set(base + DESCRIPTION, status.description);
1217
1218            if (status.classPath.length > 0) {
1219                config.set(base + CLASS_PATH, status.classPath);
1220      }
1221            config.set(base + DOC_ROOT, status.docRoot);
1222
1223            Enumeration e = status.initArgs.propertyNames();
1224            while (e.hasMoreElements()) {
1225                String key = (String) e.nextElement();
1226                String value = status.initArgs.getProperty(key, "");
1227                config.set(base + INIT_ARGS + "." + key, value);
1228            }
1229        }
1230    }
1231
1232    private void createChannelSection()
1233                         throws ConfigException, KeywordValueException {
1234        // Delete old channel section.
1235        config.remove(CHANNEL);
1236        // Recreate it from the current channels.
1237        String[] cmIds = connectionMethodManager.getIDs();
1238        for (int i=0; i<cmIds.length; i++) {
1239            ConnectionMethod cm = connectionMethodManager.get(cmIds[i]);
1240            if (cm == null)
1241                continue;
1242            String[] chIds = cm.getChannelIDs();
1243            for (int j=0; j<chIds.length; j++) {
1244                String base = CHANNEL + ".&quo