blob: 7b7303d3170d6cbd91064a487e450c1298606153 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2016 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.deploy.providers;
import java.io.File;
import java.io.FilenameFilter;
import java.util.Locale;
import org.eclipse.jetty.deploy.App;
import org.eclipse.jetty.deploy.ConfigurationManager;
import org.eclipse.jetty.deploy.util.FileID;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.xml.XmlConfiguration;
/**
* The webapps directory scanning provider.
* <p>
* This provider scans one or more directories (typically "webapps") for contexts to
* deploy, which may be:<ul>
* <li>A standard WAR file (must end in ".war")</li>
* <li>A directory containing an expanded WAR file</li>
* <li>A directory containing static content</li>
* <li>An XML descriptor in {@link XmlConfiguration} format that configures a {@link ContextHandler} instance</li>
* </ul>
* <p>
* To avoid double deployments and allow flexibility of the content of the scanned directories, the provider
* implements some heuristics to ignore some files found in the scans: <ul>
* <li>Hidden files (starting with ".") are ignored</li>
* <li>Directories with names ending in ".d" are ignored</li>
* <li>If a directory and a WAR file exist ( eg foo/ and foo.war) then the directory is assumed to be
* the unpacked WAR and only the WAR is deployed (which may reused the unpacked directory)</li>
* <li>If a directory and a matching XML file exist ( eg foo/ and foo.xml) then the directory is assumed to be
* an unpacked WAR and only the XML is deployed (which may used the directory in it's configuration)</li>
* <li>If a WAR file and a matching XML exist (eg foo.war and foo.xml) then the WAR is assumed to
* be configured by the XML and only the XML is deployed.
* </ul>
* <p>For XML configured contexts, the ID map will contain a reference to the {@link Server} instance called "Server" and
* properties for the webapp file as "jetty.webapp" and directory as "jetty.webapps".
*/
@ManagedObject("Provider for start-up deployement of webapps based on presence in directory")
public class WebAppProvider extends ScanningAppProvider
{
private boolean _extractWars = false;
private boolean _parentLoaderPriority = false;
private ConfigurationManager _configurationManager;
private String _defaultsDescriptor;
private File _tempDirectory;
private String[] _configurationClasses;
public class Filter implements FilenameFilter
{
@Override
public boolean accept(File dir, String name)
{
if (!dir.exists())
{
return false;
}
String lowername = name.toLowerCase(Locale.ENGLISH);
File file = new File(dir,name);
// ignore hidden files
if (lowername.startsWith("."))
return false;
// Ignore some directories
if (file.isDirectory())
{
// is it a nominated config directory
if (lowername.endsWith(".d"))
return false;
// is it an unpacked directory for an existing war file?
if (exists(name+".war")||exists(name+".WAR"))
return false;
// is it a directory for an existing xml file?
if (exists(name+".xml")||exists(name+".XML"))
return false;
//is it a sccs dir?
if ("cvs".equals(lowername) || "cvsroot".equals(lowername))
return false;
// OK to deploy it then
return true;
}
// else is it a war file
if (lowername.endsWith(".war"))
{
//defer deployment decision to fileChanged()
return true;
}
// else is it a context XML file
if (lowername.endsWith(".xml"))
return true;
return false;
}
}
/* ------------------------------------------------------------ */
public WebAppProvider()
{
super();
setFilenameFilter(new Filter());
setScanInterval(0);
}
/* ------------------------------------------------------------ */
/** Get the extractWars.
* @return the extractWars
*/
@ManagedAttribute("extract war files")
public boolean isExtractWars()
{
return _extractWars;
}
/* ------------------------------------------------------------ */
/** Set the extractWars.
* @param extractWars the extractWars to set
*/
public void setExtractWars(boolean extractWars)
{
_extractWars = extractWars;
}
/* ------------------------------------------------------------ */
/** Get the parentLoaderPriority.
* @return the parentLoaderPriority
*/
@ManagedAttribute("parent classloader has priority")
public boolean isParentLoaderPriority()
{
return _parentLoaderPriority;
}
/* ------------------------------------------------------------ */
/** Set the parentLoaderPriority.
* @param parentLoaderPriority the parentLoaderPriority to set
*/
public void setParentLoaderPriority(boolean parentLoaderPriority)
{
_parentLoaderPriority = parentLoaderPriority;
}
/* ------------------------------------------------------------ */
/** Get the defaultsDescriptor.
* @return the defaultsDescriptor
*/
@ManagedAttribute("default descriptor for webapps")
public String getDefaultsDescriptor()
{
return _defaultsDescriptor;
}
/* ------------------------------------------------------------ */
/** Set the defaultsDescriptor.
* @param defaultsDescriptor the defaultsDescriptor to set
*/
public void setDefaultsDescriptor(String defaultsDescriptor)
{
_defaultsDescriptor = defaultsDescriptor;
}
/* ------------------------------------------------------------ */
public ConfigurationManager getConfigurationManager()
{
return _configurationManager;
}
/* ------------------------------------------------------------ */
/** Set the configurationManager.
* @param configurationManager the configurationManager to set
*/
public void setConfigurationManager(ConfigurationManager configurationManager)
{
_configurationManager = configurationManager;
}
/* ------------------------------------------------------------ */
/**
* @param configurations The configuration class names.
*/
public void setConfigurationClasses(String[] configurations)
{
_configurationClasses = configurations==null?null:(String[])configurations.clone();
}
/* ------------------------------------------------------------ */
@ManagedAttribute("configuration classes for webapps to be processed through")
public String[] getConfigurationClasses()
{
return _configurationClasses;
}
/**
* Set the Work directory where unpacked WAR files are managed from.
* <p>
* Default is the same as the <code>java.io.tmpdir</code> System Property.
*
* @param directory the new work directory
*/
public void setTempDir(File directory)
{
_tempDirectory = directory;
}
/* ------------------------------------------------------------ */
/**
* Get the user supplied Work Directory.
*
* @return the user supplied work directory (null if user has not set Temp Directory yet)
*/
@ManagedAttribute("temp directory for use, null if no user set temp directory")
public File getTempDir()
{
return _tempDirectory;
}
/* ------------------------------------------------------------ */
@Override
public ContextHandler createContextHandler(final App app) throws Exception
{
Resource resource = Resource.newResource(app.getOriginId());
File file = resource.getFile();
if (!resource.exists())
throw new IllegalStateException("App resouce does not exist "+resource);
String context = file.getName();
if (resource.exists() && FileID.isXmlFile(file))
{
XmlConfiguration xmlc = new XmlConfiguration(resource.getURI().toURL())
{
@Override
public void initializeDefaults(Object context)
{
super.initializeDefaults(context);
if (context instanceof WebAppContext)
{
WebAppContext webapp = (WebAppContext)context;
webapp.setParentLoaderPriority(_parentLoaderPriority);
if (_defaultsDescriptor != null)
webapp.setDefaultsDescriptor(_defaultsDescriptor);
}
}
};
xmlc.getIdMap().put("Server", getDeploymentManager().getServer());
xmlc.getProperties().put("jetty.home",System.getProperty("jetty.home","."));
xmlc.getProperties().put("jetty.base",System.getProperty("jetty.base","."));
xmlc.getProperties().put("jetty.webapp",file.getCanonicalPath());
xmlc.getProperties().put("jetty.webapps",file.getParentFile().getCanonicalPath());
if (getConfigurationManager() != null)
xmlc.getProperties().putAll(getConfigurationManager().getProperties());
return (ContextHandler)xmlc.configure();
}
else if (file.isDirectory())
{
// must be a directory
}
else if (FileID.isWebArchiveFile(file))
{
// Context Path is the same as the archive.
context = context.substring(0,context.length() - 4);
}
else
{
throw new IllegalStateException("unable to create ContextHandler for "+app);
}
// Ensure "/" is Not Trailing in context paths.
if (context.endsWith("/") && context.length() > 0)
{
context = context.substring(0,context.length() - 1);
}
// Start building the webapplication
WebAppContext webAppContext = new WebAppContext();
webAppContext.setDisplayName(context);
// special case of archive (or dir) named "root" is / context
if (context.equalsIgnoreCase("root"))
{
context = URIUtil.SLASH;
}
else if (context.toLowerCase(Locale.ENGLISH).startsWith("root-"))
{
int dash=context.toLowerCase(Locale.ENGLISH).indexOf('-');
String virtual = context.substring(dash+1);
webAppContext.setVirtualHosts(new String[]{virtual});
context = URIUtil.SLASH;
}
// Ensure "/" is Prepended to all context paths.
if (context.charAt(0) != '/')
{
context = "/" + context;
}
webAppContext.setContextPath(context);
webAppContext.setWar(file.getAbsolutePath());
if (_defaultsDescriptor != null)
{
webAppContext.setDefaultsDescriptor(_defaultsDescriptor);
}
webAppContext.setExtractWAR(_extractWars);
webAppContext.setParentLoaderPriority(_parentLoaderPriority);
if (_configurationClasses != null)
{
webAppContext.setConfigurationClasses(_configurationClasses);
}
if (_tempDirectory != null)
{
/* Since the Temp Dir is really a context base temp directory,
* Lets set the Temp Directory in a way similar to how WebInfConfiguration does it,
* instead of setting the
* WebAppContext.setTempDirectory(File).
* If we used .setTempDirectory(File) all webapps will wind up in the
* same temp / work directory, overwriting each others work.
*/
webAppContext.setAttribute(WebAppContext.BASETEMPDIR, _tempDirectory);
}
return webAppContext;
}
/* ------------------------------------------------------------ */
@Override
protected void fileChanged(String filename) throws Exception
{
File file = new File(filename);
if (!file.exists())
return;
File parent = file.getParentFile();
//is the file that changed a directory?
if (file.isDirectory())
{
//is there a .xml file of the same name?
if (exists(file.getName()+".xml")||exists(file.getName()+".XML"))
return; //ignore it
//is there .war file of the same name?
if (exists(file.getName()+".war")||exists(file.getName()+".WAR"))
return; //ignore it
super.fileChanged(filename);
return;
}
String lowname = file.getName().toLowerCase(Locale.ENGLISH);
//is the file that changed a .war file?
if (lowname.endsWith(".war"))
{
String name = file.getName();
String base=name.substring(0,name.length()-4);
String xmlname = base+".xml";
if (exists(xmlname))
{
//if a .xml file exists for it, then redeploy that instead
File xml = new File (parent, xmlname);
super.fileChanged(xml.getCanonicalPath());
return;
}
xmlname = base+".XML";
if (exists(xmlname))
{
//if a .XML file exists for it, then redeploy that instead
File xml = new File(parent, xmlname);
super.fileChanged(xml.getCanonicalPath());
return;
}
//redeploy the changed war
super.fileChanged(filename);
return;
}
//is the file that changed a .xml file?
if (lowname.endsWith(".xml"))
super.fileChanged(filename);
}
/* ------------------------------------------------------------ */
@Override
protected void fileAdded(String filename) throws Exception
{
File file = new File(filename);
if (!file.exists())
return;
//is the file that was added a directory?
if (file.isDirectory())
{
//is there a .xml file of the same name?
if (exists(file.getName()+".xml")||exists(file.getName()+".XML"))
return; //assume we will get added events for the xml file
//is there .war file of the same name?
if (exists(file.getName()+".war")||exists(file.getName()+".WAR"))
return; //assume we will get added events for the war file
super.fileAdded(filename);
return;
}
//is the file that was added a .war file?
String lowname = file.getName().toLowerCase(Locale.ENGLISH);
if (lowname.endsWith(".war"))
{
String name = file.getName();
String base=name.substring(0,name.length()-4);
//is there a .xml file of the same name?
if (exists(base+".xml")||exists(base+".XML"))
return; //ignore it as we should get addition of the xml file
super.fileAdded(filename);
return;
}
//is the file that was added an .xml file?
if (lowname.endsWith(".xml"))
super.fileAdded(filename);
}
/* ------------------------------------------------------------ */
@Override
protected void fileRemoved(String filename) throws Exception
{
File file = new File(filename);
//is the file that was removed a directory?
if (file.isDirectory())
{
//is there a .xml file of the same name?
if (exists(file.getName()+".xml")||exists(file.getName()+".XML"))
return; //assume we will get removed events for the xml file
//is there .war file of the same name?
if (exists(file.getName()+".war")||exists(file.getName()+".WAR"))
return; //assume we will get removed events for the war file
super.fileRemoved(filename);
return;
}
//is the file that was removed a .war file?
String lowname = file.getName().toLowerCase(Locale.ENGLISH);
if (lowname.endsWith(".war"))
{
//is there a .xml file of the same name?
String name = file.getName();
String base=name.substring(0,name.length()-4);
if (exists(base+".xml")||exists(base+".XML"))
return; //ignore it as we should get removal of the xml file
super.fileRemoved(filename);
return;
}
//is the file that was removed an .xml file?
if (lowname.endsWith(".xml"))
super.fileRemoved(filename);
}
}