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)