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