xref: /unit/src/java/nginx/unit/Context.java (revision 1693:579324e12261)
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