1 package nginx.unit; 2 3 import io.github.classgraph.ClassGraph; 4 import io.github.classgraph.ClassInfo; 5 import io.github.classgraph.ClassInfoList; 6 import io.github.classgraph.ScanResult; 7 8 import java.io.File; 9 import java.io.FileInputStream; 10 import java.io.FileNotFoundException; 11 import java.io.IOException; 12 import java.io.InputStream; 13 import java.io.PrintWriter; 14 15 import java.lang.ClassLoader; 16 import java.lang.ClassNotFoundException; 17 import java.lang.IllegalArgumentException; 18 import java.lang.IllegalStateException; 19 import java.lang.reflect.Constructor; 20 21 import java.net.MalformedURLException; 22 import java.net.URI; 23 import java.net.URISyntaxException; 24 import java.net.URL; 25 import java.net.URLClassLoader; 26 27 import java.nio.file.FileVisitResult; 28 import java.nio.file.Files; 29 import java.nio.file.Path; 30 import java.nio.file.Paths; 31 import java.nio.file.SimpleFileVisitor; 32 import java.nio.file.StandardCopyOption; 33 import java.nio.file.attribute.BasicFileAttributes; 34 35 import java.util.ArrayList; 36 import java.util.Arrays; 37 import java.util.Collection; 38 import java.util.Collections; 39 import java.util.EnumSet; 40 import java.util.Enumeration; 41 import java.util.EventListener; 42 import java.util.HashMap; 43 import java.util.HashSet; 44 import java.util.List; 45 import java.util.Map; 46 import java.util.ServiceLoader; 47 import java.util.Set; 48 import java.util.UUID; 49 import java.util.jar.JarEntry; 50 import java.util.jar.JarFile; 51 import java.util.jar.JarInputStream; 52 import java.util.zip.ZipEntry; 53 54 import javax.servlet.DispatcherType; 55 import javax.servlet.Filter; 56 import javax.servlet.FilterChain; 57 import javax.servlet.FilterConfig; 58 import javax.servlet.FilterRegistration.Dynamic; 59 import javax.servlet.FilterRegistration; 60 import javax.servlet.MultipartConfigElement; 61 import javax.servlet.Registration; 62 import javax.servlet.RequestDispatcher; 63 import javax.servlet.Servlet; 64 import javax.servlet.ServletConfig; 65 import javax.servlet.ServletContainerInitializer; 66 import javax.servlet.ServletContext; 67 import javax.servlet.ServletContextAttributeEvent; 68 import javax.servlet.ServletContextAttributeListener; 69 import javax.servlet.ServletContextEvent; 70 import javax.servlet.ServletContextListener; 71 import javax.servlet.ServletException; 72 import javax.servlet.ServletOutputStream; 73 import javax.servlet.ServletRegistration; 74 import javax.servlet.ServletResponse; 75 import javax.servlet.ServletRequest; 76 import javax.servlet.ServletRequestAttributeEvent; 77 import javax.servlet.ServletRequestAttributeListener; 78 import javax.servlet.ServletRequestEvent; 79 import javax.servlet.ServletRequestListener; 80 import javax.servlet.ServletSecurityElement; 81 import javax.servlet.SessionCookieConfig; 82 import javax.servlet.SessionTrackingMode; 83 import javax.servlet.annotation.HandlesTypes; 84 import javax.servlet.annotation.MultipartConfig; 85 import javax.servlet.annotation.WebInitParam; 86 import javax.servlet.annotation.WebServlet; 87 import javax.servlet.annotation.WebFilter; 88 import javax.servlet.annotation.WebListener; 89 import javax.servlet.descriptor.JspConfigDescriptor; 90 import javax.servlet.descriptor.JspPropertyGroupDescriptor; 91 import javax.servlet.descriptor.TaglibDescriptor; 92 import javax.servlet.http.HttpServlet; 93 import javax.servlet.http.HttpServletRequest; 94 import javax.servlet.http.HttpServletResponse; 95 import javax.servlet.http.HttpSessionAttributeListener; 96 import javax.servlet.http.HttpSessionBindingEvent; 97 import javax.servlet.http.HttpSessionEvent; 98 import javax.servlet.http.HttpSessionIdListener; 99 import javax.servlet.http.HttpSessionListener; 100 101 import javax.websocket.server.ServerEndpoint; 102 103 import javax.xml.parsers.DocumentBuilder; 104 import javax.xml.parsers.DocumentBuilderFactory; 105 import javax.xml.parsers.ParserConfigurationException; 106 107 import nginx.unit.websocket.WsSession; 108 109 import org.eclipse.jetty.http.MimeTypes; 110 111 import org.w3c.dom.Document; 112 import org.w3c.dom.Element; 113 import org.w3c.dom.Node; 114 import org.w3c.dom.NodeList; 115 import org.xml.sax.SAXException; 116 117 import org.apache.jasper.servlet.JspServlet; 118 import org.apache.jasper.servlet.JasperInitializer; 119 120 121 public class Context implements ServletContext, InitParams 122 { 123 public final static int SERVLET_MAJOR_VERSION = 3; 124 public final static int SERVLET_MINOR_VERSION = 1; 125 126 private String context_path_ = ""; 127 private String server_info_ = "unit"; 128 private String app_version_ = ""; 129 private MimeTypes mime_types_; 130 private boolean metadata_complete_ = false; 131 private boolean welcome_files_list_found_ = false; 132 private boolean ctx_initialized_ = false; 133 134 private ClassLoader loader_; 135 private File webapp_; 136 private File extracted_dir_; 137 private File temp_dir_; 138 139 private final Map<String, String> init_params_ = new HashMap<>(); 140 private final Map<String, Object> attributes_ = new HashMap<>(); 141 142 private final Map<String, URLPattern> parsed_patterns_ = new HashMap<>(); 143 144 private final List<FilterReg> filters_ = new ArrayList<>(); 145 private final Map<String, FilterReg> name2filter_ = new HashMap<>(); 146 private final List<FilterMap> filter_maps_ = new ArrayList<>(); 147 148 private final List<ServletReg> servlets_ = new ArrayList<>(); 149 private final Map<String, ServletReg> name2servlet_ = new HashMap<>(); 150 private final Map<String, ServletReg> pattern2servlet_ = new HashMap<>(); 151 private final Map<String, ServletReg> exact2servlet_ = new HashMap<>(); 152 private final List<PrefixPattern> prefix_patterns_ = new ArrayList<>(); 153 private final Map<String, ServletReg> suffix2servlet_ = new HashMap<>(); 154 private ServletReg default_servlet_; 155 private ServletReg system_default_servlet_; 156 157 private final List<String> welcome_files_ = new ArrayList<>(); 158 159 private final Map<String, String> exception2location_ = new HashMap<>(); 160 private final Map<Integer, String> error2location_ = new HashMap<>(); 161 162 public static final Class<?>[] LISTENER_TYPES = new Class[] { 163 ServletContextListener.class, 164 ServletContextAttributeListener.class, 165 ServletRequestListener.class, 166 ServletRequestAttributeListener.class, 167 HttpSessionAttributeListener.class, 168 HttpSessionIdListener.class, 169 HttpSessionListener.class 170 }; 171 172 private final List<String> pending_listener_classnames_ = new ArrayList<>(); 173 private final Set<String> listener_classnames_ = new HashSet<>(); 174 175 private final List<ServletContextListener> ctx_listeners_ = new ArrayList<>(); 176 private final List<ServletContextListener> destroy_listeners_ = new ArrayList<>(); 177 private final List<ServletContextAttributeListener> ctx_attr_listeners_ = new ArrayList<>(); 178 private final List<ServletRequestListener> req_init_listeners_ = new ArrayList<>(); 179 private final List<ServletRequestListener> req_destroy_listeners_ = new ArrayList<>(); 180 private final List<ServletRequestAttributeListener> req_attr_listeners_ = new ArrayList<>(); 181 182 private ServletRequestAttributeListener req_attr_proxy_ = null; 183 184 private final List<HttpSessionAttributeListener> sess_attr_listeners_ = new ArrayList<>(); 185 private final List<HttpSessionIdListener> sess_id_listeners_ = new ArrayList<>(); 186 private final List<HttpSessionListener> sess_listeners_ = new ArrayList<>(); 187 188 private HttpSessionAttributeListener sess_attr_proxy_ = null; 189 190 private final SessionCookieConfig session_cookie_config_ = new UnitSessionCookieConfig(); 191 private final Set<SessionTrackingMode> default_session_tracking_modes_ = new HashSet<>(); 192 private Set<SessionTrackingMode> session_tracking_modes_ = default_session_tracking_modes_; 193 private int session_timeout_ = 60; 194 195 private final Map<String, Session> sessions_ = new HashMap<>(); 196 197 private static final String WEB_INF = "WEB-INF/"; 198 private static final String WEB_INF_CLASSES = WEB_INF + "classes/"; 199 private static final String WEB_INF_LIB = WEB_INF + "lib/"; 200 201 private class PrefixPattern implements Comparable<PrefixPattern> 202 { 203 public final String pattern; 204 public final ServletReg servlet; 205 PrefixPattern(String p, ServletReg s)206 public PrefixPattern(String p, ServletReg s) 207 { 208 pattern = p; 209 servlet = s; 210 } 211 match(String url)212 public boolean match(String url) 213 { 214 return url.startsWith(pattern) && ( 215 url.length() == pattern.length() 216 || url.charAt(pattern.length()) == '/'); 217 } 218 219 @Override compareTo(PrefixPattern p)220 public int compareTo(PrefixPattern p) 221 { 222 return p.pattern.length() - pattern.length(); 223 } 224 } 225 226 private class StaticServlet extends HttpServlet 227 { 228 @Override doPost(HttpServletRequest request, HttpServletResponse response)229 public void doPost(HttpServletRequest request, HttpServletResponse response) 230 throws IOException, ServletException 231 { 232 doGet(request, response); 233 } 234 235 @Override doGet(HttpServletRequest request, HttpServletResponse response)236 public void doGet(HttpServletRequest request, HttpServletResponse response) 237 throws IOException, ServletException 238 { 239 String path = null; 240 241 if (request.getDispatcherType() == DispatcherType.INCLUDE) { 242 path = (String) request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH); 243 } 244 245 if (path == null) { 246 path = request.getServletPath(); 247 } 248 249 /* 250 10.6 Web Application Archive File 251 ... 252 This directory [META-INF] must not be directly served as 253 content by the container in response to a Web client's request, 254 though its contents are visible to servlet code via the 255 getResource and getResourceAsStream calls on the 256 ServletContext. Also, any requests to access the resources in 257 META-INF directory must be returned with a SC_NOT_FOUND(404) 258 response. 259 */ 260 if (request.getDispatcherType() == DispatcherType.REQUEST 261 && (path.equals("/WEB-INF") || path.startsWith("/WEB-INF/") 262 || path.equals("/META-INF") || path.startsWith("/META-INF/"))) 263 { 264 response.sendError(response.SC_NOT_FOUND); 265 return; 266 } 267 268 if (path.startsWith("/")) { 269 path = path.substring(1); 270 } 271 272 File f = new File(webapp_, path); 273 if (!f.exists()) { 274 if (request.getDispatcherType() == DispatcherType.INCLUDE) { 275 /* 276 9.3 The Include Method 277 ... 278 If the default servlet is the target of a 279 RequestDispatch.include() and the requested resource 280 does not exist, then the default servlet MUST throw 281 FileNotFoundException. 282 */ 283 284 throw new FileNotFoundException(); 285 } 286 287 response.sendError(response.SC_NOT_FOUND); 288 return; 289 } 290 291 long ims = request.getDateHeader("If-Modified-Since"); 292 long lm = f.lastModified(); 293 294 if (lm < ims) { 295 response.sendError(response.SC_NOT_MODIFIED); 296 return; 297 } 298 299 response.setDateHeader("Last-Modified", f.lastModified()); 300 301 if (f.isDirectory()) { 302 String url = request.getRequestURL().toString(); 303 if (!url.endsWith("/")) { 304 response.setHeader("Location", url + "/"); 305 response.sendError(response.SC_FOUND); 306 return; 307 } 308 309 String[] ls = f.list(); 310 311 PrintWriter writer = response.getWriter(); 312 313 for (String n : ls) { 314 writer.println("<a href=\"" + n + "\">" + n + "</a><br>"); 315 } 316 317 writer.close(); 318 319 } else { 320 response.setContentLengthLong(f.length()); 321 response.setContentType(getMimeType(f.getName())); 322 323 InputStream is = new FileInputStream(f); 324 byte[] buffer = new byte[response.getBufferSize()]; 325 ServletOutputStream os = response.getOutputStream(); 326 while (true) { 327 int read = is.read(buffer); 328 if (read == -1) { 329 break; 330 } 331 os.write(buffer, 0, read); 332 } 333 334 os.close(); 335 } 336 } 337 } 338 start(String webapp, URL[] classpaths)339 public static Context start(String webapp, URL[] classpaths) 340 throws Exception 341 { 342 Context ctx = new Context(); 343 344 ctx.loadApp(webapp, classpaths); 345 ctx.initialized(); 346 347 return ctx; 348 } 349 Context()350 public Context() 351 { 352 default_session_tracking_modes_.add(SessionTrackingMode.COOKIE); 353 354 context_path_ = System.getProperty("nginx.unit.context.path", "").trim(); 355 356 if (context_path_.endsWith("/")) { 357 context_path_ = context_path_.substring(0, context_path_.length() - 1); 358 } 359 360 if (!context_path_.isEmpty() && !context_path_.startsWith("/")) { 361 context_path_ = "/" + context_path_; 362 } 363 364 if (context_path_.isEmpty()) { 365 session_cookie_config_.setPath("/"); 366 } else { 367 session_cookie_config_.setPath(context_path_); 368 } 369 } 370 loadApp(String webapp, URL[] classpaths)371 public void loadApp(String webapp, URL[] classpaths) 372 throws Exception 373 { 374 File root = new File(webapp); 375 if (!root.exists()) { 376 throw new FileNotFoundException( 377 "Unable to determine code source archive from " + root); 378 } 379 380 ArrayList<URL> url_list = new ArrayList<>(); 381 382 for (URL u : classpaths) { 383 url_list.add(u); 384 } 385 386 if (!root.isDirectory()) { 387 root = extractWar(root); 388 extracted_dir_ = root; 389 } 390 391 webapp_ = root; 392 393 Path tmpDir = Files.createTempDirectory("webapp"); 394 temp_dir_ = tmpDir.toFile(); 395 setAttribute(ServletContext.TEMPDIR, temp_dir_); 396 397 File web_inf_classes = new File(root, WEB_INF_CLASSES); 398 if (web_inf_classes.exists() && web_inf_classes.isDirectory()) { 399 url_list.add(new URL("file:" + root.getAbsolutePath() + "/" + WEB_INF_CLASSES)); 400 } 401 402 File lib = new File(root, WEB_INF_LIB); 403 File[] libs = lib.listFiles(); 404 405 if (libs != null) { 406 for (File l : libs) { 407 url_list.add(new URL("file:" + l.getAbsolutePath())); 408 } 409 } 410 411 URL[] urls = new URL[url_list.size()]; 412 413 for (int i = 0; i < url_list.size(); i++) { 414 urls[i] = url_list.get(i); 415 trace("archives: " + urls[i]); 416 } 417 418 String custom_listener = System.getProperty("nginx.unit.context.listener", "").trim(); 419 if (!custom_listener.isEmpty()) { 420 pending_listener_classnames_.add(custom_listener); 421 } 422 423 processWebXml(root); 424 425 loader_ = new UnitClassLoader(urls, 426 Context.class.getClassLoader().getParent()); 427 428 Class wsSession_class = WsSession.class; 429 trace("wsSession.test: " + WsSession.wsSession_test()); 430 431 ClassLoader old = Thread.currentThread().getContextClassLoader(); 432 Thread.currentThread().setContextClassLoader(loader_); 433 434 try { 435 for (String listener_classname : pending_listener_classnames_) { 436 addListener(listener_classname); 437 } 438 439 ClassGraph classgraph = new ClassGraph() 440 //.verbose() 441 .overrideClassLoaders(loader_) 442 .ignoreParentClassLoaders() 443 .enableClassInfo() 444 .enableAnnotationInfo() 445 //.enableSystemPackages() 446 .acceptModules("javax.*") 447 //.enableAllInfo() 448 ; 449 450 String verbose = System.getProperty("nginx.unit.context.classgraph.verbose", "").trim(); 451 452 if (verbose.equals("true")) { 453 classgraph.verbose(); 454 } 455 456 ScanResult scan_res = classgraph.scan(); 457 458 javax.websocket.server.ServerEndpointConfig.Configurator.setDefault(new nginx.unit.websocket.server.DefaultServerEndpointConfigurator()); 459 460 loadInitializer(new nginx.unit.websocket.server.WsSci(), scan_res); 461 462 if (!metadata_complete_) { 463 loadInitializers(scan_res); 464 } 465 466 if (!metadata_complete_) { 467 scanClasses(scan_res); 468 } 469 470 /* 471 8.1.6 Other annotations / conventions 472 ... 473 By default all applications will have index.htm(l) and index.jsp 474 in the list of welcome-file-list. The descriptor may to be used 475 to override these default settings. 476 */ 477 if (!welcome_files_list_found_) { 478 welcome_files_.add("index.htm"); 479 welcome_files_.add("index.html"); 480 welcome_files_.add("index.jsp"); 481 } 482 483 ServletReg jsp_servlet = name2servlet_.get("jsp"); 484 if (jsp_servlet == null) { 485 jsp_servlet = new ServletReg("jsp", JspServlet.class); 486 jsp_servlet.system_jsp_servlet_ = true; 487 servlets_.add(jsp_servlet); 488 name2servlet_.put("jsp", jsp_servlet); 489 } 490 491 if (jsp_servlet.getClassName() == null) { 492 jsp_servlet.setClass(JspServlet.class); 493 jsp_servlet.system_jsp_servlet_ = true; 494 } 495 496 if (jsp_servlet.patterns_.isEmpty()) { 497 parseURLPattern("*.jsp", jsp_servlet); 498 parseURLPattern("*.jspx", jsp_servlet); 499 } 500 501 ServletReg def_servlet = name2servlet_.get("default"); 502 if (def_servlet == null) { 503 def_servlet = new ServletReg("default", new StaticServlet()); 504 def_servlet.servlet_ = new StaticServlet(); 505 servlets_.add(def_servlet); 506 name2servlet_.put("default", def_servlet); 507 } 508 509 if (def_servlet.getClassName() == null) { 510 def_servlet.setClass(StaticServlet.class); 511 def_servlet.servlet_ = new StaticServlet(); 512 } 513 514 system_default_servlet_ = def_servlet; 515 516 for (PrefixPattern p : prefix_patterns_) { 517 /* 518 Optimization: add prefix patterns to exact2servlet_ map. 519 This should not affect matching result because full path 520 is the longest matched prefix. 521 */ 522 if (!exact2servlet_.containsKey(p.pattern)) { 523 trace("adding prefix pattern " + p.pattern + " to exact patterns map"); 524 exact2servlet_.put(p.pattern, p.servlet); 525 } 526 } 527 528 Collections.sort(prefix_patterns_); 529 } finally { 530 Thread.currentThread().setContextClassLoader(old); 531 } 532 } 533 534 private static class UnitClassLoader extends URLClassLoader 535 { 536 static { ClassLoader.registerAsParallelCapable()537 ClassLoader.registerAsParallelCapable(); 538 } 539 540 private final static String[] system_prefix = 541 { 542 "java/", // Java SE classes (per servlet spec v2.5 / SRV.9.7.2) 543 "javax/", // Java SE classes (per servlet spec v2.5 / SRV.9.7.2) 544 "org/w3c/", // needed by javax.xml 545 "org/xml/", // needed by javax.xml 546 }; 547 548 private ClassLoader system_loader; 549 UnitClassLoader(URL[] urls, ClassLoader parent)550 public UnitClassLoader(URL[] urls, ClassLoader parent) 551 { 552 super(urls, parent); 553 554 ClassLoader j = String.class.getClassLoader(); 555 if (j == null) { 556 j = getSystemClassLoader(); 557 while (j.getParent() != null) { 558 j = j.getParent(); 559 } 560 } 561 system_loader = j; 562 } 563 isSystemPath(String path)564 private boolean isSystemPath(String path) 565 { 566 int i = Arrays.binarySearch(system_prefix, path); 567 568 if (i >= 0) { 569 return true; 570 } 571 572 i = -i - 1; 573 574 if (i > 0) { 575 return path.startsWith(system_prefix[i - 1]); 576 } 577 578 return false; 579 } 580 581 @Override getResource(String name)582 public URL getResource(String name) 583 { 584 URL res; 585 586 String s = "getResource: " + name; 587 trace(0, s, s.length()); 588 589 /* 590 This is a required for compatibility with Tomcat which 591 stores all resources prefixed with '/' and application code 592 may try to get resource with leading '/' (like Jira). Jetty 593 also has such workaround in WebAppClassLoader.getResource(). 594 */ 595 if (name.startsWith("/")) { 596 name = name.substring(1); 597 } 598 599 if (isSystemPath(name)) { 600 return super.getResource(name); 601 } 602 603 res = system_loader.getResource(name); 604 if (res != null) { 605 return res; 606 } 607 608 res = findResource(name); 609 if (res != null) { 610 return res; 611 } 612 613 return super.getResource(name); 614 } 615 616 @Override loadClass(String name, boolean resolve)617 protected Class<?> loadClass(String name, boolean resolve) 618 throws ClassNotFoundException 619 { 620 synchronized (this) { 621 Class<?> res = findLoadedClass(name); 622 if (res != null) { 623 return res; 624 } 625 626 try { 627 res = system_loader.loadClass(name); 628 629 if (resolve) { 630 resolveClass(res); 631 } 632 633 return res; 634 } catch (ClassNotFoundException e) { 635 } 636 637 String path = name.replace('.', '/').concat(".class"); 638 639 if (isSystemPath(path)) { 640 return super.loadClass(name, resolve); 641 } 642 643 URL url = findResource(path); 644 645 if (url != null) { 646 res = super.findClass(name); 647 648 if (resolve) { 649 resolveClass(res); 650 } 651 652 return res; 653 } 654 655 return super.loadClass(name, resolve); 656 } 657 658 } 659 } 660 extractWar(File war)661 private File extractWar(File war) throws IOException 662 { 663 Path tmpDir = Files.createTempDirectory("webapp"); 664 665 JarFile jf = new JarFile(war); 666 667 for (Enumeration<JarEntry> en = jf.entries(); en.hasMoreElements();) { 668 JarEntry e = en.nextElement(); 669 long mod_time = e.getTime(); 670 Path ep = tmpDir.resolve(e.getName()); 671 Path p; 672 if (e.isDirectory()) { 673 p = ep; 674 } else { 675 p = ep.getParent(); 676 } 677 678 if (!p.toFile().isDirectory()) { 679 Files.createDirectories(p); 680 } 681 682 if (!e.isDirectory()) { 683 Files.copy(jf.getInputStream(e), ep, 684 StandardCopyOption.REPLACE_EXISTING); 685 } 686 687 if (mod_time > 0) { 688 ep.toFile().setLastModified(mod_time); 689 } 690 } 691 692 return tmpDir.toFile(); 693 } 694 695 private class CtxFilterChain implements FilterChain 696 { 697 private int filter_index_ = 0; 698 private final ServletReg servlet_; 699 private final List<FilterReg> filters_; 700 CtxFilterChain(ServletReg servlet, String path, DispatcherType dtype)701 CtxFilterChain(ServletReg servlet, String path, DispatcherType dtype) 702 { 703 servlet_ = servlet; 704 705 List<FilterReg> filters = new ArrayList<>(); 706 707 for (FilterMap m : filter_maps_) { 708 if (filters.indexOf(m.filter_) != -1) { 709 continue; 710 } 711 712 if (!m.dtypes_.contains(dtype)) { 713 continue; 714 } 715 716 if (m.pattern_.match(path)) { 717 filters.add(m.filter_); 718 719 trace("add filter (matched): " + m.filter_.getName()); 720 } 721 } 722 723 for (FilterMap m : servlet.filters_) { 724 if (filters.indexOf(m.filter_) != -1) { 725 continue; 726 } 727 728 if (!m.dtypes_.contains(dtype)) { 729 continue; 730 } 731 732 filters.add(m.filter_); 733 734 trace("add filter (servlet): " + m.filter_.getName()); 735 } 736 737 filters_ = filters; 738 } 739 740 @Override doFilter(ServletRequest request, ServletResponse response)741 public void doFilter (ServletRequest request, ServletResponse response) 742 throws IOException, ServletException 743 { 744 if (filter_index_ < filters_.size()) { 745 filters_.get(filter_index_++).filter_.doFilter(request, response, this); 746 747 return; 748 } 749 750 servlet_.service(request, response); 751 } 752 } 753 findServlet(String path, DynamicPathRequest req)754 private ServletReg findServlet(String path, DynamicPathRequest req) 755 { 756 /* 757 12.1 Use of URL Paths 758 ... 759 1. The container will try to find an exact match of the path of the 760 request to the path of the servlet. A successful match selects 761 the servlet. 762 */ 763 ServletReg servlet = exact2servlet_.get(path); 764 if (servlet != null) { 765 trace("findServlet: '" + path + "' exact matched pattern"); 766 req.setServletPath(path, null); 767 return servlet; 768 } 769 770 /* 771 2. The container will recursively try to match the longest 772 path-prefix. This is done by stepping down the path tree a 773 directory at a time, using the '/' character as a path separator. 774 The longest match determines the servlet selected. 775 */ 776 for (PrefixPattern p : prefix_patterns_) { 777 if (p.match(path)) { 778 trace("findServlet: '" + path + "' matched prefix pattern '" + p.pattern + "'"); 779 if (p.pattern.length() == path.length()) { 780 log("findServlet: WARNING: it is expected '" + path + "' exactly matches " + p.pattern); 781 req.setServletPath(path, p.pattern, null); 782 } else { 783 req.setServletPath(path, p.pattern, path.substring(p.pattern.length())); 784 } 785 return p.servlet; 786 } 787 } 788 789 /* 790 3. If the last segment in the URL path contains an extension 791 (e.g. .jsp), the servlet container will try to match a servlet 792 that handles requests for the extension. An extension is defined 793 as the part of the last segment after the last '.' character. 794 */ 795 int suffix_start = path.lastIndexOf('.'); 796 if (suffix_start != -1) { 797 String suffix = path.substring(suffix_start); 798 servlet = suffix2servlet_.get(suffix); 799 if (servlet != null) { 800 trace("findServlet: '" + path + "' matched suffix pattern"); 801 req.setServletPath(path, null); 802 return servlet; 803 } 804 } 805 806 /* 807 4. If neither of the previous three rules result in a servlet match, 808 the container will attempt to serve content appropriate for the 809 resource requested. If a "default" servlet is defined for the 810 application, it will be used. ... 811 */ 812 if (default_servlet_ != null) { 813 trace("findServlet: '" + path + "' matched default servlet"); 814 req.setServletPath(path, null); 815 return default_servlet_; 816 } 817 818 trace("findServlet: '" + path + "' no servlet found"); 819 820 /* 821 10.10 Welcome Files 822 ... 823 If a Web container receives a valid partial request, the Web 824 container must examine the welcome file list defined in the 825 deployment descriptor. 826 ... 827 */ 828 if (path.endsWith("/")) { 829 830 /* 831 The Web server must append each welcome file in the order 832 specified in the deployment descriptor to the partial request 833 and check whether a static resource in the WAR is mapped to 834 that request URI. 835 */ 836 for (String wf : welcome_files_) { 837 String wpath = path + wf; 838 839 File f = new File(webapp_, wpath.substring(1)); 840 if (!f.exists()) { 841 continue; 842 } 843 844 trace("findServlet: '" + path + "' found static welcome " 845 + "file '" + wf + "'"); 846 847 /* 848 Even if static file found, we should try to find matching 849 servlet for JSP serving etc. 850 */ 851 servlet = findWelcomeServlet(wpath, true, req); 852 if (servlet != null) { 853 return servlet; 854 } 855 856 req.setServletPath(wpath, null); 857 858 return system_default_servlet_; 859 } 860 861 /* 862 If no match is found, the Web server MUST again append each 863 welcome file in the order specified in the deployment 864 descriptor to the partial request and check if a servlet is 865 mapped to that request URI. The Web container must send the 866 request to the first resource in the WAR that matches. 867 */ 868 for (String wf : welcome_files_) { 869 String wpath = path + wf; 870 871 servlet = findWelcomeServlet(wpath, false, req); 872 if (servlet != null) { 873 return servlet; 874 } 875 } 876 } 877 878 trace("findServlet: '" + path + "' fallback to system default servlet"); 879 req.setServletPath(path, null); 880 881 return system_default_servlet_; 882 } 883 findWelcomeServlet(String path, boolean exists, DynamicPathRequest req)884 private ServletReg findWelcomeServlet(String path, boolean exists, 885 DynamicPathRequest req) 886 { 887 ServletReg servlet = exact2servlet_.get(path); 888 if (servlet != null) { 889 trace("findWelcomeServlet: '" + path + "' exact matched pattern"); 890 req.setServletPath(path, null); 891 892 return servlet; 893 } 894 895 int suffix_start = path.lastIndexOf('.'); 896 if (suffix_start == -1) { 897 return null; 898 } 899 900 String suffix = path.substring(suffix_start); 901 servlet = suffix2servlet_.get(suffix); 902 if (servlet == null) { 903 return null; 904 } 905 906 trace("findWelcomeServlet: '" + path + "' matched suffix pattern"); 907 908 /* 909 If we want to show the directory content when 910 index.jsp is absent, then we have to check file 911 presence here. Otherwise user will get 404. 912 */ 913 914 if (servlet.system_jsp_servlet_ && !exists) { 915 trace("findWelcomeServlet: '" + path + "' not exists"); 916 return null; 917 } 918 919 req.setServletPath(path, null); 920 921 return servlet; 922 } 923 service(Request req, Response resp)924 public void service(Request req, Response resp) 925 throws ServletException, IOException 926 { 927 ClassLoader old = Thread.currentThread().getContextClassLoader(); 928 Thread.currentThread().setContextClassLoader(loader_); 929 930 ServletRequestEvent sre = null; 931 932 try { 933 if (!req_init_listeners_.isEmpty()) { 934 sre = new ServletRequestEvent(this, req); 935 936 for (ServletRequestListener l : req_init_listeners_) { 937 l.requestInitialized(sre); 938 } 939 } 940 941 URI uri = new URI(req.getRequestURI()); 942 String path = uri.getPath(); 943 944 if (!path.startsWith(context_path_) 945 || (path.length() > context_path_.length() 946 && path.charAt(context_path_.length()) != '/')) 947 { 948 trace("service: '" + path + "' not started with '" + context_path_ + "'"); 949 950 resp.sendError(resp.SC_NOT_FOUND); 951 return; 952 } 953 954 if (path.equals(context_path_)) { 955 String url = req.getRequestURL().toString(); 956 if (!url.endsWith("/")) { 957 resp.setHeader("Location", url + "/"); 958 resp.sendError(resp.SC_FOUND); 959 return; 960 } 961 } 962 963 path = path.substring(context_path_.length()); 964 965 ServletReg servlet = findServlet(path, req); 966 967 req.setMultipartConfig(servlet.multipart_config_); 968 969 FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.REQUEST); 970 971 fc.doFilter(req, resp); 972 973 Object code = req.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); 974 if (code != null && code instanceof Integer) { 975 handleStatusCode((Integer) code, req, resp); 976 } 977 } catch (Throwable e) { 978 trace("service: caught " + e); 979 980 try { 981 if (!resp.isCommitted() && !exception2location_.isEmpty()) { 982 handleException(e, req, resp); 983 } 984 985 if (!resp.isCommitted()) { 986 resp.reset(); 987 resp.setStatus(resp.SC_INTERNAL_SERVER_ERROR); 988 resp.setContentType("text/plain"); 989 990 PrintWriter w = resp.getWriter(); 991 w.println("Unhandled exception: " + e); 992 e.printStackTrace(w); 993 994 w.close(); 995 } 996 } finally { 997 throw new ServletException(e); 998 } 999 } finally { 1000 resp.flushBuffer(); 1001 1002 try { 1003 if (!req_destroy_listeners_.isEmpty()) { 1004 for (ServletRequestListener l : req_destroy_listeners_) { 1005 l.requestDestroyed(sre); 1006 } 1007 } 1008 } finally { 1009 Thread.currentThread().setContextClassLoader(old); 1010 } 1011 } 1012 } 1013 handleException(Throwable e, Request req, Response resp)1014 private void handleException(Throwable e, Request req, Response resp) 1015 throws ServletException, IOException 1016 { 1017 String location; 1018 1019 Class<?> cls = e.getClass(); 1020 while (cls != null && !cls.equals(Throwable.class)) { 1021 location = exception2location_.get(cls.getName()); 1022 1023 if (location != null) { 1024 trace("Exception " + e + " matched. Error page location: " + location); 1025 1026 req.setAttribute_(RequestDispatcher.ERROR_EXCEPTION, e); 1027 req.setAttribute_(RequestDispatcher.ERROR_EXCEPTION_TYPE, e.getClass()); 1028 req.setAttribute_(RequestDispatcher.ERROR_REQUEST_URI, req.getRequestURI()); 1029 req.setAttribute_(RequestDispatcher.ERROR_STATUS_CODE, resp.SC_INTERNAL_SERVER_ERROR); 1030 1031 handleError(location, req, resp); 1032 1033 return; 1034 } 1035 1036 cls = cls.getSuperclass(); 1037 } 1038 1039 if (ServletException.class.isAssignableFrom(e.getClass())) { 1040 ServletException se = (ServletException) e; 1041 1042 handleException(se.getRootCause(), req, resp); 1043 } 1044 } 1045 handleStatusCode(int code, Request req, Response resp)1046 private void handleStatusCode(int code, Request req, Response resp) 1047 throws ServletException, IOException 1048 { 1049 String location; 1050 1051 location = error2location_.get(code); 1052 1053 if (location != null) { 1054 trace("Status " + code + " matched. Error page location: " + location); 1055 1056 req.setAttribute_(RequestDispatcher.ERROR_REQUEST_URI, req.getRequestURI()); 1057 1058 handleError(location, req, resp); 1059 } 1060 } 1061 handleError(String location, Request req, Response resp)1062 public void handleError(String location, Request req, Response resp) 1063 throws ServletException, IOException 1064 { 1065 try { 1066 log("handleError: " + location); 1067 1068 String filter_path = req.getFilterPath(); 1069 String servlet_path = req.getServletPath(); 1070 String path_info = req.getPathInfo(); 1071 String req_uri = req.getRequestURI(); 1072 DispatcherType dtype = req.getDispatcherType(); 1073 1074 URI uri; 1075 1076 if (location.startsWith("/")) { 1077 uri = new URI(context_path_ + location); 1078 } else { 1079 uri = new URI(req_uri).resolve(location); 1080 } 1081 1082 req.setRequestURI(uri.getRawPath()); 1083 req.setDispatcherType(DispatcherType.ERROR); 1084 1085 String path = uri.getPath().substring(context_path_.length()); 1086 1087 ServletReg servlet = findServlet(path, req); 1088 1089 req.setMultipartConfig(servlet.multipart_config_); 1090 1091 FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.ERROR); 1092 1093 fc.doFilter(req, resp); 1094 1095 req.setServletPath(filter_path, servlet_path, path_info); 1096 req.setRequestURI(req_uri); 1097 req.setDispatcherType(dtype); 1098 } catch (URISyntaxException e) { 1099 throw new ServletException(e); 1100 } 1101 } 1102 processWebXml(File root)1103 private void processWebXml(File root) throws Exception 1104 { 1105 if (root.isDirectory()) { 1106 File web_xml = new File(root, "WEB-INF/web.xml"); 1107 if (web_xml.exists()) { 1108 trace("start: web.xml file found"); 1109 1110 InputStream is = new FileInputStream(web_xml); 1111 1112 processWebXml(is); 1113 1114 is.close(); 1115 } 1116 } else { 1117 JarFile jf = new JarFile(root); 1118 ZipEntry ze = jf.getEntry("WEB-INF/web.xml"); 1119 1120 if (ze == null) { 1121 trace("start: web.xml entry NOT found"); 1122 } else { 1123 trace("start: web.xml entry found"); 1124 1125 processWebXml(jf.getInputStream(ze)); 1126 } 1127 1128 jf.close(); 1129 } 1130 } 1131 processWebXml(InputStream is)1132 private void processWebXml(InputStream is) 1133 throws ParserConfigurationException, SAXException, IOException 1134 { 1135 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 1136 DocumentBuilder builder = factory.newDocumentBuilder(); 1137 1138 Document doc = builder.parse(is); 1139 1140 Element doc_elem = doc.getDocumentElement(); 1141 String doc_elem_name = doc_elem.getNodeName(); 1142 if (!doc_elem_name.equals("web-app")) { 1143 throw new RuntimeException("Invalid web.xml: 'web-app' element expected, not '" + doc_elem_name + "'"); 1144 } 1145 1146 metadata_complete_ = doc_elem.getAttribute("metadata-complete").equals("true"); 1147 app_version_ = doc_elem.getAttribute("version"); 1148 1149 NodeList welcome_file_lists = doc_elem.getElementsByTagName("welcome-file-list"); 1150 1151 if (welcome_file_lists.getLength() > 0) { 1152 welcome_files_list_found_ = true; 1153 } 1154 1155 for (int i = 0; i < welcome_file_lists.getLength(); i++) { 1156 Element list_el = (Element) welcome_file_lists.item(i); 1157 NodeList files = list_el.getElementsByTagName("welcome-file"); 1158 for (int j = 0; j < files.getLength(); j++) { 1159 Node node = files.item(j); 1160 String wf = node.getTextContent().trim(); 1161 1162 /* 1163 10.10 Welcome Files 1164 ... 1165 The welcome file list is an ordered list of partial URLs 1166 with no trailing or leading /. 1167 */ 1168 1169 if (wf.startsWith("/") || wf.endsWith("/")) { 1170 log("invalid welcome file: " + wf); 1171 continue; 1172 } 1173 1174 welcome_files_.add(wf); 1175 } 1176 } 1177 1178 NodeList context_params = doc_elem.getElementsByTagName("context-param"); 1179 for (int i = 0; i < context_params.getLength(); i++) { 1180 processXmlInitParam(this, (Element) context_params.item(i)); 1181 } 1182 1183 NodeList filters = doc_elem.getElementsByTagName("filter"); 1184 1185 for (int i = 0; i < filters.getLength(); i++) { 1186 Element filter_el = (Element) filters.item(i); 1187 NodeList names = filter_el.getElementsByTagName("filter-name"); 1188 if (names == null || names.getLength() != 1) { 1189 throw new RuntimeException("Invalid web.xml: 'filter-name' tag not found"); 1190 } 1191 1192 String filter_name = names.item(0).getTextContent().trim(); 1193 trace("filter-name=" + filter_name); 1194 1195 FilterReg reg = new FilterReg(filter_name); 1196 1197 NodeList child_nodes = filter_el.getChildNodes(); 1198 for(int j = 0; j < child_nodes.getLength(); j++) { 1199 Node child_node = child_nodes.item(j); 1200 String tag_name = child_node.getNodeName(); 1201 1202 if (tag_name.equals("filter-class")) { 1203 reg.setClassName(child_node.getTextContent().trim()); 1204 continue; 1205 } 1206 1207 if (tag_name.equals("async-supported")) { 1208 reg.setAsyncSupported(child_node.getTextContent().trim() 1209 .equals("true")); 1210 continue; 1211 } 1212 1213 if (tag_name.equals("init-param")) { 1214 processXmlInitParam(reg, (Element) child_node); 1215 continue; 1216 } 1217 1218 if (tag_name.equals("filter-name") 1219 || tag_name.equals("#text") 1220 || tag_name.equals("#comment")) 1221 { 1222 continue; 1223 } 1224 1225 log("processWebXml: tag '" + tag_name + "' for filter '" 1226 + filter_name + "' is ignored"); 1227 } 1228 1229 filters_.add(reg); 1230 name2filter_.put(filter_name, reg); 1231 } 1232 1233 NodeList filter_mappings = doc_elem.getElementsByTagName("filter-mapping"); 1234 1235 for(int i = 0; i < filter_mappings.getLength(); i++) { 1236 Element mapping_el = (Element) filter_mappings.item(i); 1237 NodeList names = mapping_el.getElementsByTagName("filter-name"); 1238 if (names == null || names.getLength() != 1) { 1239 throw new RuntimeException("Invalid web.xml: 'filter-name' tag not found"); 1240 } 1241 1242 String filter_name = names.item(0).getTextContent().trim(); 1243 trace("filter-name=" + filter_name); 1244 1245 FilterReg reg = name2filter_.get(filter_name); 1246 if (reg == null) { 1247 throw new RuntimeException("Invalid web.xml: filter '" + filter_name + "' not found"); 1248 } 1249 1250 EnumSet<DispatcherType> dtypes = EnumSet.noneOf(DispatcherType.class); 1251 NodeList dispatchers = mapping_el.getElementsByTagName("dispatcher"); 1252 for (int j = 0; j < dispatchers.getLength(); j++) { 1253 Node child_node = dispatchers.item(j); 1254 dtypes.add(DispatcherType.valueOf(child_node.getTextContent().trim())); 1255 } 1256 1257 if (dtypes.isEmpty()) { 1258 dtypes.add(DispatcherType.REQUEST); 1259 } 1260 1261 boolean match_after = false; 1262 1263 NodeList child_nodes = mapping_el.getChildNodes(); 1264 for (int j = 0; j < child_nodes.getLength(); j++) { 1265 Node child_node = child_nodes.item(j); 1266 String tag_name = child_node.getNodeName(); 1267 1268 if (tag_name.equals("url-pattern")) { 1269 reg.addMappingForUrlPatterns(dtypes, match_after, child_node.getTextContent().trim()); 1270 continue; 1271 } 1272 1273 if (tag_name.equals("servlet-name")) { 1274 reg.addMappingForServletNames(dtypes, match_after, child_node.getTextContent().trim()); 1275 continue; 1276 } 1277 } 1278 } 1279 1280 NodeList servlets = doc_elem.getElementsByTagName("servlet"); 1281 1282 for (int i = 0; i < servlets.getLength(); i++) { 1283 Element servlet_el = (Element) servlets.item(i); 1284 NodeList names = servlet_el.getElementsByTagName("servlet-name"); 1285 if (names == null || names.getLength() != 1) { 1286 throw new RuntimeException("Invalid web.xml: 'servlet-name' tag not found"); 1287 } 1288 1289 String servlet_name = names.item(0).getTextContent().trim(); 1290 trace("servlet-name=" + servlet_name); 1291 1292 ServletReg reg = new ServletReg(servlet_name); 1293 1294 NodeList child_nodes = servlet_el.getChildNodes(); 1295 for(int j = 0; j < child_nodes.getLength(); j++) { 1296 Node child_node = child_nodes.item(j); 1297 String tag_name = child_node.getNodeName(); 1298 1299 if (tag_name.equals("servlet-class")) { 1300 reg.setClassName(child_node.getTextContent().trim()); 1301 continue; 1302 } 1303 1304 if (tag_name.equals("async-supported")) { 1305 reg.setAsyncSupported(child_node.getTextContent().trim() 1306 .equals("true")); 1307 continue; 1308 } 1309 1310 if (tag_name.equals("init-param")) { 1311 processXmlInitParam(reg, (Element) child_node); 1312 continue; 1313 } 1314 1315 if (tag_name.equals("load-on-startup")) { 1316 reg.setLoadOnStartup(Integer.parseInt(child_node.getTextContent().trim())); 1317 continue; 1318 } 1319 1320 if (tag_name.equals("jsp-file")) { 1321 reg.setJspFile(child_node.getTextContent().trim()); 1322 continue; 1323 } 1324 1325 if (tag_name.equals("servlet-name") 1326 || tag_name.equals("display-name") 1327 || tag_name.equals("#text") 1328 || tag_name.equals("#comment")) 1329 { 1330 continue; 1331 } 1332 1333 log("processWebXml: tag '" + tag_name + "' for servlet '" 1334 + servlet_name + "' is ignored"); 1335 } 1336 1337 servlets_.add(reg); 1338 name2servlet_.put(servlet_name, reg); 1339 } 1340 1341 NodeList servlet_mappings = doc_elem.getElementsByTagName("servlet-mapping"); 1342 1343 for(int i = 0; i < servlet_mappings.getLength(); i++) { 1344 Element mapping_el = (Element) servlet_mappings.item(i); 1345 NodeList names = mapping_el.getElementsByTagName("servlet-name"); 1346 if (names == null || names.getLength() != 1) { 1347 throw new RuntimeException("Invalid web.xml: 'servlet-name' tag not found"); 1348 } 1349 1350 String servlet_name = names.item(0).getTextContent().trim(); 1351 trace("servlet-name=" + servlet_name); 1352 1353 ServletReg reg = name2servlet_.get(servlet_name); 1354 if (reg == null) { 1355 throw new RuntimeException("Invalid web.xml: servlet '" + servlet_name + "' not found"); 1356 } 1357 1358 NodeList child_nodes = mapping_el.getElementsByTagName("url-pattern"); 1359 String patterns[] = new String[child_nodes.getLength()]; 1360 for(int j = 0; j < child_nodes.getLength(); j++) { 1361 Node child_node = child_nodes.item(j); 1362 patterns[j] = child_node.getTextContent().trim(); 1363 } 1364 1365 reg.addMapping(patterns); 1366 } 1367 1368 NodeList listeners = doc_elem.getElementsByTagName("listener"); 1369 1370 for (int i = 0; i < listeners.getLength(); i++) { 1371 Element listener_el = (Element) listeners.item(i); 1372 NodeList classes = listener_el.getElementsByTagName("listener-class"); 1373 if (classes == null || classes.getLength() != 1) { 1374 throw new RuntimeException("Invalid web.xml: 'listener-class' tag not found"); 1375 } 1376 1377 String class_name = classes.item(0).getTextContent().trim(); 1378 trace("listener-class=" + class_name); 1379 1380 pending_listener_classnames_.add(class_name); 1381 } 1382 1383 NodeList error_pages = doc_elem.getElementsByTagName("error-page"); 1384 1385 for (int i = 0; i < error_pages.getLength(); i++) { 1386 Element error_page_el = (Element) error_pages.item(i); 1387 NodeList locations = error_page_el.getElementsByTagName("location"); 1388 if (locations == null || locations.getLength() != 1) { 1389 throw new RuntimeException("Invalid web.xml: 'location' tag not found"); 1390 } 1391 1392 String location = locations.item(0).getTextContent().trim(); 1393 1394 NodeList child_nodes = error_page_el.getChildNodes(); 1395 for(int j = 0; j < child_nodes.getLength(); j++) { 1396 Node child_node = child_nodes.item(j); 1397 String tag_name = child_node.getNodeName(); 1398 1399 if (tag_name.equals("exception-type")) { 1400 String ex = child_node.getTextContent().trim(); 1401 1402 exception2location_.put(ex, location); 1403 trace("error-page: exception " + ex + " -> " + location); 1404 continue; 1405 } 1406 1407 if (tag_name.equals("error-code")) { 1408 Integer code = Integer.parseInt(child_node.getTextContent().trim()); 1409 1410 error2location_.put(code, location); 1411 trace("error-page: code " + code + " -> " + location); 1412 continue; 1413 } 1414 } 1415 } 1416 1417 NodeList session_config = doc_elem.getElementsByTagName("session-config"); 1418 1419 for (int i = 0; i < session_config.getLength(); i++) { 1420 Element session_config_el = (Element) session_config.item(i); 1421 NodeList session_timeout = session_config_el.getElementsByTagName("session-timeout"); 1422 if (session_timeout != null) { 1423 String timeout = session_timeout.item(0).getTextContent().trim(); 1424 1425 trace("session_timeout: " + timeout); 1426 session_timeout_ = Integer.parseInt(timeout); 1427 break; 1428 } 1429 } 1430 1431 NodeList jsp_configs = doc_elem.getElementsByTagName("jsp-config"); 1432 1433 for (int i = 0; i < jsp_configs.getLength(); i++) { 1434 Element jsp_config_el = (Element) jsp_configs.item(i); 1435 1436 NodeList jsp_nodes = jsp_config_el.getChildNodes(); 1437 1438 for(int j = 0; j < jsp_nodes.getLength(); j++) { 1439 Node jsp_node = jsp_nodes.item(j); 1440 String tag_name = jsp_node.getNodeName(); 1441 1442 if (tag_name.equals("taglib")) { 1443 NodeList tl_nodes = ((Element) jsp_node).getChildNodes(); 1444 Taglib tl = new Taglib(tl_nodes); 1445 1446 trace("add taglib"); 1447 1448 taglibs_.add(tl); 1449 continue; 1450 } 1451 1452 if (tag_name.equals("jsp-property-group")) { 1453 NodeList jpg_nodes = ((Element) jsp_node).getChildNodes(); 1454 JspPropertyGroup conf = new JspPropertyGroup(jpg_nodes); 1455 1456 trace("add prop group"); 1457 1458 prop_groups_.add(conf); 1459 continue; 1460 } 1461 } 1462 } 1463 } 1464 compareVersion(String ver1, String ver2)1465 private static int compareVersion(String ver1, String ver2) 1466 { 1467 String[] varr1 = ver1.split("\\."); 1468 String[] varr2 = ver2.split("\\."); 1469 1470 int max_len = varr1.length > varr2.length ? varr1.length : varr2.length; 1471 for (int i = 0; i < max_len; i++) { 1472 int l = i < varr1.length ? Integer.parseInt(varr1[i]) : 0; 1473 int r = i < varr2.length ? Integer.parseInt(varr2[i]) : 0; 1474 1475 int res = l - r; 1476 1477 if (res != 0) { 1478 return res; 1479 } 1480 } 1481 1482 return 0; 1483 } 1484 1485 private void processXmlInitParam(InitParams params, Element elem) 1486 throws RuntimeException 1487 { 1488 NodeList n = elem.getElementsByTagName("param-name"); 1489 if (n == null || n.getLength() != 1) { 1490 throw new RuntimeException("Invalid web.xml: 'param-name' tag not found"); 1491 } 1492 1493 NodeList v = elem.getElementsByTagName("param-value"); 1494 if (v == null || v.getLength() != 1) { 1495 throw new RuntimeException("Invalid web.xml: 'param-value' tag not found"); 1496 } 1497 params.setInitParameter(n.item(0).getTextContent().trim(), 1498 v.item(0).getTextContent().trim()); 1499 } 1500 1501 private void loadInitializers(ScanResult scan_res) 1502 { 1503 trace("load initializer(s)"); 1504 1505 ServiceLoader<ServletContainerInitializer> initializers = 1506 ServiceLoader.load(ServletContainerInitializer.class, loader_); 1507 1508 for (ServletContainerInitializer sci : initializers) { 1509 loadInitializer(sci, scan_res); 1510 } 1511 } 1512 1513 private void loadInitializer(ServletContainerInitializer sci, ScanResult scan_res) 1514 { 1515 trace("loadInitializer: initializer: " + sci.getClass().getName()); 1516 1517 /* 1518 Unit WebSocket container is a copy of Tomcat WsSci with own 1519 transport implementation. Tomcat implementation will not work in 1520 Unit and should be ignored here. 1521 */ 1522 if (sci.getClass().getName() 1523 .equals("org.apache.tomcat.websocket.server.WsSci")) 1524 { 1525 trace("loadInitializer: ignore"); 1526 return; 1527 } 1528 1529 HandlesTypes ann = sci.getClass().getAnnotation(HandlesTypes.class); 1530 if (ann == null) { 1531 trace("loadInitializer: no HandlesTypes annotation"); 1532 return; 1533 } 1534 1535 Class<?>[] classes = ann.value(); 1536 if (classes == null) { 1537 trace("loadInitializer: no handles classes"); 1538 return; 1539 } 1540 1541 Set<Class<?>> handles_classes = new HashSet<>(); 1542 1543 for (Class<?> c : classes) { 1544 trace("loadInitializer: find handles: " + c.getName()); 1545 1546 ClassInfoList handles = 1547 c.isAnnotation() 1548 ? scan_res.getClassesWithAnnotation(c.getName()) 1549 : c.isInterface() 1550 ? scan_res.getClassesImplementing(c.getName()) 1551 : scan_res.getSubclasses(c.getName()); 1552 1553 for (ClassInfo ci : handles) { 1554 if (ci.isInterface() 1555 || ci.isAnnotation() 1556 || ci.isAbstract()) 1557 { 1558 continue; 1559 } 1560 1561 trace("loadInitializer: handles class: " + ci.getName()); 1562 handles_classes.add(ci.loadClass()); 1563 } 1564 } 1565 1566 if (handles_classes.isEmpty()) { 1567 trace("loadInitializer: no handles implementations"); 1568 return; 1569 } 1570 1571 try { 1572 sci.onStartup(handles_classes, this); 1573 } catch(Exception e) { 1574 System.err.println("loadInitializer: exception caught: " + e.toString()); 1575 } 1576 } 1577 1578 private void scanClasses(ScanResult scan_res) 1579 throws ReflectiveOperationException 1580 { 1581 ClassInfoList filters = scan_res.getClassesWithAnnotation(WebFilter.class.getName()); 1582 1583 for (ClassInfo ci : filters) { 1584 if (ci.isInterface() 1585 || ci.isAnnotation() 1586 || ci.isAbstract() 1587 || !ci.implementsInterface(Filter.class.getName())) 1588 { 1589 trace("scanClasses: ignoring Filter impl: " + ci.getName()); 1590 continue; 1591 } 1592 1593 trace("scanClasses: found Filter class: " + ci.getName()); 1594 1595 Class<?> cls = ci.loadClass(); 1596 if (!Filter.class.isAssignableFrom(cls)) { 1597 trace("scanClasses: " + ci.getName() + " cannot be assigned to Filter"); 1598 continue; 1599 } 1600 1601 WebFilter ann = cls.getAnnotation(WebFilter.class); 1602 1603 if (ann == null) { 1604 trace("scanClasses: no WebFilter annotation for " + ci.getName()); 1605 continue; 1606 } 1607 1608 String filter_name = ann.filterName(); 1609 1610 if (filter_name.isEmpty()) { 1611 filter_name = ci.getName(); 1612 } 1613 1614 FilterReg reg = name2filter_.get(filter_name); 1615 1616 if (reg == null) { 1617 reg = new FilterReg(filter_name, cls); 1618 filters_.add(reg); 1619 name2filter_.put(filter_name, reg); 1620 } else { 1621 reg.setClass(cls); 1622 } 1623 1624 EnumSet<DispatcherType> dtypes = EnumSet.noneOf(DispatcherType.class); 1625 DispatcherType[] dispatchers = ann.dispatcherTypes(); 1626 for (DispatcherType d : dispatchers) { 1627 dtypes.add(d); 1628 } 1629 1630 if (dtypes.isEmpty()) { 1631 dtypes.add(DispatcherType.REQUEST); 1632 } 1633 1634 boolean match_after = false; 1635 1636 reg.addMappingForUrlPatterns(dtypes, match_after, ann.value()); 1637 reg.addMappingForUrlPatterns(dtypes, match_after, ann.urlPatterns()); 1638 reg.addMappingForServletNames(dtypes, match_after, ann.servletNames()); 1639 1640 for (WebInitParam p : ann.initParams()) { 1641 reg.setInitParameter(p.name(), p.value()); 1642 } 1643 1644 reg.setAsyncSupported(ann.asyncSupported()); 1645 } 1646 1647 ClassInfoList servlets = scan_res.getClassesWithAnnotation(WebServlet.class.getName()); 1648 1649 for (ClassInfo ci : servlets) { 1650 if (ci.isInterface() 1651 || ci.isAnnotation() 1652 || ci.isAbstract() 1653 || !ci.extendsSuperclass(HttpServlet.class.getName())) 1654 { 1655 trace("scanClasses: ignoring HttpServlet subclass: " + ci.getName()); 1656 continue; 1657 } 1658 1659 trace("scanClasses: found HttpServlet class: " + ci.getName()); 1660 1661 Class<?> cls = ci.loadClass(); 1662 if (!HttpServlet.class.isAssignableFrom(cls)) { 1663 trace("scanClasses: " + ci.getName() + " cannot be assigned to HttpFilter"); 1664 continue; 1665 } 1666 1667 WebServlet ann = cls.getAnnotation(WebServlet.class); 1668 1669 if (ann == null) { 1670 trace("scanClasses: no WebServlet annotation"); 1671 continue; 1672 } 1673 1674 String servlet_name = ann.name(); 1675 1676 if (servlet_name.isEmpty()) { 1677 servlet_name = ci.getName(); 1678 } 1679 1680 ServletReg reg = name2servlet_.get(servlet_name); 1681 1682 if (reg == null) { 1683 reg = new ServletReg(servlet_name, cls); 1684 servlets_.add(reg); 1685 name2servlet_.put(servlet_name, reg); 1686 } else { 1687 reg.setClass(cls); 1688 } 1689 1690 reg.addMapping(ann.value()); 1691 reg.addMapping(ann.urlPatterns()); 1692 1693 for (WebInitParam p : ann.initParams()) { 1694 reg.setInitParameter(p.name(), p.value()); 1695 } 1696 1697 reg.setAsyncSupported(ann.asyncSupported()); 1698 } 1699 1700 1701 ClassInfoList lstnrs = scan_res.getClassesWithAnnotation(WebListener.class.getName()); 1702 1703 for (ClassInfo ci : lstnrs) { 1704 if (ci.isInterface() 1705 || ci.isAnnotation() 1706 || ci.isAbstract()) 1707 { 1708 trace("scanClasses: listener impl: " + ci.getName()); 1709 continue; 1710 } 1711 1712 trace("scanClasses: listener class: " + ci.getName()); 1713 1714 if (listener_classnames_.contains(ci.getName())) { 1715 trace("scanClasses: " + ci.getName() + " already added as listener"); 1716 continue; 1717 } 1718 1719 Class<?> cls = ci.loadClass(); 1720 Class<?> lclass = null; 1721 for (Class<?> c : LISTENER_TYPES) { 1722 if (c.isAssignableFrom(cls)) { 1723 lclass = c; 1724 break; 1725 } 1726 } 1727 1728 if (lclass == null) { 1729 log("scanClasses: " + ci.getName() + " implements none of known listener interfaces"); 1730 continue; 1731 } 1732 1733 WebListener ann = cls.getAnnotation(WebListener.class); 1734 1735 if (ann == null) { 1736 log("scanClasses: no WebListener annotation"); 1737 continue; 1738 } 1739 1740 Constructor<?> ctor = cls.getConstructor(); 1741 EventListener listener = (EventListener) ctor.newInstance(); 1742 1743 addListener(listener); 1744 1745 listener_classnames_.add(ci.getName()); 1746 } 1747 1748 1749 ClassInfoList endpoints = scan_res.getClassesWithAnnotation(ServerEndpoint.class.getName()); 1750 1751 for (ClassInfo ci : endpoints) { 1752 if (ci.isInterface() 1753 || ci.isAnnotation() 1754 || ci.isAbstract()) 1755 { 1756 trace("scanClasses: skip server end point: " + ci.getName()); 1757 continue; 1758 } 1759 1760 trace("scanClasses: server end point: " + ci.getName()); 1761 } 1762 } 1763 1764 public void stop() throws IOException 1765 { 1766 ClassLoader old = Thread.currentThread().getContextClassLoader(); 1767 Thread.currentThread().setContextClassLoader(loader_); 1768 1769 try { 1770 for (ServletReg s : servlets_) { 1771 s.destroy(); 1772 } 1773 1774 for (FilterReg f : filters_) { 1775 f.destroy(); 1776 } 1777 1778 if (!destroy_listeners_.isEmpty()) { 1779 ServletContextEvent event = new ServletContextEvent(this); 1780 for (ServletContextListener listener : destroy_listeners_) { 1781 listener.contextDestroyed(event); 1782 } 1783 } 1784 1785 if (extracted_dir_ != null) { 1786 removeDir(extracted_dir_); 1787 } 1788 1789 if (temp_dir_ != null) { 1790 removeDir(temp_dir_); 1791 } 1792 } finally { 1793 Thread.currentThread().setContextClassLoader(old); 1794 } 1795 } 1796 1797 private void removeDir(File dir) throws IOException 1798 { 1799 Files.walkFileTree(dir.toPath(), 1800 new SimpleFileVisitor<Path>() { 1801 @Override 1802 public FileVisitResult postVisitDirectory( 1803 Path dir, IOException exc) throws IOException { 1804 Files.delete(dir); 1805 return FileVisitResult.CONTINUE; 1806 } 1807 1808 @Override 1809 public FileVisitResult visitFile( 1810 Path file, BasicFileAttributes attrs) 1811 throws IOException { 1812 Files.delete(file); 1813 return FileVisitResult.CONTINUE; 1814 } 1815 }); 1816 } 1817 1818 private class CtxInitParams implements InitParams 1819 { 1820 private final Map<String, String> init_params_ = 1821 new HashMap<String, String>(); 1822 1823 public boolean setInitParameter(String name, String value) 1824 { 1825 trace("CtxInitParams.setInitParameter " + name + " = " + value); 1826 1827 return init_params_.putIfAbsent(name, value) == null; 1828 } 1829 1830 public String getInitParameter(String name) 1831 { 1832 trace("CtxInitParams.getInitParameter for " + name); 1833 1834 return init_params_.get(name); 1835 } 1836 1837 public Set<String> setInitParameters(Map<String, String> initParameters) 1838 { 1839 // illegalStateIfContextStarted(); 1840 Set<String> clash = null; 1841 for (Map.Entry<String, String> entry : initParameters.entrySet()) 1842 { 1843 if (entry.getKey() == null) { 1844 throw new IllegalArgumentException("init parameter name required"); 1845 } 1846 1847 if (entry.getValue() == null) { 1848 throw new IllegalArgumentException("non-null value required for init parameter " + entry.getKey()); 1849 } 1850 1851 if (init_params_.get(entry.getKey()) != null) 1852 { 1853 if (clash == null) 1854 clash = new HashSet<String>(); 1855 clash.add(entry.getKey()); 1856 } 1857 1858 trace("CtxInitParams.setInitParameters " + entry.getKey() + " = " + entry.getValue()); 1859 } 1860 1861 if (clash != null) { 1862 return clash; 1863 } 1864 1865 init_params_.putAll(initParameters); 1866 return Collections.emptySet(); 1867 } 1868 1869 public Map<String, String> getInitParameters() 1870 { 1871 trace("CtxInitParams.getInitParameters"); 1872 return init_params_; 1873 } 1874 1875 public Enumeration<String> getInitParameterNames() 1876 { 1877 return Collections.enumeration(init_params_.keySet()); 1878 } 1879 } 1880 1881 private class NamedReg extends CtxInitParams 1882 implements Registration 1883 { 1884 private final String name_; 1885 private String class_name_; 1886 1887 public NamedReg(String name) 1888 { 1889 name_ = name; 1890 } 1891 1892 public NamedReg(String name, String class_name) 1893 { 1894 name_ = name; 1895 class_name_ = class_name; 1896 } 1897 1898 @Override 1899 public String getName() 1900 { 1901 return name_; 1902 } 1903 1904 @Override 1905 public String getClassName() 1906 { 1907 return class_name_; 1908 } 1909 1910 public void setClassName(String class_name) 1911 { 1912 class_name_ = class_name; 1913 } 1914 } 1915 1916 private class ServletReg extends NamedReg 1917 implements ServletRegistration.Dynamic, ServletConfig 1918 { 1919 private Class<?> servlet_class_; 1920 private Servlet servlet_; 1921 private String role_; 1922 private boolean async_supported_ = false; 1923 private final List<String> patterns_ = new ArrayList<>(); 1924 private int load_on_startup_ = -1; 1925 private boolean initialized_ = false; 1926 private final List<FilterMap> filters_ = new ArrayList<>(); 1927 private boolean system_jsp_servlet_ = false; 1928 private String jsp_file_; 1929 private MultipartConfigElement multipart_config_; 1930 1931 public ServletReg(String name, Class<?> servlet_class) 1932 { 1933 super(name, servlet_class.getName()); 1934 servlet_class_ = servlet_class; 1935 getAnnotationMultipartConfig(); 1936 } 1937 1938 public ServletReg(String name, Servlet servlet) 1939 { 1940 super(name, servlet.getClass().getName()); 1941 servlet_ = servlet; 1942 } 1943 1944 public ServletReg(String name, String servlet_class_name) 1945 { 1946 super(name, servlet_class_name); 1947 } 1948 1949 public ServletReg(String name) 1950 { 1951 super(name); 1952 } 1953 1954 private void init() throws ServletException 1955 { 1956 if (initialized_) { 1957 return; 1958 } 1959 1960 trace("ServletReg.init(): " + getName()); 1961 1962 if (jsp_file_ != null) { 1963 setInitParameter("jspFile", jsp_file_); 1964 jsp_file_ = null; 1965 1966 ServletReg jsp_servlet = name2servlet_.get("jsp"); 1967 1968 if (jsp_servlet.servlet_class_ != null) { 1969 servlet_class_ = jsp_servlet.servlet_class_; 1970 } else { 1971 setClassName(jsp_servlet.getClassName()); 1972 } 1973 1974 system_jsp_servlet_ = jsp_servlet.system_jsp_servlet_; 1975 } 1976 1977 if (system_jsp_servlet_) { 1978 JasperInitializer ji = new JasperInitializer(); 1979 1980 ji.onStartup(Collections.emptySet(), Context.this); 1981 } 1982 1983 if (servlet_ == null) { 1984 try { 1985 if (servlet_class_ == null) { 1986 servlet_class_ = loader_.loadClass(getClassName()); 1987 getAnnotationMultipartConfig(); 1988 } 1989 1990 Constructor<?> ctor = servlet_class_.getConstructor(); 1991 servlet_ = (Servlet) ctor.newInstance(); 1992 } catch(Exception e) { 1993 log("ServletReg.init() failed " + e); 1994 throw new ServletException(e); 1995 } 1996 } 1997 1998 servlet_.init((ServletConfig) this); 1999 2000 initialized_ = true; 2001 } 2002 2003 public void startup() throws ServletException 2004 { 2005 if (load_on_startup_ < 0) { 2006 return; 2007 } 2008 2009 init(); 2010 } 2011 2012 public void destroy() 2013 { 2014 if (initialized_) { 2015 servlet_.destroy(); 2016 } 2017 } 2018 2019 public void setClassName(String class_name) throws IllegalStateException 2020 { 2021 if (servlet_ != null 2022 || servlet_class_ != null 2023 || getClassName() != null) 2024 { 2025 throw new IllegalStateException("Class already initialized"); 2026 } 2027 2028 if (jsp_file_ != null) { 2029 throw new IllegalStateException("jsp-file already initialized"); 2030 } 2031 2032 super.setClassName(class_name); 2033 } 2034 2035 public void setClass(Class<?> servlet_class) 2036 throws IllegalStateException 2037 { 2038 if (servlet_ != null 2039 || servlet_class_ != null 2040 || getClassName() != null) 2041 { 2042 throw new IllegalStateException("Class already initialized"); 2043 } 2044 2045 if (jsp_file_ != null) { 2046 throw new IllegalStateException("jsp-file already initialized"); 2047 } 2048 2049 super.setClassName(servlet_class.getName()); 2050 servlet_class_ = servlet_class; 2051 getAnnotationMultipartConfig(); 2052 } 2053 2054 public void setJspFile(String jsp_file) throws IllegalStateException 2055 { 2056 if (servlet_ != null 2057 || servlet_class_ != null 2058 || getClassName() != null) 2059 { 2060 throw new IllegalStateException("Class already initialized"); 2061 } 2062 2063 if (jsp_file_ != null) { 2064 throw new IllegalStateException("jsp-file already initialized"); 2065 } 2066 2067 jsp_file_ = jsp_file; 2068 } 2069 2070 private void getAnnotationMultipartConfig() { 2071 if (servlet_class_ == null) { 2072 return; 2073 } 2074 2075 MultipartConfig mpc = servlet_class_.getAnnotation(MultipartConfig.class); 2076 if (mpc == null) { 2077 return; 2078 } 2079 2080 multipart_config_ = new MultipartConfigElement(mpc); 2081 } 2082 2083 public void service(ServletRequest request, ServletResponse response) 2084 throws ServletException, IOException 2085 { 2086 init(); 2087 2088 servlet_.service(request, response); 2089 } 2090 2091 public void addFilter(FilterMap fmap) 2092 { 2093 filters_.add(fmap); 2094 } 2095 2096 @Override 2097 public Set<String> addMapping(String... urlPatterns) 2098 { 2099 checkContextState(); 2100 2101 Set<String> clash = null; 2102 for (String pattern : urlPatterns) { 2103 trace("ServletReg.addMapping: " + pattern); 2104 2105 if (pattern2servlet_.containsKey(pattern)) { 2106 if (clash == null) { 2107 clash = new HashSet<String>(); 2108 } 2109 clash.add(pattern); 2110 } 2111 } 2112 2113 /* if there were any clashes amongst the urls, return them */ 2114 if (clash != null) { 2115 return clash; 2116 } 2117 2118 for (String pattern : urlPatterns) { 2119 patterns_.add(pattern); 2120 pattern2servlet_.put(pattern, this); 2121 parseURLPattern(pattern, this); 2122 } 2123 2124 return Collections.emptySet(); 2125 } 2126 2127 @Override 2128 public Collection<String> getMappings() 2129 { 2130 trace("ServletReg.getMappings"); 2131 return patterns_; 2132 } 2133 2134 @Override 2135 public String getRunAsRole() 2136 { 2137 return role_; 2138 } 2139 2140 @Override 2141 public void setLoadOnStartup(int loadOnStartup) 2142 { 2143 checkContextState(); 2144 2145 trace("ServletReg.setLoadOnStartup: " + loadOnStartup); 2146 load_on_startup_ = loadOnStartup; 2147 } 2148 2149 @Override 2150 public Set<String> setServletSecurity(ServletSecurityElement constraint) 2151 { 2152 log("ServletReg.setServletSecurity"); 2153 return Collections.emptySet(); 2154 } 2155 2156 @Override 2157 public void setMultipartConfig( 2158 MultipartConfigElement multipartConfig) 2159 { 2160 trace("ServletReg.setMultipartConfig"); 2161 multipart_config_ = multipartConfig; 2162 } 2163 2164 @Override 2165 public void setRunAsRole(String roleName) 2166 { 2167 log("ServletReg.setRunAsRole: " + roleName); 2168 role_ = roleName; 2169 } 2170 2171 @Override 2172 public void setAsyncSupported(boolean isAsyncSupported) 2173 { 2174 log("ServletReg.setAsyncSupported: " + isAsyncSupported); 2175 async_supported_ = isAsyncSupported; 2176 } 2177 2178 @Override 2179 public String getServletName() 2180 { 2181 return getName(); 2182 } 2183 2184 @Override 2185 public ServletContext getServletContext() 2186 { 2187 return (ServletContext) Context.this; 2188 } 2189 } 2190 2191 public void checkContextState() throws IllegalStateException 2192 { 2193 if (ctx_initialized_) { 2194 throw new IllegalStateException("Context already initialized"); 2195 } 2196 } 2197 2198 public void parseURLPattern(String p, ServletReg servlet) 2199 throws IllegalArgumentException 2200 { 2201 URLPattern pattern = parseURLPattern(p); 2202 2203 switch (pattern.type_) { 2204 case PREFIX: 2205 prefix_patterns_.add(new PrefixPattern(pattern.pattern_, servlet)); 2206 return; 2207 2208 case SUFFIX: 2209 suffix2servlet_.put(pattern.pattern_, servlet); 2210 return; 2211 2212 case EXACT: 2213 exact2servlet_.put(pattern.pattern_, servlet); 2214 return; 2215 2216 case DEFAULT: 2217 default_servlet_ = servlet; 2218 return; 2219 } 2220 2221 /* TODO process other cases, throw IllegalArgumentException */ 2222 } 2223 2224 public URLPattern parseURLPattern(String p) 2225 throws IllegalArgumentException 2226 { 2227 URLPattern pattern = parsed_patterns_.get(p); 2228 if (pattern == null) { 2229 pattern = new URLPattern(p); 2230 parsed_patterns_.put(p, pattern); 2231 } 2232 2233 return pattern; 2234 } 2235 2236 private static enum URLPatternType { 2237 PREFIX, 2238 SUFFIX, 2239 DEFAULT, 2240 EXACT, 2241 }; 2242 2243 private class URLPattern 2244 { 2245 private final String pattern_; 2246 private final URLPatternType type_; 2247 2248 public URLPattern(String p) 2249 throws IllegalArgumentException 2250 { 2251 /* 2252 12.2 Specification of Mappings 2253 ... 2254 A string beginning with a '/' character and ending with a '/*' 2255 suffix is used for path mapping. 2256 */ 2257 if (p.startsWith("/") && p.endsWith("/*")) { 2258 trace("URLPattern: '" + p + "' is a prefix pattern"); 2259 pattern_ = p.substring(0, p.length() - 2); 2260 type_ = URLPatternType.PREFIX; 2261 return; 2262 } 2263 2264 /* 2265 A string beginning with a '*.' prefix is used as an extension 2266 mapping. 2267 */ 2268 if (p.startsWith("*.")) { 2269 trace("URLPattern: '" + p + "' is a suffix pattern"); 2270 pattern_ = p.substring(1, p.length()); 2271 type_ = URLPatternType.SUFFIX; 2272 return; 2273 } 2274 2275 /* 2276 The empty string ("") is a special URL pattern that exactly maps to 2277 the application's context root, i.e., requests of the form 2278 http://host:port/<context- root>/. In this case the path info is '/' 2279 and the servlet path and context path is empty string (""). 2280 */ 2281 if (p.isEmpty()) { 2282 trace("URLPattern: '" + p + "' is a root"); 2283 pattern_ = "/"; 2284 type_ = URLPatternType.EXACT; 2285 return; 2286 } 2287 2288 /* 2289 A string containing only the '/' character indicates the "default" 2290 servlet of the application. In this case the servlet path is the 2291 request URI minus the context path and the path info is null. 2292 */ 2293 if (p.equals("/")) { 2294 trace("URLPattern: '" + p + "' is a default"); 2295 pattern_ = p; 2296 type_ = URLPatternType.DEFAULT; 2297 return; 2298 } 2299 2300 /* 2301 All other strings are used for exact matches only. 2302 */ 2303 trace("URLPattern: '" + p + "' is an exact pattern"); 2304 pattern_ = p; 2305 type_ = URLPatternType.EXACT; 2306 2307 /* TODO process other cases, throw IllegalArgumentException */ 2308 } 2309 2310 public boolean match(String url) 2311 { 2312 switch (type_) { 2313 case PREFIX: 2314 return url.startsWith(pattern_) && ( 2315 url.length() == pattern_.length() 2316 || url.charAt(pattern_.length()) == '/'); 2317 2318 case SUFFIX: 2319 return url.endsWith(pattern_); 2320 2321 case EXACT: 2322 return url.equals(pattern_); 2323 2324 case DEFAULT: 2325 return true; 2326 } 2327 2328 return false; 2329 } 2330 } 2331 2332 private class FilterReg extends NamedReg 2333 implements FilterRegistration.Dynamic, FilterConfig 2334 { 2335 private Class<?> filter_class_; 2336 private Filter filter_; 2337 private boolean async_supported_ = false; 2338 private boolean initialized_ = false; 2339 2340 public FilterReg(String name, Class<?> filter_class) 2341 { 2342 super(name, filter_class.getName()); 2343 filter_class_ = filter_class; 2344 } 2345 2346 public FilterReg(String name, Filter filter) 2347 { 2348 super(name, filter.getClass().getName()); 2349 filter_ = filter; 2350 } 2351 2352 public FilterReg(String name, String filter_class_name) 2353 { 2354 super(name, filter_class_name); 2355 } 2356 2357 public FilterReg(String name) 2358 { 2359 super(name); 2360 } 2361 2362 public void setClassName(String class_name) throws IllegalStateException 2363 { 2364 if (filter_ != null 2365 || filter_class_ != null 2366 || getClassName() != null) 2367 { 2368 throw new IllegalStateException("Class already initialized"); 2369 } 2370 2371 super.setClassName(class_name); 2372 } 2373 2374 public void setClass(Class<?> filter_class) throws IllegalStateException 2375 { 2376 if (filter_ != null 2377 || filter_class_ != null 2378 || getClassName() != null) 2379 { 2380 throw new IllegalStateException("Class already initialized"); 2381 } 2382 2383 super.setClassName(filter_class.getName()); 2384 filter_class_ = filter_class; 2385 } 2386 2387 public void init() throws ServletException 2388 { 2389 if (filter_ == null) { 2390 try { 2391 if (filter_class_ == null) { 2392 filter_class_ = loader_.loadClass(getClassName()); 2393 } 2394 2395 Constructor<?> ctor = filter_class_.getConstructor(); 2396 filter_ = (Filter) ctor.newInstance(); 2397 } catch(Exception e) { 2398 log("FilterReg.init() failed " + e); 2399 throw new ServletException(e); 2400 } 2401 } 2402 2403 filter_.init((FilterConfig) this); 2404 2405 initialized_ = true; 2406 } 2407 2408 public void destroy() 2409 { 2410 if (initialized_) { 2411 filter_.destroy(); 2412 } 2413 } 2414 2415 @Override 2416 public void addMappingForServletNames( 2417 EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, 2418 String... servletNames) 2419 { 2420 checkContextState(); 2421 2422 for (String n : servletNames) { 2423 trace("FilterReg.addMappingForServletNames: ... " + n); 2424 2425 ServletReg sreg = name2servlet_.get(n); 2426 if (sreg == null) { 2427 sreg = new ServletReg(n); 2428 servlets_.add(sreg); 2429 name2servlet_.put(n, sreg); 2430 } 2431 2432 FilterMap map = new FilterMap(this, sreg, dispatcherTypes, 2433 isMatchAfter); 2434 2435 sreg.addFilter(map); 2436 } 2437 } 2438 2439 @Override 2440 public Collection<String> getServletNameMappings() 2441 { 2442 checkContextState(); 2443 2444 log("FilterReg.getServletNameMappings"); 2445 return Collections.emptySet(); 2446 } 2447 2448 @Override 2449 public void addMappingForUrlPatterns( 2450 EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, 2451 String... urlPatterns) 2452 { 2453 checkContextState(); 2454 2455 for (String u : urlPatterns) { 2456 trace("FilterReg.addMappingForUrlPatterns: ... " + u); 2457 2458 URLPattern p = parseURLPattern(u); 2459 FilterMap map = new FilterMap(this, p, dispatcherTypes, 2460 isMatchAfter); 2461 2462 filter_maps_.add(map); 2463 } 2464 } 2465 2466 @Override 2467 public Collection<String> getUrlPatternMappings() 2468 { 2469 log("FilterReg.getUrlPatternMappings"); 2470 return Collections.emptySet(); 2471 } 2472 2473 @Override 2474 public void setAsyncSupported(boolean isAsyncSupported) 2475 { 2476 log("FilterReg.setAsyncSupported: " + isAsyncSupported); 2477 async_supported_ = isAsyncSupported; 2478 } 2479 2480 @Override 2481 public String getFilterName() 2482 { 2483 return getName(); 2484 } 2485 2486 @Override 2487 public ServletContext getServletContext() 2488 { 2489 return (ServletContext) Context.this; 2490 } 2491 } 2492 2493 private class FilterMap 2494 { 2495 private final FilterReg filter_; 2496 private final ServletReg servlet_; 2497 private final URLPattern pattern_; 2498 private final EnumSet<DispatcherType> dtypes_; 2499 private final boolean match_after_; 2500 2501 public FilterMap(FilterReg filter, ServletReg servlet, 2502 EnumSet<DispatcherType> dtypes, boolean match_after) 2503 { 2504 filter_ = filter; 2505 servlet_ = servlet; 2506 pattern_ = null; 2507 dtypes_ = dtypes; 2508 match_after_ = match_after; 2509 } 2510 2511 public FilterMap(FilterReg filter, URLPattern pattern, 2512 EnumSet<DispatcherType> dtypes, boolean match_after) 2513 { 2514 filter_ = filter; 2515 servlet_ = null; 2516 pattern_ = pattern; 2517 dtypes_ = dtypes; 2518 match_after_ = match_after; 2519 } 2520 } 2521 2522 private void initialized() 2523 { 2524 if (!sess_attr_listeners_.isEmpty()) { 2525 sess_attr_proxy_ = new SessionAttrProxy(sess_attr_listeners_); 2526 } 2527 2528 if (!req_attr_listeners_.isEmpty()) { 2529 req_attr_proxy_ = new RequestAttrProxy(req_attr_listeners_); 2530 } 2531 2532 ClassLoader old = Thread.currentThread().getContextClassLoader(); 2533 Thread.currentThread().setContextClassLoader(loader_); 2534 2535 try { 2536 // Call context listeners 2537 destroy_listeners_.clear(); 2538 if (!ctx_listeners_.isEmpty()) { 2539 ServletContextEvent event = new ServletContextEvent(this); 2540 for (ServletContextListener listener : ctx_listeners_) 2541 { 2542 try { 2543 listener.contextInitialized(event); 2544 } catch(AbstractMethodError e) { 2545 log("initialized: AbstractMethodError exception caught: " + e); 2546 } 2547 destroy_listeners_.add(0, listener); 2548 } 2549 } 2550 2551 for (ServletReg sr : servlets_) { 2552 try { 2553 sr.startup(); 2554 } catch(ServletException e) { 2555 log("initialized: exception caught: " + e); 2556 } 2557 } 2558 2559 for (FilterReg fr : filters_) { 2560 try { 2561 fr.init(); 2562 } catch(ServletException e) { 2563 log("initialized: exception caught: " + e); 2564 } 2565 } 2566 2567 ctx_initialized_ = true; 2568 } finally { 2569 Thread.currentThread().setContextClassLoader(old); 2570 } 2571 } 2572 2573 @Override 2574 public ServletContext getContext(String uripath) 2575 { 2576 trace("getContext for " + uripath); 2577 return this; 2578 } 2579 2580 @Override 2581 public int getMajorVersion() 2582 { 2583 trace("getMajorVersion"); 2584 return SERVLET_MAJOR_VERSION; 2585 } 2586 2587 @Override 2588 public String getMimeType(String file) 2589 { 2590 log("getMimeType for " + file); 2591 if (mime_types_ == null) { 2592 mime_types_ = new MimeTypes(); 2593 } 2594 return mime_types_.getMimeByExtension(file); 2595 } 2596 2597 @Override 2598 public int getMinorVersion() 2599 { 2600 trace("getMinorVersion"); 2601 return SERVLET_MINOR_VERSION; 2602 } 2603 2604 private class URIRequestDispatcher implements RequestDispatcher 2605 { 2606 private final URI uri_; 2607 2608 public URIRequestDispatcher(URI uri) 2609 { 2610 uri_ = uri; 2611 } 2612 2613 public URIRequestDispatcher(String uri) 2614 throws URISyntaxException 2615 { 2616 uri_ = new URI(uri); 2617 } 2618 2619 @Override 2620 public void forward(ServletRequest request, ServletResponse response) 2621 throws ServletException, IOException 2622 { 2623 /* 2624 9.4 The Forward Method 2625 ... 2626 If the response has been committed, an IllegalStateException 2627 must be thrown. 2628 */ 2629 if (response.isCommitted()) { 2630 throw new IllegalStateException("Response already committed"); 2631 } 2632 2633 ForwardRequestWrapper req = new ForwardRequestWrapper(request); 2634 2635 try { 2636 trace("URIRequestDispatcher.forward"); 2637 2638 String path = uri_.getPath().substring(context_path_.length()); 2639 2640 ServletReg servlet = findServlet(path, req); 2641 2642 req.setMultipartConfig(servlet.multipart_config_); 2643 2644 req.setRequestURI(uri_.getRawPath()); 2645 req.setQueryString(uri_.getRawQuery()); 2646 req.setDispatcherType(DispatcherType.FORWARD); 2647 2648 /* 2649 9.4 The Forward Method 2650 ... 2651 If output data exists in the response buffer that has not 2652 been committed, the content must be cleared before the 2653 target servlet's service method is called. 2654 */ 2655 response.resetBuffer(); 2656 2657 FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.FORWARD); 2658 2659 fc.doFilter(request, response); 2660 2661 /* 2662 9.4 The Forward Method 2663 ... 2664 Before the forward method of the RequestDispatcher interface 2665 returns without exception, the response content must be sent 2666 and committed, and closed by the servlet container, unless 2667 the request was put into the asynchronous mode. If an error 2668 occurs in the target of the RequestDispatcher.forward() the 2669 exception may be propagated back through all the calling 2670 filters and servlets and eventually back to the container 2671 */ 2672 if (!request.isAsyncStarted()) { 2673 response.flushBuffer(); 2674 } 2675 2676 /* 2677 9.5 Error Handling 2678 2679 If the servlet that is the target of a request dispatcher 2680 throws a runtime exception or a checked exception of type 2681 ServletException or IOException, it should be propagated 2682 to the calling servlet. All other exceptions should be 2683 wrapped as ServletExceptions and the root cause of the 2684 exception set to the original exception, as it should 2685 not be propagated. 2686 */ 2687 } catch (ServletException e) { 2688 throw e; 2689 } catch (IOException e) { 2690 throw e; 2691 } catch (Exception e) { 2692 throw new ServletException(e); 2693 } finally { 2694 req.close(); 2695 2696 trace("URIRequestDispatcher.forward done"); 2697 } 2698 } 2699 2700 @Override 2701 public void include(ServletRequest request, ServletResponse response) 2702 throws ServletException, IOException 2703 { 2704 IncludeRequestWrapper req = new IncludeRequestWrapper(request); 2705 2706 try { 2707 trace("URIRequestDispatcher.include"); 2708 2709 String path = uri_.getPath().substring(context_path_.length()); 2710 2711 ServletReg servlet = findServlet(path, req); 2712 2713 req.setMultipartConfig(servlet.multipart_config_); 2714 2715 req.setRequestURI(uri_.getRawPath()); 2716 req.setQueryString(uri_.getRawQuery()); 2717 req.setDispatcherType(DispatcherType.INCLUDE); 2718 2719 FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.INCLUDE); 2720 2721 fc.doFilter(request, new IncludeResponseWrapper(response)); 2722 2723 } catch (ServletException e) { 2724 throw e; 2725 } catch (IOException e) { 2726 throw e; 2727 } catch (Exception e) { 2728 throw new ServletException(e); 2729 } finally { 2730 req.close(); 2731 2732 trace("URIRequestDispatcher.include done"); 2733 } 2734 } 2735 } 2736 2737 private class ServletDispatcher implements RequestDispatcher 2738 { 2739 private final ServletReg servlet_; 2740 2741 public ServletDispatcher(ServletReg servlet) 2742 { 2743 servlet_ = servlet; 2744 } 2745 2746 @Override 2747 public void forward(ServletRequest request, ServletResponse response) 2748 throws ServletException, IOException 2749 { 2750 /* 2751 9.4 The Forward Method 2752 ... 2753 If the response has been committed, an IllegalStateException 2754 must be thrown. 2755 */ 2756 if (response.isCommitted()) { 2757 throw new IllegalStateException("Response already committed"); 2758 } 2759 2760 trace("ServletDispatcher.forward"); 2761 2762 DispatcherType dtype = request.getDispatcherType(); 2763 2764 Request req; 2765 if (request instanceof Request) { 2766 req = (Request) request; 2767 } else { 2768 req = (Request) request.getAttribute(Request.BARE); 2769 } 2770 2771 try { 2772 req.setDispatcherType(DispatcherType.FORWARD); 2773 2774 /* 2775 9.4 The Forward Method 2776 ... 2777 If output data exists in the response buffer that has not 2778 been committed, the content must be cleared before the 2779 target servlet's service method is called. 2780 */ 2781 response.resetBuffer(); 2782 2783 servlet_.service(request, response); 2784 2785 /* 2786 9.4 The Forward Method 2787 ... 2788 Before the forward method of the RequestDispatcher interface 2789 returns without exception, the response content must be sent 2790 and committed, and closed by the servlet container, unless 2791 the request was put into the asynchronous mode. If an error 2792 occurs in the target of the RequestDispatcher.forward() the 2793 exception may be propagated back through all the calling 2794 filters and servlets and eventually back to the container 2795 */ 2796 if (!request.isAsyncStarted()) { 2797 response.flushBuffer(); 2798 } 2799 2800 /* 2801 9.5 Error Handling 2802 2803 If the servlet that is the target of a request dispatcher 2804 throws a runtime exception or a checked exception of type 2805 ServletException or IOException, it should be propagated 2806 to the calling servlet. All other exceptions should be 2807 wrapped as ServletExceptions and the root cause of the 2808 exception set to the original exception, as it should 2809 not be propagated. 2810 */ 2811 } catch (ServletException e) { 2812 throw e; 2813 } catch (IOException e) { 2814 throw e; 2815 } catch (Exception e) { 2816 throw new ServletException(e); 2817 } finally { 2818 req.setDispatcherType(dtype); 2819 2820 trace("ServletDispatcher.forward done"); 2821 } 2822 } 2823 2824 @Override 2825 public void include(ServletRequest request, ServletResponse response) 2826 throws ServletException, IOException 2827 { 2828 trace("ServletDispatcher.include"); 2829 2830 DispatcherType dtype = request.getDispatcherType(); 2831 2832 Request req; 2833 if (request instanceof Request) { 2834 req = (Request) request; 2835 } else { 2836 req = (Request) request.getAttribute(Request.BARE); 2837 } 2838 2839 try { 2840 req.setDispatcherType(DispatcherType.INCLUDE); 2841 2842 servlet_.service(request, new IncludeResponseWrapper(response)); 2843 2844 } catch (ServletException e) { 2845 throw e; 2846 } catch (IOException e) { 2847 throw e; 2848 } catch (Exception e) { 2849 throw new ServletException(e); 2850 } finally { 2851 req.setDispatcherType(dtype); 2852 2853 trace("ServletDispatcher.include done"); 2854 } 2855 } 2856 } 2857 2858 @Override 2859 public RequestDispatcher getNamedDispatcher(String name) 2860 { 2861 trace("getNamedDispatcher for " + name); 2862 2863 ServletReg servlet = name2servlet_.get(name); 2864 if (servlet != null) { 2865 return new ServletDispatcher(servlet); 2866 } 2867 2868 return null; 2869 } 2870 2871 @Override 2872 public RequestDispatcher getRequestDispatcher(String uriInContext) 2873 { 2874 trace("getRequestDispatcher for " + uriInContext); 2875 try { 2876 return new URIRequestDispatcher(context_path_ + uriInContext); 2877 } catch (URISyntaxException e) { 2878 log("getRequestDispatcher: failed to create dispatcher: " + e); 2879 } 2880 2881 return null; 2882 } 2883 2884 public RequestDispatcher getRequestDispatcher(URI uri) 2885 { 2886 trace("getRequestDispatcher for " + uri.getRawPath()); 2887 return new URIRequestDispatcher(uri); 2888 } 2889 2890 @Override 2891 public String getRealPath(String path) 2892 { 2893 trace("getRealPath for " + path); 2894 2895 File f = new File(webapp_, path.isEmpty() ? "" : path.substring(1)); 2896 2897 return f.getAbsolutePath(); 2898 } 2899 2900 @Override 2901 public URL getResource(String path) throws MalformedURLException 2902 { 2903 trace("getResource for " + path); 2904 2905 File f = new File(webapp_, path.substring(1)); 2906 2907 if (f.exists()) { 2908 return new URL("file:" + f.getAbsolutePath()); 2909 } 2910 2911 return null; 2912 } 2913 2914 @Override 2915 public InputStream getResourceAsStream(String path) 2916 { 2917 trace("getResourceAsStream for " + path); 2918 2919 try { 2920 File f = new File(webapp_, path.substring(1)); 2921 2922 return new FileInputStream(f); 2923 } catch (FileNotFoundException e) { 2924 log("getResourceAsStream: failed " + e); 2925 2926 return null; 2927 } 2928 } 2929 2930 @Override 2931 public Set<String> getResourcePaths(String path) 2932 { 2933 trace("getResourcePaths for " + path); 2934 2935 File dir = new File(webapp_, path.substring(1)); 2936 File[] list = dir.listFiles(); 2937 2938 if (list == null) { 2939 return null; 2940 } 2941 2942 Set<String> res = new HashSet<>(); 2943 Path root = webapp_.toPath(); 2944 2945 for (File f : list) { 2946 String r = "/" + root.relativize(f.toPath()); 2947 if (f.isDirectory()) { 2948 r += "/"; 2949 } 2950 2951 trace("getResourcePaths: " + r); 2952 2953 res.add(r); 2954 } 2955 2956 return res; 2957 } 2958 2959 @Override 2960 public String getServerInfo() 2961 { 2962 trace("getServerInfo: " + server_info_); 2963 return server_info_; 2964 } 2965 2966 @Override 2967 @Deprecated 2968 public Servlet getServlet(String name) throws ServletException 2969 { 2970 log("getServlet for " + name); 2971 return null; 2972 } 2973 2974 @SuppressWarnings("unchecked") 2975 @Override 2976 @Deprecated 2977 public Enumeration<String> getServletNames() 2978 { 2979 log("getServletNames"); 2980 return Collections.enumeration(Collections.EMPTY_LIST); 2981 } 2982 2983 @SuppressWarnings("unchecked") 2984 @Override 2985 @Deprecated 2986 public Enumeration<Servlet> getServlets() 2987 { 2988 log("getServlets"); 2989 return Collections.enumeration(Collections.EMPTY_LIST); 2990 } 2991 2992 @Override 2993 @Deprecated 2994 public void log(Exception exception, String msg) 2995 { 2996 log(msg, exception); 2997 } 2998 2999 @Override 3000 public void log(String msg) 3001 { 3002 msg = "Context." + msg; 3003 log(0, msg, msg.length()); 3004 } 3005 3006 @Override 3007 public void log(String message, Throwable throwable) 3008 { 3009 log(message); 3010 } 3011 3012 private static native void log(long ctx_ptr, String msg, int msg_len); 3013 3014 3015 public static void trace(String msg) 3016 { 3017 msg = "Context." + msg; 3018 trace(0, msg, msg.length()); 3019 } 3020 3021 private static native void trace(long ctx_ptr, String msg, int msg_len); 3022 3023 @Override 3024 public String getInitParameter(String name) 3025 { 3026 trace("getInitParameter for " + name); 3027 return init_params_.get(name); 3028 } 3029 3030 @SuppressWarnings("unchecked") 3031 @Override 3032 public Enumeration<String> getInitParameterNames() 3033 { 3034 trace("getInitParameterNames"); 3035 return Collections.enumeration(Collections.EMPTY_LIST); 3036 } 3037 3038 @Override 3039 public String getServletContextName() 3040 { 3041 log("getServletContextName"); 3042 return "No Context"; 3043 } 3044 3045 @Override 3046 public String getContextPath() 3047 { 3048 trace("getContextPath"); 3049 return context_path_; 3050 } 3051 3052 @Override 3053 public boolean setInitParameter(String name, String value) 3054 { 3055 trace("setInitParameter " + name + " = " + value); 3056 return init_params_.putIfAbsent(name, value) == null; 3057 } 3058 3059 @Override 3060 public Object getAttribute(String name) 3061 { 3062 trace("getAttribute " + name); 3063 3064 return attributes_.get(name); 3065 } 3066 3067 @Override 3068 public Enumeration<String> getAttributeNames() 3069 { 3070 trace("getAttributeNames"); 3071 3072 Set<String> names = attributes_.keySet(); 3073 return Collections.enumeration(names); 3074 } 3075 3076 @Override 3077 public void setAttribute(String name, Object object) 3078 { 3079 trace("setAttribute " + name); 3080 3081 Object prev = attributes_.put(name, object); 3082 3083 if (ctx_attr_listeners_.isEmpty()) { 3084 return; 3085 } 3086 3087 ServletContextAttributeEvent scae = new ServletContextAttributeEvent( 3088 this, name, prev == null ? object : prev); 3089 3090 for (ServletContextAttributeListener l : ctx_attr_listeners_) { 3091 if (prev == null) { 3092 l.attributeAdded(scae); 3093 } else { 3094 l.attributeReplaced(scae); 3095 } 3096 } 3097 } 3098 3099 @Override 3100 public void removeAttribute(String name) 3101 { 3102 trace("removeAttribute " + name); 3103 3104 Object value = attributes_.remove(name); 3105 3106 if (ctx_attr_listeners_.isEmpty()) { 3107 return; 3108 } 3109 3110 ServletContextAttributeEvent scae = new ServletContextAttributeEvent( 3111 this, name, value); 3112 3113 for (ServletContextAttributeListener l : ctx_attr_listeners_) { 3114 l.attributeRemoved(scae); 3115 } 3116 } 3117 3118 @Override 3119 public FilterRegistration.Dynamic addFilter(String name, 3120 Class<? extends Filter> filterClass) 3121 { 3122 log("addFilter<C> " + name + ", " + filterClass.getName()); 3123 3124 checkContextState(); 3125 3126 FilterReg reg = new FilterReg(name, filterClass); 3127 filters_.add(reg); 3128 name2filter_.put(name, reg); 3129 return reg; 3130 } 3131 3132 @Override 3133 public FilterRegistration.Dynamic addFilter(String name, Filter filter) 3134 { 3135 log("addFilter<F> " + name); 3136 3137 checkContextState(); 3138 3139 FilterReg reg = new FilterReg(name, filter); 3140 filters_.add(reg); 3141 name2filter_.put(name, reg); 3142 return reg; 3143 } 3144 3145 @Override 3146 public FilterRegistration.Dynamic addFilter(String name, String className) 3147 { 3148 log("addFilter<N> " + name + ", " + className); 3149 3150 checkContextState(); 3151 3152 FilterReg reg = new FilterReg(name, className); 3153 filters_.add(reg); 3154 name2filter_.put(name, reg); 3155 return reg; 3156 } 3157 3158 @Override 3159 public ServletRegistration.Dynamic addServlet(String name, 3160 Class<? extends Servlet> servletClass) 3161 { 3162 log("addServlet<C> " + name + ", " + servletClass.getName()); 3163 3164 checkContextState(); 3165 3166 ServletReg reg = null; 3167 try { 3168 reg = new ServletReg(name, servletClass); 3169 servlets_.add(reg); 3170 name2servlet_.put(name, reg); 3171 } catch(Exception e) { 3172 System.err.println("addServlet: exception caught: " + e.toString()); 3173 } 3174 3175 return reg; 3176 } 3177 3178 @Override 3179 public ServletRegistration.Dynamic addServlet(String name, Servlet servlet) 3180 { 3181 log("addServlet<S> " + name); 3182 3183 checkContextState(); 3184 3185 ServletReg reg = null; 3186 try { 3187 reg = new ServletReg(name, servlet); 3188 servlets_.add(reg); 3189 name2servlet_.put(name, reg); 3190 } catch(Exception e) { 3191 System.err.println("addServlet: exception caught: " + e.toString()); 3192 } 3193 3194 return reg; 3195 } 3196 3197 @Override 3198 public ServletRegistration.Dynamic addServlet(String name, String className) 3199 { 3200 log("addServlet<N> " + name + ", " + className); 3201 3202 checkContextState(); 3203 3204 ServletReg reg = null; 3205 try { 3206 reg = new ServletReg(name, className); 3207 servlets_.add(reg); 3208 name2servlet_.put(name, reg); 3209 } catch(Exception e) { 3210 System.err.println("addServlet: exception caught: " + e.toString()); 3211 } 3212 3213 return reg; 3214 } 3215 3216 @Override 3217 public ServletRegistration.Dynamic addJspFile(String jspName, String jspFile) 3218 { 3219 log("addJspFile: " + jspName + " " + jspFile); 3220 3221 return null; 3222 } 3223 3224 @Override 3225 public <T extends Filter> T createFilter(Class<T> c) throws ServletException 3226 { 3227 log("createFilter<C> " + c.getName()); 3228 3229 checkContextState(); 3230 3231 try { 3232 Constructor<T> ctor = c.getConstructor(); 3233 T filter = ctor.newInstance(); 3234 return filter; 3235 } catch (Exception e) { 3236 log("createFilter() failed " + e); 3237 3238 throw new ServletException(e); 3239 } 3240 } 3241 3242 @Override 3243 public <T extends Servlet> T createServlet(Class<T> c) throws ServletException 3244 { 3245 log("createServlet<C> " + c.getName()); 3246 3247 checkContextState(); 3248 3249 try { 3250 Constructor<T> ctor = c.getConstructor(); 3251 T servlet = ctor.newInstance(); 3252 return servlet; 3253 } catch (Exception e) { 3254 log("createServlet() failed " + e); 3255 3256 throw new ServletException(e); 3257 } 3258 } 3259 3260 @Override 3261 public Set<SessionTrackingMode> getDefaultSessionTrackingModes() 3262 { 3263 log("getDefaultSessionTrackingModes"); 3264 3265 return default_session_tracking_modes_; 3266 } 3267 3268 @Override 3269 public Set<SessionTrackingMode> getEffectiveSessionTrackingModes() 3270 { 3271 log("getEffectiveSessionTrackingModes"); 3272 3273 return session_tracking_modes_; 3274 } 3275 3276 public boolean isSessionIdValid(String id) 3277 { 3278 synchronized (sessions_) { 3279 return sessions_.containsKey(id); 3280 } 3281 } 3282 3283 public Session getSession(String id) 3284 { 3285 synchronized (sessions_) { 3286 Session s = sessions_.get(id); 3287 3288 if (s != null) { 3289 s.accessed(); 3290 3291 if (s.checkTimeOut()) { 3292 s.invalidate(); 3293 return null; 3294 } 3295 } 3296 3297 return s; 3298 } 3299 } 3300 3301 public Session createSession() 3302 { 3303 Session session = new Session(this, generateSessionId(), 3304 sess_attr_proxy_, session_timeout_ * 60); 3305 3306 if (!sess_listeners_.isEmpty()) 3307 { 3308 HttpSessionEvent event = new HttpSessionEvent(session); 3309 3310 for (HttpSessionListener l : sess_listeners_) 3311 { 3312 l.sessionCreated(event); 3313 } 3314 } 3315 3316 synchronized (sessions_) { 3317 sessions_.put(session.getId(), session); 3318 3319 return session; 3320 } 3321 } 3322 3323 public void invalidateSession(Session session) 3324 { 3325 synchronized (sessions_) { 3326 sessions_.remove(session.getId()); 3327 } 3328 3329 if (!sess_listeners_.isEmpty()) 3330 { 3331 HttpSessionEvent event = new HttpSessionEvent(session); 3332 3333 for (int i = sess_listeners_.size() - 1; i >= 0; i--) 3334 { 3335 sess_listeners_.get(i).sessionDestroyed(event); 3336 } 3337 } 3338 } 3339 3340 public void changeSessionId(Session session) 3341 { 3342 String old_id; 3343 3344 synchronized (sessions_) { 3345 old_id = session.getId(); 3346 sessions_.remove(old_id); 3347 3348 session.setId(generateSessionId()); 3349 3350 sessions_.put(session.getId(), session); 3351 } 3352 3353 if (!sess_id_listeners_.isEmpty()) 3354 { 3355 HttpSessionEvent event = new HttpSessionEvent(session); 3356 for (HttpSessionIdListener l : sess_id_listeners_) 3357 { 3358 l.sessionIdChanged(event, old_id); 3359 } 3360 } 3361 } 3362 3363 private String generateSessionId() 3364 { 3365 return UUID.randomUUID().toString(); 3366 } 3367 3368 @Override 3369 public FilterRegistration getFilterRegistration(String filterName) 3370 { 3371 log("getFilterRegistration " + filterName); 3372 return name2filter_.get(filterName); 3373 } 3374 3375 @Override 3376 public Map<String, ? extends FilterRegistration> getFilterRegistrations() 3377 { 3378 log("getFilterRegistrations"); 3379 return name2filter_; 3380 } 3381 3382 @Override 3383 public ServletRegistration getServletRegistration(String servletName) 3384 { 3385 log("getServletRegistration " + servletName); 3386 return name2servlet_.get(servletName); 3387 } 3388 3389 @Override 3390 public Map<String, ? extends ServletRegistration> getServletRegistrations() 3391 { 3392 log("getServletRegistrations"); 3393 return name2servlet_; 3394 } 3395 3396 @Override 3397 public SessionCookieConfig getSessionCookieConfig() 3398 { 3399 log("getSessionCookieConfig"); 3400 3401 return session_cookie_config_; 3402 } 3403 3404 @Override 3405 public void setSessionTrackingModes(Set<SessionTrackingMode> modes) 3406 { 3407 log("setSessionTrackingModes"); 3408 3409 session_tracking_modes_ = modes; 3410 } 3411 3412 @Override 3413 public void addListener(String className) 3414 { 3415 trace("addListener<N> " + className); 3416 3417 checkContextState(); 3418 3419 if (listener_classnames_.contains(className)) { 3420 log("addListener<N> " + className + " already added as listener"); 3421 return; 3422 } 3423 3424 try { 3425 Class<?> cls = loader_.loadClass(className); 3426 3427 Constructor<?> ctor = cls.getConstructor(); 3428 EventListener listener = (EventListener) ctor.newInstance(); 3429 3430 addListener(listener); 3431 3432 listener_classnames_.add(className); 3433 } catch (Exception e) { 3434 log("addListener<N>: exception caught: " + e.toString()); 3435 } 3436 } 3437 3438 @Override 3439 public <T extends EventListener> void addListener(T t) 3440 { 3441 trace("addListener<T> " + t.getClass().getName()); 3442 3443 checkContextState(); 3444 3445 for (int i = 0; i < LISTENER_TYPES.length; i++) { 3446 Class<?> c = LISTENER_TYPES[i]; 3447 if (c.isAssignableFrom(t.getClass())) { 3448 trace("addListener<T>: assignable to " + c.getName()); 3449 } 3450 } 3451 3452 if (t instanceof ServletContextListener) { 3453 ctx_listeners_.add((ServletContextListener) t); 3454 } 3455 3456 if (t instanceof ServletContextAttributeListener) { 3457 ctx_attr_listeners_.add((ServletContextAttributeListener) t); 3458 } 3459 3460 if (t instanceof ServletRequestListener) { 3461 req_init_listeners_.add((ServletRequestListener) t); 3462 req_destroy_listeners_.add(0, (ServletRequestListener) t); 3463 } 3464 3465 if (t instanceof ServletRequestAttributeListener) { 3466 req_attr_listeners_.add((ServletRequestAttributeListener) t); 3467 } 3468 3469 if (t instanceof HttpSessionAttributeListener) { 3470 sess_attr_listeners_.add((HttpSessionAttributeListener) t); 3471 } 3472 3473 if (t instanceof HttpSessionIdListener) { 3474 sess_id_listeners_.add((HttpSessionIdListener) t); 3475 } 3476 3477 if (t instanceof HttpSessionListener) { 3478 sess_listeners_.add((HttpSessionListener) t); 3479 } 3480 } 3481 3482 @Override 3483 public void addListener(Class<? extends EventListener> listenerClass) 3484 { 3485 String className = listenerClass.getName(); 3486 trace("addListener<C> " + className); 3487 3488 checkContextState(); 3489 3490 if (listener_classnames_.contains(className)) { 3491 log("addListener<C> " + className + " already added as listener"); 3492 return; 3493 } 3494 3495 try { 3496 Constructor<?> ctor = listenerClass.getConstructor(); 3497 EventListener listener = (EventListener) ctor.newInstance(); 3498 3499 addListener(listener); 3500 3501 listener_classnames_.add(className); 3502 } catch (Exception e) { 3503 log("addListener<C>: exception caught: " + e.toString()); 3504 } 3505 } 3506 3507 @Override 3508 public <T extends EventListener> T createListener(Class<T> clazz) 3509 throws ServletException 3510 { 3511 trace("createListener<C> " + clazz.getName()); 3512 3513 checkContextState(); 3514 3515 try 3516 { 3517 return clazz.getDeclaredConstructor().newInstance(); 3518 } 3519 catch (Exception e) 3520 { 3521 throw new ServletException(e); 3522 } 3523 } 3524 3525 @Override 3526 public ClassLoader getClassLoader() 3527 { 3528 trace("getClassLoader"); 3529 return loader_; 3530 } 3531 3532 @Override 3533 public int getEffectiveMajorVersion() 3534 { 3535 log("getEffectiveMajorVersion"); 3536 return SERVLET_MAJOR_VERSION; 3537 } 3538 3539 @Override 3540 public int getEffectiveMinorVersion() 3541 { 3542 log("getEffectiveMinorVersion"); 3543 return SERVLET_MINOR_VERSION; 3544 } 3545 3546 private final List<TaglibDescriptor> taglibs_ = new ArrayList<>(); 3547 private final List<JspPropertyGroupDescriptor> prop_groups_ = new ArrayList<>(); 3548 3549 private class JspConfig implements JspConfigDescriptor 3550 { 3551 @Override 3552 public Collection<TaglibDescriptor> getTaglibs() 3553 { 3554 trace("getTaglibs"); 3555 return taglibs_; 3556 } 3557 3558 @Override 3559 public Collection<JspPropertyGroupDescriptor> getJspPropertyGroups() 3560 { 3561 trace("getJspPropertyGroups"); 3562 return prop_groups_; 3563 } 3564 } 3565 3566 private final JspConfig jsp_config_ = new JspConfig(); 3567 3568 @Override 3569 public JspConfigDescriptor getJspConfigDescriptor() 3570 { 3571 trace("getJspConfigDescriptor"); 3572 3573 return jsp_config_; 3574 } 3575 3576 @Override 3577 public void declareRoles(String... roleNames) 3578 { 3579 log("declareRoles"); 3580 //LOG.warn(__unimplmented); 3581 } 3582 3583 @Override 3584 public String getVirtualServerName() 3585 { 3586 log("getVirtualServerName"); 3587 return null; 3588 } 3589 3590 @Override 3591 public int getSessionTimeout() 3592 { 3593 trace("getSessionTimeout"); 3594 3595 return session_timeout_; 3596 } 3597 3598 @Override 3599 public void setSessionTimeout(int sessionTimeout) 3600 { 3601 trace("setSessionTimeout: " + sessionTimeout); 3602 3603 session_timeout_ = sessionTimeout; 3604 } 3605 3606 @Override 3607 public String getRequestCharacterEncoding() 3608 { 3609 log("getRequestCharacterEncoding"); 3610 3611 return null; 3612 } 3613 3614 @Override 3615 public void setRequestCharacterEncoding(String encoding) 3616 { 3617 log("setRequestCharacterEncoding: " + encoding); 3618 } 3619 3620 @Override 3621 public String getResponseCharacterEncoding() 3622 { 3623 log("getResponseCharacterEncoding"); 3624 3625 return null; 3626 } 3627 3628 @Override 3629 public void setResponseCharacterEncoding(String encoding) 3630 { 3631 log("setResponseCharacterEncoding: " + encoding); 3632 } 3633 3634 public ServletRequestAttributeListener getRequestAttributeListener() 3635 { 3636 return req_attr_proxy_; 3637 } 3638 } 3639