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