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