Java tutorial
// ======================================================================== // Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.webapp; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.security.PermissionCollection; import java.util.EventListener; import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpSessionActivationListener; import javax.servlet.http.HttpSessionAttributeListener; import javax.servlet.http.HttpSessionBindingListener; import javax.servlet.http.HttpSessionListener; import org.eclipse.jetty.security.SecurityHandler; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.HandlerContainer; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ErrorHandler; import org.eclipse.jetty.server.session.SessionHandler; import org.eclipse.jetty.servlet.ErrorPageErrorHandler; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.util.LazyList; import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.ResourceCollection; /* ------------------------------------------------------------ */ /** Web Application Context Handler. * The WebAppContext handler is an extension of ContextHandler that * coordinates the construction and configuration of nested handlers: * {@link org.eclipse.jetty.security.ConstraintSecurityHandler}, {@link org.eclipse.jetty.server.session.SessionHandler} * and {@link org.eclipse.jetty.servlet.ServletHandler}. * The handlers are configured by pluggable configuration classes, with * the default being {@link org.eclipse.jetty.webapp.WebXmlConfiguration} and * {@link org.eclipse.jetty.webapp.JettyWebXmlConfiguration}. * * @org.apache.xbean.XBean description="Creates a servlet web application at a given context from a resource base" * * * */ public class WebAppContext extends ServletContextHandler { public static final String TEMPDIR = "javax.servlet.context.tempdir"; public static final String BASETEMPDIR = "org.eclipse.jetty.webapp.basetempdir"; public final static String WEB_DEFAULTS_XML = "org/eclipse/jetty/webapp/webdefault.xml"; public final static String ERROR_PAGE = "org.eclipse.jetty.server.error_page"; public final static String SERVER_CONFIG = "org.eclipse.jetty.webapp.configuration"; public final static String SERVER_SYS_CLASSES = "org.eclipse.jetty.webapp.systemClasses"; public final static String SERVER_SRV_CLASSES = "org.eclipse.jetty.webapp.serverClasses"; private static String[] __dftConfigurationClasses = { "org.eclipse.jetty.webapp.WebInfConfiguration", "org.eclipse.jetty.webapp.WebXmlConfiguration", "org.eclipse.jetty.webapp.MetaInfConfiguration", "org.eclipse.jetty.webapp.FragmentConfiguration", "org.eclipse.jetty.webapp.JettyWebXmlConfiguration", "org.eclipse.jetty.webapp.TagLibConfiguration" }; // System classes are classes that cannot be replaced by // the web application, and they are *always* loaded via // system classloader. private final static String[] __dftSystemClasses = { "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.xml.", // needed by javax.xml "org.w3c.", // needed by javax.xml "org.apache.commons.logging.", // TODO: review if special case still needed "org.eclipse.jetty.continuation.", // webapp cannot change continuation classes "org.eclipse.jetty.jndi.", // webapp cannot change naming classes "org.eclipse.jetty.plus.jaas.", // webapp cannot change jaas classes "org.eclipse.jetty.websocket.", // WebSocket is a jetty extension "org.eclipse.jetty.servlet.DefaultServlet" // webapp cannot change default servlets }; // Server classes are classes that are hidden from being // loaded by the web application using system classloader, // so if web application needs to load any of such classes, // it has to include them in its distribution. private final static String[] __dftServerClasses = { "-org.eclipse.jetty.continuation.", // don't hide continuation classes "-org.eclipse.jetty.jndi.", // don't hide naming classes "-org.eclipse.jetty.plus.jaas.", // don't hide jaas classes "-org.eclipse.jetty.websocket.", // don't hide websocket extension "-org.eclipse.jetty.servlet.DefaultServlet", // don't hide default servlet "org.eclipse.jetty." // hide other jetty classes }; private String[] _configurationClasses = __dftConfigurationClasses; private ClasspathPattern _systemClasses = null; private ClasspathPattern _serverClasses = null; private Configuration[] _configurations; private String _defaultsDescriptor = WEB_DEFAULTS_XML; private String _descriptor = null; private String _overrideDescriptor = null; private boolean _distributable = false; private boolean _extractWAR = true; private boolean _copyDir = false; private boolean _logUrlOnStart = false; private boolean _parentLoaderPriority = Boolean .getBoolean("org.eclipse.jetty.server.webapp.parentLoaderPriority"); private PermissionCollection _permissions; private File _tmpDir; private String _war; private String _extraClasspath; private Throwable _unavailableException; private Map _resourceAliases; private boolean _ownClassLoader = false; private boolean _configurationDiscovered = true; private boolean _configurationClassesSet = false; private boolean _configurationsSet = false; public static WebAppContext getCurrentWebAppContext() { ContextHandler.Context context = ContextHandler.getCurrentContext(); if (context != null) { ContextHandler handler = context.getContextHandler(); if (handler instanceof WebAppContext) return (WebAppContext) handler; } return null; } /* ------------------------------------------------------------ */ public WebAppContext() { super(SESSIONS | SECURITY); _scontext = new Context(); setErrorHandler(new ErrorPageErrorHandler()); } /* ------------------------------------------------------------ */ /** * @param contextPath The context path * @param webApp The URL or filename of the webapp directory or war file. */ public WebAppContext(String webApp, String contextPath) { super(null, contextPath, SESSIONS | SECURITY); _scontext = new Context(); setContextPath(contextPath); setWar(webApp); setErrorHandler(new ErrorPageErrorHandler()); } /* ------------------------------------------------------------ */ /** * @param parent The parent HandlerContainer. * @param contextPath The context path * @param webApp The URL or filename of the webapp directory or war file. */ public WebAppContext(HandlerContainer parent, String webApp, String contextPath) { super(parent, contextPath, SESSIONS | SECURITY); _scontext = new Context(); setWar(webApp); setErrorHandler(new ErrorPageErrorHandler()); } /* ------------------------------------------------------------ */ /** */ public WebAppContext(SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler) { super(null, sessionHandler, securityHandler, servletHandler, errorHandler); _scontext = new Context(); setErrorHandler(errorHandler != null ? errorHandler : new ErrorPageErrorHandler()); } /* ------------------------------------------------------------ */ /** * @param servletContextName The servletContextName to set. */ @Override public void setDisplayName(String servletContextName) { super.setDisplayName(servletContextName); ClassLoader cl = getClassLoader(); if (cl != null && cl instanceof WebAppClassLoader) ((WebAppClassLoader) cl).setName(servletContextName); } /* ------------------------------------------------------------ */ /** Get an exception that caused the webapp to be unavailable * @return A throwable if the webapp is unavailable or null */ public Throwable getUnavailableException() { return _unavailableException; } /* ------------------------------------------------------------ */ /** Set Resource Alias. * Resource aliases map resource uri's within a context. * They may optionally be used by a handler when looking for * a resource. * @param alias * @param uri */ public void setResourceAlias(String alias, String uri) { if (_resourceAliases == null) _resourceAliases = new HashMap(5); _resourceAliases.put(alias, uri); } /* ------------------------------------------------------------ */ public Map getResourceAliases() { if (_resourceAliases == null) return null; return _resourceAliases; } /* ------------------------------------------------------------ */ public void setResourceAliases(Map map) { _resourceAliases = map; } /* ------------------------------------------------------------ */ public String getResourceAlias(String alias) { if (_resourceAliases == null) return null; return (String) _resourceAliases.get(alias); } /* ------------------------------------------------------------ */ public String removeResourceAlias(String alias) { if (_resourceAliases == null) return null; return (String) _resourceAliases.remove(alias); } /* ------------------------------------------------------------ */ /* (non-Javadoc) * @see org.eclipse.jetty.server.server.handler.ContextHandler#setClassLoader(java.lang.ClassLoader) */ @Override public void setClassLoader(ClassLoader classLoader) { super.setClassLoader(classLoader); if (classLoader != null && classLoader instanceof WebAppClassLoader) ((WebAppClassLoader) classLoader).setName(getDisplayName()); } /* ------------------------------------------------------------ */ @Override public Resource getResource(String uriInContext) throws MalformedURLException { IOException ioe = null; Resource resource = null; int loop = 0; while (uriInContext != null && loop++ < 100) { try { resource = super.getResource(uriInContext); if (resource != null && resource.exists()) return resource; uriInContext = getResourceAlias(uriInContext); } catch (IOException e) { Log.ignore(e); if (ioe == null) ioe = e; } } if (ioe != null && ioe instanceof MalformedURLException) throw (MalformedURLException) ioe; return resource; } /* ------------------------------------------------------------ */ /** Is the context Automatically configured. * * @return true if configuration discovery. */ public boolean isConfigurationDiscovered() { return _configurationDiscovered; } /* ------------------------------------------------------------ */ /** Set the configuration discovery mode. * If configuration discovery is set to true, then the JSR315 * servlet 3.0 discovered configuration features are enabled. * These are:<ul> * <li>Web Fragments</li> * <li>META-INF/resource directories</li> * </ul> * @param discovered true if configuration discovery is enabled for automatic configuration from the context */ public void setConfigurationDiscovered(boolean discovered) { _configurationDiscovered = discovered; } /* ------------------------------------------------------------ */ /* * @see org.eclipse.thread.AbstractLifeCycle#doStart() */ @Override protected void doStart() throws Exception { try { // Setup configurations loadConfigurations(); // Setup system classes loadSystemClasses(); // Setup server classes loadServerClasses(); // Configure classloader _ownClassLoader = false; if (getClassLoader() == null) { WebAppClassLoader classLoader = new WebAppClassLoader(this); setClassLoader(classLoader); _ownClassLoader = true; } if (Log.isDebugEnabled()) { ClassLoader loader = getClassLoader(); Log.debug("Thread Context class loader is: " + loader); loader = loader.getParent(); while (loader != null) { Log.debug("Parent class loader is: " + loader); loader = loader.getParent(); } } // Prepare for configuration for (int i = 0; i < _configurations.length; i++) _configurations[i].preConfigure(this); super.doStart(); // Clean up after configuration for (int i = 0; i < _configurations.length; i++) _configurations[i].postConfigure(this); if (isLogUrlOnStart()) dumpUrl(); } catch (Exception e) { //start up of the webapp context failed, make sure it is not started Log.warn("Failed startup of context " + this, e); _unavailableException = e; setAvailable(false); } } /* ------------------------------------------------------------ */ /* * Dumps the current web app name and URL to the log */ public void dumpUrl() { Connector[] connectors = getServer().getConnectors(); for (int i = 0; i < connectors.length; i++) { String connectorName = connectors[i].getName(); String displayName = getDisplayName(); if (displayName == null) displayName = "WebApp@" + connectors.hashCode(); Log.info(displayName + " at http://" + connectorName + getContextPath()); } } /* ------------------------------------------------------------ */ /* * @see org.eclipse.thread.AbstractLifeCycle#doStop() */ @Override protected void doStop() throws Exception { super.doStop(); try { for (int i = _configurations.length; i-- > 0;) _configurations[i].deconfigure(this); _configurations = null; } finally { if (_ownClassLoader) setClassLoader(null); setAvailable(true); _unavailableException = null; } } /* ------------------------------------------------------------ */ /** * @return Returns the configurations. */ public String[] getConfigurationClasses() { return _configurationClasses; } /* ------------------------------------------------------------ */ /** * @return Returns the configurations. */ public Configuration[] getConfigurations() { return _configurations; } /* ------------------------------------------------------------ */ /** * The default descriptor is a web.xml format file that is applied to the context before the standard WEB-INF/web.xml * @return Returns the defaultsDescriptor. */ public String getDefaultsDescriptor() { return _defaultsDescriptor; } /* ------------------------------------------------------------ */ /** * The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml * @return Returns the Override Descriptor. */ public String getOverrideDescriptor() { return _overrideDescriptor; } /* ------------------------------------------------------------ */ /** * @return Returns the permissions. */ public PermissionCollection getPermissions() { return _permissions; } /* ------------------------------------------------------------ */ /** * @see #setServerClasses(String[]) * @return Returns the serverClasses. */ public String[] getServerClasses() { if (_serverClasses == null) loadServerClasses(); return _serverClasses.getPatterns(); } public void addServerClass(String classname) { if (_serverClasses == null) loadServerClasses(); _serverClasses.addPattern(classname); } /* ------------------------------------------------------------ */ /** * @see #setSystemClasses(String[]) * @return Returns the systemClasses. */ public String[] getSystemClasses() { if (_systemClasses == null) loadSystemClasses(); return _systemClasses.getPatterns(); } public void addSystemClass(String classname) { if (_systemClasses == null) loadSystemClasses(); _systemClasses.addPattern(classname); } /* ------------------------------------------------------------ */ public boolean isServerClass(String name) { if (_serverClasses == null) loadServerClasses(); return _serverClasses.match(name); } /* ------------------------------------------------------------ */ public boolean isSystemClass(String name) { if (_systemClasses == null) loadSystemClasses(); return _systemClasses.match(name); } private void loadSystemClasses() { if (_systemClasses != null) return; //look for a Server attribute with the list of System classes //to apply to every web application. If not present, use our defaults. Server server = getServer(); if (server != null) { Object systemClasses = server.getAttribute(SERVER_SYS_CLASSES); if (systemClasses != null && systemClasses instanceof String[]) _systemClasses = ClasspathPattern.fromArray((String[]) systemClasses); } if (_systemClasses == null) _systemClasses = ClasspathPattern.fromArray(__dftSystemClasses); } private void loadServerClasses() { if (_serverClasses != null) return; //look for a Server attribute with the list of Server classes //to apply to every web application. If not present, use our defaults. Server server = getServer(); if (server != null) { Object serverClasses = server.getAttribute(SERVER_SRV_CLASSES); if (serverClasses != null || serverClasses instanceof String[]) _serverClasses = ClasspathPattern.fromArray((String[]) serverClasses); } if (_serverClasses == null) _serverClasses = ClasspathPattern.fromArray(__dftServerClasses); } /* ------------------------------------------------------------ */ /** * @return Returns the war as a file or URL string (Resource) */ public String getWar() { if (_war == null) _war = getResourceBase(); return _war; } /* ------------------------------------------------------------ */ public Resource getWebInf() throws IOException { if (super.getBaseResource() == null) return null; // Iw there a WEB-INF directory? Resource web_inf = super.getBaseResource().addPath("WEB-INF/"); if (!web_inf.exists() || !web_inf.isDirectory()) return null; return web_inf; } /* ------------------------------------------------------------ */ /** * @return Returns the distributable. */ public boolean isDistributable() { return _distributable; } /* ------------------------------------------------------------ */ /** * @return Returns the extractWAR. */ public boolean isExtractWAR() { return _extractWAR; } /* ------------------------------------------------------------ */ /** * @return True if the webdir is copied (to allow hot replacement of jars) */ public boolean isCopyWebDir() { return _copyDir; } /* ------------------------------------------------------------ */ /** * @return Returns the java2compliant. */ public boolean isParentLoaderPriority() { return _parentLoaderPriority; } /* ------------------------------------------------------------ */ public String[] getDefaultConfigurationClasses() { return __dftConfigurationClasses; } /* ------------------------------------------------------------ */ protected void loadConfigurations() throws Exception { //if the configuration instances have been set explicitly, use them if (_configurations != null) return; //if the configuration classnames have been set explicitly use them if (!_configurationClassesSet) _configurationClasses = __dftConfigurationClasses; _configurations = new Configuration[_configurationClasses.length]; for (int i = 0; i < _configurationClasses.length; i++) { _configurations[i] = (Configuration) Loader.loadClass(this.getClass(), _configurationClasses[i]) .newInstance(); } } /* ------------------------------------------------------------ */ @Override protected boolean isProtectedTarget(String target) { while (target.startsWith("//")) target = URIUtil.compactPath(target); return StringUtil.startsWithIgnoreCase(target, "/web-inf") || StringUtil.startsWithIgnoreCase(target, "/meta-inf"); } /* ------------------------------------------------------------ */ @Override public String toString() { return super.toString() + (_war == null ? "" : ("," + _war)); } /* ------------------------------------------------------------ */ /** * @param configurations The configuration class names. If setConfigurations is not called * these classes are used to create a configurations array. */ public void setConfigurationClasses(String[] configurations) { _configurationClasses = configurations == null ? null : (String[]) configurations.clone(); _configurationClassesSet = true; } /* ------------------------------------------------------------ */ /** * @param configurations The configurations to set. */ public void setConfigurations(Configuration[] configurations) { _configurations = configurations == null ? null : (Configuration[]) configurations.clone(); _configurationsSet = true; } /* ------------------------------------------------------------ */ /** * The default descriptor is a web.xml format file that is applied to the context before the standard WEB-INF/web.xml * @param defaultsDescriptor The defaultsDescriptor to set. */ public void setDefaultsDescriptor(String defaultsDescriptor) { _defaultsDescriptor = defaultsDescriptor; } /* ------------------------------------------------------------ */ /** * The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml * @param overrideDescriptor The overrideDescritpor to set. */ public void setOverrideDescriptor(String overrideDescriptor) { _overrideDescriptor = overrideDescriptor; } /* ------------------------------------------------------------ */ /** * @return the web.xml descriptor to use. If set to null, WEB-INF/web.xml is used if it exists. */ public String getDescriptor() { return _descriptor; } /* ------------------------------------------------------------ */ /** * @param descriptor the web.xml descriptor to use. If set to null, WEB-INF/web.xml is used if it exists. */ public void setDescriptor(String descriptor) { _descriptor = descriptor; } /* ------------------------------------------------------------ */ /** * @param distributable The distributable to set. */ public void setDistributable(boolean distributable) { this._distributable = distributable; } /* ------------------------------------------------------------ */ @Override public void setEventListeners(EventListener[] eventListeners) { if (_sessionHandler != null) _sessionHandler.clearEventListeners(); super.setEventListeners(eventListeners); for (int i = 0; eventListeners != null && i < eventListeners.length; i++) { EventListener listener = eventListeners[i]; if ((listener instanceof HttpSessionActivationListener) || (listener instanceof HttpSessionAttributeListener) || (listener instanceof HttpSessionBindingListener) || (listener instanceof HttpSessionListener)) { if (_sessionHandler != null) _sessionHandler.addEventListener(listener); } } } /* ------------------------------------------------------------ */ /** Add EventListener * Conveniance method that calls {@link #setEventListeners(EventListener[])} * @param listener */ @Override public void addEventListener(EventListener listener) { setEventListeners( (EventListener[]) LazyList.addToArray(getEventListeners(), listener, EventListener.class)); } /* ------------------------------------------------------------ */ /** * @param extractWAR True if war files are extracted */ public void setExtractWAR(boolean extractWAR) { _extractWAR = extractWAR; } /* ------------------------------------------------------------ */ /** * * @param copy True if the webdir is copied (to allow hot replacement of jars) */ public void setCopyWebDir(boolean copy) { _copyDir = copy; } /* ------------------------------------------------------------ */ /** * @param java2compliant The java2compliant to set. */ public void setParentLoaderPriority(boolean java2compliant) { _parentLoaderPriority = java2compliant; } /* ------------------------------------------------------------ */ /** * @param permissions The permissions to set. */ public void setPermissions(PermissionCollection permissions) { _permissions = permissions; } /* ------------------------------------------------------------ */ /** * Set the server classes patterns. * <p> * Server classes/packages are classes used to implement the server and are hidden * from the context. If the context needs to load these classes, it must have its * own copy of them in WEB-INF/lib or WEB-INF/classes. * A class pattern is a string of one of the forms:<dl> * <dt>org.package.Classname</dt><dd>Match a specific class</dd> * <dt>org.package.</dt><dd>Match a specific package hierarchy</dd> * <dt>-org.package.Classname</dt><dd>Exclude a specific class</dd> * <dt>-org.package.</dt><dd>Exclude a specific package hierarchy</dd> * </dl> * @param serverClasses The serverClasses to set. */ public void setServerClasses(String[] serverClasses) { _serverClasses = ClasspathPattern.fromArray(serverClasses); } /* ------------------------------------------------------------ */ /** * Set the system classes patterns. * <p> * System classes/packages are classes provided by the JVM and that * cannot be replaced by classes of the same name from WEB-INF, * regardless of the value of {@link #setParentLoaderPriority(boolean)}. * A class pattern is a string of one of the forms:<dl> * <dt>org.package.Classname</dt><dd>Match a specific class</dd> * <dt>org.package.</dt><dd>Match a specific package hierarchy</dd> * <dt>-org.package.Classname</dt><dd>Exclude a specific class</dd> * <dt>-org.package.</dt><dd>Exclude a specific package hierarchy</dd> * </dl> * @param systemClasses The systemClasses to set. */ public void setSystemClasses(String[] systemClasses) { _systemClasses = ClasspathPattern.fromArray(systemClasses); } /* ------------------------------------------------------------ */ /** Set temporary directory for context. * The javax.servlet.context.tempdir attribute is also set. * @param dir Writable temporary directory. */ public void setTempDirectory(File dir) { if (isStarted()) throw new IllegalStateException("Started"); if (dir != null) { try { dir = new File(dir.getCanonicalPath()); } catch (IOException e) { Log.warn(Log.EXCEPTION, e); } } if (dir != null && !dir.exists()) { dir.mkdir(); dir.deleteOnExit(); } if (dir != null && (!dir.exists() || !dir.isDirectory() || !dir.canWrite())) throw new IllegalArgumentException("Bad temp directory: " + dir); _tmpDir = dir; setAttribute(TEMPDIR, _tmpDir); } public File getTempDirectory() { return _tmpDir; } /* ------------------------------------------------------------ */ /** * @param war The war to set as a file name or URL */ public void setWar(String war) { _war = war; } /* ------------------------------------------------------------ */ /** * @return Comma or semicolon separated path of filenames or URLs * pointing to directories or jar files. Directories should end * with '/'. */ public String getExtraClasspath() { return _extraClasspath; } /* ------------------------------------------------------------ */ /** * @param extraClasspath Comma or semicolon separated path of filenames or URLs * pointing to directories or jar files. Directories should end * with '/'. */ public void setExtraClasspath(String extraClasspath) { _extraClasspath = extraClasspath; } /* ------------------------------------------------------------ */ public boolean isLogUrlOnStart() { return _logUrlOnStart; } /* ------------------------------------------------------------ */ /** * Sets whether or not the web app name and URL is logged on startup * * @param logOnStart whether or not the log message is created */ public void setLogUrlOnStart(boolean logOnStart) { this._logUrlOnStart = logOnStart; } /* ------------------------------------------------------------ */ @Override public void setServer(Server server) { super.setServer(server); //if we haven't been given a set of configuration instances to //use, and we haven't been given a set of configuration classes //to use, use the configuration classes that came from the //Server (if there are any) if (!_configurationsSet && !_configurationClassesSet && server != null) { String[] serverConfigs = (String[]) server.getAttribute(SERVER_CONFIG); if (serverConfigs != null) setConfigurationClasses(serverConfigs); } } /* ------------------------------------------------------------ */ @Override protected void startContext() throws Exception { // Configure webapp for (int i = 0; i < _configurations.length; i++) _configurations[i].configure(this); super.startContext(); } /* ------------------------------------------------------------ */ public class Context extends ServletContextHandler.Context { /* ------------------------------------------------------------ */ public URL getResource(String path) throws MalformedURLException { Resource resource = WebAppContext.this.getResource(path); if (resource == null || !resource.exists()) return null; // Should we go to the original war? if (resource.isDirectory() && resource instanceof ResourceCollection && !WebAppContext.this.isExtractWAR()) { Resource[] resources = ((ResourceCollection) resource).getResources(); for (int i = resources.length; i-- > 0;) { if (resources[i].getName().startsWith("jar:file")) return resources[i].getURL(); } } return resource.getURL(); } } }