summaryrefslogtreecommitdiffhomepage
path: root/src/java/nginx/unit/Context.java
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/java/nginx/unit/Context.java3502
1 files changed, 3502 insertions, 0 deletions
diff --git a/src/java/nginx/unit/Context.java b/src/java/nginx/unit/Context.java
new file mode 100644
index 00000000..643a336b
--- /dev/null
+++ b/src/java/nginx/unit/Context.java
@@ -0,0 +1,3502 @@
+package nginx.unit;
+
+import io.github.classgraph.ClassGraph;
+import io.github.classgraph.ClassInfo;
+import io.github.classgraph.ClassInfoList;
+import io.github.classgraph.ScanResult;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+
+import java.lang.ClassLoader;
+import java.lang.ClassNotFoundException;
+import java.lang.IllegalArgumentException;
+import java.lang.IllegalStateException;
+import java.lang.reflect.Constructor;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.BasicFileAttributes;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.UUID;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarInputStream;
+import java.util.zip.ZipEntry;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.FilterRegistration.Dynamic;
+import javax.servlet.FilterRegistration;
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.Registration;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextAttributeEvent;
+import javax.servlet.ServletContextAttributeListener;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestAttributeEvent;
+import javax.servlet.ServletRequestAttributeListener;
+import javax.servlet.ServletRequestEvent;
+import javax.servlet.ServletRequestListener;
+import javax.servlet.ServletSecurityElement;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.annotation.HandlesTypes;
+import javax.servlet.annotation.WebInitParam;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.annotation.WebFilter;
+import javax.servlet.annotation.WebListener;
+import javax.servlet.descriptor.JspConfigDescriptor;
+import javax.servlet.descriptor.JspPropertyGroupDescriptor;
+import javax.servlet.descriptor.TaglibDescriptor;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionIdListener;
+import javax.servlet.http.HttpSessionListener;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.eclipse.jetty.http.MimeTypes;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import org.apache.jasper.servlet.JspServlet;
+import org.apache.jasper.servlet.JasperInitializer;
+
+
+public class Context implements ServletContext, InitParams
+{
+ public final static int SERVLET_MAJOR_VERSION = 3;
+ public final static int SERVLET_MINOR_VERSION = 1;
+
+ private String context_path_ = "";
+ private String server_info_ = "unit";
+ private String app_version_ = "";
+ private MimeTypes mime_types_;
+ private boolean metadata_complete_ = false;
+ private boolean welcome_files_list_found_ = false;
+ private boolean ctx_initialized_ = false;
+
+ private ClassLoader loader_;
+ private File webapp_;
+ private File extracted_dir_;
+ private File temp_dir_;
+
+ private final Map<String, String> init_params_ = new HashMap<>();
+ private final Map<String, Object> attributes_ = new HashMap<>();
+
+ private final Map<String, URLPattern> parsed_patterns_ = new HashMap<>();
+
+ private final List<FilterReg> filters_ = new ArrayList<>();
+ private final Map<String, FilterReg> name2filter_ = new HashMap<>();
+ private final List<FilterMap> filter_maps_ = new ArrayList<>();
+
+ private final List<ServletReg> servlets_ = new ArrayList<>();
+ private final Map<String, ServletReg> name2servlet_ = new HashMap<>();
+ private final Map<String, ServletReg> pattern2servlet_ = new HashMap<>();
+ private final Map<String, ServletReg> exact2servlet_ = new HashMap<>();
+ private final List<PrefixPattern> prefix_patterns_ = new ArrayList<>();
+ private final Map<String, ServletReg> suffix2servlet_ = new HashMap<>();
+ private ServletReg default_servlet_;
+ private ServletReg system_default_servlet_;
+
+ private final List<String> welcome_files_ = new ArrayList<>();
+
+ private final Map<String, String> exception2location_ = new HashMap<>();
+ private final Map<Integer, String> error2location_ = new HashMap<>();
+
+ public static final Class<?>[] LISTENER_TYPES = new Class[] {
+ ServletContextListener.class,
+ ServletContextAttributeListener.class,
+ ServletRequestListener.class,
+ ServletRequestAttributeListener.class,
+ HttpSessionAttributeListener.class,
+ HttpSessionIdListener.class,
+ HttpSessionListener.class
+ };
+
+ private final List<String> pending_listener_classnames_ = new ArrayList<>();
+ private final Set<String> listener_classnames_ = new HashSet<>();
+
+ private final List<ServletContextListener> ctx_listeners_ = new ArrayList<>();
+ private final List<ServletContextListener> destroy_listeners_ = new ArrayList<>();
+ private final List<ServletContextAttributeListener> ctx_attr_listeners_ = new ArrayList<>();
+ private final List<ServletRequestListener> req_init_listeners_ = new ArrayList<>();
+ private final List<ServletRequestListener> req_destroy_listeners_ = new ArrayList<>();
+ private final List<ServletRequestAttributeListener> req_attr_listeners_ = new ArrayList<>();
+
+ private ServletRequestAttributeListener req_attr_proxy_ = null;
+
+ private final List<HttpSessionAttributeListener> sess_attr_listeners_ = new ArrayList<>();
+ private final List<HttpSessionIdListener> sess_id_listeners_ = new ArrayList<>();
+ private final List<HttpSessionListener> sess_listeners_ = new ArrayList<>();
+
+ private HttpSessionAttributeListener sess_attr_proxy_ = null;
+
+ private final SessionCookieConfig session_cookie_config_ = new UnitSessionCookieConfig();
+ private final Set<SessionTrackingMode> default_session_tracking_modes_ = new HashSet<>();
+ private Set<SessionTrackingMode> session_tracking_modes_ = default_session_tracking_modes_;
+ private int session_timeout_ = 60;
+
+ private final Map<String, Session> sessions_ = new HashMap<>();
+
+ private static final String WEB_INF = "WEB-INF/";
+ private static final String WEB_INF_CLASSES = WEB_INF + "classes/";
+ private static final String WEB_INF_LIB = WEB_INF + "lib/";
+
+ private class PrefixPattern implements Comparable<PrefixPattern>
+ {
+ public final String pattern;
+ public final ServletReg servlet;
+
+ public PrefixPattern(String p, ServletReg s)
+ {
+ pattern = p;
+ servlet = s;
+ }
+
+ public boolean match(String url)
+ {
+ return url.startsWith(pattern) && (
+ url.length() == pattern.length()
+ || url.charAt(pattern.length()) == '/');
+ }
+
+ @Override
+ public int compareTo(PrefixPattern p)
+ {
+ return p.pattern.length() - pattern.length();
+ }
+ }
+
+ private class StaticServlet extends HttpServlet
+ {
+ @Override
+ public void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ doGet(request, response);
+ }
+
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ String path = null;
+
+ if (request.getDispatcherType() == DispatcherType.INCLUDE) {
+ path = (String) request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
+ }
+
+ if (path == null) {
+ path = request.getServletPath();
+ }
+
+ /*
+ 10.6 Web Application Archive File
+ ...
+ This directory [META-INF] must not be directly served as
+ content by the container in response to a Web client's request,
+ though its contents are visible to servlet code via the
+ getResource and getResourceAsStream calls on the
+ ServletContext. Also, any requests to access the resources in
+ META-INF directory must be returned with a SC_NOT_FOUND(404)
+ response.
+ */
+ if (request.getDispatcherType() == DispatcherType.REQUEST
+ && (path.equals("/WEB-INF") || path.startsWith("/WEB-INF/")
+ || path.equals("/META-INF") || path.startsWith("/META-INF/")))
+ {
+ response.sendError(response.SC_NOT_FOUND);
+ return;
+ }
+
+ if (path.startsWith("/")) {
+ path = path.substring(1);
+ }
+
+ File f = new File(webapp_, path);
+ if (!f.exists()) {
+ if (request.getDispatcherType() == DispatcherType.INCLUDE) {
+ /*
+ 9.3 The Include Method
+ ...
+ If the default servlet is the target of a
+ RequestDispatch.include() and the requested resource
+ does not exist, then the default servlet MUST throw
+ FileNotFoundException.
+ */
+
+ throw new FileNotFoundException();
+ }
+
+ response.sendError(response.SC_NOT_FOUND);
+ return;
+ }
+
+ long ims = request.getDateHeader("If-Modified-Since");
+ long lm = f.lastModified();
+
+ if (lm < ims) {
+ response.sendError(response.SC_NOT_MODIFIED);
+ return;
+ }
+
+ response.setDateHeader("Last-Modified", f.lastModified());
+
+ if (f.isDirectory()) {
+ String url = request.getRequestURL().toString();
+ if (!url.endsWith("/")) {
+ response.setHeader("Location", url + "/");
+ response.sendError(response.SC_FOUND);
+ return;
+ }
+
+ String[] ls = f.list();
+
+ PrintWriter writer = response.getWriter();
+
+ for (String n : ls) {
+ writer.println("<a href=\"" + n + "\">" + n + "</a><br>");
+ }
+
+ writer.close();
+
+ } else {
+ response.setContentLengthLong(f.length());
+
+ InputStream is = new FileInputStream(f);
+ byte[] buffer = new byte[response.getBufferSize()];
+ ServletOutputStream os = response.getOutputStream();
+ while (true) {
+ int read = is.read(buffer);
+ if (read == -1) {
+ break;
+ }
+ os.write(buffer, 0, read);
+ }
+
+ os.close();
+ }
+ }
+ }
+
+ public static Context start(String webapp, URL[] classpaths)
+ throws Exception
+ {
+ Context ctx = new Context();
+
+ ctx.loadApp(webapp, classpaths);
+ ctx.initialized();
+
+ return ctx;
+ }
+
+ public Context()
+ {
+ default_session_tracking_modes_.add(SessionTrackingMode.COOKIE);
+
+ context_path_ = System.getProperty("nginx.unit.context.path", "").trim();
+
+ if (context_path_.endsWith("/")) {
+ context_path_ = context_path_.substring(0, context_path_.length() - 1);
+ }
+
+ if (!context_path_.isEmpty() && !context_path_.startsWith("/")) {
+ context_path_ = "/" + context_path_;
+ }
+
+ if (context_path_.isEmpty()) {
+ session_cookie_config_.setPath("/");
+ } else {
+ session_cookie_config_.setPath(context_path_);
+ }
+ }
+
+ public void loadApp(String webapp, URL[] classpaths)
+ throws Exception
+ {
+ File root = new File(webapp);
+ if (!root.exists()) {
+ throw new FileNotFoundException(
+ "Unable to determine code source archive from " + root);
+ }
+
+ ArrayList<URL> url_list = new ArrayList<>();
+
+ for (URL u : classpaths) {
+ url_list.add(u);
+ }
+
+ if (!root.isDirectory()) {
+ root = extractWar(root);
+ extracted_dir_ = root;
+ }
+
+ webapp_ = root;
+
+ Path tmpDir = Files.createTempDirectory("webapp");
+ temp_dir_ = tmpDir.toFile();
+ setAttribute(ServletContext.TEMPDIR, temp_dir_);
+
+ File web_inf_classes = new File(root, WEB_INF_CLASSES);
+ if (web_inf_classes.exists() && web_inf_classes.isDirectory()) {
+ url_list.add(new URL("file:" + root.getAbsolutePath() + "/" + WEB_INF_CLASSES));
+ }
+
+ File lib = new File(root, WEB_INF_LIB);
+ File[] libs = lib.listFiles();
+
+ if (libs != null) {
+ for (File l : libs) {
+ url_list.add(new URL("file:" + l.getAbsolutePath()));
+ }
+ }
+
+ URL[] urls = new URL[url_list.size()];
+
+ for (int i = 0; i < url_list.size(); i++) {
+ urls[i] = url_list.get(i);
+ trace("archives: " + urls[i]);
+ }
+
+ String custom_listener = System.getProperty("nginx.unit.context.listener", "").trim();
+ if (!custom_listener.isEmpty()) {
+ pending_listener_classnames_.add(custom_listener);
+ }
+
+ processWebXml(root);
+
+ loader_ = new AppClassLoader(urls,
+ Context.class.getClassLoader().getParent());
+
+ ClassLoader old = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(loader_);
+
+ try {
+ for (String listener_classname : pending_listener_classnames_) {
+ addListener(listener_classname);
+ }
+
+ ScanResult scan_res = null;
+
+ if (!metadata_complete_) {
+ ClassGraph classgraph = new ClassGraph()
+ //.verbose()
+ .overrideClassLoaders(loader_)
+ .ignoreParentClassLoaders()
+ .enableClassInfo()
+ .enableAnnotationInfo()
+ //.enableSystemPackages()
+ .whitelistModules("javax.*")
+ //.enableAllInfo()
+ ;
+
+ String verbose = System.getProperty("nginx.unit.context.classgraph.verbose", "").trim();
+
+ if (verbose.equals("true")) {
+ classgraph.verbose();
+ }
+
+ scan_res = classgraph.scan();
+
+ loadInitializers(scan_res);
+ }
+
+ if (!metadata_complete_) {
+ scanClasses(scan_res);
+ }
+
+ /*
+ 8.1.6 Other annotations / conventions
+ ...
+ By default all applications will have index.htm(l) and index.jsp
+ in the list of welcome-file-list. The descriptor may to be used
+ to override these default settings.
+ */
+ if (!welcome_files_list_found_) {
+ welcome_files_.add("index.htm");
+ welcome_files_.add("index.html");
+ welcome_files_.add("index.jsp");
+ }
+
+ ServletReg jsp_servlet = name2servlet_.get("jsp");
+ if (jsp_servlet == null) {
+ jsp_servlet = new ServletReg("jsp", JspServlet.class);
+ jsp_servlet.system_jsp_servlet_ = true;
+ servlets_.add(jsp_servlet);
+ name2servlet_.put("jsp", jsp_servlet);
+ }
+
+ if (jsp_servlet.getClassName() == null) {
+ jsp_servlet.setClass(JspServlet.class);
+ jsp_servlet.system_jsp_servlet_ = true;
+ }
+
+ if (jsp_servlet.patterns_.isEmpty()) {
+ parseURLPattern("*.jsp", jsp_servlet);
+ parseURLPattern("*.jspx", jsp_servlet);
+ }
+
+ ServletReg def_servlet = name2servlet_.get("default");
+ if (def_servlet == null) {
+ def_servlet = new ServletReg("default", new StaticServlet());
+ def_servlet.servlet_ = new StaticServlet();
+ servlets_.add(def_servlet);
+ name2servlet_.put("default", def_servlet);
+ }
+
+ if (def_servlet.getClassName() == null) {
+ def_servlet.setClass(StaticServlet.class);
+ def_servlet.servlet_ = new StaticServlet();
+ }
+
+ system_default_servlet_ = def_servlet;
+
+ for (PrefixPattern p : prefix_patterns_) {
+ /*
+ Optimization: add prefix patterns to exact2servlet_ map.
+ This should not affect matching result because full path
+ is the longest matched prefix.
+ */
+ if (!exact2servlet_.containsKey(p.pattern)) {
+ trace("adding prefix pattern " + p.pattern + " to exact patterns map");
+ exact2servlet_.put(p.pattern, p.servlet);
+ }
+ }
+
+ Collections.sort(prefix_patterns_);
+ } finally {
+ Thread.currentThread().setContextClassLoader(old);
+ }
+ }
+
+ private static class AppClassLoader extends URLClassLoader
+ {
+ static {
+ ClassLoader.registerAsParallelCapable();
+ }
+
+ private final static String[] system_prefix =
+ {
+ "java/", // Java SE classes (per servlet spec v2.5 / SRV.9.7.2)
+ "javax/", // Java SE classes (per servlet spec v2.5 / SRV.9.7.2)
+ "org/w3c/", // needed by javax.xml
+ "org/xml/", // needed by javax.xml
+ };
+
+ private ClassLoader system_loader;
+
+ public AppClassLoader(URL[] urls, ClassLoader parent)
+ {
+ super(urls, parent);
+
+ ClassLoader j = String.class.getClassLoader();
+ if (j == null) {
+ j = getSystemClassLoader();
+ while (j.getParent() != null) {
+ j = j.getParent();
+ }
+ }
+ system_loader = j;
+ }
+
+ private boolean isSystemPath(String path)
+ {
+ int i = Arrays.binarySearch(system_prefix, path);
+
+ if (i >= 0) {
+ return true;
+ }
+
+ i = -i - 1;
+
+ if (i > 0) {
+ return path.startsWith(system_prefix[i - 1]);
+ }
+
+ return false;
+ }
+
+ @Override
+ public URL getResource(String name)
+ {
+ URL res;
+
+ String s = "getResource: " + name;
+ trace(0, s, s.length());
+
+ /*
+ This is a required for compatibility with Tomcat which
+ stores all resources prefixed with '/' and application code
+ may try to get resource with leading '/' (like Jira). Jetty
+ also has such workaround in WebAppClassLoader.getResource().
+ */
+ if (name.startsWith("/")) {
+ name = name.substring(1);
+ }
+
+ if (isSystemPath(name)) {
+ return super.getResource(name);
+ }
+
+ res = system_loader.getResource(name);
+ if (res != null) {
+ return res;
+ }
+
+ res = findResource(name);
+ if (res != null) {
+ return res;
+ }
+
+ return super.getResource(name);
+ }
+
+ @Override
+ protected Class<?> loadClass(String name, boolean resolve)
+ throws ClassNotFoundException
+ {
+ synchronized (this) {
+ Class<?> res = findLoadedClass(name);
+ if (res != null) {
+ return res;
+ }
+
+ try {
+ res = system_loader.loadClass(name);
+
+ if (resolve) {
+ resolveClass(res);
+ }
+
+ return res;
+ } catch (ClassNotFoundException e) {
+ }
+
+ String path = name.replace('.', '/').concat(".class");
+
+ if (isSystemPath(path)) {
+ return super.loadClass(name, resolve);
+ }
+
+ URL url = findResource(path);
+
+ if (url != null) {
+ res = super.findClass(name);
+
+ if (resolve) {
+ resolveClass(res);
+ }
+
+ return res;
+ }
+
+ return super.loadClass(name, resolve);
+ }
+
+ }
+ }
+
+ private File extractWar(File war) throws IOException
+ {
+ Path tmpDir = Files.createTempDirectory("webapp");
+
+ JarFile jf = new JarFile(war);
+
+ for (Enumeration<JarEntry> en = jf.entries(); en.hasMoreElements();) {
+ JarEntry e = en.nextElement();
+ long mod_time = e.getTime();
+ Path ep = tmpDir.resolve(e.getName());
+ Path p;
+ if (e.isDirectory()) {
+ p = ep;
+ } else {
+ p = ep.getParent();
+ }
+
+ if (!p.toFile().isDirectory()) {
+ Files.createDirectories(p);
+ }
+
+ if (!e.isDirectory()) {
+ Files.copy(jf.getInputStream(e), ep,
+ StandardCopyOption.REPLACE_EXISTING);
+ }
+
+ if (mod_time > 0) {
+ ep.toFile().setLastModified(mod_time);
+ }
+ }
+
+ return tmpDir.toFile();
+ }
+
+ private class CtxFilterChain implements FilterChain
+ {
+ private int filter_index_ = 0;
+ private final ServletReg servlet_;
+ private final List<FilterReg> filters_;
+
+ CtxFilterChain(ServletReg servlet, String path, DispatcherType dtype)
+ {
+ servlet_ = servlet;
+
+ List<FilterReg> filters = new ArrayList<>();
+
+ for (FilterMap m : filter_maps_) {
+ if (filters.indexOf(m.filter_) != -1) {
+ continue;
+ }
+
+ if (!m.dtypes_.contains(dtype)) {
+ continue;
+ }
+
+ if (m.pattern_.match(path)) {
+ filters.add(m.filter_);
+
+ trace("add filter (matched): " + m.filter_.getName());
+ }
+ }
+
+ for (FilterMap m : servlet.filters_) {
+ if (filters.indexOf(m.filter_) != -1) {
+ continue;
+ }
+
+ if (!m.dtypes_.contains(dtype)) {
+ continue;
+ }
+
+ filters.add(m.filter_);
+
+ trace("add filter (servlet): " + m.filter_.getName());
+ }
+
+ filters_ = filters;
+ }
+
+ @Override
+ public void doFilter (ServletRequest request, ServletResponse response)
+ throws IOException, ServletException
+ {
+ if (filter_index_ < filters_.size()) {
+ filters_.get(filter_index_++).filter_.doFilter(request, response, this);
+
+ return;
+ }
+
+ servlet_.service(request, response);
+ }
+ }
+
+ private ServletReg findServlet(String path, DynamicPathRequest req)
+ {
+ /*
+ 12.1 Use of URL Paths
+ ...
+ 1. The container will try to find an exact match of the path of the
+ request to the path of the servlet. A successful match selects
+ the servlet.
+ */
+ ServletReg servlet = exact2servlet_.get(path);
+ if (servlet != null) {
+ trace("findServlet: '" + path + "' exact matched pattern");
+ req.setServletPath(path, null);
+ return servlet;
+ }
+
+ /*
+ 2. The container will recursively try to match the longest
+ path-prefix. This is done by stepping down the path tree a
+ directory at a time, using the '/' character as a path separator.
+ The longest match determines the servlet selected.
+ */
+ for (PrefixPattern p : prefix_patterns_) {
+ if (p.match(path)) {
+ trace("findServlet: '" + path + "' matched prefix pattern '" + p.pattern + "'");
+ if (p.pattern.length() == path.length()) {
+ log("findServlet: WARNING: it is expected '" + path + "' exactly matches " + p.pattern);
+ req.setServletPath(path, p.pattern, null);
+ } else {
+ req.setServletPath(path, p.pattern, path.substring(p.pattern.length()));
+ }
+ return p.servlet;
+ }
+ }
+
+ /*
+ 3. If the last segment in the URL path contains an extension
+ (e.g. .jsp), the servlet container will try to match a servlet
+ that handles requests for the extension. An extension is defined
+ as the part of the last segment after the last '.' character.
+ */
+ int suffix_start = path.lastIndexOf('.');
+ if (suffix_start != -1) {
+ String suffix = path.substring(suffix_start);
+ servlet = suffix2servlet_.get(suffix);
+ if (servlet != null) {
+ trace("findServlet: '" + path + "' matched suffix pattern");
+ req.setServletPath(path, null);
+ return servlet;
+ }
+ }
+
+ /*
+ 4. If neither of the previous three rules result in a servlet match,
+ the container will attempt to serve content appropriate for the
+ resource requested. If a "default" servlet is defined for the
+ application, it will be used. ...
+ */
+ if (default_servlet_ != null) {
+ trace("findServlet: '" + path + "' matched default servlet");
+ req.setServletPath(path, null);
+ return default_servlet_;
+ }
+
+ trace("findServlet: '" + path + "' no servlet found");
+
+ /*
+ 10.10 Welcome Files
+ ...
+ If a Web container receives a valid partial request, the Web
+ container must examine the welcome file list defined in the
+ deployment descriptor.
+ ...
+ */
+ if (path.endsWith("/")) {
+
+ /*
+ The Web server must append each welcome file in the order
+ specified in the deployment descriptor to the partial request
+ and check whether a static resource in the WAR is mapped to
+ that request URI.
+ */
+ for (String wf : welcome_files_) {
+ String wpath = path + wf;
+
+ File f = new File(webapp_, wpath.substring(1));
+ if (!f.exists()) {
+ continue;
+ }
+
+ trace("findServlet: '" + path + "' found static welcome "
+ + "file '" + wf + "'");
+
+ /*
+ Even if static file found, we should try to find matching
+ servlet for JSP serving etc.
+ */
+ servlet = findWelcomeServlet(wpath, true, req);
+ if (servlet != null) {
+ return servlet;
+ }
+
+ req.setServletPath(wpath, null);
+
+ return system_default_servlet_;
+ }
+
+ /*
+ If no match is found, the Web server MUST again append each
+ welcome file in the order specified in the deployment
+ descriptor to the partial request and check if a servlet is
+ mapped to that request URI. The Web container must send the
+ request to the first resource in the WAR that matches.
+ */
+ for (String wf : welcome_files_) {
+ String wpath = path + wf;
+
+ servlet = findWelcomeServlet(wpath, false, req);
+ if (servlet != null) {
+ return servlet;
+ }
+ }
+ }
+
+ trace("findServlet: '" + path + "' fallback to system default servlet");
+ req.setServletPath(path, null);
+
+ return system_default_servlet_;
+ }
+
+ private ServletReg findWelcomeServlet(String path, boolean exists,
+ DynamicPathRequest req)
+ {
+ ServletReg servlet = exact2servlet_.get(path);
+ if (servlet != null) {
+ trace("findWelcomeServlet: '" + path + "' exact matched pattern");
+ req.setServletPath(path, null);
+
+ return servlet;
+ }
+
+ int suffix_start = path.lastIndexOf('.');
+ if (suffix_start == -1) {
+ return null;
+ }
+
+ String suffix = path.substring(suffix_start);
+ servlet = suffix2servlet_.get(suffix);
+ if (servlet == null) {
+ return null;
+ }
+
+ trace("findWelcomeServlet: '" + path + "' matched suffix pattern");
+
+ /*
+ If we want to show the directory content when
+ index.jsp is absent, then we have to check file
+ presence here. Otherwise user will get 404.
+ */
+
+ if (servlet.system_jsp_servlet_ && !exists) {
+ trace("findWelcomeServlet: '" + path + "' not exists");
+ return null;
+ }
+
+ req.setServletPath(path, null);
+
+ return servlet;
+ }
+
+ public void service(Request req, Response resp)
+ throws ServletException, IOException
+ {
+ ClassLoader old = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(loader_);
+
+ ServletRequestEvent sre = null;
+
+ try {
+ if (!req_init_listeners_.isEmpty()) {
+ sre = new ServletRequestEvent(this, req);
+
+ for (ServletRequestListener l : req_init_listeners_) {
+ l.requestInitialized(sre);
+ }
+ }
+
+ URI uri = new URI(req.getRequestURI());
+ String path = uri.getPath();
+
+ if (!path.startsWith(context_path_)
+ || (path.length() > context_path_.length()
+ && path.charAt(context_path_.length()) != '/'))
+ {
+ trace("service: '" + path + "' not started with '" + context_path_ + "'");
+
+ resp.sendError(resp.SC_NOT_FOUND);
+ return;
+ }
+
+ if (path.equals(context_path_)) {
+ String url = req.getRequestURL().toString();
+ if (!url.endsWith("/")) {
+ resp.setHeader("Location", url + "/");
+ resp.sendError(resp.SC_FOUND);
+ return;
+ }
+ }
+
+ path = path.substring(context_path_.length());
+
+ ServletReg servlet = findServlet(path, req);
+
+ FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.REQUEST);
+
+ fc.doFilter(req, resp);
+
+ Object code = req.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
+ if (code != null && code instanceof Integer) {
+ handleStatusCode((Integer) code, req, resp);
+ }
+ } catch (Throwable e) {
+ trace("service: caught " + e);
+
+ try {
+ if (!resp.isCommitted() && !exception2location_.isEmpty()) {
+ handleException(e, req, resp);
+ }
+
+ if (!resp.isCommitted()) {
+ resp.reset();
+ resp.setStatus(resp.SC_INTERNAL_SERVER_ERROR);
+ resp.setContentType("text/plain");
+
+ PrintWriter w = resp.getWriter();
+ w.println("Unhandled exception: " + e);
+ e.printStackTrace(w);
+
+ w.close();
+ }
+ } finally {
+ throw new ServletException(e);
+ }
+ } finally {
+ resp.flushBuffer();
+
+ try {
+ if (!req_destroy_listeners_.isEmpty()) {
+ for (ServletRequestListener l : req_destroy_listeners_) {
+ l.requestDestroyed(sre);
+ }
+ }
+ } finally {
+ Thread.currentThread().setContextClassLoader(old);
+ }
+ }
+ }
+
+ private void handleException(Throwable e, Request req, Response resp)
+ throws ServletException, IOException
+ {
+ String location;
+
+ Class<?> cls = e.getClass();
+ while (cls != null && !cls.equals(Throwable.class)) {
+ location = exception2location_.get(cls.getName());
+
+ if (location != null) {
+ trace("Exception " + e + " matched. Error page location: " + location);
+
+ req.setAttribute_(RequestDispatcher.ERROR_EXCEPTION, e);
+ req.setAttribute_(RequestDispatcher.ERROR_EXCEPTION_TYPE, e.getClass());
+ req.setAttribute_(RequestDispatcher.ERROR_REQUEST_URI, req.getRequestURI());
+ req.setAttribute_(RequestDispatcher.ERROR_STATUS_CODE, resp.SC_INTERNAL_SERVER_ERROR);
+
+ handleError(location, req, resp);
+
+ return;
+ }
+
+ cls = cls.getSuperclass();
+ }
+
+ if (ServletException.class.isAssignableFrom(e.getClass())) {
+ ServletException se = (ServletException) e;
+
+ handleException(se.getRootCause(), req, resp);
+ }
+ }
+
+ private void handleStatusCode(int code, Request req, Response resp)
+ throws ServletException, IOException
+ {
+ String location;
+
+ location = error2location_.get(code);
+
+ if (location != null) {
+ trace("Status " + code + " matched. Error page location: " + location);
+
+ req.setAttribute_(RequestDispatcher.ERROR_REQUEST_URI, req.getRequestURI());
+
+ handleError(location, req, resp);
+ }
+ }
+
+ public void handleError(String location, Request req, Response resp)
+ throws ServletException, IOException
+ {
+ try {
+ log("handleError: " + location);
+
+ String filter_path = req.getFilterPath();
+ String servlet_path = req.getServletPath();
+ String path_info = req.getPathInfo();
+ String req_uri = req.getRequestURI();
+ DispatcherType dtype = req.getDispatcherType();
+
+ URI uri;
+
+ if (location.startsWith("/")) {
+ uri = new URI(context_path_ + location);
+ } else {
+ uri = new URI(req_uri).resolve(location);
+ }
+
+ req.setRequestURI(uri.getRawPath());
+ req.setDispatcherType(DispatcherType.ERROR);
+
+ String path = uri.getPath().substring(context_path_.length());
+
+ ServletReg servlet = findServlet(path, req);
+
+ FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.ERROR);
+
+ fc.doFilter(req, resp);
+
+ req.setServletPath(filter_path, servlet_path, path_info);
+ req.setRequestURI(req_uri);
+ req.setDispatcherType(dtype);
+ } catch (URISyntaxException e) {
+ throw new ServletException(e);
+ }
+ }
+
+ private void processWebXml(File root) throws Exception
+ {
+ if (root.isDirectory()) {
+ File web_xml = new File(root, "WEB-INF/web.xml");
+ if (web_xml.exists()) {
+ trace("start: web.xml file found");
+
+ InputStream is = new FileInputStream(web_xml);
+
+ processWebXml(is);
+
+ is.close();
+ }
+ } else {
+ JarFile jf = new JarFile(root);
+ ZipEntry ze = jf.getEntry("WEB-INF/web.xml");
+
+ if (ze == null) {
+ trace("start: web.xml entry NOT found");
+ } else {
+ trace("start: web.xml entry found");
+
+ processWebXml(jf.getInputStream(ze));
+ }
+
+ jf.close();
+ }
+ }
+
+ private void processWebXml(InputStream is)
+ throws ParserConfigurationException, SAXException, IOException
+ {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+
+ Document doc = builder.parse(is);
+
+ Element doc_elem = doc.getDocumentElement();
+ String doc_elem_name = doc_elem.getNodeName();
+ if (!doc_elem_name.equals("web-app")) {
+ throw new RuntimeException("Invalid web.xml: 'web-app' element expected, not '" + doc_elem_name + "'");
+ }
+
+ metadata_complete_ = doc_elem.getAttribute("metadata-complete").equals("true");
+ app_version_ = doc_elem.getAttribute("version");
+
+ NodeList welcome_file_lists = doc_elem.getElementsByTagName("welcome-file-list");
+
+ if (welcome_file_lists.getLength() > 0) {
+ welcome_files_list_found_ = true;
+ }
+
+ for (int i = 0; i < welcome_file_lists.getLength(); i++) {
+ Element list_el = (Element) welcome_file_lists.item(i);
+ NodeList files = list_el.getElementsByTagName("welcome-file");
+ for (int j = 0; j < files.getLength(); j++) {
+ Node node = files.item(j);
+ String wf = node.getTextContent().trim();
+
+ /*
+ 10.10 Welcome Files
+ ...
+ The welcome file list is an ordered list of partial URLs
+ with no trailing or leading /.
+ */
+
+ if (wf.startsWith("/") || wf.endsWith("/")) {
+ log("invalid welcome file: " + wf);
+ continue;
+ }
+
+ welcome_files_.add(wf);
+ }
+ }
+
+ NodeList context_params = doc_elem.getElementsByTagName("context-param");
+ for (int i = 0; i < context_params.getLength(); i++) {
+ processXmlInitParam(this, (Element) context_params.item(i));
+ }
+
+ NodeList filters = doc_elem.getElementsByTagName("filter");
+
+ for (int i = 0; i < filters.getLength(); i++) {
+ Element filter_el = (Element) filters.item(i);
+ NodeList names = filter_el.getElementsByTagName("filter-name");
+ if (names == null || names.getLength() != 1) {
+ throw new RuntimeException("Invalid web.xml: 'filter-name' tag not found");
+ }
+
+ String filter_name = names.item(0).getTextContent().trim();
+ trace("filter-name=" + filter_name);
+
+ FilterReg reg = new FilterReg(filter_name);
+
+ NodeList child_nodes = filter_el.getChildNodes();
+ for(int j = 0; j < child_nodes.getLength(); j++) {
+ Node child_node = child_nodes.item(j);
+ String tag_name = child_node.getNodeName();
+
+ if (tag_name.equals("filter-class")) {
+ reg.setClassName(child_node.getTextContent().trim());
+ continue;
+ }
+
+ if (tag_name.equals("async-supported")) {
+ reg.setAsyncSupported(child_node.getTextContent().trim()
+ .equals("true"));
+ continue;
+ }
+
+ if (tag_name.equals("init-param")) {
+ processXmlInitParam(reg, (Element) child_node);
+ continue;
+ }
+ }
+
+ filters_.add(reg);
+ name2filter_.put(filter_name, reg);
+ }
+
+ NodeList filter_mappings = doc_elem.getElementsByTagName("filter-mapping");
+
+ for(int i = 0; i < filter_mappings.getLength(); i++) {
+ Element mapping_el = (Element) filter_mappings.item(i);
+ NodeList names = mapping_el.getElementsByTagName("filter-name");
+ if (names == null || names.getLength() != 1) {
+ throw new RuntimeException("Invalid web.xml: 'filter-name' tag not found");
+ }
+
+ String filter_name = names.item(0).getTextContent().trim();
+ trace("filter-name=" + filter_name);
+
+ FilterReg reg = name2filter_.get(filter_name);
+ if (reg == null) {
+ throw new RuntimeException("Invalid web.xml: filter '" + filter_name + "' not found");
+ }
+
+ EnumSet<DispatcherType> dtypes = EnumSet.noneOf(DispatcherType.class);
+ NodeList dispatchers = mapping_el.getElementsByTagName("dispatcher");
+ for (int j = 0; j < dispatchers.getLength(); j++) {
+ Node child_node = dispatchers.item(j);
+ dtypes.add(DispatcherType.valueOf(child_node.getTextContent().trim()));
+ }
+
+ if (dtypes.isEmpty()) {
+ dtypes.add(DispatcherType.REQUEST);
+ }
+
+ boolean match_after = false;
+
+ NodeList child_nodes = mapping_el.getChildNodes();
+ for (int j = 0; j < child_nodes.getLength(); j++) {
+ Node child_node = child_nodes.item(j);
+ String tag_name = child_node.getNodeName();
+
+ if (tag_name.equals("url-pattern")) {
+ reg.addMappingForUrlPatterns(dtypes, match_after, child_node.getTextContent().trim());
+ continue;
+ }
+
+ if (tag_name.equals("servlet-name")) {
+ reg.addMappingForServletNames(dtypes, match_after, child_node.getTextContent().trim());
+ continue;
+ }
+ }
+ }
+
+ NodeList servlets = doc_elem.getElementsByTagName("servlet");
+
+ for (int i = 0; i < servlets.getLength(); i++) {
+ Element servlet_el = (Element) servlets.item(i);
+ NodeList names = servlet_el.getElementsByTagName("servlet-name");
+ if (names == null || names.getLength() != 1) {
+ throw new RuntimeException("Invalid web.xml: 'servlet-name' tag not found");
+ }
+
+ String servlet_name = names.item(0).getTextContent().trim();
+ trace("servlet-name=" + servlet_name);
+
+ ServletReg reg = new ServletReg(servlet_name);
+
+ NodeList child_nodes = servlet_el.getChildNodes();
+ for(int j = 0; j < child_nodes.getLength(); j++) {
+ Node child_node = child_nodes.item(j);
+ String tag_name = child_node.getNodeName();
+
+ if (tag_name.equals("servlet-class")) {
+ reg.setClassName(child_node.getTextContent().trim());
+ continue;
+ }
+
+ if (tag_name.equals("async-supported")) {
+ reg.setAsyncSupported(child_node.getTextContent().trim()
+ .equals("true"));
+ continue;
+ }
+
+ if (tag_name.equals("init-param")) {
+ processXmlInitParam(reg, (Element) child_node);
+ continue;
+ }
+
+ if (tag_name.equals("load-on-startup")) {
+ reg.setLoadOnStartup(Integer.parseInt(child_node.getTextContent().trim()));
+ continue;
+ }
+ }
+
+ servlets_.add(reg);
+ name2servlet_.put(servlet_name, reg);
+ }
+
+ NodeList servlet_mappings = doc_elem.getElementsByTagName("servlet-mapping");
+
+ for(int i = 0; i < servlet_mappings.getLength(); i++) {
+ Element mapping_el = (Element) servlet_mappings.item(i);
+ NodeList names = mapping_el.getElementsByTagName("servlet-name");
+ if (names == null || names.getLength() != 1) {
+ throw new RuntimeException("Invalid web.xml: 'servlet-name' tag not found");
+ }
+
+ String servlet_name = names.item(0).getTextContent().trim();
+ trace("servlet-name=" + servlet_name);
+
+ ServletReg reg = name2servlet_.get(servlet_name);
+ if (reg == null) {
+ throw new RuntimeException("Invalid web.xml: servlet '" + servlet_name + "' not found");
+ }
+
+ NodeList child_nodes = mapping_el.getElementsByTagName("url-pattern");
+ String patterns[] = new String[child_nodes.getLength()];
+ for(int j = 0; j < child_nodes.getLength(); j++) {
+ Node child_node = child_nodes.item(j);
+ patterns[j] = child_node.getTextContent().trim();
+ }
+
+ reg.addMapping(patterns);
+ }
+
+ NodeList listeners = doc_elem.getElementsByTagName("listener");
+
+ for (int i = 0; i < listeners.getLength(); i++) {
+ Element listener_el = (Element) listeners.item(i);
+ NodeList classes = listener_el.getElementsByTagName("listener-class");
+ if (classes == null || classes.getLength() != 1) {
+ throw new RuntimeException("Invalid web.xml: 'listener-class' tag not found");
+ }
+
+ String class_name = classes.item(0).getTextContent().trim();
+ trace("listener-class=" + class_name);
+
+ pending_listener_classnames_.add(class_name);
+ }
+
+ NodeList error_pages = doc_elem.getElementsByTagName("error-page");
+
+ for (int i = 0; i < error_pages.getLength(); i++) {
+ Element error_page_el = (Element) error_pages.item(i);
+ NodeList locations = error_page_el.getElementsByTagName("location");
+ if (locations == null || locations.getLength() != 1) {
+ throw new RuntimeException("Invalid web.xml: 'location' tag not found");
+ }
+
+ String location = locations.item(0).getTextContent().trim();
+
+ NodeList child_nodes = error_page_el.getChildNodes();
+ for(int j = 0; j < child_nodes.getLength(); j++) {
+ Node child_node = child_nodes.item(j);
+ String tag_name = child_node.getNodeName();
+
+ if (tag_name.equals("exception-type")) {
+ String ex = child_node.getTextContent().trim();
+
+ exception2location_.put(ex, location);
+ trace("error-page: exception " + ex + " -> " + location);
+ continue;
+ }
+
+ if (tag_name.equals("error-code")) {
+ Integer code = Integer.parseInt(child_node.getTextContent().trim());
+
+ error2location_.put(code, location);
+ trace("error-page: code " + code + " -> " + location);
+ continue;
+ }
+ }
+ }
+
+ NodeList session_config = doc_elem.getElementsByTagName("session-config");
+
+ for (int i = 0; i < session_config.getLength(); i++) {
+ Element session_config_el = (Element) session_config.item(i);
+ NodeList session_timeout = session_config_el.getElementsByTagName("session-timeout");
+ if (session_timeout != null) {
+ String timeout = session_timeout.item(0).getTextContent().trim();
+
+ trace("session_timeout: " + timeout);
+ session_timeout_ = Integer.parseInt(timeout);
+ break;
+ }
+ }
+
+ NodeList jsp_configs = doc_elem.getElementsByTagName("jsp-config");
+
+ for (int i = 0; i < jsp_configs.getLength(); i++) {
+ Element jsp_config_el = (Element) jsp_configs.item(i);
+
+ NodeList jsp_nodes = jsp_config_el.getChildNodes();
+
+ for(int j = 0; j < jsp_nodes.getLength(); j++) {
+ Node jsp_node = jsp_nodes.item(j);
+ String tag_name = jsp_node.getNodeName();
+
+ if (tag_name.equals("taglib")) {
+ NodeList tl_nodes = ((Element) jsp_node).getChildNodes();
+ Taglib tl = new Taglib(tl_nodes);
+
+ trace("add taglib");
+
+ taglibs_.add(tl);
+ continue;
+ }
+
+ if (tag_name.equals("jsp-property-group")) {
+ NodeList jpg_nodes = ((Element) jsp_node).getChildNodes();
+ JspPropertyGroup conf = new JspPropertyGroup(jpg_nodes);
+
+ trace("add prop group");
+
+ prop_groups_.add(conf);
+ continue;
+ }
+ }
+ }
+ }
+
+ private static int compareVersion(String ver1, String ver2)
+ {
+ String[] varr1 = ver1.split("\\.");
+ String[] varr2 = ver2.split("\\.");
+
+ int max_len = varr1.length > varr2.length ? varr1.length : varr2.length;
+ for (int i = 0; i < max_len; i++) {
+ int l = i < varr1.length ? Integer.parseInt(varr1[i]) : 0;
+ int r = i < varr2.length ? Integer.parseInt(varr2[i]) : 0;
+
+ int res = l - r;
+
+ if (res != 0) {
+ return res;
+ }
+ }
+
+ return 0;
+ }
+
+ private void processXmlInitParam(InitParams params, Element elem)
+ throws RuntimeException
+ {
+ NodeList n = elem.getElementsByTagName("param-name");
+ if (n == null || n.getLength() != 1) {
+ throw new RuntimeException("Invalid web.xml: 'param-name' tag not found");
+ }
+
+ NodeList v = elem.getElementsByTagName("param-value");
+ if (v == null || v.getLength() != 1) {
+ throw new RuntimeException("Invalid web.xml: 'param-value' tag not found");
+ }
+ params.setInitParameter(n.item(0).getTextContent().trim(),
+ v.item(0).getTextContent().trim());
+ }
+
+ private void loadInitializers(ScanResult scan_res)
+ {
+ trace("load initializer(s)");
+
+ ServiceLoader<ServletContainerInitializer> initializers =
+ ServiceLoader.load(ServletContainerInitializer.class, loader_);
+
+ for (ServletContainerInitializer sci : initializers) {
+
+ trace("loadInitializers: initializer: " + sci.getClass().getName());
+
+ HandlesTypes ann = sci.getClass().getAnnotation(HandlesTypes.class);
+ if (ann == null) {
+ trace("loadInitializers: no HandlesTypes annotation");
+ continue;
+ }
+
+ Class<?>[] classes = ann.value();
+ if (classes == null) {
+ trace("loadInitializers: no handles classes");
+ continue;
+ }
+
+ Set<Class<?>> handles_classes = new HashSet<>();
+
+ for (Class<?> c : classes) {
+ trace("loadInitializers: find handles: " + c.getName());
+
+ ClassInfoList handles = c.isInterface()
+ ? scan_res.getClassesImplementing(c.getName())
+ : scan_res.getSubclasses(c.getName());
+
+ for (ClassInfo ci : handles) {
+ if (ci.isInterface()
+ || ci.isAnnotation()
+ || ci.isAbstract())
+ {
+ continue;
+ }
+
+ trace("loadInitializers: handles class: " + ci.getName());
+ handles_classes.add(ci.loadClass());
+ }
+ }
+
+ if (handles_classes.isEmpty()) {
+ trace("loadInitializers: no handles implementations");
+ continue;
+ }
+
+ try {
+ sci.onStartup(handles_classes, this);
+ metadata_complete_ = true;
+ } catch(Exception e) {
+ System.err.println("loadInitializers: exception caught: " + e.toString());
+ }
+ }
+ }
+
+ private void scanClasses(ScanResult scan_res)
+ throws ReflectiveOperationException
+ {
+ ClassInfoList filters = scan_res.getClassesWithAnnotation(WebFilter.class.getName());
+
+ for (ClassInfo ci : filters) {
+ if (ci.isInterface()
+ || ci.isAnnotation()
+ || ci.isAbstract()
+ || !ci.implementsInterface(Filter.class.getName()))
+ {
+ trace("scanClasses: ignoring Filter impl: " + ci.getName());
+ continue;
+ }
+
+ trace("scanClasses: found Filter class: " + ci.getName());
+
+ Class<?> cls = ci.loadClass();
+ if (!Filter.class.isAssignableFrom(cls)) {
+ trace("scanClasses: " + ci.getName() + " cannot be assigned to Filter");
+ continue;
+ }
+
+ WebFilter ann = cls.getAnnotation(WebFilter.class);
+
+ if (ann == null) {
+ trace("scanClasses: no WebFilter annotation for " + ci.getName());
+ continue;
+ }
+
+ String filter_name = ann.filterName();
+
+ if (filter_name.isEmpty()) {
+ filter_name = ci.getName();
+ }
+
+ FilterReg reg = name2filter_.get(filter_name);
+
+ if (reg == null) {
+ reg = new FilterReg(filter_name, cls);
+ filters_.add(reg);
+ name2filter_.put(filter_name, reg);
+ } else {
+ reg.setClass(cls);
+ }
+
+ EnumSet<DispatcherType> dtypes = EnumSet.noneOf(DispatcherType.class);
+ DispatcherType[] dispatchers = ann.dispatcherTypes();
+ for (DispatcherType d : dispatchers) {
+ dtypes.add(d);
+ }
+
+ if (dtypes.isEmpty()) {
+ dtypes.add(DispatcherType.REQUEST);
+ }
+
+ boolean match_after = false;
+
+ reg.addMappingForUrlPatterns(dtypes, match_after, ann.value());
+ reg.addMappingForUrlPatterns(dtypes, match_after, ann.urlPatterns());
+ reg.addMappingForServletNames(dtypes, match_after, ann.servletNames());
+
+ for (WebInitParam p : ann.initParams()) {
+ reg.setInitParameter(p.name(), p.value());
+ }
+
+ reg.setAsyncSupported(ann.asyncSupported());
+ }
+
+ ClassInfoList servlets = scan_res.getClassesWithAnnotation(WebServlet.class.getName());
+
+ for (ClassInfo ci : servlets) {
+ if (ci.isInterface()
+ || ci.isAnnotation()
+ || ci.isAbstract()
+ || !ci.extendsSuperclass(HttpServlet.class.getName()))
+ {
+ trace("scanClasses: ignoring HttpServlet subclass: " + ci.getName());
+ continue;
+ }
+
+ trace("scanClasses: found HttpServlet class: " + ci.getName());
+
+ Class<?> cls = ci.loadClass();
+ if (!HttpServlet.class.isAssignableFrom(cls)) {
+ trace("scanClasses: " + ci.getName() + " cannot be assigned to HttpFilter");
+ continue;
+ }
+
+ WebServlet ann = cls.getAnnotation(WebServlet.class);
+
+ if (ann == null) {
+ trace("scanClasses: no WebServlet annotation");
+ continue;
+ }
+
+ String servlet_name = ann.name();
+
+ if (servlet_name.isEmpty()) {
+ servlet_name = ci.getName();
+ }
+
+ ServletReg reg = name2servlet_.get(servlet_name);
+
+ if (reg == null) {
+ reg = new ServletReg(servlet_name, cls);
+ servlets_.add(reg);
+ name2servlet_.put(servlet_name, reg);
+ } else {
+ reg.setClass(cls);
+ }
+
+ reg.addMapping(ann.value());
+ reg.addMapping(ann.urlPatterns());
+
+ for (WebInitParam p : ann.initParams()) {
+ reg.setInitParameter(p.name(), p.value());
+ }
+
+ reg.setAsyncSupported(ann.asyncSupported());
+ }
+
+
+ ClassInfoList lstnrs = scan_res.getClassesWithAnnotation(WebListener.class.getName());
+
+ for (ClassInfo ci : lstnrs) {
+ if (ci.isInterface()
+ || ci.isAnnotation()
+ || ci.isAbstract())
+ {
+ trace("scanClasses: listener impl: " + ci.getName());
+ continue;
+ }
+
+ trace("scanClasses: listener class: " + ci.getName());
+
+ if (listener_classnames_.contains(ci.getName())) {
+ trace("scanClasses: " + ci.getName() + " already added as listener");
+ continue;
+ }
+
+ Class<?> cls = ci.loadClass();
+ Class<?> lclass = null;
+ for (Class<?> c : LISTENER_TYPES) {
+ if (c.isAssignableFrom(cls)) {
+ lclass = c;
+ break;
+ }
+ }
+
+ if (lclass == null) {
+ log("scanClasses: " + ci.getName() + " implements none of known listener interfaces");
+ continue;
+ }
+
+ WebListener ann = cls.getAnnotation(WebListener.class);
+
+ if (ann == null) {
+ log("scanClasses: no WebListener annotation");
+ continue;
+ }
+
+ Constructor<?> ctor = cls.getConstructor();
+ EventListener listener = (EventListener) ctor.newInstance();
+
+ addListener(listener);
+
+ listener_classnames_.add(ci.getName());
+ }
+ }
+
+ public void stop() throws IOException
+ {
+ ClassLoader old = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(loader_);
+
+ try {
+ for (ServletReg s : servlets_) {
+ s.destroy();
+ }
+
+ for (FilterReg f : filters_) {
+ f.destroy();
+ }
+
+ if (!destroy_listeners_.isEmpty()) {
+ ServletContextEvent event = new ServletContextEvent(this);
+ for (ServletContextListener listener : destroy_listeners_) {
+ listener.contextDestroyed(event);
+ }
+ }
+
+ if (extracted_dir_ != null) {
+ removeDir(extracted_dir_);
+ }
+
+ if (temp_dir_ != null) {
+ removeDir(temp_dir_);
+ }
+ } finally {
+ Thread.currentThread().setContextClassLoader(old);
+ }
+ }
+
+ private void removeDir(File dir) throws IOException
+ {
+ Files.walkFileTree(dir.toPath(),
+ new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult postVisitDirectory(
+ Path dir, IOException exc) throws IOException {
+ Files.delete(dir);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult visitFile(
+ Path file, BasicFileAttributes attrs)
+ throws IOException {
+ Files.delete(file);
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+
+ private class CtxInitParams implements InitParams
+ {
+ private final Map<String, String> init_params_ =
+ new HashMap<String, String>();
+
+ public boolean setInitParameter(String name, String value)
+ {
+ trace("CtxInitParams.setInitParameter " + name + " = " + value);
+
+ return init_params_.putIfAbsent(name, value) == null;
+ }
+
+ public String getInitParameter(String name)
+ {
+ trace("CtxInitParams.getInitParameter for " + name);
+
+ return init_params_.get(name);
+ }
+
+ public Set<String> setInitParameters(Map<String, String> initParameters)
+ {
+ // illegalStateIfContextStarted();
+ Set<String> clash = null;
+ for (Map.Entry<String, String> entry : initParameters.entrySet())
+ {
+ if (entry.getKey() == null) {
+ throw new IllegalArgumentException("init parameter name required");
+ }
+
+ if (entry.getValue() == null) {
+ throw new IllegalArgumentException("non-null value required for init parameter " + entry.getKey());
+ }
+
+ if (init_params_.get(entry.getKey()) != null)
+ {
+ if (clash == null)
+ clash = new HashSet<String>();
+ clash.add(entry.getKey());
+ }
+
+ trace("CtxInitParams.setInitParameters " + entry.getKey() + " = " + entry.getValue());
+ }
+
+ if (clash != null) {
+ return clash;
+ }
+
+ init_params_.putAll(initParameters);
+ return Collections.emptySet();
+ }
+
+ public Map<String, String> getInitParameters()
+ {
+ trace("CtxInitParams.getInitParameters");
+ return init_params_;
+ }
+
+ public Enumeration<String> getInitParameterNames()
+ {
+ return Collections.enumeration(init_params_.keySet());
+ }
+ }
+
+ private class NamedReg extends CtxInitParams
+ implements Registration
+ {
+ private final String name_;
+ private String class_name_;
+
+ public NamedReg(String name)
+ {
+ name_ = name;
+ }
+
+ public NamedReg(String name, String class_name)
+ {
+ name_ = name;
+ class_name_ = class_name;
+ }
+
+ @Override
+ public String getName()
+ {
+ return name_;
+ }
+
+ @Override
+ public String getClassName()
+ {
+ return class_name_;
+ }
+
+ public void setClassName(String class_name)
+ {
+ class_name_ = class_name;
+ }
+ }
+
+ private class ServletReg extends NamedReg
+ implements ServletRegistration.Dynamic, ServletConfig
+ {
+ private Class<?> servlet_class_;
+ private Servlet servlet_;
+ private String role_;
+ private boolean async_supported_ = false;
+ private final List<String> patterns_ = new ArrayList<>();
+ private int load_on_startup_ = -1;
+ private boolean initialized_ = false;
+ private final List<FilterMap> filters_ = new ArrayList<>();
+ private boolean system_jsp_servlet_ = false;
+
+ public ServletReg(String name, Class<?> servlet_class)
+ {
+ super(name, servlet_class.getName());
+ servlet_class_ = servlet_class;
+ }
+
+ public ServletReg(String name, Servlet servlet)
+ {
+ super(name, servlet.getClass().getName());
+ servlet_ = servlet;
+ }
+
+ public ServletReg(String name, String servlet_class_name)
+ {
+ super(name, servlet_class_name);
+ }
+
+ public ServletReg(String name)
+ {
+ super(name);
+ }
+
+ private void init() throws ServletException
+ {
+ if (initialized_) {
+ return;
+ }
+
+ trace("ServletReg.init(): " + getName());
+
+ if (system_jsp_servlet_) {
+ JasperInitializer ji = new JasperInitializer();
+
+ ji.onStartup(Collections.emptySet(), Context.this);
+ }
+
+ if (servlet_ == null) {
+ try {
+ if (servlet_class_ == null) {
+ servlet_class_ = loader_.loadClass(getClassName());
+ }
+
+ Constructor<?> ctor = servlet_class_.getConstructor();
+ servlet_ = (Servlet) ctor.newInstance();
+ } catch(Exception e) {
+ log("ServletReg.init() failed " + e);
+ throw new ServletException(e);
+ }
+ }
+
+ servlet_.init((ServletConfig) this);
+
+ initialized_ = true;
+ }
+
+ public void startup() throws ServletException
+ {
+ if (load_on_startup_ < 0) {
+ return;
+ }
+
+ init();
+ }
+
+ public void destroy()
+ {
+ if (initialized_) {
+ servlet_.destroy();
+ }
+ }
+
+ public void setClassName(String class_name) throws IllegalStateException
+ {
+ if (servlet_ != null
+ || servlet_class_ != null
+ || getClassName() != null)
+ {
+ throw new IllegalStateException("Class already initialized");
+ }
+
+ super.setClassName(class_name);
+ }
+
+ public void setClass(Class<?> servlet_class)
+ throws IllegalStateException
+ {
+ if (servlet_ != null
+ || servlet_class_ != null
+ || getClassName() != null)
+ {
+ throw new IllegalStateException("Class already initialized");
+ }
+
+ super.setClassName(servlet_class.getName());
+ servlet_class_ = servlet_class;
+ }
+
+ public void service(ServletRequest request, ServletResponse response)
+ throws ServletException, IOException
+ {
+ init();
+
+ servlet_.service(request, response);
+ }
+
+ public void addFilter(FilterMap fmap)
+ {
+ filters_.add(fmap);
+ }
+
+ @Override
+ public Set<String> addMapping(String... urlPatterns)
+ {
+ checkContextState();
+
+ Set<String> clash = null;
+ for (String pattern : urlPatterns) {
+ trace("ServletReg.addMapping: " + pattern);
+
+ if (pattern2servlet_.containsKey(pattern)) {
+ if (clash == null) {
+ clash = new HashSet<String>();
+ }
+ clash.add(pattern);
+ }
+ }
+
+ /* if there were any clashes amongst the urls, return them */
+ if (clash != null) {
+ return clash;
+ }
+
+ for (String pattern : urlPatterns) {
+ patterns_.add(pattern);
+ pattern2servlet_.put(pattern, this);
+ parseURLPattern(pattern, this);
+ }
+
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Collection<String> getMappings()
+ {
+ trace("ServletReg.getMappings");
+ return patterns_;
+ }
+
+ @Override
+ public String getRunAsRole()
+ {
+ return role_;
+ }
+
+ @Override
+ public void setLoadOnStartup(int loadOnStartup)
+ {
+ checkContextState();
+
+ trace("ServletReg.setLoadOnStartup: " + loadOnStartup);
+ load_on_startup_ = loadOnStartup;
+ }
+
+ @Override
+ public Set<String> setServletSecurity(ServletSecurityElement constraint)
+ {
+ log("ServletReg.setServletSecurity");
+ return Collections.emptySet();
+ }
+
+ @Override
+ public void setMultipartConfig(
+ MultipartConfigElement multipartConfig)
+ {
+ log("ServletReg.setMultipartConfig");
+ }
+
+ @Override
+ public void setRunAsRole(String roleName)
+ {
+ log("ServletReg.setRunAsRole: " + roleName);
+ role_ = roleName;
+ }
+
+ @Override
+ public void setAsyncSupported(boolean isAsyncSupported)
+ {
+ log("ServletReg.setAsyncSupported: " + isAsyncSupported);
+ async_supported_ = isAsyncSupported;
+ }
+
+ @Override
+ public String getServletName()
+ {
+ return getName();
+ }
+
+ @Override
+ public ServletContext getServletContext()
+ {
+ return (ServletContext) Context.this;
+ }
+ }
+
+ public void checkContextState() throws IllegalStateException
+ {
+ if (ctx_initialized_) {
+ throw new IllegalStateException("Context already initialized");
+ }
+ }
+
+ public void parseURLPattern(String p, ServletReg servlet)
+ throws IllegalArgumentException
+ {
+ URLPattern pattern = parseURLPattern(p);
+
+ switch (pattern.type_) {
+ case PREFIX:
+ prefix_patterns_.add(new PrefixPattern(pattern.pattern_, servlet));
+ return;
+
+ case SUFFIX:
+ suffix2servlet_.put(pattern.pattern_, servlet);
+ return;
+
+ case EXACT:
+ exact2servlet_.put(pattern.pattern_, servlet);
+ return;
+
+ case DEFAULT:
+ default_servlet_ = servlet;
+ return;
+ }
+
+ /* TODO process other cases, throw IllegalArgumentException */
+ }
+
+ public URLPattern parseURLPattern(String p)
+ throws IllegalArgumentException
+ {
+ URLPattern pattern = parsed_patterns_.get(p);
+ if (pattern == null) {
+ pattern = new URLPattern(p);
+ parsed_patterns_.put(p, pattern);
+ }
+
+ return pattern;
+ }
+
+ private static enum URLPatternType {
+ PREFIX,
+ SUFFIX,
+ DEFAULT,
+ EXACT,
+ };
+
+ private class URLPattern
+ {
+ private final String pattern_;
+ private final URLPatternType type_;
+
+ public URLPattern(String p)
+ throws IllegalArgumentException
+ {
+ /*
+ 12.2 Specification of Mappings
+ ...
+ A string beginning with a '/' character and ending with a '/*'
+ suffix is used for path mapping.
+ */
+ if (p.startsWith("/") && p.endsWith("/*")) {
+ trace("URLPattern: '" + p + "' is a prefix pattern");
+ pattern_ = p.substring(0, p.length() - 2);
+ type_ = URLPatternType.PREFIX;
+ return;
+ }
+
+ /*
+ A string beginning with a '*.' prefix is used as an extension
+ mapping.
+ */
+ if (p.startsWith("*.")) {
+ trace("URLPattern: '" + p + "' is a suffix pattern");
+ pattern_ = p.substring(1, p.length());
+ type_ = URLPatternType.SUFFIX;
+ return;
+ }
+
+ /*
+ The empty string ("") is a special URL pattern that exactly maps to
+ the application's context root, i.e., requests of the form
+ http://host:port/<context- root>/. In this case the path info is '/'
+ and the servlet path and context path is empty string ("").
+ */
+ if (p.isEmpty()) {
+ trace("URLPattern: '" + p + "' is a root");
+ pattern_ = "/";
+ type_ = URLPatternType.EXACT;
+ return;
+ }
+
+ /*
+ A string containing only the '/' character indicates the "default"
+ servlet of the application. In this case the servlet path is the
+ request URI minus the context path and the path info is null.
+ */
+ if (p.equals("/")) {
+ trace("URLPattern: '" + p + "' is a default");
+ pattern_ = p;
+ type_ = URLPatternType.DEFAULT;
+ return;
+ }
+
+ /*
+ All other strings are used for exact matches only.
+ */
+ trace("URLPattern: '" + p + "' is an exact pattern");
+ pattern_ = p;
+ type_ = URLPatternType.EXACT;
+
+ /* TODO process other cases, throw IllegalArgumentException */
+ }
+
+ public boolean match(String url)
+ {
+ switch (type_) {
+ case PREFIX:
+ return url.startsWith(pattern_) && (
+ url.length() == pattern_.length()
+ || url.charAt(pattern_.length()) == '/');
+
+ case SUFFIX:
+ return url.endsWith(pattern_);
+
+ case EXACT:
+ return url.equals(pattern_);
+
+ case DEFAULT:
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ private class FilterReg extends NamedReg
+ implements FilterRegistration.Dynamic, FilterConfig
+ {
+ private Class<?> filter_class_;
+ private Filter filter_;
+ private boolean async_supported_ = false;
+ private boolean initialized_ = false;
+
+ public FilterReg(String name, Class<?> filter_class)
+ {
+ super(name, filter_class.getName());
+ filter_class_ = filter_class;
+ }
+
+ public FilterReg(String name, Filter filter)
+ {
+ super(name, filter.getClass().getName());
+ filter_ = filter;
+ }
+
+ public FilterReg(String name, String filter_class_name)
+ {
+ super(name, filter_class_name);
+ }
+
+ public FilterReg(String name)
+ {
+ super(name);
+ }
+
+ public void setClassName(String class_name) throws IllegalStateException
+ {
+ if (filter_ != null
+ || filter_class_ != null
+ || getClassName() != null)
+ {
+ throw new IllegalStateException("Class already initialized");
+ }
+
+ super.setClassName(class_name);
+ }
+
+ public void setClass(Class<?> filter_class) throws IllegalStateException
+ {
+ if (filter_ != null
+ || filter_class_ != null
+ || getClassName() != null)
+ {
+ throw new IllegalStateException("Class already initialized");
+ }
+
+ super.setClassName(filter_class.getName());
+ filter_class_ = filter_class;
+ }
+
+ public void init() throws ServletException
+ {
+ if (filter_ == null) {
+ try {
+ if (filter_class_ == null) {
+ filter_class_ = loader_.loadClass(getClassName());
+ }
+
+ Constructor<?> ctor = filter_class_.getConstructor();
+ filter_ = (Filter) ctor.newInstance();
+ } catch(Exception e) {
+ log("FilterReg.init() failed " + e);
+ throw new ServletException(e);
+ }
+ }
+
+ filter_.init((FilterConfig) this);
+
+ initialized_ = true;
+ }
+
+ public void destroy()
+ {
+ if (initialized_) {
+ filter_.destroy();
+ }
+ }
+
+ @Override
+ public void addMappingForServletNames(
+ EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter,
+ String... servletNames)
+ {
+ checkContextState();
+
+ for (String n : servletNames) {
+ trace("FilterReg.addMappingForServletNames: ... " + n);
+
+ ServletReg sreg = name2servlet_.get(n);
+ if (sreg == null) {
+ sreg = new ServletReg(n);
+ servlets_.add(sreg);
+ name2servlet_.put(n, sreg);
+ }
+
+ FilterMap map = new FilterMap(this, sreg, dispatcherTypes,
+ isMatchAfter);
+
+ sreg.addFilter(map);
+ }
+ }
+
+ @Override
+ public Collection<String> getServletNameMappings()
+ {
+ checkContextState();
+
+ log("FilterReg.getServletNameMappings");
+ return Collections.emptySet();
+ }
+
+ @Override
+ public void addMappingForUrlPatterns(
+ EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter,
+ String... urlPatterns)
+ {
+ checkContextState();
+
+ for (String u : urlPatterns) {
+ trace("FilterReg.addMappingForUrlPatterns: ... " + u);
+
+ URLPattern p = parseURLPattern(u);
+ FilterMap map = new FilterMap(this, p, dispatcherTypes,
+ isMatchAfter);
+
+ filter_maps_.add(map);
+ }
+ }
+
+ @Override
+ public Collection<String> getUrlPatternMappings()
+ {
+ log("FilterReg.getUrlPatternMappings");
+ return Collections.emptySet();
+ }
+
+ @Override
+ public void setAsyncSupported(boolean isAsyncSupported)
+ {
+ log("FilterReg.setAsyncSupported: " + isAsyncSupported);
+ async_supported_ = isAsyncSupported;
+ }
+
+ @Override
+ public String getFilterName()
+ {
+ return getName();
+ }
+
+ @Override
+ public ServletContext getServletContext()
+ {
+ return (ServletContext) Context.this;
+ }
+ }
+
+ private class FilterMap
+ {
+ private final FilterReg filter_;
+ private final ServletReg servlet_;
+ private final URLPattern pattern_;
+ private final EnumSet<DispatcherType> dtypes_;
+ private final boolean match_after_;
+
+ public FilterMap(FilterReg filter, ServletReg servlet,
+ EnumSet<DispatcherType> dtypes, boolean match_after)
+ {
+ filter_ = filter;
+ servlet_ = servlet;
+ pattern_ = null;
+ dtypes_ = dtypes;
+ match_after_ = match_after;
+ }
+
+ public FilterMap(FilterReg filter, URLPattern pattern,
+ EnumSet<DispatcherType> dtypes, boolean match_after)
+ {
+ filter_ = filter;
+ servlet_ = null;
+ pattern_ = pattern;
+ dtypes_ = dtypes;
+ match_after_ = match_after;
+ }
+ }
+
+ private void initialized()
+ {
+ if (!sess_attr_listeners_.isEmpty()) {
+ sess_attr_proxy_ = new SessionAttrProxy(sess_attr_listeners_);
+ }
+
+ if (!req_attr_listeners_.isEmpty()) {
+ req_attr_proxy_ = new RequestAttrProxy(req_attr_listeners_);
+ }
+
+ ClassLoader old = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(loader_);
+
+ try {
+ // Call context listeners
+ destroy_listeners_.clear();
+ if (!ctx_listeners_.isEmpty()) {
+ ServletContextEvent event = new ServletContextEvent(this);
+ for (ServletContextListener listener : ctx_listeners_)
+ {
+ try {
+ listener.contextInitialized(event);
+ } catch(AbstractMethodError e) {
+ log("initialized: AbstractMethodError exception caught: " + e);
+ }
+ destroy_listeners_.add(0, listener);
+ }
+ }
+
+ for (ServletReg sr : servlets_) {
+ try {
+ sr.startup();
+ } catch(ServletException e) {
+ log("initialized: exception caught: " + e);
+ }
+ }
+
+ for (FilterReg fr : filters_) {
+ try {
+ fr.init();
+ } catch(ServletException e) {
+ log("initialized: exception caught: " + e);
+ }
+ }
+
+ ctx_initialized_ = true;
+ } finally {
+ Thread.currentThread().setContextClassLoader(old);
+ }
+ }
+
+ @Override
+ public ServletContext getContext(String uripath)
+ {
+ trace("getContext for " + uripath);
+ return this;
+ }
+
+ @Override
+ public int getMajorVersion()
+ {
+ trace("getMajorVersion");
+ return SERVLET_MAJOR_VERSION;
+ }
+
+ @Override
+ public String getMimeType(String file)
+ {
+ log("getMimeType for " + file);
+ if (mime_types_ == null) {
+ mime_types_ = new MimeTypes();
+ }
+ return mime_types_.getMimeByExtension(file);
+ }
+
+ @Override
+ public int getMinorVersion()
+ {
+ trace("getMinorVersion");
+ return SERVLET_MINOR_VERSION;
+ }
+
+ private class URIRequestDispatcher implements RequestDispatcher
+ {
+ private final URI uri_;
+
+ public URIRequestDispatcher(URI uri)
+ {
+ uri_ = uri;
+ }
+
+ public URIRequestDispatcher(String uri)
+ throws URISyntaxException
+ {
+ uri_ = new URI(uri);
+ }
+
+ @Override
+ public void forward(ServletRequest request, ServletResponse response)
+ throws ServletException, IOException
+ {
+ /*
+ 9.4 The Forward Method
+ ...
+ If the response has been committed, an IllegalStateException
+ must be thrown.
+ */
+ if (response.isCommitted()) {
+ throw new IllegalStateException("Response already committed");
+ }
+
+ ForwardRequestWrapper req = new ForwardRequestWrapper(request);
+
+ try {
+ trace("URIRequestDispatcher.forward");
+
+ String path = uri_.getPath().substring(context_path_.length());
+
+ ServletReg servlet = findServlet(path, req);
+
+ req.setRequestURI(uri_.getRawPath());
+ req.setQueryString(uri_.getRawQuery());
+ req.setDispatcherType(DispatcherType.FORWARD);
+
+ /*
+ 9.4 The Forward Method
+ ...
+ If output data exists in the response buffer that has not
+ been committed, the content must be cleared before the
+ target servlet's service method is called.
+ */
+ response.resetBuffer();
+
+ FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.FORWARD);
+
+ fc.doFilter(request, response);
+
+ /*
+ 9.4 The Forward Method
+ ...
+ Before the forward method of the RequestDispatcher interface
+ returns without exception, the response content must be sent
+ and committed, and closed by the servlet container, unless
+ the request was put into the asynchronous mode. If an error
+ occurs in the target of the RequestDispatcher.forward() the
+ exception may be propagated back through all the calling
+ filters and servlets and eventually back to the container
+ */
+ if (!request.isAsyncStarted()) {
+ response.flushBuffer();
+ }
+
+ /*
+ 9.5 Error Handling
+
+ If the servlet that is the target of a request dispatcher
+ throws a runtime exception or a checked exception of type
+ ServletException or IOException, it should be propagated
+ to the calling servlet. All other exceptions should be
+ wrapped as ServletExceptions and the root cause of the
+ exception set to the original exception, as it should
+ not be propagated.
+ */
+ } catch (ServletException e) {
+ throw e;
+ } catch (IOException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ServletException(e);
+ } finally {
+ req.close();
+
+ trace("URIRequestDispatcher.forward done");
+ }
+ }
+
+ @Override
+ public void include(ServletRequest request, ServletResponse response)
+ throws ServletException, IOException
+ {
+ IncludeRequestWrapper req = new IncludeRequestWrapper(request);
+
+ try {
+ trace("URIRequestDispatcher.include");
+
+ String path = uri_.getPath().substring(context_path_.length());
+
+ ServletReg servlet = findServlet(path, req);
+
+ req.setRequestURI(uri_.getRawPath());
+ req.setQueryString(uri_.getRawQuery());
+ req.setDispatcherType(DispatcherType.INCLUDE);
+
+ FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.INCLUDE);
+
+ fc.doFilter(request, new IncludeResponseWrapper(response));
+
+ } catch (ServletException e) {
+ throw e;
+ } catch (IOException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ServletException(e);
+ } finally {
+ req.close();
+
+ trace("URIRequestDispatcher.include done");
+ }
+ }
+ }
+
+ private class ServletDispatcher implements RequestDispatcher
+ {
+ private final ServletReg servlet_;
+
+ public ServletDispatcher(ServletReg servlet)
+ {
+ servlet_ = servlet;
+ }
+
+ @Override
+ public void forward(ServletRequest request, ServletResponse response)
+ throws ServletException, IOException
+ {
+ /*
+ 9.4 The Forward Method
+ ...
+ If the response has been committed, an IllegalStateException
+ must be thrown.
+ */
+ if (response.isCommitted()) {
+ throw new IllegalStateException("Response already committed");
+ }
+
+ trace("ServletDispatcher.forward");
+
+ DispatcherType dtype = request.getDispatcherType();
+
+ Request req;
+ if (request instanceof Request) {
+ req = (Request) request;
+ } else {
+ req = (Request) request.getAttribute(Request.BARE);
+ }
+
+ try {
+ req.setDispatcherType(DispatcherType.FORWARD);
+
+ /*
+ 9.4 The Forward Method
+ ...
+ If output data exists in the response buffer that has not
+ been committed, the content must be cleared before the
+ target servlet's service method is called.
+ */
+ response.resetBuffer();
+
+ servlet_.service(request, response);
+
+ /*
+ 9.4 The Forward Method
+ ...
+ Before the forward method of the RequestDispatcher interface
+ returns without exception, the response content must be sent
+ and committed, and closed by the servlet container, unless
+ the request was put into the asynchronous mode. If an error
+ occurs in the target of the RequestDispatcher.forward() the
+ exception may be propagated back through all the calling
+ filters and servlets and eventually back to the container
+ */
+ if (!request.isAsyncStarted()) {
+ response.flushBuffer();
+ }
+
+ /*
+ 9.5 Error Handling
+
+ If the servlet that is the target of a request dispatcher
+ throws a runtime exception or a checked exception of type
+ ServletException or IOException, it should be propagated
+ to the calling servlet. All other exceptions should be
+ wrapped as ServletExceptions and the root cause of the
+ exception set to the original exception, as it should
+ not be propagated.
+ */
+ } catch (ServletException e) {
+ throw e;
+ } catch (IOException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ServletException(e);
+ } finally {
+ req.setDispatcherType(dtype);
+
+ trace("ServletDispatcher.forward done");
+ }
+ }
+
+ @Override
+ public void include(ServletRequest request, ServletResponse response)
+ throws ServletException, IOException
+ {
+ trace("ServletDispatcher.include");
+
+ DispatcherType dtype = request.getDispatcherType();
+
+ Request req;
+ if (request instanceof Request) {
+ req = (Request) request;
+ } else {
+ req = (Request) request.getAttribute(Request.BARE);
+ }
+
+ try {
+ req.setDispatcherType(DispatcherType.INCLUDE);
+
+ servlet_.service(request, new IncludeResponseWrapper(response));
+
+ } catch (ServletException e) {
+ throw e;
+ } catch (IOException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ServletException(e);
+ } finally {
+ req.setDispatcherType(dtype);
+
+ trace("ServletDispatcher.include done");
+ }
+ }
+ }
+
+ @Override
+ public RequestDispatcher getNamedDispatcher(String name)
+ {
+ trace("getNamedDispatcher for " + name);
+
+ ServletReg servlet = name2servlet_.get(name);
+ if (servlet != null) {
+ return new ServletDispatcher(servlet);
+ }
+
+ return null;
+ }
+
+ @Override
+ public RequestDispatcher getRequestDispatcher(String uriInContext)
+ {
+ trace("getRequestDispatcher for " + uriInContext);
+ try {
+ return new URIRequestDispatcher(context_path_ + uriInContext);
+ } catch (URISyntaxException e) {
+ log("getRequestDispatcher: failed to create dispatcher: " + e);
+ }
+
+ return null;
+ }
+
+ public RequestDispatcher getRequestDispatcher(URI uri)
+ {
+ trace("getRequestDispatcher for " + uri.getRawPath());
+ return new URIRequestDispatcher(uri);
+ }
+
+ @Override
+ public String getRealPath(String path)
+ {
+ trace("getRealPath for " + path);
+
+ File f = new File(webapp_, path.substring(1));
+
+ return f.getAbsolutePath();
+ }
+
+ @Override
+ public URL getResource(String path) throws MalformedURLException
+ {
+ trace("getResource for " + path);
+
+ File f = new File(webapp_, path.substring(1));
+
+ if (f.exists()) {
+ return new URL("file:" + f.getAbsolutePath());
+ }
+
+ return null;
+ }
+
+ @Override
+ public InputStream getResourceAsStream(String path)
+ {
+ trace("getResourceAsStream for " + path);
+
+ try {
+ File f = new File(webapp_, path.substring(1));
+
+ return new FileInputStream(f);
+ } catch (FileNotFoundException e) {
+ log("getResourceAsStream: failed " + e);
+
+ return null;
+ }
+ }
+
+ @Override
+ public Set<String> getResourcePaths(String path)
+ {
+ trace("getResourcePaths for " + path);
+
+ File dir = new File(webapp_, path.substring(1));
+ File[] list = dir.listFiles();
+
+ if (list == null) {
+ return null;
+ }
+
+ Set<String> res = new HashSet<>();
+ Path root = webapp_.toPath();
+
+ for (File f : list) {
+ String r = "/" + root.relativize(f.toPath());
+ if (f.isDirectory()) {
+ r += "/";
+ }
+
+ trace("getResourcePaths: " + r);
+
+ res.add(r);
+ }
+
+ return res;
+ }
+
+ @Override
+ public String getServerInfo()
+ {
+ trace("getServerInfo: " + server_info_);
+ return server_info_;
+ }
+
+ @Override
+ @Deprecated
+ public Servlet getServlet(String name) throws ServletException
+ {
+ log("getServlet for " + name);
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ @Deprecated
+ public Enumeration<String> getServletNames()
+ {
+ log("getServletNames");
+ return Collections.enumeration(Collections.EMPTY_LIST);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ @Deprecated
+ public Enumeration<Servlet> getServlets()
+ {
+ log("getServlets");
+ return Collections.enumeration(Collections.EMPTY_LIST);
+ }
+
+ @Override
+ @Deprecated
+ public void log(Exception exception, String msg)
+ {
+ log(msg, exception);
+ }
+
+ @Override
+ public void log(String msg)
+ {
+ msg = "Context." + msg;
+ log(0, msg, msg.length());
+ }
+
+ @Override
+ public void log(String message, Throwable throwable)
+ {
+ log(message);
+ }
+
+ private static native void log(long ctx_ptr, String msg, int msg_len);
+
+
+ public static void trace(String msg)
+ {
+ msg = "Context." + msg;
+ trace(0, msg, msg.length());
+ }
+
+ private static native void trace(long ctx_ptr, String msg, int msg_len);
+
+ @Override
+ public String getInitParameter(String name)
+ {
+ trace("getInitParameter for " + name);
+ return init_params_.get(name);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Enumeration<String> getInitParameterNames()
+ {
+ trace("getInitParameterNames");
+ return Collections.enumeration(Collections.EMPTY_LIST);
+ }
+
+ @Override
+ public String getServletContextName()
+ {
+ log("getServletContextName");
+ return "No Context";
+ }
+
+ @Override
+ public String getContextPath()
+ {
+ trace("getContextPath");
+ return context_path_;
+ }
+
+ @Override
+ public boolean setInitParameter(String name, String value)
+ {
+ trace("setInitParameter " + name + " = " + value);
+ return init_params_.putIfAbsent(name, value) == null;
+ }
+
+ @Override
+ public Object getAttribute(String name)
+ {
+ trace("getAttribute " + name);
+
+ return attributes_.get(name);
+ }
+
+ @Override
+ public Enumeration<String> getAttributeNames()
+ {
+ trace("getAttributeNames");
+
+ Set<String> names = attributes_.keySet();
+ return Collections.enumeration(names);
+ }
+
+ @Override
+ public void setAttribute(String name, Object object)
+ {
+ trace("setAttribute " + name);
+
+ Object prev = attributes_.put(name, object);
+
+ if (ctx_attr_listeners_.isEmpty()) {
+ return;
+ }
+
+ ServletContextAttributeEvent scae = new ServletContextAttributeEvent(
+ this, name, prev == null ? object : prev);
+
+ for (ServletContextAttributeListener l : ctx_attr_listeners_) {
+ if (prev == null) {
+ l.attributeAdded(scae);
+ } else {
+ l.attributeReplaced(scae);
+ }
+ }
+ }
+
+ @Override
+ public void removeAttribute(String name)
+ {
+ trace("removeAttribute " + name);
+
+ Object value = attributes_.remove(name);
+
+ if (ctx_attr_listeners_.isEmpty()) {
+ return;
+ }
+
+ ServletContextAttributeEvent scae = new ServletContextAttributeEvent(
+ this, name, value);
+
+ for (ServletContextAttributeListener l : ctx_attr_listeners_) {
+ l.attributeRemoved(scae);
+ }
+ }
+
+ @Override
+ public FilterRegistration.Dynamic addFilter(String name,
+ Class<? extends Filter> filterClass)
+ {
+ log("addFilter<C> " + name + ", " + filterClass.getName());
+
+ checkContextState();
+
+ FilterReg reg = new FilterReg(name, filterClass);
+ filters_.add(reg);
+ name2filter_.put(name, reg);
+ return reg;
+ }
+
+ @Override
+ public FilterRegistration.Dynamic addFilter(String name, Filter filter)
+ {
+ log("addFilter<F> " + name);
+
+ checkContextState();
+
+ FilterReg reg = new FilterReg(name, filter);
+ filters_.add(reg);
+ name2filter_.put(name, reg);
+ return reg;
+ }
+
+ @Override
+ public FilterRegistration.Dynamic addFilter(String name, String className)
+ {
+ log("addFilter<N> " + name + ", " + className);
+
+ checkContextState();
+
+ FilterReg reg = new FilterReg(name, className);
+ filters_.add(reg);
+ name2filter_.put(name, reg);
+ return reg;
+ }
+
+ @Override
+ public ServletRegistration.Dynamic addServlet(String name,
+ Class<? extends Servlet> servletClass)
+ {
+ log("addServlet<C> " + name + ", " + servletClass.getName());
+
+ checkContextState();
+
+ ServletReg reg = null;
+ try {
+ reg = new ServletReg(name, servletClass);
+ servlets_.add(reg);
+ name2servlet_.put(name, reg);
+ } catch(Exception e) {
+ System.err.println("addServlet: exception caught: " + e.toString());
+ }
+
+ return reg;
+ }
+
+ @Override
+ public ServletRegistration.Dynamic addServlet(String name, Servlet servlet)
+ {
+ log("addServlet<S> " + name);
+
+ checkContextState();
+
+ ServletReg reg = null;
+ try {
+ reg = new ServletReg(name, servlet);
+ servlets_.add(reg);
+ name2servlet_.put(name, reg);
+ } catch(Exception e) {
+ System.err.println("addServlet: exception caught: " + e.toString());
+ }
+
+ return reg;
+ }
+
+ @Override
+ public ServletRegistration.Dynamic addServlet(String name, String className)
+ {
+ log("addServlet<N> " + name + ", " + className);
+
+ checkContextState();
+
+ ServletReg reg = null;
+ try {
+ reg = new ServletReg(name, className);
+ servlets_.add(reg);
+ name2servlet_.put(name, reg);
+ } catch(Exception e) {
+ System.err.println("addServlet: exception caught: " + e.toString());
+ }
+
+ return reg;
+ }
+
+ @Override
+ public ServletRegistration.Dynamic addJspFile(String jspName, String jspFile)
+ {
+ log("addJspFile: " + jspName + " " + jspFile);
+
+ return null;
+ }
+
+ @Override
+ public <T extends Filter> T createFilter(Class<T> c) throws ServletException
+ {
+ log("createFilter<C> " + c.getName());
+
+ checkContextState();
+
+ try {
+ Constructor<T> ctor = c.getConstructor();
+ T filter = ctor.newInstance();
+ return filter;
+ } catch (Exception e) {
+ log("createFilter() failed " + e);
+
+ throw new ServletException(e);
+ }
+ }
+
+ @Override
+ public <T extends Servlet> T createServlet(Class<T> c) throws ServletException
+ {
+ log("createServlet<C> " + c.getName());
+
+ checkContextState();
+
+ try {
+ Constructor<T> ctor = c.getConstructor();
+ T servlet = ctor.newInstance();
+ return servlet;
+ } catch (Exception e) {
+ log("createServlet() failed " + e);
+
+ throw new ServletException(e);
+ }
+ }
+
+ @Override
+ public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
+ {
+ log("getDefaultSessionTrackingModes");
+
+ return default_session_tracking_modes_;
+ }
+
+ @Override
+ public Set<SessionTrackingMode> getEffectiveSessionTrackingModes()
+ {
+ log("getEffectiveSessionTrackingModes");
+
+ return session_tracking_modes_;
+ }
+
+ public boolean isSessionIdValid(String id)
+ {
+ synchronized (sessions_) {
+ return sessions_.containsKey(id);
+ }
+ }
+
+ public Session getSession(String id)
+ {
+ synchronized (sessions_) {
+ Session s = sessions_.get(id);
+
+ if (s != null) {
+ s.accessed();
+
+ if (s.checkTimeOut()) {
+ s.invalidate();
+ return null;
+ }
+ }
+
+ return s;
+ }
+ }
+
+ public Session createSession()
+ {
+ Session session = new Session(this, generateSessionId(),
+ sess_attr_proxy_, session_timeout_ * 60);
+
+ if (!sess_listeners_.isEmpty())
+ {
+ HttpSessionEvent event = new HttpSessionEvent(session);
+
+ for (HttpSessionListener l : sess_listeners_)
+ {
+ l.sessionCreated(event);
+ }
+ }
+
+ synchronized (sessions_) {
+ sessions_.put(session.getId(), session);
+
+ return session;
+ }
+ }
+
+ public void invalidateSession(Session session)
+ {
+ synchronized (sessions_) {
+ sessions_.remove(session.getId());
+ }
+
+ if (!sess_listeners_.isEmpty())
+ {
+ HttpSessionEvent event = new HttpSessionEvent(session);
+
+ for (int i = sess_listeners_.size() - 1; i >= 0; i--)
+ {
+ sess_listeners_.get(i).sessionDestroyed(event);
+ }
+ }
+ }
+
+ public void changeSessionId(Session session)
+ {
+ String old_id;
+
+ synchronized (sessions_) {
+ old_id = session.getId();
+ sessions_.remove(old_id);
+
+ session.setId(generateSessionId());
+
+ sessions_.put(session.getId(), session);
+ }
+
+ if (!sess_id_listeners_.isEmpty())
+ {
+ HttpSessionEvent event = new HttpSessionEvent(session);
+ for (HttpSessionIdListener l : sess_id_listeners_)
+ {
+ l.sessionIdChanged(event, old_id);
+ }
+ }
+ }
+
+ private String generateSessionId()
+ {
+ return UUID.randomUUID().toString();
+ }
+
+ @Override
+ public FilterRegistration getFilterRegistration(String filterName)
+ {
+ log("getFilterRegistration " + filterName);
+ return name2filter_.get(filterName);
+ }
+
+ @Override
+ public Map<String, ? extends FilterRegistration> getFilterRegistrations()
+ {
+ log("getFilterRegistrations");
+ return name2filter_;
+ }
+
+ @Override
+ public ServletRegistration getServletRegistration(String servletName)
+ {
+ log("getServletRegistration " + servletName);
+ return name2servlet_.get(servletName);
+ }
+
+ @Override
+ public Map<String, ? extends ServletRegistration> getServletRegistrations()
+ {
+ log("getServletRegistrations");
+ return name2servlet_;
+ }
+
+ @Override
+ public SessionCookieConfig getSessionCookieConfig()
+ {
+ log("getSessionCookieConfig");
+
+ return session_cookie_config_;
+ }
+
+ @Override
+ public void setSessionTrackingModes(Set<SessionTrackingMode> modes)
+ {
+ log("setSessionTrackingModes");
+
+ session_tracking_modes_ = modes;
+ }
+
+ @Override
+ public void addListener(String className)
+ {
+ trace("addListener<N> " + className);
+
+ checkContextState();
+
+ if (listener_classnames_.contains(className)) {
+ log("addListener<N> " + className + " already added as listener");
+ return;
+ }
+
+ try {
+ Class<?> cls = loader_.loadClass(className);
+
+ Constructor<?> ctor = cls.getConstructor();
+ EventListener listener = (EventListener) ctor.newInstance();
+
+ addListener(listener);
+
+ listener_classnames_.add(className);
+ } catch (Exception e) {
+ log("addListener<N>: exception caught: " + e.toString());
+ }
+ }
+
+ @Override
+ public <T extends EventListener> void addListener(T t)
+ {
+ trace("addListener<T> " + t.getClass().getName());
+
+ checkContextState();
+
+ for (int i = 0; i < LISTENER_TYPES.length; i++) {
+ Class<?> c = LISTENER_TYPES[i];
+ if (c.isAssignableFrom(t.getClass())) {
+ trace("addListener<T>: assignable to " + c.getName());
+ }
+ }
+
+ if (t instanceof ServletContextListener) {
+ ctx_listeners_.add((ServletContextListener) t);
+ }
+
+ if (t instanceof ServletContextAttributeListener) {
+ ctx_attr_listeners_.add((ServletContextAttributeListener) t);
+ }
+
+ if (t instanceof ServletRequestListener) {
+ req_init_listeners_.add((ServletRequestListener) t);
+ req_destroy_listeners_.add(0, (ServletRequestListener) t);
+ }
+
+ if (t instanceof ServletRequestAttributeListener) {
+ req_attr_listeners_.add((ServletRequestAttributeListener) t);
+ }
+
+ if (t instanceof HttpSessionAttributeListener) {
+ sess_attr_listeners_.add((HttpSessionAttributeListener) t);
+ }
+
+ if (t instanceof HttpSessionIdListener) {
+ sess_id_listeners_.add((HttpSessionIdListener) t);
+ }
+
+ if (t instanceof HttpSessionListener) {
+ sess_listeners_.add((HttpSessionListener) t);
+ }
+ }
+
+ @Override
+ public void addListener(Class<? extends EventListener> listenerClass)
+ {
+ String className = listenerClass.getName();
+ trace("addListener<C> " + className);
+
+ checkContextState();
+
+ if (listener_classnames_.contains(className)) {
+ log("addListener<C> " + className + " already added as listener");
+ return;
+ }
+
+ try {
+ Constructor<?> ctor = listenerClass.getConstructor();
+ EventListener listener = (EventListener) ctor.newInstance();
+
+ addListener(listener);
+
+ listener_classnames_.add(className);
+ } catch (Exception e) {
+ log("addListener<C>: exception caught: " + e.toString());
+ }
+ }
+
+ @Override
+ public <T extends EventListener> T createListener(Class<T> clazz)
+ throws ServletException
+ {
+ trace("createListener<C> " + clazz.getName());
+
+ checkContextState();
+
+ try
+ {
+ return clazz.getDeclaredConstructor().newInstance();
+ }
+ catch (Exception e)
+ {
+ throw new ServletException(e);
+ }
+ }
+
+ @Override
+ public ClassLoader getClassLoader()
+ {
+ trace("getClassLoader");
+ return loader_;
+ }
+
+ @Override
+ public int getEffectiveMajorVersion()
+ {
+ log("getEffectiveMajorVersion");
+ return SERVLET_MAJOR_VERSION;
+ }
+
+ @Override
+ public int getEffectiveMinorVersion()
+ {
+ log("getEffectiveMinorVersion");
+ return SERVLET_MINOR_VERSION;
+ }
+
+ private final List<TaglibDescriptor> taglibs_ = new ArrayList<>();
+ private final List<JspPropertyGroupDescriptor> prop_groups_ = new ArrayList<>();
+
+ private class JspConfig implements JspConfigDescriptor
+ {
+ @Override
+ public Collection<TaglibDescriptor> getTaglibs()
+ {
+ trace("getTaglibs");
+ return taglibs_;
+ }
+
+ @Override
+ public Collection<JspPropertyGroupDescriptor> getJspPropertyGroups()
+ {
+ trace("getJspPropertyGroups");
+ return prop_groups_;
+ }
+ }
+
+ private final JspConfig jsp_config_ = new JspConfig();
+
+ @Override
+ public JspConfigDescriptor getJspConfigDescriptor()
+ {
+ trace("getJspConfigDescriptor");
+
+ return jsp_config_;
+ }
+
+ @Override
+ public void declareRoles(String... roleNames)
+ {
+ log("declareRoles");
+ //LOG.warn(__unimplmented);
+ }
+
+ @Override
+ public String getVirtualServerName()
+ {
+ log("getVirtualServerName");
+ return null;
+ }
+
+ @Override
+ public int getSessionTimeout()
+ {
+ trace("getSessionTimeout");
+
+ return session_timeout_;
+ }
+
+ @Override
+ public void setSessionTimeout(int sessionTimeout)
+ {
+ trace("setSessionTimeout: " + sessionTimeout);
+
+ session_timeout_ = sessionTimeout;
+ }
+
+ @Override
+ public String getRequestCharacterEncoding()
+ {
+ log("getRequestCharacterEncoding");
+
+ return null;
+ }
+
+ @Override
+ public void setRequestCharacterEncoding(String encoding)
+ {
+ log("setRequestCharacterEncoding: " + encoding);
+ }
+
+ @Override
+ public String getResponseCharacterEncoding()
+ {
+ log("getResponseCharacterEncoding");
+
+ return null;
+ }
+
+ @Override
+ public void setResponseCharacterEncoding(String encoding)
+ {
+ log("setResponseCharacterEncoding: " + encoding);
+ }
+
+ public ServletRequestAttributeListener getRequestAttributeListener()
+ {
+ return req_attr_proxy_;
+ }
+}