KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mmbase > servlet > MMBaseServlet


1 /*
2
3 This software is OSI Certified Open Source Software.
4 OSI Certified is a certification mark of the Open Source Initiative.
5
6 The license (Mozilla version 1.0) can be read at the MMBase site.
7 See http://www.MMBase.org/license
8
9 */

10 package org.mmbase.servlet;
11
12 import org.mmbase.module.Module;
13 import org.mmbase.module.core.MMBase;
14 import org.mmbase.module.core.MMBaseContext;
15
16 import javax.servlet.ServletContext JavaDoc;
17 import javax.servlet.ServletException JavaDoc;
18
19 import javax.servlet.http.HttpServlet JavaDoc;
20 import javax.servlet.http.HttpServletRequest JavaDoc;
21 import javax.servlet.http.HttpServletResponse JavaDoc;
22
23 import java.io.IOException JavaDoc;
24 import java.io.PrintWriter JavaDoc;
25
26 import java.util.*;
27
28 import org.w3c.dom.*;
29 import org.xml.sax.InputSource JavaDoc;
30 import org.mmbase.util.logging.Logging;
31 import org.mmbase.util.logging.Logger;
32 import org.mmbase.util.xml.DocumentReader;
33
34
35 /**
36  * MMBaseServlet is a base class for other MMBase servlets (like ImageServlet). Its main goal is to
37  * store a MMBase instance for all its descendants, but it can also be used as a serlvet itself, to
38  * show MMBase version information.
39  *
40  * @version $Id: MMBaseServlet.java,v 1.53 2006/07/18 12:45:02 michiel Exp $
41  * @author Michiel Meeuwissen
42  * @since MMBase-1.6
43  */

44 public class MMBaseServlet extends HttpServlet JavaDoc implements MMBaseStarter {
45
46     private static final Logger log = Logging.getLoggerInstance(MMBaseServlet.class);
47     /**
48      * MMBase reference. While null, servlet does not accept request.
49      */

50     protected MMBase mmbase = null;
51     // private static String context;
52

53
54     // ----------------
55
// members needed for refcount functionality.
56

57     /**
58      * To keep track of the currently running servlets
59      * switch the following boolean to true.
60      *
61      * @bad-constant
62      */

63     private static final boolean logServlets = true;
64     private static int servletCount; // Number of running servlets
65
/**
66      * Lock to sync add and remove of threads
67      */

68     private static Object JavaDoc servletCountLock = new Object JavaDoc();
69     /**
70      * Hashtable containing currently running servlets
71      */

72     private static Map runningServlets = new HashMap();
73     /**
74      * Toggle to print running servlets to log.
75      * @javadoc Not clear, I don't understand it.
76      */

77     private static int printCount;
78
79     private static int servletInstanceCount = 0;
80     // servletname -> servletmapping
81
// obtained from web.xml
82
private static Map servletMappings = new Hashtable();
83     // topic -> servletname
84
// set by isntantiated servlets
85
private static Map associatedServlets = new Hashtable();
86     // topic -> servletmapping
87
// set by instantiated servlets
88
private static Map associatedServletMappings = new Hashtable();
89     // mapping to servlet instance
90
private static Map mapToServlet = new Hashtable();
91
92     private long start = System.currentTimeMillis();
93
94     /**
95      * Boolean indicating whether MMBase has been started. Used by {@link #checkInited}, set to true {@link #by setMMBase}.
96      * @since MMBase-1.7
97      */

98     private static boolean mmbaseInited = false;
99
100     /**
101      * If MMBase has not been started, a 503 is given, with this value for the 'Retry-After' header.
102      * See <a HREF="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.4">rfc 2616, section 10.5.4</a>.
103      * Defaults to 60 seconds, can be configured in web.xml with the 'retry-after' propery on the servlets.
104      * @since MMBase-1.7.2
105      */

106     protected int retryAfter = 60;
107
108
109     /**
110      * Thread starting MMBase
111      */

112     private Thread JavaDoc initThread;
113
114
115     /**
116      * On default, servlets are not associated with any function.
117      *
118      * This function is called in the init method.
119      *
120      * @return A map of Strings (function) -> Integer (priority). Never null.
121      */

122
123     protected Map getAssociations() {
124         return new Hashtable();
125     }
126
127     /**
128      * Used in association map
129      */

130     private static class ServletEntry {
131         ServletEntry(String JavaDoc n) {
132             this(n, null);
133         }
134         ServletEntry(String JavaDoc n, Integer JavaDoc p) {
135             name = n;
136             if (p == null) {
137                 priority = 0;
138             } else {
139                 priority = p.intValue();
140             }
141         }
142         String JavaDoc name;
143         int priority;
144     }
145
146
147     /**
148      * Returns the MMBase instance.
149      * @since MMBase-1.7
150      */

151     public MMBase getMMBase() {
152         return mmbase;
153     }
154
155     /**
156      * Sets the mmbase member. Can be overriden to implement extra initalization for the servlet which needs a running MMBase.
157      * @since MMBase-1.7
158      */

159     public void setMMBase(MMBase mmb) {
160         Calendar cal = Calendar.getInstance();
161         cal.setTime(new java.util.Date JavaDoc(System.currentTimeMillis()-start));
162         if (! mmbaseInited) {
163             log.info("MMBase servlets are ready to receive requests, started in " +cal.get(Calendar.MINUTE)+" min "+cal.get(Calendar.SECOND)+" sec.");
164         }
165
166         mmbase = mmb;
167         mmbaseInited = true;
168     }
169
170
171
172     /**
173      * Used in checkInited.
174      */

175     private static ServletException JavaDoc initException = null;
176
177     /**
178      * Called by MMBaseStartThread, if something went wrong during
179      * initialization of MMBase. It will be thrown by checkInited
180      * then.
181      * @since MMBase-1.7
182      */

183     public void setInitException(ServletException JavaDoc e) {
184         initException = e;
185     }
186
187     /**
188      * The init of an MMBaseServlet checks if MMBase is running. It not then it is started.
189      */

190     public void init() throws ServletException JavaDoc {
191
192         String JavaDoc retryAfterParameter = getInitParameter("retry-after");
193         if (retryAfterParameter == null) {
194             // default: one minute
195
retryAfter = 60;
196         } else {
197             retryAfter = new Integer JavaDoc(retryAfterParameter).intValue();
198         }
199
200         if (! MMBaseContext.isInitialized()) {
201             ServletContext JavaDoc servletContext = getServletConfig().getServletContext();
202             MMBaseContext.init(servletContext);
203             MMBaseContext.initHtmlRoot();
204         }
205
206         log.info("Init of servlet " + getServletName() + ".");
207         boolean initialize = false;
208         // for retrieving servletmappings, determine status
209
synchronized (servletMappings) {
210             initialize = (servletInstanceCount == 0);
211             servletInstanceCount++;
212         }
213         if (initialize) {
214             // used to determine the accurate way to access a servlet
215
try {
216
217                 MMBaseContext.initHtmlRoot();
218                 // get config and do stuff.
219
java.net.URL JavaDoc url;
220                 try {
221                     url = getServletConfig().getServletContext().getResource("/WEB-INF/web.xml");
222                 } catch (NoSuchMethodError JavaDoc nsme) {
223                     // for old app-servers.
224
log.error(nsme);
225                     url = (new java.io.File JavaDoc(getServletConfig().getServletContext().getRealPath("/WEB-INF/web.xml"))).toURL();
226                 }
227                 if (url == null) {
228                     log.warn("No web.xml found");
229                 } else {
230                     InputSource JavaDoc path = new InputSource JavaDoc(url.openStream());
231                     log.service("Reading servlet mappings from " + url);
232                     DocumentReader webDotXml = new DocumentReader(path, false);
233
234                     for (Iterator mappingsIter = webDotXml.getChildElements("web-app", "servlet-mapping"); mappingsIter.hasNext();) {
235                         Element mapping = (Element) mappingsIter.next();
236                         Element servName = webDotXml.getElementByPath(mapping, "servlet-mapping.servlet-name");
237                         String JavaDoc name = webDotXml.getElementValue(servName);
238                         if (!(name.equals(""))) {
239                             Element urlPattern=webDotXml.getElementByPath(mapping, "servlet-mapping.url-pattern");
240                             String JavaDoc pattern=webDotXml.getElementValue(urlPattern);
241                             if (!(pattern.equals(""))) {
242                                 List ls = (List) servletMappings.get(name);
243                                 if (ls == null) {
244                                     ls = new ArrayList();
245                                     servletMappings.put(name, ls);
246                                 }
247                                 ls.add(pattern);
248                             }
249                         }
250                     }
251                 }
252             } catch (Exception JavaDoc e) {
253                 log.error(e.getMessage() + Logging.stackTrace(e));
254             }
255             log.debug("Loaded servlet mappings");
256         }
257         log.debug("Associating this servlet with functions");
258         Iterator i = getAssociations().entrySet().iterator();
259         while (i.hasNext()) {
260             Map.Entry e = (Map.Entry) i.next();
261             associate((String JavaDoc) e.getKey(), getServletName(), (Integer JavaDoc) e.getValue());
262         }
263         log.debug("Associating this servlet with mappings");
264         i = getServletMappings(getServletConfig().getServletName()).iterator();
265         while (i.hasNext()) {
266             String JavaDoc mapping=(String JavaDoc)i.next();
267             mapToServlet.put(mapping,this);
268         }
269
270         if (initialize) {
271             // stuff that can take indefinite amount of time (database down and so on) is done in separate thread
272
initThread = new MMBaseStartThread(this);
273             initThread.start();
274         }
275     }
276
277     /**
278      * Gets the servlet that belongs to the given mapping
279      *
280      * @param mapping the mapping used to access the servlet
281      * @return the Servlet that handles the mapping
282      */

283     public static HttpServlet JavaDoc getServletByMapping(String JavaDoc mapping) {
284         return (HttpServlet JavaDoc)mapToServlet.get(mapping);
285     }
286
287     /**
288      * Gets all the mappings for a given servlet. So, this is a method to obtain info from web.xml.
289      *
290      * @param servletName the name of the servlet
291      * @return an unmodifiable list of servlet mappings for this servlet
292      */

293     public static List getServletMappings(String JavaDoc servletName) {
294         List ls = (List) servletMappings.get(servletName);
295         if (ls==null) {
296             return Collections.EMPTY_LIST;
297         } else {
298             return Collections.unmodifiableList(ls);
299         }
300     }
301
302     /**
303      * Gets all the mappings for a given association.
304      *
305      * Use this to find out how to call a servlet to handle a certain
306      * type of operation or data (i.e 'images', 'attachments').
307      *
308      *
309      * @param function the function that identifies the type of association
310      * @return an unmodifiable list of servlet mappings associated with the function
311      */

312     public static List getServletMappingsByAssociation(String JavaDoc function) {
313         // check if any mappings were explicitly set for this function
314
// if so, return that list.
315
ServletEntry mapping = (ServletEntry)associatedServletMappings.get(function);
316         if (mapping != null) {
317             List mappings = new ArrayList();
318             mappings.add(mapping.name);
319             return mappings;
320         }
321         // otherwise, get the associated servet
322
String JavaDoc name = getServletByAssociation(function);
323         if (name != null) {
324             return getServletMappings(name);
325         } else {
326             return Collections.EMPTY_LIST;
327         }
328     }
329
330     /**
331      * Gets the name of the servlet that performs actions associated with the
332      * the given function.
333      *
334      * Use this to find a servlet to handle a certain type of
335      * operation or data (i.e 'imageservlet', 'myimageservlet',
336      * 'images');
337      *
338      * @param function the function that identifies the type of association
339      * @return the name of the servlet associated with the function, or null if there is none
340      */

341     public static String JavaDoc getServletByAssociation(String JavaDoc function) {
342         ServletEntry e = ((ServletEntry) associatedServlets.get(function));
343         if (e != null) {
344             return e.name;
345         } else {
346             return null;
347         }
348     }
349
350
351     /**
352      * Associate a given servlet with the given function.
353      * Use this to set a servlet to handle a certain type of operation or data (i.e 'image-processing');
354      * For now, only one servlet can be registered.
355      * @param function the function that deidentifies the type of association
356      * @param servletname name of the servlet to associate with the function
357      * @param priority priority of this association, the association only occurs if no servlet or servletmapping
358      * with higher priority for the same function is present already
359      */

360     private static synchronized void associate(String JavaDoc function, String JavaDoc servletName, Integer JavaDoc priority) {
361         if (priority == null) priority = new Integer JavaDoc(0);
362         ServletEntry m = (ServletEntry) associatedServletMappings.get(function);
363         if (m != null && (priority.intValue() < m.priority)) return;
364         ServletEntry e = (ServletEntry) associatedServlets.get(function);
365         if (e != null && (priority.intValue() < e.priority)) return;
366         log.service("Associating function '" + function + "' with servlet name " + servletName +
367            (e == null ? "" : " (previous assocation was with " + e.name +")")+
368            (m == null ? "" : " (previous assocation was with " + m.name +")"));
369         associatedServlets.put(function, new ServletEntry(servletName, priority));
370         if (m != null) {
371             associatedServletMappings.remove(function);
372         }
373     }
374
375     /**
376      * Associate a given servletmapping with the given function.
377      * Use this to set a servletmapping to call for a certain type of operation or data (i.e 'image-processing');
378      * For now, only one servletmapping can be registered.
379      * @param function the function that identifies the type of association
380      * @param servletMapping mapping of the servlet to associate with the function
381      * @param priority priority of this association, the association only occurs if no servlet or servletmapping
382      * with higher priority for the same function is present already
383      */

384     protected static synchronized void associateMapping(String JavaDoc function, String JavaDoc servletMapping, Integer JavaDoc priority) {
385         if (priority == null) priority = new Integer JavaDoc(0);
386         ServletEntry m = (ServletEntry) associatedServletMappings.get(function);
387         if (m != null && (priority.intValue() < m.priority)) return;
388         ServletEntry e = (ServletEntry) associatedServlets.get(function);
389         if (e != null && (priority.intValue() < e.priority)) return;
390         log.service("Associating function '" + function + "' with servlet mapping " + servletMapping +
391            (e == null ? "" : " (previous assocation was with " + e.name +")")+
392            (m == null ? "" : " (previous assocation was with " + m.name +")"));
393         associatedServletMappings.put(function, new ServletEntry(servletMapping, priority));
394         if (e != null) {
395             associatedServlets.remove(function);
396         }
397     }
398
399     /**
400      * Serves MMBase version information. This doesn't do much usefull
401      * yet, but one could image lots of cool stuff here. Any other
402      * MMBase servlet will probably override this method.
403      */

404     protected void doGet(HttpServletRequest JavaDoc req, HttpServletResponse JavaDoc res) throws ServletException JavaDoc, IOException JavaDoc {
405         res.setContentType("text/plain");
406         PrintWriter JavaDoc pw = res.getWriter();
407         pw.print(org.mmbase.Version.get());
408         String JavaDoc q = req.getQueryString();
409         if ("starttime".equals(q)) {
410             pw.print("\nUp since " + new Date((long) MMBase.startTime * 1000));
411         } else if ("uptime".equals(q)) {
412             int seconds = (int) (System.currentTimeMillis() / 1000) - MMBase.startTime;
413             int days = seconds / (60 * 60 * 24);
414             seconds %= 60 * 60 * 24;
415             int hours = seconds / (60 * 60);
416             seconds %= 60 * 60;
417             int minutes = seconds / 60;
418             seconds %= 60;
419             pw.print("\nUptime: " + (days == 1 ? "1 day" : ( days > 1 ? "" + days + " days" : "")) +
420                      (hours > 0 || days > 0 ? " " + (hours == 1 ? "1 hour" : "" + hours + " hours") : "") +
421                      (minutes > 0 || hours > 0 ? " " + (minutes == 1 ? "1 minute" : "" + minutes + " minutes") : "") +
422                      (seconds > 0 || minutes > 0 ? " " + (seconds == 1 ? "1 second" : "" + seconds + " seconds") : ""));
423
424         } else if ("server".equals(q)) {
425             String JavaDoc appserver = System.getProperty("catalina.base"); // to do: similar arrangment for other ap-servers.
426
pw.print("\n" + getServletContext().getServerInfo() + " " + System.getProperty("java.version") + " (" + System.getProperty("java.vendor") + ") " + (appserver == null ? "" : appserver) + "@" + java.net.InetAddress.getLocalHost().getHostName() + " " + System.getProperty("os.name") + " " + System.getProperty("os.version") + " " + System.getProperty("os.arch"));
427         }
428         pw.close();
429     }
430
431
432     /**
433      * This methods can be (and is) called in the beginning of
434      * service. It sends an UNAVAILABLE error if MMBase has not bee
435      * started, or throws an exeption if that was unsuccessful.
436      * @return A boolean. If false, then service must return immediately (because mmbase has not been inited yet).
437      * @since MMBase-1.7.2
438      */

439     protected boolean checkInited(HttpServletResponse JavaDoc res) throws ServletException JavaDoc, IOException JavaDoc {
440         if (initException != null) {
441             throw initException;
442         }
443
444         if (! mmbaseInited) {
445             res.setHeader("Retry-After", "" + retryAfter);
446             res.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "MMBase not yet, or not successfully initialized (check mmbase log)");
447         }
448         return mmbaseInited;
449     }
450
451
452
453
454     /**
455      * The service method is extended with calls for the refCount
456      * functionality (for performance related debugging). So you can
457      * simply override doGet in extension classes, and this stays
458      * working, without having to think about it.
459      */

460     public void service(HttpServletRequest JavaDoc req, HttpServletResponse JavaDoc res) throws ServletException JavaDoc,IOException JavaDoc {
461         if (!checkInited(res)) {
462             return;
463         }
464         incRefCount(req);
465         try {
466             super.service(req, res);
467         } finally { // whatever happens, decrease the refcount:
468
decRefCount(req);
469         }
470     }
471
472     /**
473      * Returns information about this servlet. Don't forget to override it.
474      */

475
476     public String JavaDoc getServletInfo() {
477         return "Serves MMBase version information";
478     }
479
480
481
482     // ----------------------
483
// functions needed for refcount functionality.
484

485     /**
486      * Return URI with QueryString appended
487      * @param req The HttpServletRequest.
488      */

489     protected static String JavaDoc getRequestURL(HttpServletRequest JavaDoc req) {
490         String JavaDoc result = req.getRequestURI();
491         String JavaDoc queryString = req.getQueryString();
492         if (queryString!=null) result += "?" + queryString;
493         return result;
494     }
495
496
497     /**
498      * Decrease the reference count of the servlet
499      * @param req The HttpServletRequest.
500      */

501
502     protected void decRefCount(HttpServletRequest JavaDoc req) {
503         if (logServlets) {
504             String JavaDoc url = getRequestURL(req) + " " + req.getMethod();
505             synchronized (servletCountLock) {
506                 servletCount--;
507                 ReferenceCountServlet s = (ReferenceCountServlet) runningServlets.get(this);
508                 if (s!=null) {
509                     if (s.refCount == 0) {
510                         runningServlets.remove(this);
511                     } else {
512                         s.refCount--;
513                         int i = s.uris.indexOf(url);
514                         if (i >= 0) s.uris.remove(i);
515                     }
516
517                 }// s!=null
518
}//sync
519
}// if (logServlets)
520
}
521
522     /**
523      * Increase the reference count of the servlet (for debugging)
524      * and send running servlets to log once every 32 requests
525      * @param req The HttpServletRequest.
526      * @scope private
527      * @bad-constant 31 should be configurable.
528      */

529
530     protected void incRefCount(HttpServletRequest JavaDoc req) {
531         if (logServlets) {
532             String JavaDoc url = getRequestURL(req) + " " + req.getMethod();
533             int curCount;
534             synchronized (servletCountLock) {
535                 servletCount++;
536                 curCount = servletCount;
537                 printCount++;
538                 ReferenceCountServlet s = (ReferenceCountServlet) runningServlets.get(this);
539                 if (s==null) {
540                     runningServlets.put(this, new ReferenceCountServlet(this, url, 0));
541                 } else {
542                     s.refCount++;
543                     s.uris.add(url);
544                 }
545             }// sync
546

547             if ((printCount & 31) == 0) { // Why not (printCount % <configurable number>) == 0?
548
if (curCount > 0) {
549                     synchronized(servletCountLock) {
550                         log.info("Running servlets: " + curCount);
551                         for(Iterator e = runningServlets.values().iterator(); e.hasNext();)
552                             log.info(e.next());
553                     }
554
555                 }// curCount>0
556
}
557         }
558     }
559
560     public void destroy() {
561         log.info("Servlet " + getServletName() + " is taken out of service");
562         if (initThread != null) {
563             initThread.interrupt();
564         } else {
565             log.debug(" " + getServletName() + " was not initialized");
566         }
567         log.debug("Disassociating this servlet with mappings");
568         Iterator i = getServletMappings(getServletConfig().getServletName()).iterator();
569         while (i.hasNext()) {
570             String JavaDoc mapping=(String JavaDoc)i.next();
571             mapToServlet.remove(mapping);
572         }
573         super.destroy();
574          // for retrieving servletmappings, determine status
575
synchronized (servletMappings) {
576
577            servletInstanceCount--;
578             if (servletInstanceCount == 0) {
579                 log.info("Unloaded servlet mappings");
580                 associatedServlets.clear();
581                 servletMappings.clear();
582                 log.info("No MMBase servlets left; modules can be shut down");
583                 Module.shutdownModules();
584                 ThreadGroup JavaDoc threads = MMBaseContext.getThreadGroup();
585                 log.service("Send interrupt to " + threads.activeCount() + " threads in " +
586                             threads + " of " + threads.getParent());
587                 threads.interrupt();
588                 Thread.yield();
589                 org.mmbase.util.FileWatcher.shutdown();
590                 org.mmbase.cache.CacheManager.shutdown();
591                 Logging.shutdown();
592                 mmbase = null;
593             }
594         }
595    }
596
597     /**
598      * This class maintains current state information for a running servlet.
599      * It contains a reference count, as well as a list of URI's being handled by the servlet.
600      */

601     private class ReferenceCountServlet {
602         /**
603          * The servlet do debug
604          * @scope private
605          */

606         MMBaseServlet servlet;
607         /**
608          * List of URIs that call the servlet
609          * @scope private
610          */

611         List uris = new ArrayList();
612         /**
613          * Nr. of references
614          * @scope private
615          */

616         int refCount;
617
618         /**
619          * Create a new ReferenceCountServlet using the jamesServlet
620          */

621         ReferenceCountServlet(MMBaseServlet servlet, String JavaDoc uri, int refCount) {
622             this.servlet = servlet;
623             uris.add(uri);
624             this.refCount = refCount;
625         }
626
627         /**
628          * Return a description containing servlet info and URI's
629          */

630         public String JavaDoc toString() {
631             return "servlet("+servlet+"), refcount("+(refCount+1)+"), uri's("+uris+")";
632         }
633     }
634
635
636 }
637
Popular Tags