blob: 069559120c4abc831f27380660a285a791d981d6 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2014 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.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
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 = null;
if (context.getClassLoader() != null)
loader = context.getClassLoader().getParent();
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)
{
tmpDir = null;
throw new IllegalStateException("Cannot create tmp dir in "+System.getProperty("java.io.tmpdir")+ " for context "+context,e);
}
}
}
/**
* 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();
}
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()+" file="+(web_app.getFile()));
// 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(Locale.ENGLISH).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
{
//Use a sentinel file that will exist only whilst the extraction is taking place.
//This will help us detect interrupted extractions.
File extractionLock = new File (context.getTempDirectory(), ".extract_lock");
if (!extractedWebAppDir.exists())
{
//it hasn't been extracted before so extract it
extractionLock.createNewFile();
extractedWebAppDir.mkdir();
LOG.info("Extract " + web_app + " to " + extractedWebAppDir);
Resource jar_web_app = JarResource.newJarResource(web_app);
jar_web_app.copyTo(extractedWebAppDir);
extractionLock.delete();
}
else
{
//only extract if the war file is newer, or a .extract_lock file is left behind meaning a possible partial extraction
if (web_app.lastModified() > extractedWebAppDir.lastModified() || extractionLock.exists())
{
extractionLock.createNewFile();
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);
extractionLock.delete();
}
}
}
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() && !context.isCopyWebDir())
{
Resource web_inf= web_app.addPath("WEB-INF/");
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(Locale.ENGLISH);
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;
}
}