package org.eclipse.jetty.webapp; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.PatternMatcher; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.resource.JarResource; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.ResourceCollection; public class WebInfConfiguration extends AbstractConfiguration { private static final Logger LOG = Log.getLogger(WebInfConfiguration.class); public static final String TEMPDIR_CONFIGURED = "org.eclipse.jetty.tmpdirConfigured"; public static final String CONTAINER_JAR_PATTERN = "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern"; public static final String WEBINF_JAR_PATTERN = "org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern"; /** * If set, to a list of URLs, these resources are added to the context * resource base as a resource collection. */ public static final String RESOURCE_URLS = "org.eclipse.jetty.resources"; protected Resource _preUnpackBaseResource; @Override public void preConfigure(final WebAppContext context) throws Exception { // Look for a work directory File work = findWorkDirectory(context); if (work != null) makeTempDirectory(work, context, false); //Make a temp directory for the webapp if one is not already set resolveTempDirectory(context); //Extract webapp if necessary unpack (context); //Apply an initial ordering to the jars which governs which will be scanned for META-INF //info and annotations. The ordering is based on inclusion patterns. String tmp = (String)context.getAttribute(WEBINF_JAR_PATTERN); Pattern webInfPattern = (tmp==null?null:Pattern.compile(tmp)); tmp = (String)context.getAttribute(CONTAINER_JAR_PATTERN); Pattern containerPattern = (tmp==null?null:Pattern.compile(tmp)); //Apply ordering to container jars - if no pattern is specified, we won't //match any of the container jars PatternMatcher containerJarNameMatcher = new PatternMatcher () { public void matched(URI uri) throws Exception { context.getMetaData().addContainerJar(Resource.newResource(uri)); } }; ClassLoader loader = context.getClassLoader(); while (loader != null && (loader instanceof URLClassLoader)) { URL[] urls = ((URLClassLoader)loader).getURLs(); if (urls != null) { URI[] containerUris = new URI[urls.length]; int i=0; for (URL u : urls) { try { containerUris[i] = u.toURI(); } catch (URISyntaxException e) { containerUris[i] = new URI(u.toString().replaceAll(" ", "%20")); } i++; } containerJarNameMatcher.match(containerPattern, containerUris, false); } loader = loader.getParent(); } //Apply ordering to WEB-INF/lib jars PatternMatcher webInfJarNameMatcher = new PatternMatcher () { @Override public void matched(URI uri) throws Exception { context.getMetaData().addWebInfJar(Resource.newResource(uri)); } }; List<Resource> jars = findJars(context); //Convert to uris for matching URI[] uris = null; if (jars != null) { uris = new URI[jars.size()]; int i=0; for (Resource r: jars) { uris[i++] = r.getURI(); } } webInfJarNameMatcher.match(webInfPattern, uris, true); //null is inclusive, no pattern == all jars match } @Override public void configure(WebAppContext context) throws Exception { //cannot configure if the context is already started if (context.isStarted()) { if (LOG.isDebugEnabled()) LOG.debug("Cannot configure webapp "+context+" after it is started"); return; } Resource web_inf = context.getWebInf(); // Add WEB-INF classes and lib classpaths if (web_inf != null && web_inf.isDirectory() && context.getClassLoader() instanceof WebAppClassLoader) { // Look for classes directory Resource classes= web_inf.addPath("classes/"); if (classes.exists()) ((WebAppClassLoader)context.getClassLoader()).addClassPath(classes); // Look for jars Resource lib= web_inf.addPath("lib/"); if (lib.exists() || lib.isDirectory()) ((WebAppClassLoader)context.getClassLoader()).addJars(lib); } // Look for extra resource @SuppressWarnings("unchecked") List<Resource> resources = (List<Resource>)context.getAttribute(RESOURCE_URLS); if (resources!=null) { Resource[] collection=new Resource[resources.size()+1]; int i=0; collection[i++]=context.getBaseResource(); for (Resource resource : resources) collection[i++]=resource; context.setBaseResource(new ResourceCollection(collection)); } } @Override public void deconfigure(WebAppContext context) throws Exception { // delete temp directory if we had to create it or if it isn't called work Boolean tmpdirConfigured = (Boolean)context.getAttribute(TEMPDIR_CONFIGURED); if (context.getTempDirectory()!=null && (tmpdirConfigured == null || !tmpdirConfigured.booleanValue()) && !isTempWorkDirectory(context.getTempDirectory())) { IO.delete(context.getTempDirectory()); context.setTempDirectory(null); //clear out the context attributes for the tmp dir only if we had to //create the tmp dir context.setAttribute(TEMPDIR_CONFIGURED, null); context.setAttribute(WebAppContext.TEMPDIR, null); } //reset the base resource back to what it was before we did any unpacking of resources context.setBaseResource(_preUnpackBaseResource); } /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.webapp.AbstractConfiguration#cloneConfigure(org.eclipse.jetty.webapp.WebAppContext, org.eclipse.jetty.webapp.WebAppContext) */ @Override public void cloneConfigure(WebAppContext template, WebAppContext context) throws Exception { File tmpDir=File.createTempFile(WebInfConfiguration.getCanonicalNameForWebAppTmpDir(context),"",template.getTempDirectory().getParentFile()); if (tmpDir.exists()) { IO.delete(tmpDir); } tmpDir.mkdir(); tmpDir.deleteOnExit(); context.setTempDirectory(tmpDir); } /* ------------------------------------------------------------ */ /** * Get a temporary directory in which to unpack the war etc etc. * The algorithm for determining this is to check these alternatives * in the order shown: * * <p>A. Try to use an explicit directory specifically for this webapp:</p> * <ol> * <li> * Iff an explicit directory is set for this webapp, use it. Do NOT set * delete on exit. * </li> * <li> * Iff javax.servlet.context.tempdir context attribute is set for * this webapp && exists && writeable, then use it. Do NOT set delete on exit. * </li> * </ol> * * <p>B. Create a directory based on global settings. The new directory * will be called "Jetty_"+host+"_"+port+"__"+context+"_"+virtualhost * Work out where to create this directory: * <ol> * <li> * Iff $(jetty.home)/work exists create the directory there. Do NOT * set delete on exit. Do NOT delete contents if dir already exists. * </li> * <li> * Iff WEB-INF/work exists create the directory there. Do NOT set * delete on exit. Do NOT delete contents if dir already exists. * </li> * <li> * Else create dir in $(java.io.tmpdir). Set delete on exit. Delete * contents if dir already exists. * </li> * </ol> */ public void resolveTempDirectory (WebAppContext context) { //If a tmp directory is already set, we're done File tmpDir = context.getTempDirectory(); if (tmpDir != null && tmpDir.isDirectory() && tmpDir.canWrite()) { context.setAttribute(TEMPDIR_CONFIGURED, Boolean.TRUE); return; // Already have a suitable tmp dir configured } // No temp directory configured, try to establish one. // First we check the context specific, javax.servlet specified, temp directory attribute File servletTmpDir = asFile(context.getAttribute(WebAppContext.TEMPDIR)); if (servletTmpDir != null && servletTmpDir.isDirectory() && servletTmpDir.canWrite()) { // Use as tmpDir tmpDir = servletTmpDir; // Ensure Attribute has File object context.setAttribute(WebAppContext.TEMPDIR,tmpDir); // Set as TempDir in context. context.setTempDirectory(tmpDir); return; } try { // Put the tmp dir in the work directory if we had one File work = new File(System.getProperty("jetty.home"),"work"); if (work.exists() && work.canWrite() && work.isDirectory()) { makeTempDirectory(work, context, false); //make a tmp dir inside work, don't delete if it exists } else { File baseTemp = asFile(context.getAttribute(WebAppContext.BASETEMPDIR)); if (baseTemp != null && baseTemp.isDirectory() && baseTemp.canWrite()) { // Use baseTemp directory (allow the funky Jetty_0_0_0_0.. subdirectory logic to kick in makeTempDirectory(baseTemp,context,false); } else { makeTempDirectory(new File(System.getProperty("java.io.tmpdir")),context,true); //make a tmpdir, delete if it already exists } } } catch(Exception e) { tmpDir=null; LOG.ignore(e); } //Third ... Something went wrong trying to make the tmp directory, just make //a jvm managed tmp directory if (context.getTempDirectory() == null) { try { // Last resort tmpDir=File.createTempFile("JettyContext",""); if (tmpDir.exists()) IO.delete(tmpDir); tmpDir.mkdir(); tmpDir.deleteOnExit(); context.setTempDirectory(tmpDir); } catch(IOException e) { LOG.warn("tmpdir",e); System.exit(1); } } } /** * Given an Object, return File reference for object. * Typically used to convert anonymous Object from getAttribute() calls to a File object. * @param fileattr the file attribute to analyze and return from (supports type File and type String, all others return null) * @return the File object, null if null, or null if not a File or String */ private File asFile(Object fileattr) { if (fileattr == null) { return null; } if (fileattr instanceof File) { return (File)fileattr; } if (fileattr instanceof String) { return new File((String)fileattr); } return null; } public void makeTempDirectory (File parent, WebAppContext context, boolean deleteExisting) throws IOException { if (parent != null && parent.exists() && parent.canWrite() && parent.isDirectory()) { String temp = getCanonicalNameForWebAppTmpDir(context); File tmpDir = new File(parent,temp); if (deleteExisting && tmpDir.exists()) { if (!IO.delete(tmpDir)) { if(LOG.isDebugEnabled())LOG.debug("Failed to delete temp dir "+tmpDir); } //If we can't delete the existing tmp dir, create a new one if (tmpDir.exists()) { String old=tmpDir.toString(); tmpDir=File.createTempFile(temp+"_",""); if (tmpDir.exists()) IO.delete(tmpDir); LOG.warn("Can't reuse "+old+", using "+tmpDir); } } if (!tmpDir.exists()) tmpDir.mkdir(); //If the parent is not a work directory if (!isTempWorkDirectory(tmpDir)) { tmpDir.deleteOnExit(); //TODO why is this here? File sentinel = new File(tmpDir, ".active"); if(!sentinel.exists()) sentinel.mkdir(); } if(LOG.isDebugEnabled()) LOG.debug("Set temp dir "+tmpDir); context.setTempDirectory(tmpDir); } } public void unpack (WebAppContext context) throws IOException { Resource web_app = context.getBaseResource(); _preUnpackBaseResource = context.getBaseResource(); if (web_app == null) { String war = context.getWar(); if (war!=null && war.length()>0) web_app = context.newResource(war); else web_app=context.getBaseResource(); // Accept aliases for WAR files if (web_app.getAlias() != null) { LOG.debug(web_app + " anti-aliased to " + web_app.getAlias()); web_app = context.newResource(web_app.getAlias()); } if (LOG.isDebugEnabled()) LOG.debug("Try webapp=" + web_app + ", exists=" + web_app.exists() + ", directory=" + web_app.isDirectory()); // Is the WAR usable directly? if (web_app.exists() && !web_app.isDirectory() && !web_app.toString().startsWith("jar:")) { // No - then lets see if it can be turned into a jar URL. Resource jarWebApp = JarResource.newJarResource(web_app); if (jarWebApp.exists() && jarWebApp.isDirectory()) web_app= jarWebApp; } // If we should extract or the URL is still not usable if (web_app.exists() && ( (context.isCopyWebDir() && web_app.getFile() != null && web_app.getFile().isDirectory()) || (context.isExtractWAR() && web_app.getFile() != null && !web_app.getFile().isDirectory()) || (context.isExtractWAR() && web_app.getFile() == null) || !web_app.isDirectory()) ) { // Look for sibling directory. File extractedWebAppDir = null; if (war!=null) { // look for a sibling like "foo/" to a "foo.war" File warfile=Resource.newResource(war).getFile(); if (warfile!=null && warfile.getName().toLowerCase().endsWith(".war")) { File sibling = new File(warfile.getParent(),warfile.getName().substring(0,warfile.getName().length()-4)); if (sibling.exists() && sibling.isDirectory() && sibling.canWrite()) extractedWebAppDir=sibling; } } if (extractedWebAppDir==null) // Then extract it if necessary to the temporary location extractedWebAppDir= new File(context.getTempDirectory(), "webapp"); if (web_app.getFile()!=null && web_app.getFile().isDirectory()) { // Copy directory LOG.info("Copy " + web_app + " to " + extractedWebAppDir); web_app.copyTo(extractedWebAppDir); } else { if (!extractedWebAppDir.exists()) { //it hasn't been extracted before so extract it extractedWebAppDir.mkdir(); LOG.info("Extract " + web_app + " to " + extractedWebAppDir); Resource jar_web_app = JarResource.newJarResource(web_app); jar_web_app.copyTo(extractedWebAppDir); } else { //only extract if the war file is newer if (web_app.lastModified() > extractedWebAppDir.lastModified()) { IO.delete(extractedWebAppDir); extractedWebAppDir.mkdir(); LOG.info("Extract " + web_app + " to " + extractedWebAppDir); Resource jar_web_app = JarResource.newJarResource(web_app); jar_web_app.copyTo(extractedWebAppDir); } } } web_app = Resource.newResource(extractedWebAppDir.getCanonicalPath()); } // Now do we have something usable? if (!web_app.exists() || !web_app.isDirectory()) { LOG.warn("Web application not found " + war); throw new java.io.FileNotFoundException(war); } context.setBaseResource(web_app); if (LOG.isDebugEnabled()) LOG.debug("webapp=" + web_app); } // Do we need to extract WEB-INF/lib? if (context.isCopyWebInf()) { Resource web_inf= web_app.addPath("WEB-INF/"); if (web_inf instanceof ResourceCollection || web_inf.exists() && web_inf.isDirectory() && (web_inf.getFile()==null || !web_inf.getFile().isDirectory())) { File extractedWebInfDir= new File(context.getTempDirectory(), "webinf"); if (extractedWebInfDir.exists()) IO.delete(extractedWebInfDir); extractedWebInfDir.mkdir(); Resource web_inf_lib = web_inf.addPath("lib/"); File webInfDir=new File(extractedWebInfDir,"WEB-INF"); webInfDir.mkdir(); if (web_inf_lib.exists()) { File webInfLibDir = new File(webInfDir, "lib"); if (webInfLibDir.exists()) IO.delete(webInfLibDir); webInfLibDir.mkdir(); LOG.info("Copying WEB-INF/lib " + web_inf_lib + " to " + webInfLibDir); web_inf_lib.copyTo(webInfLibDir); } Resource web_inf_classes = web_inf.addPath("classes/"); if (web_inf_classes.exists()) { File webInfClassesDir = new File(webInfDir, "classes"); if (webInfClassesDir.exists()) IO.delete(webInfClassesDir); webInfClassesDir.mkdir(); LOG.info("Copying WEB-INF/classes from "+web_inf_classes+" to "+webInfClassesDir.getAbsolutePath()); web_inf_classes.copyTo(webInfClassesDir); } web_inf=Resource.newResource(extractedWebInfDir.getCanonicalPath()); ResourceCollection rc = new ResourceCollection(web_inf,web_app); if (LOG.isDebugEnabled()) LOG.debug("context.resourcebase = "+rc); context.setBaseResource(rc); } } } public File findWorkDirectory (WebAppContext context) throws IOException { if (context.getBaseResource() != null) { Resource web_inf = context.getWebInf(); if (web_inf !=null && web_inf.exists()) { return new File(web_inf.getFile(),"work"); } } return null; } /** * Check if the tmpDir itself is called "work", or if the tmpDir * is in a directory called "work". * @return true if File is a temporary or work directory */ public boolean isTempWorkDirectory (File tmpDir) { if (tmpDir == null) return false; if (tmpDir.getName().equalsIgnoreCase("work")) return true; File t = tmpDir.getParentFile(); if (t == null) return false; return (t.getName().equalsIgnoreCase("work")); } /** * Create a canonical name for a webapp temp directory. * The form of the name is: * <code>"Jetty_"+host+"_"+port+"__"+resourceBase+"_"+context+"_"+virtualhost+base36_hashcode_of_whole_string</code> * * host and port uniquely identify the server * context and virtual host uniquely identify the webapp * @return the canonical name for the webapp temp directory */ public static String getCanonicalNameForWebAppTmpDir (WebAppContext context) { StringBuffer canonicalName = new StringBuffer(); canonicalName.append("jetty-"); //get the host and the port from the first connector Server server=context.getServer(); if (server!=null) { Connector[] connectors = context.getServer().getConnectors(); if (connectors.length>0) { //Get the host String host = (connectors==null||connectors[0]==null?"":connectors[0].getHost()); if (host == null) host = "0.0.0.0"; canonicalName.append(host); //Get the port canonicalName.append("-"); //try getting the real port being listened on int port = (connectors==null||connectors[0]==null?0:connectors[0].getLocalPort()); //if not available (eg no connectors or connector not started), //try getting one that was configured. if (port < 0) port = connectors[0].getPort(); canonicalName.append(port); canonicalName.append("-"); } } //Resource base try { Resource resource = context.getBaseResource(); if (resource == null) { if (context.getWar()==null || context.getWar().length()==0) resource=context.newResource(context.getResourceBase()); // Set dir or WAR resource = context.newResource(context.getWar()); } String tmp = URIUtil.decodePath(resource.getURL().getPath()); if (tmp.endsWith("/")) tmp = tmp.substring(0, tmp.length()-1); if (tmp.endsWith("!")) tmp = tmp.substring(0, tmp.length() -1); //get just the last part which is the filename int i = tmp.lastIndexOf("/"); canonicalName.append(tmp.substring(i+1, tmp.length())); canonicalName.append("-"); } catch (Exception e) { LOG.warn("Can't generate resourceBase as part of webapp tmp dir name", e); } //Context name String contextPath = context.getContextPath(); contextPath=contextPath.replace('/','_'); contextPath=contextPath.replace('\\','_'); canonicalName.append(contextPath); //Virtual host (if there is one) canonicalName.append("-"); String[] vhosts = context.getVirtualHosts(); if (vhosts == null || vhosts.length <= 0) canonicalName.append("any"); else canonicalName.append(vhosts[0]); // sanitize for (int i=0;i<canonicalName.length();i++) { char c=canonicalName.charAt(i); if (!Character.isJavaIdentifierPart(c) && "-.".indexOf(c)<0) canonicalName.setCharAt(i,'.'); } canonicalName.append("-"); return canonicalName.toString(); } /** * Look for jars in WEB-INF/lib * @param context * @return the list of jar resources found within context * @throws Exception */ protected List<Resource> findJars (WebAppContext context) throws Exception { List<Resource> jarResources = new ArrayList<Resource>(); Resource web_inf = context.getWebInf(); if (web_inf==null || !web_inf.exists()) return null; Resource web_inf_lib = web_inf.addPath("/lib"); if (web_inf_lib.exists() && web_inf_lib.isDirectory()) { String[] files=web_inf_lib.list(); for (int f=0;files!=null && f<files.length;f++) { try { Resource file = web_inf_lib.addPath(files[f]); String fnlc = file.getName().toLowerCase(); int dot = fnlc.lastIndexOf('.'); String extension = (dot < 0 ? null : fnlc.substring(dot)); if (extension != null && (extension.equals(".jar") || extension.equals(".zip"))) { jarResources.add(file); } } catch (Exception ex) { LOG.warn(Log.EXCEPTION,ex); } } } return jarResources; } }