All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.webapp.MetaInfConfiguration Maven / Gradle / Ivy

//
// ========================================================================
// 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.webapp;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;

import org.eclipse.jetty.util.PatternMatcher;
import org.eclipse.jetty.util.resource.EmptyResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
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: *
    *
  1. those from the container classpath whose pattern matched the WebInfConfiguration.CONTAINER_JAR_PATTERN
  2. *
  3. those from WEB-INF/lib
  4. *
*/ 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 = (List)Arrays.asList(METAINF_TLDS, METAINF_RESOURCES, METAINF_FRAGMENTS); /** * ContainerPathNameMatcher * * Matches names of jars on the container classpath * against a pattern. If no pattern is specified, no * jars match. */ public class ContainerPathNameMatcher extends PatternMatcher { protected final WebAppContext _context; protected final String _pattern; public ContainerPathNameMatcher(WebAppContext context, String pattern) { if (context == null) throw new IllegalArgumentException("Context null"); _context = context; _pattern = pattern; } public void match(List uris) throws Exception { if (uris == null) return; match(_pattern, uris.toArray(new URI[uris.size()]), false); } @Override public void matched(URI uri) throws Exception { _context.getMetaData().addContainerResource(Resource.newResource(uri)); } } /** * WebAppPathNameMatcher * * Matches names of jars or dirs on the webapp classpath * against a pattern. If there is no pattern, all jars or dirs * will match. */ public class WebAppPathNameMatcher extends PatternMatcher { protected final WebAppContext _context; protected final String _pattern; public WebAppPathNameMatcher(WebAppContext context, String pattern) { if (context == null) throw new IllegalArgumentException("Context null"); _context = context; _pattern = pattern; } public void match(List uris) throws Exception { match(_pattern, uris.toArray(new URI[uris.size()]), true); } @Override public void matched(URI uri) throws Exception { _context.getMetaData().addWebInfResource(Resource.newResource(uri)); } } /** * 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); } /** * 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 { // 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. ContainerPathNameMatcher containerPathNameMatcher = new ContainerPathNameMatcher(context, (String)context.getAttribute(CONTAINER_JAR_PATTERN)); List containerUris = getAllContainerJars(context); if (LOG.isDebugEnabled()) LOG.debug("Matching container urls {}", containerUris); containerPathNameMatcher.match(containerUris); // When running on jvm 9 or above, we 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) { List cpUris = new ArrayList<>(); String[] entries = classPath.split(File.pathSeparator); for (String entry : entries) { File f = new File(entry); cpUris.add(f.toURI()); } if (LOG.isDebugEnabled()) LOG.debug("Matching java.class.path {}", cpUris); containerPathNameMatcher.match(cpUris); } // 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 moduleUris = new ArrayList<>(); String[] entries = modulePath.split(File.pathSeparator); for (String entry : entries) { File file = new File(entry); if (file.isDirectory()) { File[] files = file.listFiles(); if (files != null) { for (File f : files) { moduleUris.add(f.toURI()); } } } else { moduleUris.add(file.toURI()); } } if (LOG.isDebugEnabled()) LOG.debug("Matching jdk.module.path {}", moduleUris); containerPathNameMatcher.match(moduleUris); } 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 WebAppPathNameMatcher matcher = new WebAppPathNameMatcher(context, (String)context.getAttribute(WEBINF_JAR_PATTERN)); List jars = findJars(context); //Convert to uris for matching if (jars != null) { List uris = new ArrayList<>(); int i = 0; for (Resource r : jars) { uris.add(r.getURI()); } matcher.match(uris); } } protected List getAllContainerJars(final WebAppContext context) throws URISyntaxException { List uris = new ArrayList<>(); if (context.getClassLoader() != null) { ClassLoader loader = context.getClassLoader().getParent(); while (loader != null) { if (loader instanceof URLClassLoader) { URL[] urls = ((URLClassLoader)loader).getURLs(); if (urls != null) for (URL url : urls) { uris.add(new URI(url.toString().replaceAll(" ", "%20"))); } } 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()) { 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)); } } 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 */ 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) { for (Resource r : jars) { if (scanTypes.contains(METAINF_RESOURCES)) scanForResources(context, r, metaInfResourceCache); if (scanTypes.contains(METAINF_FRAGMENTS)) scanForFragment(context, r, metaInfFragmentCache); if (scanTypes.contains(METAINF_TLDS)) scanForTlds(context, r, metaInfTldCache); } } } /** * Scan for META-INF/resources dir in the given jar. * * @param context the context for the scan * @param target the target resource to scan for * @param cache the resource cache * @throws Exception if unable to scan for resources */ public void scanForResources(WebAppContext context, Resource target, ConcurrentHashMap cache) throws Exception { Resource resourcesDir = null; if (cache != null && cache.containsKey(target)) { resourcesDir = cache.get(target); if (resourcesDir == EmptyResource.INSTANCE) { if (LOG.isDebugEnabled()) LOG.debug("{} cached as containing no META-INF/resources", target); return; } else if (LOG.isDebugEnabled()) LOG.debug("{} META-INF/resources found in cache ", target); } else { //not using caches or not in the cache so check for the resources dir if (LOG.isDebugEnabled()) LOG.debug("{} META-INF/resources checked", target); if (target.isDirectory()) { //TODO think how to handle an unpacked jar file (eg for osgi) resourcesDir = target.addPath("/META-INF/resources"); } else { //Resource represents a packed jar URI uri = target.getURI(); resourcesDir = Resource.newResource(uriJarPrefix(uri, "!/META-INF/resources")); } if (!resourcesDir.exists() || !resourcesDir.isDirectory()) { resourcesDir.close(); resourcesDir = EmptyResource.INSTANCE; } if (cache != null) { Resource old = cache.putIfAbsent(target, resourcesDir); if (old != null) resourcesDir = old; else if (LOG.isDebugEnabled()) LOG.debug("{} META-INF/resources cache updated", target); } if (resourcesDir == EmptyResource.INSTANCE) { return; } } //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); } /** * Scan for META-INF/web-fragment.xml file in the given jar. * * @param context the context for the scan * @param jar the jar resource to scan for fragements in * @param cache the resource cache * @throws Exception if unable to scan for fragments */ public void scanForFragment(WebAppContext context, Resource jar, ConcurrentHashMap cache) throws Exception { Resource webFrag = null; if (cache != null && cache.containsKey(jar)) { webFrag = cache.get(jar); if (webFrag == EmptyResource.INSTANCE) { if (LOG.isDebugEnabled()) LOG.debug("{} cached as containing no META-INF/web-fragment.xml", jar); return; } else if (LOG.isDebugEnabled()) LOG.debug("{} META-INF/web-fragment.xml found in cache ", jar); } 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", jar); if (jar.isDirectory()) { webFrag = Resource.newResource(new File(jar.getFile(), "/META-INF/web-fragment.xml")); } else { URI uri = jar.getURI(); webFrag = Resource.newResource(uriJarPrefix(uri, "!/META-INF/web-fragment.xml")); } if (!webFrag.exists() || webFrag.isDirectory()) { webFrag.close(); webFrag = EmptyResource.INSTANCE; } if (cache != null) { //web-fragment.xml doesn't exist: put token in cache to signal we've seen the jar Resource old = cache.putIfAbsent(jar, webFrag); if (old != null) webFrag = old; else if (LOG.isDebugEnabled()) LOG.debug("{} META-INF/web-fragment.xml cache updated", jar); } if (webFrag == EmptyResource.INSTANCE) return; } Map fragments = (Map)context.getAttribute(METAINF_FRAGMENTS); if (fragments == null) { fragments = new HashMap(); context.setAttribute(METAINF_FRAGMENTS, fragments); } fragments.put(jar, webFrag); if (LOG.isDebugEnabled()) LOG.debug("{} added to context", webFrag); } /** * Discover META-INF/*.tld files in the given jar * * @param context the context for the scan * @param jar the jar resources to scan tlds for * @param cache the resource cache * @throws Exception if unable to scan for tlds */ public void scanForTlds(WebAppContext context, Resource jar, ConcurrentHashMap> cache) throws Exception { Collection tlds = null; if (cache != null && cache.containsKey(jar)) { Collection tmp = cache.get(jar); if (tmp.isEmpty()) { if (LOG.isDebugEnabled()) LOG.debug("{} cached as containing no tlds", jar); return; } else { tlds = tmp; if (LOG.isDebugEnabled()) LOG.debug("{} tlds found in cache ", jar); } } else { //not using caches or not in the cache so find all tlds tlds = new HashSet(); if (jar.isDirectory()) { tlds.addAll(getTlds(jar.getFile())); } else { URI uri = jar.getURI(); tlds.addAll(getTlds(uri)); } if (cache != null) { if (LOG.isDebugEnabled()) LOG.debug("{} tld cache updated", jar); Collection old = (Collection)cache.putIfAbsent(jar, 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 all subdirs of the given dir. * * @param dir the directory to scan * @return the list of tlds found * @throws IOException if unable to scan the directory */ public Collection getTlds(File dir) throws IOException { if (dir == null || !dir.isDirectory()) return Collections.emptySet(); HashSet tlds = new HashSet(); File[] files = dir.listFiles(); if (files != null) { for (File f : files) { if (f.isDirectory()) tlds.addAll(getTlds(f)); else { String name = f.getCanonicalPath(); if (name.contains("META-INF") && name.endsWith(".tld")) tlds.add(f.toURI().toURL()); } } } return tlds; } /** * Find all .tld files in the given jar. * * @param uri the uri to jar file * @return the collection of tlds as url references * @throws IOException if unable to scan the jar file */ public Collection getTlds(URI uri) throws IOException { HashSet tlds = new HashSet(); String jarUri = uriJarPrefix(uri, "!/"); URL url = new URL(jarUri); JarURLConnection jarConn = (JarURLConnection)url.openConnection(); jarConn.setUseCaches(Resource.getDefaultUseCaches()); JarFile jarFile = jarConn.getJarFile(); Enumeration entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry e = entries.nextElement(); String name = e.getName(); if (name.startsWith("META-INF") && name.endsWith(".tld")) { tlds.add(new URL(jarUri + name)); } } if (!Resource.getDefaultUseCaches()) jarFile.close(); 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); List extraClassDirs = findExtraClasspathDirs(context); if (extraClassDirs != null) classDirs.addAll(extraClassDirs); 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(); List webInfLibJars = findWebInfLibJars(context); if (webInfLibJars != null) jarResources.addAll(webInfLibJars); 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 { Resource webInf = context.getWebInf(); if (webInf == null || !webInf.exists()) return null; List jarResources = new ArrayList(); Resource webInfLib = webInf.addPath("/lib"); if (webInfLib.exists() && webInfLib.isDirectory()) { String[] files = webInfLib.list(); if (files != null) { Arrays.sort(files); } for (int f = 0; files != null && f < files.length; f++) { try { Resource file = webInfLib.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("Unable to load WEB-INF file {}", files[f], ex); } } } return jarResources; } /** * 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 (webInf != null && webInf.isDirectory()) { // Look for classes directory Resource classes = webInf.addPath("classes/"); if (classes.exists()) return classes; } 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 * @throws Exception if unable to resolve the extra classpath resources */ protected List findExtraClasspathDirs(WebAppContext context) throws Exception { if (context == null || context.getExtraClasspath() == null) return null; return context.getExtraClasspath() .stream() .filter(Resource::isDirectory) .collect(Collectors.toList()); } private String uriJarPrefix(URI uri, String suffix) { String uriString = uri.toString(); if (uriString.startsWith("jar:")) { return uriString + suffix; } else { return "jar:" + uriString + suffix; } } private boolean isFileSupported(Resource resource) { try { if (resource.isDirectory()) return false; if (resource.getFile() == null) return false; } catch (Throwable t) { if (LOG.isDebugEnabled()) LOG.debug("Bad Resource reference: {}", resource, t); return false; } String filenameLowercase = resource.getName().toLowerCase(Locale.ENGLISH); int dot = filenameLowercase.lastIndexOf('.'); String extension = (dot < 0 ? null : filenameLowercase.substring(dot)); return (extension != null && (extension.equals(".jar") || extension.equals(".zip"))); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy