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