Quadcap Embeddable Server

com/quadcap/http/server22/WebApplication.java

Go to the documentation of this file.
00001 package com.quadcap.http.server22; 00002 00003 /* Copyright 1997 - 2003 Quadcap Software. All rights reserved. 00004 * 00005 * This software is distributed under the Quadcap Free Software License. 00006 * This software may be used or modified for any purpose, personal or 00007 * commercial. Open Source redistributions are permitted. Commercial 00008 * redistribution of larger works derived from, or works which bundle 00009 * this software requires a "Commercial Redistribution License"; see 00010 * http://www.quadcap.com/purchase. 00011 * 00012 * Redistributions qualify as "Open Source" under one of the following terms: 00013 * 00014 * Redistributions are made at no charge beyond the reasonable cost of 00015 * materials and delivery. 00016 * 00017 * Redistributions are accompanied by a copy of the Source Code or by an 00018 * irrevocable offer to provide a copy of the Source Code for up to three 00019 * years at the cost of materials and delivery. Such redistributions 00020 * must allow further use, modification, and redistribution of the Source 00021 * Code under substantially the same terms as this license. 00022 * 00023 * Redistributions of source code must retain the copyright notices as they 00024 * appear in each source code file, these license terms, and the 00025 * disclaimer/limitation of liability set forth as paragraph 6 below. 00026 * 00027 * Redistributions in binary form must reproduce this Copyright Notice, 00028 * these license terms, and the disclaimer/limitation of liability set 00029 * forth as paragraph 6 below, in the documentation and/or other materials 00030 * provided with the distribution. 00031 * 00032 * The Software is provided on an "AS IS" basis. No warranty is 00033 * provided that the Software is free of defects, or fit for a 00034 * particular purpose. 00035 * 00036 * Limitation of Liability. Quadcap Software shall not be liable 00037 * for any damages suffered by the Licensee or any third party resulting 00038 * from use of the Software. 00039 */ 00040 00041 import java.io.File; 00042 import java.io.InputStream; 00043 import java.io.InputStreamReader; 00044 import java.io.IOException; 00045 import java.io.Reader; 00046 00047 import java.util.ArrayList; 00048 import java.util.Collections; 00049 import java.util.Comparator; 00050 import java.util.Enumeration; 00051 import java.util.HashMap; 00052 import java.util.Hashtable; 00053 import java.util.List; 00054 import java.util.Properties; 00055 import java.util.Vector; 00056 00057 import java.net.URL; 00058 import java.net.MalformedURLException; 00059 00060 import javax.servlet.RequestDispatcher; 00061 import javax.servlet.Servlet; 00062 import javax.servlet.ServletContext; 00063 import javax.servlet.ServletException; 00064 import javax.servlet.ServletRequest; 00065 import javax.servlet.ServletResponse; 00066 00067 import javax.servlet.http.HttpServlet; 00068 import javax.servlet.http.HttpServletRequest; 00069 import javax.servlet.http.HttpSession; 00070 import javax.servlet.http.HttpSessionContext; 00071 00072 import com.quadcap.io.dir.Directory; 00073 import com.quadcap.io.dir.Entry; 00074 00075 import com.quadcap.util.Debug; 00076 import com.quadcap.util.Util; 00077 00078 /** 00079 * This class provides a wrapper for a Web Application, implementing 00080 * the application's ServletContext interface. 00081 * 00082 * @author Stan Bailes 00083 */ 00084 public class WebApplication implements ServletContext { 00085 WebServer server; 00086 WebClassLoader classLoader; 00087 String contextPath; 00088 00089 /** 00090 * Map name -> Servlet 00091 */ 00092 HashMap servlets = new HashMap(); 00093 00094 /** 00095 * Session table: sessionId -> HttpSession 00096 */ 00097 Hashtable sessions = new Hashtable(); 00098 00099 /** 00100 * Servlet context attributes 00101 */ 00102 Hashtable attributes = new Hashtable(); 00103 00104 /** 00105 * Mime types 00106 */ 00107 Hashtable mimeTypes = new Hashtable(); 00108 00109 /** 00110 * Path mappings 00111 */ 00112 Hashtable pathMappings = new Hashtable(); 00113 Hashtable extMappings = new Hashtable(); 00114 Hashtable exactMappings = new Hashtable(); 00115 00116 WebServlet defaultServlet = null; 00117 00118 /** 00119 * Welcome file list 00120 */ 00121 List welcomeFiles = new ArrayList(); 00122 00123 /** 00124 * Error page 00125 */ 00126 String errorPage = null; 00127 00128 /** 00129 * My document root 00130 */ 00131 Directory docRoot; 00132 00133 String displayName = ""; 00134 00135 long sessionCount = 0; 00136 int sessionTimeout = 3600; 00137 00138 Properties initParams = new Properties(); 00139 00140 public WebApplication() {} 00141 00142 public void init(WebServer server, String root, Directory dir) 00143 throws ServletException, IOException 00144 { 00145 //#ifdef DEBUG 00146 if (Trace.level() > 0) { 00147 Debug.println(toString() + ".init(" + root + ", " + 00148 dir.getRootPath() + ")"); 00149 } 00150 //#endif 00151 this.server = server; 00152 this.docRoot = dir; 00153 this.contextPath = root; 00154 00155 File tmp = server.getTempDir(); 00156 File myTmp = new File(tmp, root); 00157 if (!myTmp.isDirectory() && !myTmp.mkdirs()) { 00158 throw new ServletException("Can't create repository: " + myTmp); 00159 } 00160 setAttribute("repository", myTmp); 00161 classLoader = new WebClassLoader(dir, myTmp); 00162 classLoader.init(); 00163 attributes.put("com.quadcap.classLoader", classLoader); 00164 InputStream dis = ClassLoader.getSystemResourceAsStream( 00165 "com/quadcap/http/server22/defaultContext.xml"); 00166 if (dis == null) { 00167 throw new ServletException("Can't load default context"); 00168 } 00169 Reader dfr = new InputStreamReader(dis); 00170 try { 00171 parseDeploymentDescriptor(dfr); 00172 } finally { 00173 dfr.close(); 00174 } 00175 setDisplayName(""); 00176 00177 Entry entry = dir.getEntry("WEB-INF/web.xml"); 00178 if (entry != null) { 00179 Reader ddr = new InputStreamReader(entry.getInputStream()); 00180 try { 00181 parseDeploymentDescriptor(ddr); 00182 } finally { 00183 ddr.close(); 00184 } 00185 } 00186 resolveServlets(pathMappings); 00187 resolveServlets(extMappings); 00188 resolveServlets(exactMappings); 00189 loadInitialServlets(); 00190 } 00191 00192 public void shutdown() { 00193 synchronized (sessions) { 00194 Enumeration e = sessions.keys(); 00195 while (e.hasMoreElements()) { 00196 String sessionId = (String)e.nextElement(); 00197 HSession session = (HSession)getSession(sessionId); 00198 session.invalidate(); 00199 } 00200 } 00201 if (docRoot != null) { 00202 try { 00203 docRoot.close(); 00204 } catch (Throwable t) {} 00205 } 00206 } 00207 00208 final void resolveServlets(Hashtable t) { 00209 Enumeration e = t.keys(); 00210 while (e.hasMoreElements()) { 00211 Object key = e.nextElement(); 00212 Object obj = t.get(key); 00213 if (obj instanceof String) { 00214 Object s = servlets.get(obj); 00215 if (s == null) { 00216 Debug.println("No servlet for mapping: " + obj); 00217 t.remove(key); 00218 } else { 00219 t.put(key, s); 00220 } 00221 } 00222 } 00223 } 00224 00225 void parseDeploymentDescriptor(Reader r) 00226 throws ServletException, IOException { 00227 try { 00228 new DDParser().parse(r, this); 00229 } catch (Exception e) { 00230 throw new ServletException(e); 00231 } finally { 00232 r.close(); 00233 } 00234 } 00235 00236 public void setDisplayName(String name) { 00237 this.displayName = name; 00238 } 00239 00240 public String getDisplayName() { 00241 return displayName; 00242 } 00243 00244 public void addInitParam(String prop, String val) { 00245 initParams.setProperty(prop, val); 00246 } 00247 00248 public Enumeration getInitParameterNames() { 00249 return initParams.keys(); 00250 } 00251 00252 public String getInitParameter(String name) { 00253 return initParams.getProperty(name); 00254 } 00255 00256 public void addWelcomeFile(String s) { 00257 welcomeFiles.add(s.trim()); 00258 } 00259 00260 public List getWelcomeFiles() { 00261 if (welcomeFiles.size() == 0) { 00262 welcomeFiles.add("index.html"); 00263 welcomeFiles.add("index.jsp"); 00264 } 00265 return welcomeFiles; 00266 } 00267 00268 public void addServlet(WebServlet servlet) { 00269 //#ifdef DEBUG 00270 if (Trace.level() > 1) { 00271 Debug.println(toString() + ".addServlet(" + 00272 servlet.getServletName() + ")"); 00273 } 00274 //#endif 00275 servlets.put(servlet.getServletName(), servlet); 00276 } 00277 00278 WebServlet getServletForRequest(HttpDispatcher rd) { 00279 String path = rd.getServletPath(); 00280 00281 WebServlet s = (WebServlet)exactMappings.get(path); 00282 if (s != null) { 00283 rd.setServlet(s, path); 00284 } else { 00285 int lastDot = -1; 00286 for (int i = path.length() - 1; i >= 0; i--) { 00287 char c = path.charAt(i); 00288 if (c == '/') { 00289 String subPath = path.substring(0, i); 00290 s = (WebServlet)pathMappings.get(subPath); 00291 if (s != null) { 00292 rd.setServlet(s, subPath); 00293 rd.setPathInfo(path.substring(i)); 00294 break; 00295 } 00296 } else if (c == '.' && lastDot < 0) { 00297 lastDot = i; 00298 } 00299 } 00300 if (s == null && lastDot >= 0) { 00301 String ext = path.substring(lastDot + 1); 00302 s = (WebServlet)extMappings.get(ext); 00303 if (s != null) { 00304 rd.setServlet(s, path); 00305 } 00306 } 00307 if (s == null) { 00308 s = defaultServlet; 00309 rd.setServlet(s, path); 00310 } 00311 } 00312 //#ifdef DEBUG 00313 if (Trace.level() > 1) { 00314 Debug.println(toString() + ".getServlet(" + path + ") = " + s); 00315 } 00316 //#endif 00317 return s; 00318 } 00319 00320 public void setSessionTimeout(int timeout) { 00321 this.sessionTimeout = timeout; 00322 } 00323 00324 public int getSessionTimeout() { 00325 return sessionTimeout; 00326 } 00327 00328 public void addServletMapping(String servletName, String urlPattern) { 00329 //#ifdef DEBUG 00330 if (Trace.level() > 0) { 00331 Debug.println(this + ".addServletMapping(" + servletName + 00332 ", " + urlPattern + ")"); 00333 } 00334 //#endif 00335 00336 if (urlPattern.equals("*.*")) { 00337 defaultServlet = (WebServlet)servlets.get(servletName); 00338 } else if (urlPattern.endsWith("*")) { 00339 String path = urlPattern.substring(0, urlPattern.length() - 2); 00340 pathMappings.put(path, servletName); 00341 } else if (urlPattern.startsWith("*.")) { 00342 String ext = urlPattern.substring(2); 00343 extMappings.put(ext, servletName); 00344 } else { 00345 exactMappings.put(urlPattern, servletName); 00346 } 00347 } 00348 00349 public void setErrorPage(String errorPage) { 00350 this.errorPage = errorPage; 00351 } 00352 00353 public String getErrorPage() { 00354 return errorPage; 00355 } 00356 00357 public Object getAttribute(String name) { 00358 return attributes.get(name); 00359 } 00360 00361 public Enumeration getAttributeNames() { 00362 return attributes.keys(); 00363 } 00364 00365 public void setAttribute(String name, Object val) { 00366 attributes.put(name, val); 00367 } 00368 00369 public void removeAttribute(String name) { 00370 attributes.remove(name); 00371 } 00372 00373 /** 00374 * Returns the name and version of the network service under which the 00375 * servlet is running. 00376 * 00377 * @return the server information string 00378 */ 00379 public String getServerInfo() { 00380 //> return \"Quadcap Web Server [defined VERSION]\"; 00381 //#autogen begin 00382 return "Quadcap Web Server 3.4"; 00383 //#autogen end 00384 } 00385 00386 /** 00387 * Return the server major version number 00388 */ 00389 public int getMajorVersion() { 00390 return 2; 00391 } 00392 00393 /** 00394 * Return the server minor version number 00395 */ 00396 public int getMinorVersion() { 00397 return 2; 00398 } 00399 00400 public ServletContext getContext(String path) { 00401 return server.getContext(path); 00402 } 00403 00404 /** 00405 * @param path is a string that must begin with '/' and is interpreted 00406 * relative to this context's root 00407 */ 00408 public RequestDispatcher getRequestDispatcher(String path) { 00409 HttpDispatcher rd = new HttpDispatcher(this, path); 00410 WebServlet ws = getServletForRequest(rd); 00411 return (ws != null) ? rd : null; 00412 } 00413 00414 public RequestDispatcher getNamedDispatcher(String name) { 00415 WebServlet ws = (WebServlet)servlets.get(name); 00416 if (ws != null) { 00417 return new HttpDispatcher(ws, name); 00418 } else { 00419 return null; 00420 } 00421 } 00422 00423 public RequestDispatcher getRelativeRequestDispatcher(String path, 00424 HttpRequest base) { 00425 if (path.charAt(0) == '/') { 00426 return getRequestDispatcher(path); 00427 } else { 00428 String spath = base.getServletPath(); 00429 int spos = spath.length() - 1; 00430 while (spos >= 0 && spath.charAt(spos) != '/') spos--; 00431 00432 while (path.startsWith("./") || path.startsWith("../")) { 00433 if (path.startsWith("./")) { 00434 path = path.substring(2); 00435 } else { 00436 if (spos > 0) { 00437 spos--; 00438 while (spos >= 0 && spath.charAt(spos) != '/') spos--; 00439 } else { 00440 return null; 00441 } 00442 } 00443 } 00444 String absPath = spath.substring(0, spos+1) + path; 00445 return getRequestDispatcher(absPath); 00446 } 00447 } 00448 00449 void addMimeMapping(String ext, String type) { 00450 //#ifdef DEBUG 00451 if (Trace.level() > 0) { 00452 Debug.println(toString() + ".addMimeMapping(" + ext + 00453 ", " + type + ")"); 00454 } 00455 //#endif 00456 mimeTypes.put(ext, type); 00457 } 00458 00459 public String getMimeType(String file) { 00460 String type = null; 00461 int lastDot = file.lastIndexOf('.'); 00462 int lastSep = file.lastIndexOf('/'); 00463 if (lastDot != -1 && (lastSep == -1 || lastDot > lastSep)) { 00464 String ext = file.substring(lastDot + 1); 00465 type = (String)mimeTypes.get(ext); 00466 if (type == null) { 00467 type = server.getMimeTypeForExt(ext); 00468 } 00469 } 00470 return type; 00471 } 00472 00473 /** 00474 * If a URI refers to a directory, use the welcome file list to find 00475 * a resource in the directory to serve. Return the name of the file 00476 * if found, otherwise, simply return the original directory name. 00477 */ 00478 String resolveDirectory(String path) { 00479 String ret = path; 00480 String sav = path + "/"; 00481 if (sav.endsWith("//")) { 00482 sav = sav.substring(0, sav.length()-1); 00483 } 00484 Entry e = docRoot.getEntry(path); 00485 if (e != null) { 00486 if (e.isDirectory()) { 00487 e = null; 00488 List w = getWelcomeFiles(); 00489 for (int i = 0; e == null && i < w.size(); i++) { 00490 e = docRoot.getEntry(path = sav + w.get(i)); 00491 } 00492 } 00493 } 00494 if (e != null) ret = path; 00495 return ret; 00496 } 00497 00498 public String getRealPath(String path) { 00499 String rpath = resolveDirectory(path); 00500 String ret = docRoot.getRealPath(rpath); 00501 return ret; 00502 } 00503 00504 public URL getResource(String path) throws MalformedURLException { 00505 return docRoot.getURL(path); 00506 } 00507 00508 public InputStream getResourceAsStream(String path) { 00509 try { 00510 URL url = getResource(path); 00511 if (url != null) { 00512 return url.openStream(); 00513 } 00514 } catch (Throwable t) {} 00515 return null; 00516 } 00517 00518 /** 00519 * Returns the session bound to the specified session ID. 00520 * 00521 * @param sessionID the ID of a particular session object 00522 * @return the session name. Returns null if the session ID does not refer 00523 * to a valid session. 00524 */ 00525 public HSession getSession(String sessionId) { 00526 synchronized (sessions) { 00527 return (HSession)sessions.get(sessionId); 00528 } 00529 } 00530 00531 void removeSession(HSession session) { 00532 //#ifdef DEBUG 00533 if (Trace.level() > 1) { 00534 Debug.println("removeSession(" + session.getId() + ")"); 00535 } 00536 //#endif 00537 synchronized (sessions) { 00538 sessions.remove(session.getId()); 00539 } 00540 } 00541 00542 /** 00543 * Create a new session 00544 */ 00545 public HSession createSession() { 00546 String sessionId = server.makeSessionId(); 00547 //#ifdef DEBUG 00548 if (Trace.level() > 1) { 00549 Debug.println("++++++ createSession(): " + sessionId); 00550 } 00551 //#endif 00552 HSession session = new HSession(this, sessionId, sessionTimeout); 00553 synchronized (sessions) { 00554 sessions.put(sessionId, session); 00555 } 00556 return session; 00557 } 00558 00559 /** 00560 * Reap any expired sessions (sessions not active during the last 00561 * interval) 00562 */ 00563 public void expireSessions() { 00564 synchronized (sessions) { 00565 Enumeration e = sessions.keys(); 00566 long now = System.currentTimeMillis(); 00567 while (e.hasMoreElements()) { 00568 String sessionId = (String)e.nextElement(); 00569 HSession session = (HSession)getSession(sessionId); 00570 if (now - session.getLastAccessedTime() > 00571 (session.getMaxInactiveInterval() * 1000)) { 00572 try { 00573 session.invalidate(); 00574 } catch (Throwable t) { 00575 Debug.print(t); 00576 } 00577 } 00578 } 00579 } 00580 } 00581 00582 /** 00583 * Writes a message to the server's log file. 00584 * 00585 * @param msg the message 00586 */ 00587 public void log(String msg) { 00588 Debug.println(msg); 00589 } 00590 00591 /** 00592 * Write the stack backtrace for the exception and the specified message 00593 * to the server's log file. 00594 * 00595 * @param e the exception 00596 * @param msg the message 00597 * @deprecated 00598 */ 00599 public void log(Exception e, String msg) { 00600 Debug.println(msg); 00601 Debug.print(e); 00602 } 00603 00604 /** 00605 * Write the stack backtrace for the exception and the specified message 00606 * to the server's log file. 00607 * 00608 * @param e the exception 00609 * @param msg the message 00610 */ 00611 public void log(String msg, Throwable e) { 00612 Debug.println(msg); 00613 Debug.print(e); 00614 } 00615 00616 /** 00617 * @deprecated in 2.2 00618 */ 00619 public Servlet getServlet(String name) { 00620 return null; 00621 } 00622 00623 /** 00624 * @deprecated in 2.2 00625 */ 00626 public Enumeration getServletNames() { 00627 return new Vector().elements(); 00628 } 00629 00630 /** 00631 * @deprecated in 2.2 00632 */ 00633 public Enumeration getServlets() { 00634 return new Vector().elements(); 00635 } 00636 00637 public String getContextPath() { 00638 return contextPath; 00639 } 00640 00641 /** 00642 * Return the classpath for the context's libraries and classes 00643 * under WEB-INF. 00644 */ 00645 public String getContextClassPath() { 00646 return classLoader.getClassPath(); 00647 } 00648 00649 final ClassLoader getClassLoader() { 00650 return classLoader; 00651 } 00652 00653 final void loadInitialServlets() throws ServletException { 00654 ServletException ex = null; 00655 00656 ArrayList l = new ArrayList(); 00657 l.addAll(servlets.values()); 00658 Collections.sort(l, new Comparator() { 00659 public int compare(Object a, Object b) { 00660 WebServlet wa = (WebServlet)a; 00661 WebServlet wb = (WebServlet)b; 00662 if (wa.getLoadOnStartup() < wb.getLoadOnStartup()) return -1; 00663 if (wa.getLoadOnStartup() > wb.getLoadOnStartup()) return 1; 00664 return 0; 00665 } 00666 }); 00667 for (int i = 0; i < l.size(); i++) { 00668 WebServlet w = (WebServlet)l.get(i); 00669 if (w.getLoadOnStartup() >= 0) { 00670 try { 00671 w.init(); 00672 } catch (ServletException e) { 00673 ex = e; 00674 Debug.print(e); 00675 } 00676 } 00677 } 00678 00679 if (ex != null) throw ex; 00680 } 00681 00682 public WebServer getWebServer() { return server; } 00683 00684 public String getRootPath() { return docRoot.getRootPath(); } 00685 00686 public boolean isDirectory(String d) { 00687 try { 00688 return docRoot.getEntry(d).isDirectory(); 00689 } catch (Throwable t) { 00690 return false; 00691 } 00692 } 00693 00694 //#ifdef DEBUG 00695 public String toString() { 00696 return "[" + contextPath + "]"; 00697 } 00698 //#endif 00699 }