org.eclipse.jetty.ee9.webapp.MetaInfConfiguration Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jetty-ee9-webapp Show documentation
Show all versions of jetty-ee9-webapp Show documentation
Jetty web application support
The newest version!
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.ee9.webapp;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jetty.util.FileID;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.UriPatternPredicate;
import org.eclipse.jetty.util.resource.MountedPathResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollators;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.util.resource.ResourceUriPatternPredicate;
import org.eclipse.jetty.util.resource.Resources;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* MetaInfConfiguration
*
*
* Scan META-INF of jars to find:
*
* - tlds
* - web-fragment.xml
* - resources
*
*
* The jars which are scanned are:
*
* - those from the container classpath whose pattern matched the WebInfConfiguration.CONTAINER_JAR_PATTERN
* - those from WEB-INF/lib
*
*/
public class MetaInfConfiguration extends AbstractConfiguration
{
private static final Logger LOG = LoggerFactory.getLogger(MetaInfConfiguration.class);
public static final String USE_CONTAINER_METAINF_CACHE = "org.eclipse.jetty.metainf.useCache";
public static final boolean DEFAULT_USE_CONTAINER_METAINF_CACHE = true;
public static final String CACHED_CONTAINER_TLDS = "org.eclipse.jetty.tlds.cache";
public static final String CACHED_CONTAINER_FRAGMENTS = FragmentConfiguration.FRAGMENT_RESOURCES + ".cache";
public static final String CACHED_CONTAINER_RESOURCES = "org.eclipse.jetty.resources.cache";
public static final String METAINF_TLDS = "org.eclipse.jetty.tlds";
public static final String METAINF_FRAGMENTS = FragmentConfiguration.FRAGMENT_RESOURCES;
public static final String METAINF_RESOURCES = "org.eclipse.jetty.resources";
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";
public static final List __allScanTypes = Arrays.asList(METAINF_TLDS, METAINF_RESOURCES, METAINF_FRAGMENTS);
/**
* 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_DIRS = "org.eclipse.jetty.resources";
public MetaInfConfiguration()
{
addDependencies(WebXmlConfiguration.class);
}
@Override
public void preConfigure(final WebAppContext context) throws Exception
{
//find container jars/modules and select which ones to scan
findAndFilterContainerPaths(context);
//find web-app jars and select which ones to scan
findAndFilterWebAppPaths(context);
//No pattern to appy to classes, just add to metadata
context.getMetaData().setWebInfClassesResources(findClassDirs(context));
scanJars(context);
}
@Override
public void deconfigure(WebAppContext context) throws Exception
{
super.deconfigure(context);
}
/**
* Find jars and directories that are on the container's classpath
* and apply an optional filter. The filter is a pattern applied to the
* full jar or directory names. If there is no pattern, then no jar
* or dir is considered to match.
*
* Those jars that do match will be later examined for META-INF
* information and annotations.
*
* To find them, examine the classloaders in the hierarchy above the
* webapp classloader that are URLClassLoaders. For jdk-9 we also
* look at the java.class.path, and the jdk.module.path.
*
* @param context the WebAppContext being deployed
*/
public void findAndFilterContainerPaths(final WebAppContext context) throws Exception
{
String pattern = (String)context.getAttribute(CONTAINER_JAR_PATTERN);
if (LOG.isDebugEnabled())
LOG.debug("{}={}", CONTAINER_JAR_PATTERN, pattern);
if (StringUtil.isBlank(pattern))
return; // TODO review if this short cut will allow later code simplifications
ResourceFactory resourceFactory = context.getResourceFactory();
// Apply an initial name filter to the jars to select which will be eventually
// scanned for META-INF info and annotations. The filter is based on inclusion patterns.
UriPatternPredicate uriPatternPredicate = new UriPatternPredicate(pattern, false);
Consumer addContainerResource = (resource) ->
{
if (Resources.missing(resource))
{
if (LOG.isDebugEnabled())
LOG.debug("Classpath URI doesn't exist: " + resource);
}
else
{
context.getMetaData().addContainerResource(resource);
}
};
List containerUris = getAllContainerJars(context);
if (LOG.isDebugEnabled())
LOG.debug("All container urls {}", containerUris);
containerUris.stream()
.filter(uriPatternPredicate)
.map(resourceFactory::newResource)
.filter(Objects::nonNull)
.forEach(addContainerResource);
// When running on jvm 9 or above, we won't be able to look at the application
// classloader to extract urls, so we need to examine the classpath instead.
String classPath = System.getProperty("java.class.path");
if (classPath != null)
{
resourceFactory.split(classPath, File.pathSeparator)
.stream()
.filter(Objects::nonNull)
.filter(r -> uriPatternPredicate.test(URIUtil.unwrapContainer(r.getURI())))
.forEach(addContainerResource);
}
// We also need to examine the module path.
// TODO need to consider the jdk.module.upgrade.path - how to resolve
// which modules will be actually used. If its possible, it can
// only be attempted in jetty-10 with jdk-9 specific apis.
String modulePath = System.getProperty("jdk.module.path");
if (modulePath != null)
{
List matchingBasePaths =
Stream.of(modulePath.split(File.pathSeparator))
.map(resourceFactory::newResource)
.filter(Objects::nonNull)
.map(Resource::getURI)
.filter(uriPatternPredicate)
.map(Paths::get)
.toList();
for (Path path : matchingBasePaths)
{
if (Files.isDirectory(path))
{
try (Stream listing = Files.list(path))
{
for (Path listEntry : listing.toList())
{
Resource resource = resourceFactory.newResource(listEntry);
context.getMetaData().addContainerResource(resource);
}
}
}
else
{
Resource resource = resourceFactory.newResource(path);
context.getMetaData().addContainerResource(resource);
}
}
}
if (LOG.isDebugEnabled())
LOG.debug("Container paths selected:{}", context.getMetaData().getContainerResources());
}
/**
* Finds the jars that are either physically or virtually in
* WEB-INF/lib, and applies an optional filter to their full
* pathnames.
*
* The filter selects which jars will later be examined for META-INF
* information and annotations. If there is no pattern, then
* all jars are considered selected.
*
* @param context the WebAppContext being deployed
*/
public void findAndFilterWebAppPaths(WebAppContext context)
throws Exception
{
//Apply filter to WEB-INF/lib jars
String pattern = (String)context.getAttribute(WEBINF_JAR_PATTERN);
ResourceUriPatternPredicate webinfPredicate = new ResourceUriPatternPredicate(pattern, true);
List jars = findJars(context);
if (LOG.isDebugEnabled())
LOG.debug("webapp {}={} jars {}", WEBINF_JAR_PATTERN, pattern, jars);
// Only add matching Resources to metadata.webInfResources
if (jars != null)
{
jars.stream()
.filter(webinfPredicate)
.forEach(resource -> context.getMetaData().addWebInfResource(resource));
}
}
protected List getAllContainerJars(final WebAppContext context)
{
ClassLoader loader = MetaInfConfiguration.class.getClassLoader();
List uris = new ArrayList<>();
while (loader != null)
{
if (loader instanceof URLClassLoader urlCL)
{
URIUtil.streamOf(urlCL).forEach(uris::add);
}
loader = loader.getParent();
}
return uris;
}
@Override
public void configure(WebAppContext context) throws Exception
{
// Look for extra resource
@SuppressWarnings("unchecked")
Set resources = (Set)context.getAttribute(RESOURCE_DIRS);
if (resources != null && !resources.isEmpty())
{
List collection = new ArrayList<>();
collection.add(context.getBaseResource());
collection.addAll(resources);
context.setBaseResource(ResourceFactory.combine(collection));
}
}
protected void scanJars(WebAppContext context) throws Exception
{
boolean useContainerCache = DEFAULT_USE_CONTAINER_METAINF_CACHE;
if (context.getServer() != null)
{
Boolean attr = (Boolean)context.getServer().getAttribute(USE_CONTAINER_METAINF_CACHE);
if (attr != null)
useContainerCache = attr.booleanValue();
}
if (LOG.isDebugEnabled())
LOG.debug("{} = {}", USE_CONTAINER_METAINF_CACHE, useContainerCache);
//pre-emptively create empty lists for tlds, fragments and resources as context attributes
//this signals that this class has been called. This differentiates the case where this class
//has been called but finds no META-INF data from the case where this class was never called
if (context.getAttribute(METAINF_TLDS) == null)
context.setAttribute(METAINF_TLDS, new HashSet());
if (context.getAttribute(METAINF_RESOURCES) == null)
context.setAttribute(METAINF_RESOURCES, new HashSet());
if (context.getAttribute(METAINF_FRAGMENTS) == null)
context.setAttribute(METAINF_FRAGMENTS, new HashMap());
//always scan everything from the container's classpath
scanJars(context, context.getMetaData().getContainerResources(), useContainerCache, __allScanTypes);
//only look for fragments if web.xml is not metadata complete, or it version 3.0 or greater
List scanTypes = new ArrayList<>(__allScanTypes);
if (context.getMetaData().isMetaDataComplete() || (context.getServletContext().getEffectiveMajorVersion() < 3) && !context.isConfigurationDiscovered())
scanTypes.remove(METAINF_FRAGMENTS);
scanJars(context, context.getMetaData().getWebInfResources(false), false, scanTypes);
}
/**
* For backwards compatibility. This method will always scan for all types of data.
*
* @param context the context for the scan
* @param jars the jars to scan
* @param useCaches if true, the scanned info is cached
* @throws Exception if unable to scan the jars
*/
public void scanJars(final WebAppContext context, Collection jars, boolean useCaches)
throws Exception
{
scanJars(context, jars, useCaches, __allScanTypes);
}
/**
* Look into the jars to discover info in META-INF. If useCaches == true, then we will
* cache the info discovered indexed by the jar in which it was discovered: this speeds
* up subsequent context deployments.
*
* @param context the context for the scan
* @param jars the jars resources to scan
* @param useCaches if true, cache the info discovered
* @param scanTypes the type of things to look for in the jars
* @throws Exception if unable to scan the jars
*/
@SuppressWarnings("unchecked")
public void scanJars(final WebAppContext context, Collection jars, boolean useCaches, List scanTypes)
throws Exception
{
ConcurrentHashMap metaInfResourceCache = null;
ConcurrentHashMap metaInfFragmentCache = null;
ConcurrentHashMap> metaInfTldCache = null;
if (useCaches)
{
metaInfResourceCache = (ConcurrentHashMap)context.getServer().getAttribute(CACHED_CONTAINER_RESOURCES);
if (metaInfResourceCache == null)
{
metaInfResourceCache = new ConcurrentHashMap();
context.getServer().setAttribute(CACHED_CONTAINER_RESOURCES, metaInfResourceCache);
}
metaInfFragmentCache = (ConcurrentHashMap)context.getServer().getAttribute(CACHED_CONTAINER_FRAGMENTS);
if (metaInfFragmentCache == null)
{
metaInfFragmentCache = new ConcurrentHashMap();
context.getServer().setAttribute(CACHED_CONTAINER_FRAGMENTS, metaInfFragmentCache);
}
metaInfTldCache = (ConcurrentHashMap>)context.getServer().getAttribute(CACHED_CONTAINER_TLDS);
if (metaInfTldCache == null)
{
metaInfTldCache = new ConcurrentHashMap>();
context.getServer().setAttribute(CACHED_CONTAINER_TLDS, metaInfTldCache);
}
}
//Scan jars for META-INF information
if (jars != null)
{
try (ResourceFactory.Closeable scanResourceFactory = ResourceFactory.closeable())
{
for (Resource dir : jars)
{
try
{
//if not already a directory, convert it by mounting as jar file
if (!dir.isDirectory())
dir = scanResourceFactory.newJarFileResource(dir.getURI());
}
catch (Exception e)
{
//not an appropriate uri, skip it
continue;
}
if (isEmptyResource(dir))
continue;
if (scanTypes.contains(METAINF_RESOURCES))
scanForResources(context, dir, metaInfResourceCache);
if (scanTypes.contains(METAINF_FRAGMENTS))
scanForFragment(context, dir, metaInfFragmentCache);
if (scanTypes.contains(METAINF_TLDS))
scanForTlds(context, dir, metaInfTldCache);
}
}
}
}
/**
* Scan for META-INF/resources dir in the given directory.
*
* @param context the context for the scan
* @param dir the target directory to scan
* @param cache the resource cache
*/
public void scanForResources(WebAppContext context, Resource dir, ConcurrentHashMap cache)
{
// Resource target does not exist
if (isEmptyResource(dir))
return;
Resource resourcesDir = null;
if (cache != null && cache.containsKey(dir))
{
resourcesDir = cache.get(dir);
if (isEmptyResource(resourcesDir))
{
if (LOG.isDebugEnabled())
LOG.debug("{} cached as containing no META-INF/resources", dir);
return;
}
else if (LOG.isDebugEnabled())
LOG.debug("{} META-INF/resources found in cache ", dir);
}
else
{
//not using caches or not in the cache so check for the resources dir
if (LOG.isDebugEnabled())
LOG.debug("{} META-INF/resources checked", dir);
resourcesDir = dir.resolve("/META-INF/resources");
if (isEmptyResource(resourcesDir))
return;
//convert from an ephemeral Resource to one that is associated with the context's lifecycle
resourcesDir = context.getResourceFactory().newResource(resourcesDir.getURI());
if (cache != null)
{
Resource old = cache.putIfAbsent(dir, resourcesDir);
if (old != null)
resourcesDir = old;
else if (LOG.isDebugEnabled())
LOG.debug("{} META-INF/resources cache updated", dir);
}
}
//add it to the meta inf resources for this context
Set dirs = (Set)context.getAttribute(METAINF_RESOURCES);
if (dirs == null)
{
dirs = new HashSet();
context.setAttribute(METAINF_RESOURCES, dirs);
}
if (LOG.isDebugEnabled())
LOG.debug("{} added to context", resourcesDir);
dirs.add(resourcesDir);
}
private static boolean isEmptyResource(Resource resourcesDir)
{
return resourcesDir == null || !resourcesDir.isDirectory();
}
/**
* Scan for META-INF/web-fragment.xml file in the given jar.
*
* @param context the context for the scan
* @param dir the directory to scan for fragments
* @param cache the resource cache
*/
public void scanForFragment(WebAppContext context, Resource dir, ConcurrentHashMap cache)
{
Resource webFrag = null;
if (cache != null && cache.containsKey(dir))
{
webFrag = cache.get(dir);
if (isEmptyFragment(webFrag))
{
if (LOG.isDebugEnabled())
LOG.debug("{} cached as containing no META-INF/web-fragment.xml", dir);
return;
}
else if (LOG.isDebugEnabled())
LOG.debug("{} META-INF/web-fragment.xml found in cache ", dir);
}
else
{
//not using caches or not in the cache so check for the web-fragment.xml
if (LOG.isDebugEnabled())
LOG.debug("{} META-INF/web-fragment.xml checked", dir);
webFrag = dir.resolve("META-INF/web-fragment.xml");
if (isEmptyFragment(webFrag))
return;
//convert ephemeral Resource to one associated with the context's lifecycle ResourceFactory
webFrag = context.getResourceFactory().newResource(webFrag.getURI());
if (cache != null)
{
Resource old = cache.putIfAbsent(dir, webFrag);
if (old != null)
webFrag = old;
else if (LOG.isDebugEnabled())
LOG.debug("{} META-INF/web-fragment.xml cache updated", dir);
}
}
Map fragments = (Map)context.getAttribute(METAINF_FRAGMENTS);
if (fragments == null)
{
fragments = new HashMap();
context.setAttribute(METAINF_FRAGMENTS, fragments);
}
if (dir instanceof MountedPathResource)
{
//ensure we link from the original .jar rather than jar:file:
dir = context.getResourceFactory().newResource(((MountedPathResource)dir).getContainerPath());
}
fragments.put(dir, webFrag);
if (LOG.isDebugEnabled())
LOG.debug("{} added to context", webFrag);
}
private static boolean isEmptyFragment(Resource webFrag)
{
return !Resources.isReadableFile(webFrag);
}
/**
* Discover META-INF/*.tld files in the given jar
*
* @param context the context for the scan
* @param dir the directory to scan for .tlds
* @param cache the resource cache
* @throws Exception if unable to scan for .tlds
*/
public void scanForTlds(WebAppContext context, Resource dir, ConcurrentHashMap> cache)
throws Exception
{
Collection tlds;
if (isEmptyResource(dir))
return;
if (cache != null && cache.containsKey(dir))
{
Collection tmp = cache.get(dir);
if (tmp.isEmpty())
{
if (LOG.isDebugEnabled())
LOG.debug("{} cached as containing no tlds", dir);
return;
}
else
{
tlds = tmp;
if (LOG.isDebugEnabled())
LOG.debug("{} tlds found in cache ", dir);
}
}
else
{
//not using caches or not in the cache so find all tlds
tlds = new HashSet<>();
tlds.addAll(getTlds(context, dir));
if (cache != null)
{
if (LOG.isDebugEnabled())
LOG.debug("{} tld cache updated", dir);
Collection old = cache.putIfAbsent(dir, tlds);
if (old != null)
tlds = old;
}
if (tlds.isEmpty())
return;
}
Collection metaInfTlds = (Collection)context.getAttribute(METAINF_TLDS);
if (metaInfTlds == null)
{
metaInfTlds = new HashSet<>();
context.setAttribute(METAINF_TLDS, metaInfTlds);
}
metaInfTlds.addAll(tlds);
if (LOG.isDebugEnabled())
LOG.debug("tlds added to context");
}
@Override
public void postConfigure(WebAppContext context) throws Exception
{
context.setAttribute(METAINF_RESOURCES, null);
context.setAttribute(METAINF_FRAGMENTS, null);
context.setAttribute(METAINF_TLDS, null);
}
/**
* Find all .tld files in the given jar.
*
* @param dir the dir to check for .tlds
* @return the collection of tlds as url references
* @throws IOException if unable to scan the jar file
*/
private Collection getTlds(WebAppContext context, Resource dir) throws IOException
{
HashSet tlds = new HashSet<>();
Resource metaInf = dir.resolve("META-INF");
if (isEmptyResource(metaInf))
return tlds; //no tlds
try (Stream stream = Files.walk(metaInf.getPath()))
{
Iterator it = stream
.filter(Files::isRegularFile)
.filter(FileID::isTld)
.iterator();
while (it.hasNext())
{
Path entry = it.next();
tlds.add(entry.toUri().toURL());
}
}
return tlds;
}
protected List findClassDirs(WebAppContext context)
throws Exception
{
if (context == null)
return null;
List classDirs = new ArrayList();
Resource webInfClasses = findWebInfClassesDir(context);
if (webInfClasses != null)
classDirs.add(webInfClasses);
classDirs.addAll(findExtraClasspathDirs(context));
return classDirs;
}
/**
* Look for jars that should be treated as if they are in WEB-INF/lib
*
* @param context the context to find the jars in
* @return the list of jar resources found within context
* @throws Exception if unable to find the jars
*/
protected List findJars(WebAppContext context)
throws Exception
{
List jarResources = new ArrayList<>();
jarResources.addAll(findWebInfLibJars(context));
List extraClasspathJars = findExtraClasspathJars(context);
if (extraClasspathJars != null)
jarResources.addAll(extraClasspathJars);
return jarResources;
}
/**
* Look for jars in WEB-INF/lib
*
* @param context the context to find the lib jars in
* @return the list of jars as {@link Resource}
* @throws Exception if unable to scan for lib jars
*/
protected List findWebInfLibJars(WebAppContext context)
throws Exception
{
if (context == null)
return List.of();
Resource webInf = context.getWebInf();
if (Resources.isReadableDirectory(webInf))
{
Resource webInfLib = webInf.resolve("lib");
if (Resources.isReadableDirectory(webInfLib))
{
return webInfLib.list().stream()
.filter((lib) -> FileID.isLibArchive(lib.getFileName()))
.sorted(ResourceCollators.byName(true))
.collect(Collectors.toList());
}
}
return List.of();
}
/**
* Get jars from WebAppContext.getExtraClasspath as resources
*
* @param context the context to find extra classpath jars in
* @return the list of Resources with the extra classpath, or null if not found
* @throws Exception if unable to resolve the extra classpath jars
*/
protected List findExtraClasspathJars(WebAppContext context)
throws Exception
{
if (context == null || context.getExtraClasspath() == null)
return null;
return context.getExtraClasspath()
.stream()
.filter(this::isFileSupported)
.collect(Collectors.toList());
}
/**
* Get WEB-INF/classes
dir
*
* @param context the context to look for the WEB-INF/classes
directory
* @return the Resource for the WEB-INF/classes
directory
* @throws Exception if unable to find the WEB-INF/classes
directory
*/
protected Resource findWebInfClassesDir(WebAppContext context)
throws Exception
{
if (context == null)
return null;
Resource webInf = context.getWebInf();
// Find WEB-INF/classes
if (Resources.isReadableDirectory(webInf))
{
// Look for classes directory
Resource classesDir = webInf.resolve("classes/");
if (Resources.isReadableDirectory(classesDir))
return classesDir;
}
return null;
}
/**
* Get class dirs from WebAppContext.getExtraClasspath as resources
*
* @param context the context to look for extra classpaths in
* @return the list of Resources to the extra classpath
*/
protected List findExtraClasspathDirs(WebAppContext context)
{
if (context == null || context.getExtraClasspath() == null)
return List.of();
return context.getExtraClasspath()
.stream()
.filter(Resource::isDirectory)
.collect(Collectors.toList());
}
private boolean isFileSupported(Resource resource)
{
return FileID.isLibArchive(resource.getURI());
}
}