blob: 0a2dd8cc3b9ee898d9495f5246785a5d054245f4 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2015 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.maven.plugin;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.plus.webapp.EnvConfiguration;
import org.eclipse.jetty.plus.webapp.PlusConfiguration;
import org.eclipse.jetty.quickstart.PreconfigureDescriptorProcessor;
import org.eclipse.jetty.quickstart.QuickStartDescriptorGenerator;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.FilterMapping;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.ServletMapping;
import org.eclipse.jetty.util.StringUtil;
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.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.FragmentConfiguration;
import org.eclipse.jetty.webapp.JettyWebXmlConfiguration;
import org.eclipse.jetty.webapp.MetaInfConfiguration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebInfConfiguration;
import org.eclipse.jetty.webapp.WebXmlConfiguration;
/**
* JettyWebAppContext
*
* Extends the WebAppContext to specialize for the maven environment.
* We pass in the list of files that should form the classpath for
* the webapp when executing in the plugin, and any jetty-env.xml file
* that may have been configured.
*
*/
public class JettyWebAppContext extends WebAppContext
{
private static final Logger LOG = Log.getLogger(JettyWebAppContext.class);
private static final String DEFAULT_CONTAINER_INCLUDE_JAR_PATTERN = ".*/javax.servlet-[^/]*\\.jar$|.*/servlet-api-[^/]*\\.jar$|.*javax.servlet.jsp.jstl-[^/]*\\.jar|.*taglibs-standard-impl-.*\\.jar";
private static final String WEB_INF_CLASSES_PREFIX = "/WEB-INF/classes";
private static final String WEB_INF_LIB_PREFIX = "/WEB-INF/lib";
public static final String[] DEFAULT_CONFIGURATION_CLASSES = {
"org.eclipse.jetty.maven.plugin.MavenWebInfConfiguration",
"org.eclipse.jetty.webapp.WebXmlConfiguration",
"org.eclipse.jetty.webapp.MetaInfConfiguration",
"org.eclipse.jetty.webapp.FragmentConfiguration",
"org.eclipse.jetty.plus.webapp.EnvConfiguration",
"org.eclipse.jetty.plus.webapp.PlusConfiguration",
"org.eclipse.jetty.annotations.AnnotationConfiguration",
"org.eclipse.jetty.webapp.JettyWebXmlConfiguration"
};
private final String[] QUICKSTART_CONFIGURATION_CLASSES = {
"org.eclipse.jetty.maven.plugin.MavenQuickStartConfiguration",
"org.eclipse.jetty.plus.webapp.EnvConfiguration",
"org.eclipse.jetty.plus.webapp.PlusConfiguration",
"org.eclipse.jetty.webapp.JettyWebXmlConfiguration"
};
private File _classes = null;
private File _testClasses = null;
private final List<File> _webInfClasses = new ArrayList<File>();
private final List<File> _webInfJars = new ArrayList<File>();
private final Map<String, File> _webInfJarMap = new HashMap<String, File>();
private List<File> _classpathFiles; //webInfClasses+testClasses+webInfJars
private String _jettyEnvXml;
private List<Overlay> _overlays;
private Resource _quickStartWebXml;
/**
* Set the "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern" with a pattern for matching jars on
* container classpath to scan. This is analogous to the WebAppContext.setAttribute() call.
*/
private String _containerIncludeJarPattern = null;
/**
* Set the "org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern" with a pattern for matching jars on
* webapp's classpath to scan. This is analogous to the WebAppContext.setAttribute() call.
*/
private String _webInfIncludeJarPattern = null;
/**
* If there is no maven-war-plugin config for ordering of the current project in the
* sequence of overlays, use this to control whether the current project is added
* first or last in list of overlaid resources
*/
private boolean _baseAppFirst = true;
private boolean _isGenerateQuickStart;
private PreconfigureDescriptorProcessor _preconfigProcessor;
/* ------------------------------------------------------------ */
public JettyWebAppContext ()
throws Exception
{
super();
// Turn off copyWebInf option as it is not applicable for plugin.
super.setCopyWebInf(false);
}
/* ------------------------------------------------------------ */
public void setContainerIncludeJarPattern(String pattern)
{
_containerIncludeJarPattern = pattern;
}
/* ------------------------------------------------------------ */
public String getContainerIncludeJarPattern()
{
return _containerIncludeJarPattern;
}
/* ------------------------------------------------------------ */
public String getWebInfIncludeJarPattern()
{
return _webInfIncludeJarPattern;
}
/* ------------------------------------------------------------ */
public void setWebInfIncludeJarPattern(String pattern)
{
_webInfIncludeJarPattern = pattern;
}
/* ------------------------------------------------------------ */
public List<File> getClassPathFiles()
{
return this._classpathFiles;
}
/* ------------------------------------------------------------ */
public void setJettyEnvXml (String jettyEnvXml)
{
this._jettyEnvXml = jettyEnvXml;
}
/* ------------------------------------------------------------ */
public String getJettyEnvXml()
{
return this._jettyEnvXml;
}
/* ------------------------------------------------------------ */
public void setClasses(File dir)
{
_classes = dir;
}
/* ------------------------------------------------------------ */
public File getClasses()
{
return _classes;
}
/* ------------------------------------------------------------ */
public void setWebInfLib (List<File> jars)
{
_webInfJars.addAll(jars);
}
/* ------------------------------------------------------------ */
public void setTestClasses (File dir)
{
_testClasses = dir;
}
/* ------------------------------------------------------------ */
public File getTestClasses ()
{
return _testClasses;
}
/* ------------------------------------------------------------ */
/**
* Ordered list of wars to overlay on top of the current project. The list
* may contain an overlay that represents the current project.
* @param overlays the list of overlays
*/
public void setOverlays (List<Overlay> overlays)
{
_overlays = overlays;
}
/* ------------------------------------------------------------ */
public List<Overlay> getOverlays()
{
return _overlays;
}
/* ------------------------------------------------------------ */
public void setBaseAppFirst(boolean value)
{
_baseAppFirst = value;
}
/* ------------------------------------------------------------ */
public boolean getBaseAppFirst()
{
return _baseAppFirst;
}
/* ------------------------------------------------------------ */
public void setQuickStartWebDescriptor (String quickStartWebXml) throws Exception
{
setQuickStartWebDescriptor(Resource.newResource(quickStartWebXml));
}
/* ------------------------------------------------------------ */
protected void setQuickStartWebDescriptor (Resource quickStartWebXml)
{
_quickStartWebXml = quickStartWebXml;
}
/* ------------------------------------------------------------ */
public Resource getQuickStartWebDescriptor ()
{
return _quickStartWebXml;
}
/* ------------------------------------------------------------ */
/**
* This method is provided as a convenience for jetty maven plugin configuration
* @param resourceBases Array of resources strings to set as a {@link ResourceCollection}. Each resource string may be a comma separated list of resources
* @see Resource
*/
public void setResourceBases(String[] resourceBases)
{
List<String> resources = new ArrayList<String>();
for (String rl:resourceBases)
{
String[] rs = StringUtil.csvSplit(rl);
for (String r:rs)
resources.add(r);
}
setBaseResource(new ResourceCollection(resources.toArray(new String[resources.size()])));
}
/* ------------------------------------------------------------ */
public List<File> getWebInfLib()
{
return _webInfJars;
}
/* ------------------------------------------------------------ */
public void setGenerateQuickStart (boolean quickStart)
{
_isGenerateQuickStart = quickStart;
}
/* ------------------------------------------------------------ */
public boolean isGenerateQuickStart()
{
return _isGenerateQuickStart;
}
/* ------------------------------------------------------------ */
@Override
protected void startWebapp() throws Exception
{
if (isGenerateQuickStart())
{
if (getQuickStartWebDescriptor() == null)
throw new IllegalStateException ("No location to generate quickstart descriptor");
QuickStartDescriptorGenerator generator = new QuickStartDescriptorGenerator(this, _preconfigProcessor.getXML());
try (FileOutputStream fos = new FileOutputStream(getQuickStartWebDescriptor().getFile()))
{
generator.generateQuickStartWebXml(fos);
}
}
else
{
if (LOG.isDebugEnabled()) { LOG.debug("Calling full start on webapp");}
super.startWebapp();
}
}
/* ------------------------------------------------------------ */
@Override
protected void stopWebapp() throws Exception
{
if (isGenerateQuickStart())
return;
if (LOG.isDebugEnabled()) { LOG.debug("Calling stop of fully started webapp");}
super.stopWebapp();
}
/* ------------------------------------------------------------ */
@Override
public void doStart () throws Exception
{
//choose if this will be a quickstart or normal start
if (!isGenerateQuickStart() && getQuickStartWebDescriptor() != null)
{
setConfigurationClasses(QUICKSTART_CONFIGURATION_CLASSES);
}
else
{
if (isGenerateQuickStart())
{
_preconfigProcessor = new PreconfigureDescriptorProcessor();
getMetaData().addDescriptorProcessor(_preconfigProcessor);
}
}
//Set up the pattern that tells us where the jars are that need scanning
//Allow user to set up pattern for names of jars from the container classpath
//that will be scanned - note that by default NO jars are scanned
String tmp = _containerIncludeJarPattern;
if (tmp==null || "".equals(tmp))
tmp = (String)getAttribute(WebInfConfiguration.CONTAINER_JAR_PATTERN);
tmp = addPattern(tmp, DEFAULT_CONTAINER_INCLUDE_JAR_PATTERN);
setAttribute(WebInfConfiguration.CONTAINER_JAR_PATTERN, tmp);
//Allow user to set up pattern of jar names from WEB-INF that will be scanned.
//Note that by default ALL jars considered to be in WEB-INF will be scanned - setting
//a pattern restricts scanning
if (_webInfIncludeJarPattern != null)
setAttribute(WebInfConfiguration.WEBINF_JAR_PATTERN, _webInfIncludeJarPattern);
//Set up the classes dirs that comprises the equivalent of WEB-INF/classes
if (_testClasses != null)
_webInfClasses.add(_testClasses);
if (_classes != null)
_webInfClasses.add(_classes);
// Set up the classpath
_classpathFiles = new ArrayList<File>();
_classpathFiles.addAll(_webInfClasses);
_classpathFiles.addAll(_webInfJars);
// Initialize map containing all jars in /WEB-INF/lib
_webInfJarMap.clear();
for (File file : _webInfJars)
{
// Return all jar files from class path
String fileName = file.getName();
if (fileName.endsWith(".jar"))
_webInfJarMap.put(fileName, file);
}
//check for CDI
initCDI();
// CHECK setShutdown(false);
super.doStart();
}
@Override
protected void loadConfigurations() throws Exception
{
super.loadConfigurations();
//inject configurations with config from maven plugin
for (Configuration c:getConfigurations())
{
if (c instanceof EnvConfiguration && getJettyEnvXml() != null)
((EnvConfiguration)c).setJettyEnvXml(Resource.toURL(new File(getJettyEnvXml())));
else if (c instanceof MavenQuickStartConfiguration && getQuickStartWebDescriptor() != null)
((MavenQuickStartConfiguration)c).setQuickStartWebXml(getQuickStartWebDescriptor());
}
}
/* ------------------------------------------------------------ */
public void doStop () throws Exception
{
if (_classpathFiles != null)
_classpathFiles.clear();
_classpathFiles = null;
_classes = null;
_testClasses = null;
if (_webInfJarMap != null)
_webInfJarMap.clear();
_webInfClasses.clear();
_webInfJars.clear();
// CHECK setShutdown(true);
//just wait a little while to ensure no requests are still being processed
Thread.currentThread().sleep(500L);
super.doStop();
//remove all listeners, servlets and filters. This is because we will re-apply
//any context xml file, which means they would potentially be added multiple times.
setEventListeners(new EventListener[0]);
getServletHandler().setFilters(new FilterHolder[0]);
getServletHandler().setFilterMappings(new FilterMapping[0]);
getServletHandler().setServlets(new ServletHolder[0]);
getServletHandler().setServletMappings(new ServletMapping[0]);
}
/* ------------------------------------------------------------ */
@Override
public Resource getResource(String uriInContext) throws MalformedURLException
{
Resource resource = null;
// Try to get regular resource
resource = super.getResource(uriInContext);
// If no regular resource exists check for access to /WEB-INF/lib or /WEB-INF/classes
if ((resource == null || !resource.exists()) && uriInContext != null && _classes != null)
{
String uri = URIUtil.canonicalPath(uriInContext);
if (uri == null)
return null;
try
{
// Replace /WEB-INF/classes with candidates for the classpath
if (uri.startsWith(WEB_INF_CLASSES_PREFIX))
{
if (uri.equalsIgnoreCase(WEB_INF_CLASSES_PREFIX) || uri.equalsIgnoreCase(WEB_INF_CLASSES_PREFIX+"/"))
{
//exact match for a WEB-INF/classes, so preferentially return the resource matching the web-inf classes
//rather than the test classes
if (_classes != null)
return Resource.newResource(_classes);
else if (_testClasses != null)
return Resource.newResource(_testClasses);
}
else
{
//try matching
Resource res = null;
int i=0;
while (res == null && (i < _webInfClasses.size()))
{
String newPath = uri.replace(WEB_INF_CLASSES_PREFIX, _webInfClasses.get(i).getPath());
res = Resource.newResource(newPath);
if (!res.exists())
{
res = null;
i++;
}
}
return res;
}
}
else if (uri.startsWith(WEB_INF_LIB_PREFIX))
{
// Return the real jar file for all accesses to
// /WEB-INF/lib/*.jar
String jarName = uri.replace(WEB_INF_LIB_PREFIX, "");
if (jarName.startsWith("/") || jarName.startsWith("\\"))
jarName = jarName.substring(1);
if (jarName.length()==0)
return null;
File jarFile = _webInfJarMap.get(jarName);
if (jarFile != null)
return Resource.newResource(jarFile.getPath());
return null;
}
}
catch (MalformedURLException e)
{
throw e;
}
catch (IOException e)
{
LOG.ignore(e);
}
}
return resource;
}
/* ------------------------------------------------------------ */
@Override
public Set<String> getResourcePaths(String path)
{
// Try to get regular resource paths - this will get appropriate paths from any overlaid wars etc
Set<String> paths = super.getResourcePaths(path);
if (path != null)
{
TreeSet<String> allPaths = new TreeSet<String>();
allPaths.addAll(paths);
//add in the dependency jars as a virtual WEB-INF/lib entry
if (path.startsWith(WEB_INF_LIB_PREFIX))
{
for (String fileName : _webInfJarMap.keySet())
{
// Return all jar files from class path
allPaths.add(WEB_INF_LIB_PREFIX + "/" + fileName);
}
}
else if (path.startsWith(WEB_INF_CLASSES_PREFIX))
{
int i=0;
while (i < _webInfClasses.size())
{
String newPath = path.replace(WEB_INF_CLASSES_PREFIX, _webInfClasses.get(i).getPath());
allPaths.addAll(super.getResourcePaths(newPath));
i++;
}
}
return allPaths;
}
return paths;
}
/* ------------------------------------------------------------ */
public String addPattern (String s, String pattern)
{
if (s == null)
s = "";
else
s = s.trim();
if (!s.contains(pattern))
{
if (s.length() != 0)
s = s + "|";
s = s + pattern;
}
return s;
}
/* ------------------------------------------------------------ */
public void initCDI()
{
Class cdiInitializer = null;
try
{
cdiInitializer = Thread.currentThread().getContextClassLoader().loadClass("org.eclipse.jetty.cdi.servlet.JettyWeldInitializer");
Method initWebAppMethod = cdiInitializer.getMethod("initWebApp", new Class[]{WebAppContext.class});
initWebAppMethod.invoke(null, new Object[]{this});
}
catch (ClassNotFoundException e)
{
LOG.debug("o.e.j.cdi.servlet.JettyWeldInitializer not found, no cdi integration available");
}
catch (NoSuchMethodException e)
{
LOG.warn("o.e.j.cdi.servlet.JettyWeldInitializer.initWebApp() not found, no cdi integration available");
}
catch (Exception e)
{
LOG.warn("Problem initializing cdi", e);
}
}
}