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