diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java
index c49049c38d96..711c8ec5b33c 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java
@@ -15,6 +15,7 @@
import java.io.File;
import java.io.IOException;
+import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
@@ -1841,6 +1842,28 @@ public static URI toURI(String resource)
: Paths.get(resource).toUri();
}
+ /**
+ *
Convert a URI to a URL without checked exceptions
+ *
+ * @param uri the URI to convert from
+ * @return The {@link URL} of the URI
+ * @throws RuntimeException if unable to convert the URI to URL
+ * @see URI#toURL()
+ */
+ public static URL toURL(URI uri)
+ {
+ Objects.requireNonNull(uri);
+
+ try
+ {
+ return uri.toURL();
+ }
+ catch (MalformedURLException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
/**
*
* Unwrap a URI to expose its container path reference.
diff --git a/jetty-ee10/jetty-ee10-annotations/src/main/java/org/eclipse/jetty/ee10/annotations/AnnotationConfiguration.java b/jetty-ee10/jetty-ee10-annotations/src/main/java/org/eclipse/jetty/ee10/annotations/AnnotationConfiguration.java
index 6e36dd046714..78d191c9b0f2 100644
--- a/jetty-ee10/jetty-ee10-annotations/src/main/java/org/eclipse/jetty/ee10/annotations/AnnotationConfiguration.java
+++ b/jetty-ee10/jetty-ee10-annotations/src/main/java/org/eclipse/jetty/ee10/annotations/AnnotationConfiguration.java
@@ -692,7 +692,7 @@ protected Resource getJarFor(WebAppContext context, ServletContainerInitializer
}
/**
- * Check to see if the ServletContainerIntializer loaded via the ServiceLoader came
+ * Check to see if the ServletContainerInitializer loaded via the ServiceLoader came
* from a jar that is excluded by the fragment ordering. See ServletSpec 3.0 p.85.
*
* @param context the context for the jars
@@ -754,7 +754,7 @@ public boolean isFromExcludedJar(WebAppContext context, ServletContainerInitiali
boolean included = false;
for (Resource r : orderedJars)
{
- included = r.equals(sciResource);
+ included = r.isContainedIn(sciResource);
if (included)
break;
}
@@ -968,7 +968,7 @@ else if (entry.getValue() == null) //can't work out provenance of SCI, so can't
{
for (Map.Entry entry : sciResourceMap.entrySet())
{
- if (webInfJar.equals(entry.getValue()))
+ if (webInfJar.isContainedIn(entry.getValue()))
nonExcludedInitializers.add(entry.getKey());
}
}
diff --git a/jetty-ee10/jetty-ee10-annotations/src/main/java/org/eclipse/jetty/ee10/annotations/AnnotationParser.java b/jetty-ee10/jetty-ee10-annotations/src/main/java/org/eclipse/jetty/ee10/annotations/AnnotationParser.java
index 4a9c6ecf6052..8e013351009d 100644
--- a/jetty-ee10/jetty-ee10-annotations/src/main/java/org/eclipse/jetty/ee10/annotations/AnnotationParser.java
+++ b/jetty-ee10/jetty-ee10-annotations/src/main/java/org/eclipse/jetty/ee10/annotations/AnnotationParser.java
@@ -550,7 +550,7 @@ public void parse(final Set extends Handler> handlers, Resource r) throws Exce
if (!r.exists())
return;
- if (FileID.isJavaArchive(r.getPath()))
+ if (FileID.isJavaArchive(r.getPath())) // TODO: this is now always false, as all Resource objects are directories
{
parseJar(handlers, r);
return;
diff --git a/jetty-ee10/jetty-ee10-maven-plugin/src/main/java/org/eclipse/jetty/ee10/maven/plugin/MavenMetaInfConfiguration.java b/jetty-ee10/jetty-ee10-maven-plugin/src/main/java/org/eclipse/jetty/ee10/maven/plugin/MavenMetaInfConfiguration.java
index a62e1a82b9ab..3a39b46aa46f 100644
--- a/jetty-ee10/jetty-ee10-maven-plugin/src/main/java/org/eclipse/jetty/ee10/maven/plugin/MavenMetaInfConfiguration.java
+++ b/jetty-ee10/jetty-ee10-maven-plugin/src/main/java/org/eclipse/jetty/ee10/maven/plugin/MavenMetaInfConfiguration.java
@@ -44,16 +44,15 @@ public Class extends Configuration> replaces()
}
/**
- * Get the jars to examine from the files from which we have
+ * Get the webapp paths (jars & dirs) to examine from the files from which we have
* synthesized the classpath. Note that the classpath is not
* set at this point, so we cannot get them from the classpath.
*
* @param context the web app context
- * @return the list of jars found
+ * @return the list of webapp resources found
*/
@Override
- protected List findJars(WebAppContext context)
- throws Exception
+ protected List getWebAppPaths(WebAppContext context) throws Exception
{
List list = new ArrayList<>();
MavenWebAppContext jwac = (MavenWebAppContext)context;
@@ -77,7 +76,7 @@ protected List findJars(WebAppContext context)
});
}
- List superList = super.findJars(context);
+ List superList = super.getWebAppPaths(context);
if (superList != null)
list.addAll(superList);
return list;
@@ -87,7 +86,7 @@ protected List findJars(WebAppContext context)
* Add in the classes dirs from test/classes and target/classes
*/
@Override
- protected List findClassDirs(WebAppContext context) throws Exception
+ protected List findClassesDirs(WebAppContext context) throws Exception
{
List list = new ArrayList<>();
@@ -113,7 +112,7 @@ protected List findClassDirs(WebAppContext context) throws Exception
});
}
- List classesDirs = super.findClassDirs(context);
+ List classesDirs = super.findClassesDirs(context);
if (classesDirs != null)
list.addAll(classesDirs);
return list;
diff --git a/jetty-ee10/jetty-ee10-maven-plugin/src/main/java/org/eclipse/jetty/ee10/maven/plugin/MavenWebInfConfiguration.java b/jetty-ee10/jetty-ee10-maven-plugin/src/main/java/org/eclipse/jetty/ee10/maven/plugin/MavenWebInfConfiguration.java
index 43cc6e8096bf..5be2cfa0d8fb 100644
--- a/jetty-ee10/jetty-ee10-maven-plugin/src/main/java/org/eclipse/jetty/ee10/maven/plugin/MavenWebInfConfiguration.java
+++ b/jetty-ee10/jetty-ee10-maven-plugin/src/main/java/org/eclipse/jetty/ee10/maven/plugin/MavenWebInfConfiguration.java
@@ -19,6 +19,7 @@
import org.eclipse.jetty.ee10.webapp.WebAppClassLoader;
import org.eclipse.jetty.ee10.webapp.WebAppContext;
import org.eclipse.jetty.ee10.webapp.WebInfConfiguration;
+import org.eclipse.jetty.util.URIUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -59,7 +60,28 @@ public void configure(WebAppContext context) throws Exception
LOG.debug("Setting up classpath ...");
for (URI uri : jwac.getClassPathUris())
{
- loader.addClassPath(uri.toASCIIString());
+ // Not all Resource types supported by Jetty can be supported by WebAppClassLoader
+ String scheme = uri.getScheme();
+ if (scheme == null || scheme.equals("file"))
+ {
+ // no scheme? or "file" scheme, assume it is just a path.
+ loader.addClassPath(uri.getPath());
+ continue;
+ }
+
+ if (scheme.equals("jar"))
+ {
+ URI container = URIUtil.unwrapContainer(uri);
+ if (container.getScheme().equals("file"))
+ {
+ // Just add a reference to the
+ loader.addClassPath(container.getPath());
+ continue;
+ }
+ }
+
+ // Anything else is a warning
+ LOG.warn("Skipping unsupported URI on ClassPath: {}", uri);
}
}
diff --git a/jetty-ee10/jetty-ee10-osgi/jetty-ee10-osgi-boot/src/main/java/org/eclipse/jetty/ee10/osgi/boot/OSGiMetaInfConfiguration.java b/jetty-ee10/jetty-ee10-osgi/jetty-ee10-osgi-boot/src/main/java/org/eclipse/jetty/ee10/osgi/boot/OSGiMetaInfConfiguration.java
index 257cbde1635f..a38fbd24d611 100644
--- a/jetty-ee10/jetty-ee10-osgi/jetty-ee10-osgi-boot/src/main/java/org/eclipse/jetty/ee10/osgi/boot/OSGiMetaInfConfiguration.java
+++ b/jetty-ee10/jetty-ee10-osgi/jetty-ee10-osgi-boot/src/main/java/org/eclipse/jetty/ee10/osgi/boot/OSGiMetaInfConfiguration.java
@@ -89,16 +89,18 @@ public void preConfigure(final WebAppContext context) throws Exception
}
@Override
- protected void scanJars(final WebAppContext context) throws Exception
+ protected List getContainerPaths(WebAppContext context)
+ throws Exception
{
- //Check to see if there have been any bundle symbolic names added of bundles that should be
- //regarded as being on the container classpath, and scanned for fragments, tlds etc etc.
- //This can be defined in:
- // 1. SystemProperty SYS_PROP_TLD_BUNDLES
- // 2. DeployerManager.setContextAttribute CONTAINER_BUNDLE_PATTERN
+ // Check to see if there have been any bundle symbolic names added of bundles that should be
+ // regarded as being on the container classpath, and scanned for fragments, tlds etc etc.
+ // This can be defined in:
+ // 1. SystemProperty SYS_PROP_TLD_BUNDLES
+ // 2. DeployerManager.setContextAttribute CONTAINER_BUNDLE_PATTERN
String tmp = (String)context.getAttribute(CONTAINER_BUNDLE_PATTERN);
Pattern pattern = (tmp == null ? null : Pattern.compile(tmp));
- List names = new ArrayList();
+ List names = new ArrayList<>();
+
tmp = System.getProperty(SYS_PROP_TLD_BUNDLES);
if (tmp != null)
{
@@ -109,6 +111,8 @@ protected void scanJars(final WebAppContext context) throws Exception
}
}
+ ResourceFactory resourceFactory = ResourceFactory.of(context);
+
HashSet matchingResources = new HashSet<>();
if (!names.isEmpty() || pattern != null)
{
@@ -124,23 +128,19 @@ protected void scanJars(final WebAppContext context) throws Exception
if (pattern.matcher(bundle.getSymbolicName()).matches())
{
//get the file location of the jar and put it into the list of container jars that will be scanned for stuff (including tlds)
- matchingResources.addAll(getBundleAsResource(ResourceFactory.of(context), bundle));
+ matchingResources.addAll(getBundleAsResource(resourceFactory, bundle));
}
}
if (names != null)
{
//if there is an explicit bundle name, then check if it matches
if (names.contains(bundle.getSymbolicName()))
- matchingResources.addAll(getBundleAsResource(ResourceFactory.of(context), bundle));
+ matchingResources.addAll(getBundleAsResource(resourceFactory, bundle));
}
}
}
- for (Resource r : matchingResources)
- {
- context.getMetaData().addContainerResource(r);
- }
- super.scanJars(context);
+ return matchingResources.stream().toList();
}
@Override
@@ -151,18 +151,12 @@ public void postConfigure(WebAppContext context) throws Exception
super.postConfigure(context);
}
- /**
- * Consider the fragment bundles associated with the bundle of the webapp being deployed.
- *
- * @see org.eclipse.jetty.ee10.webapp.MetaInfConfiguration#findJars(org.eclipse.jetty.ee10.webapp.WebAppContext)
- */
@Override
- protected List findJars(WebAppContext context)
- throws Exception
+ protected List getWebAppPaths(WebAppContext context) throws Exception
{
List mergedResources = new ArrayList();
- //get jars from WEB-INF/lib if there are any
- List webInfJars = super.findJars(context);
+ // get jars from WEB-INF/lib if there are any
+ List webInfJars = super.getWebAppPaths(context);
if (webInfJars != null)
mergedResources.addAll(webInfJars);
@@ -215,8 +209,8 @@ protected List findJars(WebAppContext context)
@Override
public void configure(WebAppContext context) throws Exception
{
- TreeMap prependedResourcesPath = new TreeMap();
- TreeMap appendedResourcesPath = new TreeMap();
+ TreeMap prependedResourcesPath = new TreeMap<>();
+ TreeMap appendedResourcesPath = new TreeMap<>();
Bundle bundle = (Bundle)context.getAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE);
if (bundle != null)
@@ -225,25 +219,21 @@ public void configure(WebAppContext context) throws Exception
Set fragments = (Set)context.getAttribute(FRAGMENT_AND_REQUIRED_BUNDLES);
if (fragments != null && !fragments.isEmpty())
{
+ ResourceFactory resourceFactory = ResourceFactory.of(context);
// sorted extra resource base found in the fragments.
- // the resources are either overriding the resourcebase found in the
- // web-bundle
- // or appended.
- // amongst each resource we sort them according to the alphabetical
- // order
- // of the name of the internal folder and the symbolic name of the
- // fragment.
+ // the resources are either overriding the base resource base in the web-bundle or appended.
+ // amongst each resource we sort them according to the alphabetical order
+ // of the name of the internal folder and the symbolic name of the fragment.
// this is useful to make sure that the lookup path of those
// resource base defined by fragments is always the same.
// This natural order could be abused to define the order in which
- // the base resources are
- // looked up.
+ // the base resources are looked up.
for (Bundle frag : fragments)
{
String path = Util.getManifestHeaderValue(OSGiWebappConstants.JETTY_WAR_FRAGMENT_RESOURCE_PATH, frag.getHeaders());
- convertFragmentPathToResource(ResourceFactory.of(context), path, frag, appendedResourcesPath);
+ convertFragmentPathToResource(resourceFactory, path, frag, appendedResourcesPath);
path = Util.getManifestHeaderValue(OSGiWebappConstants.JETTY_WAR_PREPEND_FRAGMENT_RESOURCE_PATH, frag.getHeaders());
- convertFragmentPathToResource(ResourceFactory.of(context), path, frag, prependedResourcesPath);
+ convertFragmentPathToResource(resourceFactory, path, frag, prependedResourcesPath);
}
if (!appendedResourcesPath.isEmpty())
{
@@ -263,13 +253,13 @@ public void configure(WebAppContext context) throws Exception
super.configure(context);
- // place the prepended resources at the beginning of the contexts's resource base
+ // place the prepended resources at the beginning of the context's resource base
if (!prependedResourcesPath.isEmpty())
{
- Resource[] resources = new Resource[1 + prependedResourcesPath.size()];
- System.arraycopy(prependedResourcesPath.values().toArray(new Resource[prependedResourcesPath.size()]), 0, resources, 0, prependedResourcesPath.size());
- resources[resources.length - 1] = context.getBaseResource();
- context.setBaseResource(ResourceFactory.combine(resources));
+ List mergedResources = new ArrayList<>();
+ mergedResources.addAll(prependedResourcesPath.values());
+ mergedResources.add(context.getBaseResource());
+ context.setBaseResource(ResourceFactory.combine(mergedResources));
}
}
@@ -289,24 +279,26 @@ private List getBundleAsResource(ResourceFactory resourceFactory, Bund
{
if (FileID.isJavaArchive(f.getName()) && f.isFile())
{
- resources.add(resourceFactory.newResource(f.toPath()));
+ // add *.jar as jar files
+ resources.add(resourceFactory.newJarFileResource(f.toPath().toUri()));
}
else if (f.isDirectory() && f.getName().equals("lib"))
{
- for (File f2 : file.listFiles())
+ for (File libFile : file.listFiles())
{
- if (FileID.isJavaArchive(f2.getName()) && f2.isFile())
+ if (FileID.isJavaArchive(libFile.getName()) && libFile.isFile())
{
- resources.add(resourceFactory.newResource(f.toPath()));
+ // add lib/*.jar as jar files
+ resources.add(resourceFactory.newJarFileResource(f.toPath().toUri()));
}
}
}
}
- resources.add(resourceFactory.newResource(file.toPath())); //TODO really???
}
else
{
- resources.add(resourceFactory.newResource(file.toPath()));
+ // Treat bundle as jar file that needs to be opened (so that resources within it can be found)
+ resources.add(resourceFactory.newJarFileResource(file.toPath().toUri()));
}
return resources;
diff --git a/jetty-ee10/jetty-ee10-osgi/test-jetty-ee10-osgi/src/test/java/org/eclipse/jetty/ee10/osgi/test/TestOSGiUtil.java b/jetty-ee10/jetty-ee10-osgi/test-jetty-ee10-osgi/src/test/java/org/eclipse/jetty/ee10/osgi/test/TestOSGiUtil.java
index 06f840980752..a40b258788ad 100644
--- a/jetty-ee10/jetty-ee10-osgi/test-jetty-ee10-osgi/src/test/java/org/eclipse/jetty/ee10/osgi/test/TestOSGiUtil.java
+++ b/jetty-ee10/jetty-ee10-osgi/test-jetty-ee10-osgi/src/test/java/org/eclipse/jetty/ee10/osgi/test/TestOSGiUtil.java
@@ -132,30 +132,9 @@ public static void coreJettyDependencies(List res)
res.add(systemProperty("org.ops4j.pax.url.mvn.settings").value(System.getProperty("settingsFilePath")));
}
- res.add(mavenBundle().groupId("org.slf4j").artifactId("slf4j-api").versionAsInProject().noStart());
-
- /*
- * Jetty 11 uses slf4j 2.0.0 by default, however we want to test with slf4j 1.7.30 for backwards compatibility.
- * To do that, we need to use slf4j-simple as the logging implementation. We make a simplelogger.properties
- * file available so that jetty logging can be configured
- */
- // BEGIN - slf4j 1.7.x
- /* slf4j-simple conflicts with both slf4j 1.7.x, and jetty-slf4j-impl. (but in different ways)
-
- TinyBundle simpleLoggingPropertiesBundle = TinyBundles.bundle();
- simpleLoggingPropertiesBundle.add("simplelogger.properties", ClassLoader.getSystemResource("simplelogger.properties"));
- simpleLoggingPropertiesBundle.set(Constants.BUNDLE_SYMBOLICNAME, "simple-logger-properties");
- simpleLoggingPropertiesBundle.set(Constants.FRAGMENT_HOST, "slf4j-simple");
- simpleLoggingPropertiesBundle.add(FragmentActivator.class);
- res.add(CoreOptions.streamBundle(simpleLoggingPropertiesBundle.build()).noStart());
- res.add(mavenBundle().groupId("org.slf4j").artifactId("slf4j-simple").versionAsInProject().noStart());
- */
- // END - slf4j 1.7.x
-
- /*
- * When running with slf4j >= 2.0.0, remove the slf4j simple logger above and uncomment the following lines
- */
- // BEGIN - slf4j 2.x
+ /* SLF4J 2.0 */
+ // slf4j-api comes from paxexam now.
+ // res.add(mavenBundle().groupId("org.slf4j").artifactId("slf4j-api").versionAsInProject().noStart());
TinyBundle loggingPropertiesBundle = TinyBundles.bundle();
loggingPropertiesBundle.add("jetty-logging.properties", ClassLoader.getSystemResource("jetty-logging.properties"));
loggingPropertiesBundle.set(Constants.BUNDLE_SYMBOLICNAME, "jetty-logging-properties");
@@ -163,8 +142,7 @@ public static void coreJettyDependencies(List res)
loggingPropertiesBundle.add(FragmentActivator.class);
res.add(CoreOptions.streamBundle(loggingPropertiesBundle.build()).noStart());
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-slf4j-impl").versionAsInProject().start());
- // END - slf4j 2.x
-
+
res.add(mavenBundle().groupId("jakarta.el").artifactId("jakarta.el-api").versionAsInProject().start());
res.add(mavenBundle().groupId("jakarta.servlet").artifactId("jakarta.servlet-api").versionAsInProject().start());
diff --git a/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/MetaInfConfiguration.java b/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/MetaInfConfiguration.java
index 436127ed58c4..b428e4feaddf 100644
--- a/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/MetaInfConfiguration.java
+++ b/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/MetaInfConfiguration.java
@@ -14,28 +14,24 @@
package org.eclipse.jetty.ee10.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.Collections;
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.server.Server;
import org.eclipse.jetty.util.FileID;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
@@ -79,7 +75,6 @@ public class MetaInfConfiguration extends AbstractConfiguration
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
@@ -87,6 +82,9 @@ public class MetaInfConfiguration extends AbstractConfiguration
*/
public static final String RESOURCE_DIRS = "org.eclipse.jetty.resources";
+ private static final Map NOOP_RESOURCE_CACHE = Collections.EMPTY_MAP;
+ private static final Map> NOOP_TLD_CACHE = Collections.EMPTY_MAP;
+
public MetaInfConfiguration()
{
super(new Builder().addDependencies(WebXmlConfiguration.class));
@@ -95,166 +93,189 @@ public MetaInfConfiguration()
@Override
public void preConfigure(final WebAppContext context) throws Exception
{
- //find container jars/modules and select which ones to scan
- findAndFilterContainerPaths(context);
+ // 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
+ //noinspection UrlHashCode
+ Set metaInfTlds = (HashSet)context.getAttribute(METAINF_TLDS); // TODO: make this not a Set to avoid URL.hashcode issues
+ if (metaInfTlds == null)
+ metaInfTlds = new HashSet<>();
+ context.setAttribute(METAINF_TLDS, metaInfTlds);
+
+ Set metaInfResources = (HashSet)context.getAttribute(METAINF_RESOURCES);
+ if (metaInfResources == null)
+ metaInfResources = new HashSet<>();
+ context.setAttribute(METAINF_RESOURCES, metaInfResources);
+
+ Map metaInfFragments = (HashMap)context.getAttribute(METAINF_FRAGMENTS);
+ if (metaInfFragments == null)
+ metaInfFragments = new HashMap<>();
+ context.setAttribute(METAINF_FRAGMENTS, metaInfFragments);
+
+ // No pattern to apply for WEB-INF/classes, so just add to metadata
+ context.getMetaData().setWebInfClassesResources(findClassesDirs(context));
+
+ // Collect container paths that have selection patterns
+ // add them to the context metadata
+ List containerResources = getContainerPaths(context);
+ containerResources.stream()
+ .forEach(r -> context.getMetaData().addContainerResource(r));
+
+ // Collect webapp paths that have selection patterns
+ // add them to the context metadata
+ List webappResources = getWebAppPaths(context);
+ webappResources.stream()
+ .forEach(r -> context.getMetaData().addWebInfResource(r));
+
+ // -- Scan of META-INF directories --
+
+ // Figure out container caching rules
+ boolean useContainerCache = getUseContainerCache(context);
+ Server server = context.getServer();
+ Map metaInfResourceCache = getResourceCache(server, useContainerCache);
+ Map metaInfFragmentCache = getFragmentCache(server, useContainerCache);
+ Map> metaInfTldCache = getTldCache(server, useContainerCache);
- //find web-app jars and select which ones to scan
- findAndFilterWebAppPaths(context);
+ // Restrict scan to resource targets that have a META-INF directory
+ List containerTargets = containerResources.stream()
+ .filter(r -> Resources.isReadableDirectory(r.resolve("META-INF")))
+ .toList();
- //No pattern to appy to classes, just add to metadata
- context.getMetaData().setWebInfClassesResources(findClassDirs(context));
+ List webappTargets = webappResources.stream()
+ .filter(r -> Resources.isReadableDirectory(r.resolve("META-INF")))
+ .toList();
- scanJars(context);
+ // Scan for META-INF/*.tld entries
+ metaInfTlds.addAll(scanTlds(streamTargets(containerTargets, webappTargets, true), metaInfTldCache));
+
+ // Scan for META-INF/resources/ entries
+ metaInfResources.addAll(scanMetaInfResources(streamTargets(containerTargets, webappTargets, true), metaInfResourceCache));
+
+ boolean scanWebAppTargets = needsServlet3FeatureScan(context);
+
+ // Scan for META-INF/web-fragment.xml entries
+ metaInfFragments.putAll(scanMetaInfFragments(streamTargets(containerTargets, webappTargets, scanWebAppTargets), metaInfFragmentCache));
}
- /**
- * 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
+ private Stream streamTargets(Collection containerTargets, Collection webappTargets, boolean scanWebAppResources)
{
- 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
+ if (scanWebAppResources)
+ return Stream.concat(containerTargets.stream(), webappTargets.stream());
+ else
+ return containerTargets.stream();
+ }
- ResourceFactory resourceFactory = ResourceFactory.of(context);
+ private Collection scanTlds(Stream targets, Map> cache)
+ {
+ assert cache != null;
- // 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 = (uri) ->
- {
- Resource resource = resourceFactory.newResource(uri);
- if (Resources.missing(resource))
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Classpath URI doesn't exist: " + uri);
- }
- else
- context.getMetaData().addContainerResource(resource);
- };
+ return targets
+ .flatMap(target -> getTlds(target, cache).stream())
+ // Using toSet to return unique hits
+ .collect(Collectors.toSet()); // TODO: consider returning Set instead to avoid URL.hashcode issues
+ }
- List containerUris = getAllContainerJars(context);
- if (LOG.isDebugEnabled())
- LOG.debug("All container urls {}", containerUris);
- containerUris.stream()
- .filter(uriPatternPredicate)
- .forEach(addContainerResource);
+ private Collection getTlds(Resource target, Map> cache)
+ {
+ Collection tlds = cache.get(target);
+ if (tlds != null)
+ return tlds;
- // 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)
- {
- Stream.of(classPath.split(File.pathSeparator))
- .map(URIUtil::toURI)
- .filter(uriPatternPredicate)
- .forEach(addContainerResource);
- }
+ Resource metaInfDir = target.resolve("META-INF");
+ if (!Resources.isReadableDirectory(metaInfDir))
+ return List.of();
- // 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 urls = metaInfDir.list()
+ .stream()
+ .filter(Resources::isReadableFile)
+ .filter(r -> FileID.isExtension(r.getFileName(), "tld"))
+ .map(Resource::getURI)
+ .map(URIUtil::toURL)
+ .toList();
+ cache.putIfAbsent(target, urls);
+
+ return urls;
+ }
+
+ private Map scanMetaInfFragments(Stream targets, Map cache)
+ {
+ assert cache != null;
+
+ return targets
+ .map(target -> getMetaInfFragment(target, cache))
+ .filter(Objects::nonNull)
+ .collect(Collectors.toMap(FragmentMapping::target, FragmentMapping::fragment));
+ }
+
+ private record FragmentMapping(Resource target, Resource fragment) {}
+
+ private FragmentMapping getMetaInfFragment(Resource target, Map cache)
+ {
+ Resource fragment = cache.get(target);
+ if (Resources.isReadableFile(fragment))
+ return new FragmentMapping(target, fragment);
+
+ fragment = target.resolve("META-INF/web-fragment.xml");
+ if (Resources.isReadableFile(fragment))
{
- List matchingBasePaths =
- Stream.of(modulePath.split(File.pathSeparator))
- .map(URIUtil::toURI)
- .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);
- }
- }
+ cache.putIfAbsent(target, fragment);
+ return new FragmentMapping(target, fragment);
}
- if (LOG.isDebugEnabled())
- LOG.debug("Container paths selected:{}", context.getMetaData().getContainerResources());
+ return null;
}
- /**
- * 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
+ private List scanMetaInfResources(Stream targets, Map cache)
{
- //Apply filter to WEB-INF/lib jars
- String pattern = (String)context.getAttribute(WEBINF_JAR_PATTERN);
- ResourceUriPatternPredicate webinfPredicate = new ResourceUriPatternPredicate(pattern, true);
+ assert cache != null;
- List jars = findJars(context);
- if (LOG.isDebugEnabled())
- LOG.debug("webapp {}={} jars {}", WEBINF_JAR_PATTERN, pattern, jars);
+ return targets
+ .map(target -> getMetaInfResourceDir(target, cache))
+ .filter(Objects::nonNull)
+ .toList();
+ }
- // Only add matching Resources to metadata.webInfResources
- if (jars != null)
+ private Resource getMetaInfResourceDir(Resource target, Map cache)
+ {
+ Resource dir = cache.get(target);
+ if (Resources.isReadableDirectory(dir))
+ return dir;
+
+ dir = target.resolve("META-INF/resources");
+ if (Resources.isReadableDirectory(dir))
{
- jars.stream()
- .filter(webinfPredicate)
- .forEach(resource -> context.getMetaData().addWebInfResource(resource));
+ cache.putIfAbsent(target, dir);
+ return dir;
}
+ return null;
}
- protected List getAllContainerJars(final WebAppContext context)
+ /**
+ * Only look for Servlet 3+ features ({@code META-INF/web-fragment.xml} and {@code META-INF/resources})
+ * if web.xml is not metadata complete, or it declares version 3.0 or greater
+ *
+ * @param context the context to evaluate
+ * @return true if servlet 3+ features should be scanned for
+ */
+ // TODO: is this behavior still needed for ee10? to support servlet behaviors pre 3.0 we would need to load javax.servlet classes, right? (seems to only be important for ee8 env)
+ private boolean needsServlet3FeatureScan(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();
- }
+ if (context == null)
+ return false;
+
+ if (context.getMetaData().isMetaDataComplete())
+ return false;
- return uris;
+ if (context.getServletContext().getEffectiveMajorVersion() < 3 && !context.isConfigurationDiscovered())
+ return false;
+
+ return true;
}
@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())
{
@@ -265,385 +286,250 @@ public void configure(WebAppContext context) throws Exception
}
}
- protected void scanJars(WebAppContext context) throws Exception
+ @Override
+ public void postConfigure(WebAppContext context) throws Exception
+ {
+ context.setAttribute(METAINF_RESOURCES, null);
+ context.setAttribute(METAINF_FRAGMENTS, null);
+ context.setAttribute(METAINF_TLDS, null);
+ }
+
+ /**
+ * Get the list of Container Paths that should be scanned for META-INF configuration.
+ *
+ * @param context the context to search from
+ * @return the List of Resource objects to search for META-INF information in (order determined by implementation).
+ * @throws Exception if unable to get the Container Paths.
+ */
+ protected List getContainerPaths(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 List.of();
+
+ // 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 containerUriPredicate = new UriPatternPredicate(pattern, false);
+
+ // We collect the unique URIs for the container first
+ // as the same URI can exist in multiple places
+ Set uniqueURIs = new HashSet<>();
+ uniqueURIs.addAll(getContainerClassLoaderEntries(context));
+ uniqueURIs.addAll(getJavaClassPathEntries());
+ uniqueURIs.addAll(getJdkModulePathEntries());
+
+ // Stream the selected paths, based on the container include pattern
+ return uniqueURIs.stream()
+ .filter(containerUriPredicate)
+ .map(uri -> newDirectoryResource(context, uri))
+ .sorted(ResourceCollators.byName(true))
+ .toList();
+ }
+
+ protected boolean getUseContainerCache(WebAppContext context)
{
boolean useContainerCache = DEFAULT_USE_CONTAINER_METAINF_CACHE;
- if (context.getServer() != null)
+ if (context != null && context.getServer() != null)
{
Boolean attr = (Boolean)context.getServer().getAttribute(USE_CONTAINER_METAINF_CACHE);
if (attr != null)
useContainerCache = attr;
}
-
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);
+ return useContainerCache;
}
/**
- * 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.
+ * The list of Container ClassLoader entries.
*
- * @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
+ * @param context the context to search from
+ * @return the List of URIs to in the classloader entries.
*/
- @SuppressWarnings("unchecked")
- public void scanJars(final WebAppContext context, Collection jars, boolean useCaches, List scanTypes)
- throws Exception
+ protected List getContainerClassLoaderEntries(WebAppContext context)
{
- ConcurrentHashMap metaInfResourceCache = null;
- ConcurrentHashMap metaInfFragmentCache = null;
- ConcurrentHashMap> metaInfTldCache = null;
- if (useCaches)
+ ClassLoader loader = MetaInfConfiguration.class.getClassLoader();
+ Set uris = new HashSet<>();
+ while (loader != null)
{
- 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)
+ if (loader instanceof URLClassLoader urlCL)
{
- metaInfTldCache = new ConcurrentHashMap<>();
- context.getServer().setAttribute(CACHED_CONTAINER_TLDS, metaInfTldCache);
+ URIUtil.streamOf(urlCL).forEach(uris::add);
}
+ loader = loader.getParent();
}
- //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);
- }
- }
+ if (LOG.isDebugEnabled())
+ LOG.debug("Found {} container classloader entries: {}", uris.size(), uris.stream().map(Objects::toString).sorted().collect(Collectors.joining(", ", "[", "]")));
+ return uris.stream().sorted().toList();
}
/**
- * Scan for META-INF/resources dir in the given jar.
+ * Get the List of {@code java.class.path} (System property) entries.
*
- * @param context the context for the scan
- * @param target the target resource to scan for
- * @param cache the resource cache
+ * @return the List of URIs in the {@code java.class.path} value.
*/
- public void scanForResources(WebAppContext context, Resource target, ConcurrentHashMap cache)
+ protected List getJavaClassPathEntries()
{
- // Resource target does not exist
- if (Resources.missing(target))
- return;
- ResourceFactory resourceFactory = ResourceFactory.of(context);
-
- Resource resourcesDir;
- if (cache != null && cache.containsKey(target))
- {
- resourcesDir = cache.get(target);
- if (isEmptyResource(resourcesDir))
- {
- 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.resolve("/META-INF/resources");
- }
- else
- {
- // Resource represents a packed jar
- URI uri = target.getURI();
- resourcesDir = resourceFactory.newResource(URIUtil.uriJarPrefix(uri, "!/META-INF/resources"));
- }
-
- if (Resources.isReadableDirectory(resourcesDir) && (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 (isEmptyResource(resourcesDir))
- {
- return;
- }
- }
+ // On some JVMs 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 (StringUtil.isBlank(classPath))
+ return List.of();
- //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);
- }
+ Set uris = Stream.of(classPath.split(File.pathSeparator))
+ .map(URIUtil::toURI)
+ .collect(Collectors.toSet());
if (LOG.isDebugEnabled())
- LOG.debug("{} added to context", resourcesDir);
-
- dirs.add(resourcesDir);
+ LOG.debug("Found {} java.class.path jars: {}", uris.size(), uris.stream().map(Objects::toString).sorted().collect(Collectors.joining(", ", "[", "]")));
+ return uris.stream().sorted().toList();
}
- private static boolean isEmptyResource(Resource resourcesDir)
+ protected List getJdkModulePathEntries()
{
- return !Resources.isReadableDirectory(resourcesDir);
+ // We also need to examine the other module path properties
+ // TODO need to consider the jdk.module.upgrade.path - how to resolve which modules will be actually used.
+ String modulePath = System.getProperty("jdk.module.path");
+ if (StringUtil.isBlank(modulePath))
+ return List.of();
+
+ Set uris = Stream.of(modulePath.split(File.pathSeparator))
+ .map(URIUtil::toURI)
+ .collect(Collectors.toSet());
+ if (LOG.isDebugEnabled())
+ LOG.debug("Found {} jdk.module.path jars: {}", uris.size(), uris.stream().map(Objects::toString).sorted().collect(Collectors.joining(", ", "[", "]")));
+ return uris.stream().sorted().toList();
}
/**
- * Scan for META-INF/web-fragment.xml file in the given jar.
+ * Get the list of WebApp Paths that should be scanned for META-INF configuration.
*
- * @param context the context for the scan
- * @param jar the jar resource to scan for fragments in
- * @param cache the resource cache
+ * @param context the context to search from
+ * @return the List of Resource objects to search for META-INF information in (order determined by implementation).
+ * @throws Exception if unable to get the WebApp Paths.
*/
- public void scanForFragment(WebAppContext context, Resource jar, ConcurrentHashMap cache)
+ protected List getWebAppPaths(WebAppContext context)
+ throws Exception
{
- ResourceFactory resourceFactory = ResourceFactory.of(context);
-
- Resource webFrag;
- if (cache != null && cache.containsKey(jar))
- {
- webFrag = cache.get(jar);
- if (isEmptyFragment(webFrag))
- {
- 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 = resourceFactory.newResource(jar.getPath().resolve("META-INF/web-fragment.xml"));
- }
- else
- {
- URI uri = jar.getURI();
- webFrag = resourceFactory.newResource(URIUtil.uriJarPrefix(uri, "!/META-INF/web-fragment.xml"));
- }
+ // Apply filter to WEB-INF/lib jars
+ String pattern = (String)context.getAttribute(WEBINF_JAR_PATTERN);
+ ResourceUriPatternPredicate webinfPredicate = new ResourceUriPatternPredicate(pattern, true);
- if (Resources.isReadable(webFrag) && (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);
- }
+ List uniquePaths = new ArrayList<>();
+ uniquePaths.addAll(getWebInfLibJars(context));
+ uniquePaths.addAll(getExtraClassPathEntries(context));
- if (isEmptyFragment(webFrag))
- 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);
- }
+ LOG.debug("WebApp Paths: {}", uniquePaths.stream().map(Resource::getURI).map(URI::toASCIIString).collect(Collectors.joining(", ", "[", "]")));
- private static boolean isEmptyFragment(Resource webFrag)
- {
- return !Resources.isReadableFile(webFrag);
+ return uniquePaths;
}
/**
- * Discover META-INF/*.tld files in the given jar
+ * Get the List of Resources that need to be scanned in the context's {@code WEB-INF/lib/} tree.
*
- * @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
+ * @param context the context to search from
+ * @return the List of Resources that need to be scanned.
*/
- public void scanForTlds(WebAppContext context, Resource jar, ConcurrentHashMap> cache)
- throws Exception
+ protected List getWebInfLibJars(WebAppContext context)
{
- Collection tlds;
+ // Selection filter to apply to discovered WEB-INF/lib jars
+ String pattern = (String)context.getAttribute(WEBINF_JAR_PATTERN);
+ ResourceUriPatternPredicate webinfPredicate = new ResourceUriPatternPredicate(pattern, true);
- 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.getPath()));
- }
- else
- {
- URI uri = jar.getURI();
- tlds.addAll(getTlds(context, uri));
- }
+ if (context == null)
+ return List.of();
- if (cache != null)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("{} tld cache updated", jar);
- Collection old = cache.putIfAbsent(jar, tlds);
- if (old != null)
- tlds = old;
- }
+ Resource webInf = context.getWebInf();
+ if (!Resources.isReadableDirectory(webInf))
+ return List.of();
- if (tlds.isEmpty())
- return;
- }
+ Resource webInfLib = webInf.resolve("lib");
+ if (!Resources.isReadableDirectory(webInfLib))
+ return List.of();
+
+ List jars = webInfLib.list().stream()
+ .filter((lib) -> FileID.isLibArchive(lib.getFileName()))
+ .map(r -> toDirectoryResource(context, r))
+ .filter(webinfPredicate)
+ .sorted(ResourceCollators.byName(true))
+ .toList();
- 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");
+ LOG.debug("WEB-INF/lib Jars ({}={}) : {}", WEBINF_JAR_PATTERN, pattern, jars.stream().map(Resource::getURI).map(URI::toASCIIString).collect(Collectors.joining(", ", "[", "]")));
+
+ return jars;
}
- @Override
- public void postConfigure(WebAppContext context) throws Exception
+ /**
+ * Get the List of Resources that have been specified in the {@link WebAppContext#setExtraClasspath(List)} style methods.
+ *
+ * @param context the context to get configuration from
+ * @return the List of extra classpath entries
+ */
+ protected List getExtraClassPathEntries(WebAppContext context)
{
- context.setAttribute(METAINF_RESOURCES, null);
+ if (context == null || context.getExtraClasspath() == null)
+ return List.of();
- context.setAttribute(METAINF_FRAGMENTS, null);
+ List jars = context.getExtraClasspath()
+ .stream()
+ .filter(r -> FileID.isLibArchive(r.getURI()))
+ .map(r -> toDirectoryResource(context, r))
+ .toList();
- context.setAttribute(METAINF_TLDS, null);
+ if (LOG.isDebugEnabled())
+ LOG.debug("Extra-Classpath Jars {}", jars.stream().map(Resource::getURI).map(URI::toASCIIString).collect(Collectors.joining(", ", "[", "]")));
+
+ return jars;
}
- /**
- * 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
- */
- private Collection getTlds(Path dir) throws IOException
+ private Map> getTldCache(Server server, boolean useCaches)
{
- if (dir == null || !Files.isDirectory(dir))
- return Collections.emptySet();
+ if (!useCaches || server == null)
+ return NOOP_TLD_CACHE;
- Set tlds = new HashSet<>();
+ Map> cache = (Map>)server.getAttribute(CACHED_CONTAINER_TLDS);
+ if (cache == null)
+ {
+ cache = new ConcurrentHashMap<>();
+ server.setAttribute(CACHED_CONTAINER_TLDS, cache);
+ }
+ return cache;
+ }
- try (Stream entries = Files.walk(dir)
- .filter(Files::isRegularFile)
- .filter(FileID::isTld))
+ private Map getFragmentCache(Server server, boolean useCaches)
+ {
+ if (!useCaches || server == null)
+ return NOOP_RESOURCE_CACHE;
+
+ Map cache = (Map)server.getAttribute(CACHED_CONTAINER_FRAGMENTS);
+ if (cache == null)
{
- Iterator iter = entries.iterator();
- while (iter.hasNext())
- {
- Path entry = iter.next();
- tlds.add(entry.toUri().toURL());
- }
+ cache = new ConcurrentHashMap<>();
+ server.setAttribute(CACHED_CONTAINER_FRAGMENTS, cache);
}
- return tlds;
+ return cache;
}
- /**
- * 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
- */
- private Collection getTlds(WebAppContext context, URI uri) throws IOException
+ private Map getResourceCache(Server server, boolean useCaches)
{
- HashSet tlds = new HashSet<>();
- Resource r = ResourceFactory.of(context).newResource(URIUtil.uriJarPrefix(uri, "!/"));
- try (Stream stream = Files.walk(r.getPath()))
+ if (!useCaches || server == null)
+ return NOOP_RESOURCE_CACHE;
+
+ Map cache = (ConcurrentHashMap)server.getAttribute(CACHED_CONTAINER_RESOURCES);
+ if (cache == null)
{
- Iterator it = stream
- .filter(Files::isRegularFile)
- .filter(FileID::isTld)
- .iterator();
- while (it.hasNext())
- {
- Path entry = it.next();
- tlds.add(entry.toUri().toURL());
- }
+ cache = new ConcurrentHashMap<>();
+ server.setAttribute(CACHED_CONTAINER_RESOURCES, cache);
}
- return tlds;
+ return cache;
}
- protected List findClassDirs(WebAppContext context)
+ protected List findClassesDirs(WebAppContext context)
throws Exception
{
if (context == null)
@@ -654,83 +540,18 @@ protected List findClassDirs(WebAppContext context)
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<>(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 (webInf == null || !webInf.exists() || !webInf.isDirectory())
- return List.of();
- 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());
- }
- else
- {
- return List.of();
- }
- }
+ classDirs.addAll(getExtraClasspathDirs(context));
- /**
- * 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
- */
- protected List findExtraClasspathJars(WebAppContext context)
- {
- if (context == null || context.getExtraClasspath() == null)
- return null;
-
- return context.getExtraClasspath()
- .stream()
- .filter(this::isFileSupported)
- .collect(Collectors.toList());
+ return classDirs;
}
/**
- * Get WEB-INF/classes
dir
+ * Get {@code 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
+ * @param context the context to look for the {@code WEB-INF/classes} directory
+ * @return the Resource for the {@code WEB-INF/classes} directory
+ * @throws Exception if unable to find the {@code WEB-INF/classes} directory
*/
protected Resource findWebInfClassesDir(WebAppContext context)
throws Exception
@@ -739,6 +560,7 @@ protected Resource findWebInfClassesDir(WebAppContext context)
return null;
Resource webInf = context.getWebInf();
+
// Find WEB-INF/classes
if (Resources.isReadableDirectory(webInf))
{
@@ -756,17 +578,46 @@ protected Resource findWebInfClassesDir(WebAppContext context)
* @param context the context to look for extra classpaths in
* @return the list of Resources to the extra classpath
*/
- protected List findExtraClasspathDirs(WebAppContext context)
+ protected List getExtraClasspathDirs(WebAppContext context)
{
if (context == null || context.getExtraClasspath() == null)
return List.of();
return context.getExtraClasspath()
.stream()
- .filter(Resource::isDirectory)
+ .map(path -> toDirectoryResource(context, path))
.collect(Collectors.toList());
}
+ private Resource newDirectoryResource(WebAppContext context, Path path)
+ {
+ if (path == null)
+ return null;
+ return newDirectoryResource(context, path.toUri());
+ }
+
+ private Resource newDirectoryResource(WebAppContext context, URI uri)
+ {
+ if (FileID.isJavaArchive(uri) &&
+ !"jar".equals(uri.getScheme()))
+ {
+ return context.getResourceFactory().newJarFileResource(uri);
+ }
+
+ return context.getResourceFactory().newResource(uri);
+ }
+
+ private Resource toDirectoryResource(WebAppContext context, Resource resource)
+ {
+ if (Resources.isReadable(resource) &&
+ FileID.isJavaArchive(resource.getURI()) &&
+ !"jar".equals(resource.getURI().getScheme()))
+ {
+ return context.getResourceFactory().newJarFileResource(resource.getURI());
+ }
+ return resource;
+ }
+
private boolean isFileSupported(Resource resource)
{
return FileID.isLibArchive(resource.getURI());
diff --git a/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java b/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java
index 7e89fec12c38..c662fcadf290 100644
--- a/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java
+++ b/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java
@@ -766,7 +766,7 @@ public String getWar()
return _war;
}
- public Resource getWebInf() throws IOException
+ public Resource getWebInf()
{
if (getBaseResource() == null)
return null;
diff --git a/jetty-ee10/jetty-ee10-webapp/src/test/java/org/eclipse/jetty/ee10/webapp/MetaInfConfigurationTest.java b/jetty-ee10/jetty-ee10-webapp/src/test/java/org/eclipse/jetty/ee10/webapp/MetaInfConfigurationTest.java
index 6cf334c03ef8..496e62d6f713 100644
--- a/jetty-ee10/jetty-ee10-webapp/src/test/java/org/eclipse/jetty/ee10/webapp/MetaInfConfigurationTest.java
+++ b/jetty-ee10/jetty-ee10-webapp/src/test/java/org/eclipse/jetty/ee10/webapp/MetaInfConfigurationTest.java
@@ -25,6 +25,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.stream.Stream;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.toolchain.test.FS;
@@ -135,9 +136,9 @@ public void testScanServlet25ConfigurationDiscoveredOff(WorkDir workDir) throws
.map(URI::toASCIIString)
.toList();
String[] expectedWebInfResources = {
- fooFragmentJar.toUri().toASCIIString(),
- barResourceJar.toUri().toASCIIString(),
- zedTldJar.toUri().toASCIIString()
+ URIUtil.toJarFileUri(fooFragmentJar.toUri()).toASCIIString(),
+ URIUtil.toJarFileUri(barResourceJar.toUri()).toASCIIString(),
+ URIUtil.toJarFileUri(zedTldJar.toUri()).toASCIIString()
};
assertThat("Discovered WEB-INF resources", discoveredWebInfResources, hasItems(expectedWebInfResources));
@@ -246,9 +247,9 @@ public void testScanServlet25ConfigurationDiscoveredDefault(WorkDir workDir) thr
.map(URI::toASCIIString)
.toList();
String[] expectedWebInfResources = {
- fooFragmentJar.toUri().toASCIIString(),
- barResourceJar.toUri().toASCIIString(),
- zedTldJar.toUri().toASCIIString()
+ URIUtil.toJarFileUri(fooFragmentJar.toUri()).toASCIIString(),
+ URIUtil.toJarFileUri(barResourceJar.toUri()).toASCIIString(),
+ URIUtil.toJarFileUri(zedTldJar.toUri()).toASCIIString()
};
assertThat("Discovered WEB-INF resources", discoveredWebInfResources, hasItems(expectedWebInfResources));
@@ -364,9 +365,9 @@ public void testScanServlet30MetadataCompleteFalse(WorkDir workDir) throws Excep
.map(URI::toASCIIString)
.toList();
String[] expectedWebInfResources = {
- fooFragmentJar.toUri().toASCIIString(),
- barResourceJar.toUri().toASCIIString(),
- zedTldJar.toUri().toASCIIString()
+ URIUtil.toJarFileUri(fooFragmentJar.toUri()).toASCIIString(),
+ URIUtil.toJarFileUri(barResourceJar.toUri()).toASCIIString(),
+ URIUtil.toJarFileUri(zedTldJar.toUri()).toASCIIString()
};
assertThat("Discovered WEB-INF resources", discoveredWebInfResources, hasItems(expectedWebInfResources));
@@ -481,9 +482,9 @@ public void testScanServlet31MetadataCompleteTrue(WorkDir workDir) throws Except
.map(URI::toASCIIString)
.toList();
String[] expectedWebInfResources = {
- fooFragmentJar.toUri().toASCIIString(),
- barResourceJar.toUri().toASCIIString(),
- zedTldJar.toUri().toASCIIString()
+ URIUtil.toJarFileUri(fooFragmentJar.toUri()).toASCIIString(),
+ URIUtil.toJarFileUri(barResourceJar.toUri()).toASCIIString(),
+ URIUtil.toJarFileUri(zedTldJar.toUri()).toASCIIString()
};
assertThat("Discovered WEB-INF resources", discoveredWebInfResources, hasItems(expectedWebInfResources));
@@ -561,10 +562,11 @@ public void testGetContainerPathsWithModuleSystem() throws Exception
.toList();
// we "correct" the bad file URLs that come from the ClassLoader
// to be the same as what comes from every non-classloader URL/URI.
- String[] expectedContainerResources = {
- URIUtil.correctFileURI(janbUri).toASCIIString(),
- URIUtil.correctFileURI(servletUri).toASCIIString()
- };
+ String[] expectedContainerResources = Stream.of(janbUri, servletUri)
+ .map(URIUtil::correctFileURI)
+ .map(URIUtil::toJarFileUri)
+ .map(URI::toASCIIString)
+ .toList().toArray(new String[2]);
assertThat("Discovered Container resources", discoveredContainerResources, hasItems(expectedContainerResources));
}
finally
diff --git a/jetty-ee9/jetty-ee9-annotations/src/main/java/org/eclipse/jetty/ee9/annotations/AnnotationConfiguration.java b/jetty-ee9/jetty-ee9-annotations/src/main/java/org/eclipse/jetty/ee9/annotations/AnnotationConfiguration.java
index 0650006f5770..de4115b362d1 100644
--- a/jetty-ee9/jetty-ee9-annotations/src/main/java/org/eclipse/jetty/ee9/annotations/AnnotationConfiguration.java
+++ b/jetty-ee9/jetty-ee9-annotations/src/main/java/org/eclipse/jetty/ee9/annotations/AnnotationConfiguration.java
@@ -688,7 +688,7 @@ public Resource getJarFor(ServletContainerInitializer service)
}
/**
- * Check to see if the ServletContainerIntializer loaded via the ServiceLoader came
+ * Check to see if the ServletContainerInitializer loaded via the ServiceLoader came
* from a jar that is excluded by the fragment ordering. See ServletSpec 3.0 p.85.
*
* @param context the context for the jars
@@ -752,7 +752,7 @@ public boolean isFromExcludedJar(WebAppContext context, ServletContainerInitiali
boolean included = false;
for (Resource r : orderedJars)
{
- included = r.equals(sciResource);
+ included = r.isContainedIn(sciResource);
if (included)
break;
}
@@ -966,7 +966,7 @@ else if (entry.getValue() == null) //can't work out provenance of SCI, so can't
{
for (Map.Entry entry : sciResourceMap.entrySet())
{
- if (webInfJar.equals(entry.getValue()))
+ if (webInfJar.isContainedIn(entry.getValue()))
nonExcludedInitializers.add(entry.getKey());
}
}
diff --git a/jetty-ee9/jetty-ee9-demos/jetty-ee9-demo-spec/jetty-ee9-demo-container-initializer/src/main/java/org/example/initializer/FooInitializer.java b/jetty-ee9/jetty-ee9-demos/jetty-ee9-demo-spec/jetty-ee9-demo-container-initializer/src/main/java/org/example/initializer/FooInitializer.java
index 23ee4dda8e5e..7691a4d5b130 100644
--- a/jetty-ee9/jetty-ee9-demos/jetty-ee9-demo-spec/jetty-ee9-demo-container-initializer/src/main/java/org/example/initializer/FooInitializer.java
+++ b/jetty-ee9/jetty-ee9-demos/jetty-ee9-demo-spec/jetty-ee9-demo-container-initializer/src/main/java/org/example/initializer/FooInitializer.java
@@ -84,7 +84,7 @@ public void onStartup(Set> classes, ServletContext context)
throw new IllegalStateException("FooInitializer on Startup already called");
context.setAttribute("org.example.Foo", new ArrayList(classes));
- ServletRegistration.Dynamic reg = context.addServlet("AnnotationTest", "org.example.AnnotationTest");
+ ServletRegistration.Dynamic reg = context.addServlet("AnnotationTest", "org.example.test.AnnotationTest");
context.setAttribute("org.example.AnnotationTest.complete", (reg == null));
context.addListener(new FooListener());
diff --git a/jetty-ee9/jetty-ee9-osgi/jetty-ee9-osgi-boot/src/main/java/org/eclipse/jetty/ee9/osgi/boot/OSGiMetaInfConfiguration.java b/jetty-ee9/jetty-ee9-osgi/jetty-ee9-osgi-boot/src/main/java/org/eclipse/jetty/ee9/osgi/boot/OSGiMetaInfConfiguration.java
index 382e6d49d534..fe1af038a756 100644
--- a/jetty-ee9/jetty-ee9-osgi/jetty-ee9-osgi-boot/src/main/java/org/eclipse/jetty/ee9/osgi/boot/OSGiMetaInfConfiguration.java
+++ b/jetty-ee9/jetty-ee9-osgi/jetty-ee9-osgi-boot/src/main/java/org/eclipse/jetty/ee9/osgi/boot/OSGiMetaInfConfiguration.java
@@ -285,28 +285,33 @@ private List getBundleAsResource(ResourceFactory resourceFactory, Bund
File file = BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(bundle);
if (file.isDirectory())
{
+ // Add directory bundle as a directory
+ resources.add(resourceFactory.newResource(file.toPath()));
+
for (File f : file.listFiles())
{
if (FileID.isJavaArchive(f.getName()) && f.isFile())
{
- resources.add(resourceFactory.newResource(f.toPath()));
+ // add *.jar as jar files
+ resources.add(resourceFactory.newJarFileResource(f.toPath().toUri()));
}
else if (f.isDirectory() && f.getName().equals("lib"))
{
- for (File f2 : file.listFiles())
+ for (File libFile : file.listFiles())
{
- if (FileID.isJavaArchive(f2.getName()) && f2.isFile())
+ if (FileID.isJavaArchive(libFile.getName()) && libFile.isFile())
{
- resources.add(resourceFactory.newResource(f.toPath()));
+ // add lib/*.jar as jar files
+ resources.add(resourceFactory.newJarFileResource(f.toPath().toUri()));
}
}
}
}
- resources.add(resourceFactory.newResource(file.toPath())); //TODO really???
}
else
{
- resources.add(resourceFactory.newResource(file.toPath()));
+ // Treat bundle as jar file that needs to be opened (so that resources within it can be found)
+ resources.add(resourceFactory.newJarFileResource(file.toPath().toUri()));
}
return resources;
diff --git a/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/MetaInfConfiguration.java b/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/MetaInfConfiguration.java
index e09e6d8e445c..af70b2b5d093 100644
--- a/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/MetaInfConfiguration.java
+++ b/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/MetaInfConfiguration.java
@@ -27,7 +27,6 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -37,7 +36,6 @@
import java.util.stream.Stream;
import org.eclipse.jetty.util.FileID;
-import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.UriPatternPredicate;
@@ -88,8 +86,6 @@ public class MetaInfConfiguration extends AbstractConfiguration
*/
public static final String RESOURCE_DIRS = "org.eclipse.jetty.resources";
- private ResourceFactory.Closeable _resourceFactory;
-
public MetaInfConfiguration()
{
addDependencies(WebXmlConfiguration.class);
@@ -98,8 +94,6 @@ public MetaInfConfiguration()
@Override
public void preConfigure(final WebAppContext context) throws Exception
{
- _resourceFactory = ResourceFactory.closeable();
-
//find container jars/modules and select which ones to scan
findAndFilterContainerPaths(context);
@@ -112,14 +106,6 @@ public void preConfigure(final WebAppContext context) throws Exception
scanJars(context);
}
- @Override
- public void deconfigure(WebAppContext context) throws Exception
- {
- IO.close(_resourceFactory);
- _resourceFactory = null;
- 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
@@ -143,19 +129,21 @@ public void findAndFilterContainerPaths(final WebAppContext context) throws Exce
if (StringUtil.isBlank(pattern))
return; // TODO review if this short cut will allow later code simplifications
+ ResourceFactory resourceFactory = ResourceFactory.of(context);
+
// 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 = (uri) ->
{
- Resource resource = _resourceFactory.newResource(uri);
+ Resource resource = resourceFactory.newResource(uri);
if (Resources.missing(resource))
{
if (LOG.isDebugEnabled())
LOG.debug("Classpath URI doesn't exist: " + uri);
}
else
- context.getMetaData().addContainerResource(resource);
+ context.getMetaData().addContainerResource(ensureJarsAreOpened(context, resource));
};
List containerUris = getAllContainerJars(context);
@@ -197,14 +185,14 @@ public void findAndFilterContainerPaths(final WebAppContext context) throws Exce
{
for (Path listEntry: listing.toList())
{
- Resource resource = _resourceFactory.newResource(listEntry);
+ Resource resource = resourceFactory.newResource(listEntry);
context.getMetaData().addContainerResource(resource);
}
}
}
else
{
- Resource resource = _resourceFactory.newResource(path);
+ Resource resource = resourceFactory.newResource(path);
context.getMetaData().addContainerResource(resource);
}
}
@@ -308,20 +296,6 @@ protected void scanJars(WebAppContext context) throws Exception
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
@@ -345,19 +319,19 @@ public void scanJars(final WebAppContext context, Collection jars, boo
metaInfResourceCache = (ConcurrentHashMap)context.getServer().getAttribute(CACHED_CONTAINER_RESOURCES);
if (metaInfResourceCache == null)
{
- metaInfResourceCache = new ConcurrentHashMap();
+ metaInfResourceCache = new ConcurrentHashMap<>();
context.getServer().setAttribute(CACHED_CONTAINER_RESOURCES, metaInfResourceCache);
}
metaInfFragmentCache = (ConcurrentHashMap)context.getServer().getAttribute(CACHED_CONTAINER_FRAGMENTS);
if (metaInfFragmentCache == null)
{
- metaInfFragmentCache = new ConcurrentHashMap();
+ metaInfFragmentCache = new ConcurrentHashMap<>();
context.getServer().setAttribute(CACHED_CONTAINER_FRAGMENTS, metaInfFragmentCache);
}
metaInfTldCache = (ConcurrentHashMap>)context.getServer().getAttribute(CACHED_CONTAINER_TLDS);
if (metaInfTldCache == null)
{
- metaInfTldCache = new ConcurrentHashMap>();
+ metaInfTldCache = new ConcurrentHashMap<>();
context.getServer().setAttribute(CACHED_CONTAINER_TLDS, metaInfTldCache);
}
}
@@ -387,10 +361,11 @@ public void scanJars(final WebAppContext context, Collection jars, boo
public void scanForResources(WebAppContext context, Resource target, ConcurrentHashMap cache)
{
// Resource target does not exist
- if (target == null)
+ if (Resources.missing(target))
return;
+ ResourceFactory resourceFactory = ResourceFactory.of(context);
- Resource resourcesDir = null;
+ Resource resourcesDir;
if (cache != null && cache.containsKey(target))
{
resourcesDir = cache.get(target);
@@ -408,17 +383,8 @@ else if (LOG.isDebugEnabled())
//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.resolve("/META-INF/resources");
- }
- else
- {
- // Resource represents a packed jar
- URI uri = target.getURI();
- resourcesDir = _resourceFactory.newResource(URIUtil.uriJarPrefix(uri, "!/META-INF/resources"));
- }
+
+ resourcesDir = target.resolve("META-INF/resources");
if (Resources.isReadableDirectory(resourcesDir) && (cache != null))
{
@@ -439,7 +405,7 @@ else if (LOG.isDebugEnabled())
Set dirs = (Set)context.getAttribute(METAINF_RESOURCES);
if (dirs == null)
{
- dirs = new HashSet();
+ dirs = new HashSet<>();
context.setAttribute(METAINF_RESOURCES, dirs);
}
if (LOG.isDebugEnabled())
@@ -450,7 +416,7 @@ else if (LOG.isDebugEnabled())
private static boolean isEmptyResource(Resource resourcesDir)
{
- return resourcesDir == null || !resourcesDir.isDirectory();
+ return !Resources.isReadableDirectory(resourcesDir);
}
/**
@@ -462,7 +428,9 @@ private static boolean isEmptyResource(Resource resourcesDir)
*/
public void scanForFragment(WebAppContext context, Resource jar, ConcurrentHashMap cache)
{
- Resource webFrag = null;
+ ResourceFactory resourceFactory = ResourceFactory.of(context);
+
+ Resource webFrag;
if (cache != null && cache.containsKey(jar))
{
webFrag = cache.get(jar);
@@ -480,17 +448,10 @@ else if (LOG.isDebugEnabled())
//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 = _resourceFactory.newResource(jar.getPath().resolve("META-INF/web-fragment.xml"));
- }
- else
- {
- URI uri = jar.getURI();
- webFrag = _resourceFactory.newResource(URIUtil.uriJarPrefix(uri, "!/META-INF/web-fragment.xml"));
- }
- if ((webFrag != null) && (cache != null))
+ webFrag = jar.resolve("META-INF/web-fragment.xml");
+
+ if (Resources.isReadable(webFrag) && (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);
@@ -507,7 +468,7 @@ else if (LOG.isDebugEnabled())
Map fragments = (Map)context.getAttribute(METAINF_FRAGMENTS);
if (fragments == null)
{
- fragments = new HashMap();
+ fragments = new HashMap<>();
context.setAttribute(METAINF_FRAGMENTS, fragments);
}
fragments.put(jar, webFrag);
@@ -531,7 +492,7 @@ private static boolean isEmptyFragment(Resource webFrag)
public void scanForTlds(WebAppContext context, Resource jar, ConcurrentHashMap> cache)
throws Exception
{
- Collection tlds = null;
+ Collection tlds;
if (cache != null && cache.containsKey(jar))
{
@@ -553,21 +514,13 @@ public void scanForTlds(WebAppContext context, Resource jar, ConcurrentHashMap();
- if (jar.isDirectory())
- {
- tlds.addAll(getTlds(jar.getPath()));
- }
- else
- {
- URI uri = jar.getURI();
- tlds.addAll(getTlds(uri));
- }
+ tlds.addAll(getTlds(jar));
if (cache != null)
{
if (LOG.isDebugEnabled())
LOG.debug("{} tld cache updated", jar);
- Collection old = (Collection)cache.putIfAbsent(jar, tlds);
+ Collection old = cache.putIfAbsent(jar, tlds);
if (old != null)
tlds = old;
}
@@ -579,7 +532,7 @@ public void scanForTlds(WebAppContext context, Resource jar, ConcurrentHashMap metaInfTlds = (Collection)context.getAttribute(METAINF_TLDS);
if (metaInfTlds == null)
{
- metaInfTlds = new HashSet();
+ metaInfTlds = new HashSet<>();
context.setAttribute(METAINF_TLDS, metaInfTlds);
}
metaInfTlds.addAll(tlds);
@@ -600,55 +553,39 @@ public void postConfigure(WebAppContext context) throws Exception
/**
* Find all .tld files in all subdirs of the given dir.
*
- * @param dir the directory to scan
+ * @param res the directory to scan
* @return the list of tlds found
* @throws IOException if unable to scan the directory
*/
- public Collection getTlds(Path dir) throws IOException
+ private Collection getTlds(Resource res) throws IOException
{
- if (dir == null || !Files.isDirectory(dir))
+ if (!Resources.isReadableDirectory(res))
return Collections.emptySet();
- Set tlds = new HashSet<>();
+ Resource metaInfDir = res.resolve("META-INF");
+ if (!Resources.isReadableDirectory(metaInfDir))
+ return Collections.emptySet();
- try (Stream entries = Files.walk(dir)
- .filter(Files::isRegularFile)
- .filter(FileID::isTld))
+ Set tldURIs = new HashSet<>();
+ for (Resource entry: metaInfDir.list())
{
- Iterator iter = entries.iterator();
- while (iter.hasNext())
+ URI uri = entry.getURI();
+ if (FileID.isExtension(entry.getFileName(), "tld"))
{
- Path entry = iter.next();
- tlds.add(entry.toUri().toURL());
+ if (Resources.isReadableFile(entry))
+ tldURIs.add(uri);
+ else
+ LOG.warn("TLD not a readable: {}", uri);
}
}
- 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<>();
- Resource r = _resourceFactory.newResource(URIUtil.uriJarPrefix(uri, "!/"));
- try (Stream stream = Files.walk(r.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;
+ // URL hashCode() and equals() calls on java.net.URL objects and calls that add URL objects to maps and sets.
+ // URL's equals() and hashCode() methods can perform a DNS lookup to resolve the host name.
+ // This is why we collect the Set as a URI, then convert to List to satisfy the collection of unique URLs
+ List tldURLs = new ArrayList<>();
+ for (URI uri: tldURIs)
+ tldURLs.add(uri.toURL()); // done this way to allow toURL to throw if need be
+ return tldURLs;
}
protected List findClassDirs(WebAppContext context)
@@ -657,7 +594,7 @@ protected List findClassDirs(WebAppContext context)
if (context == null)
return null;
- List classDirs = new ArrayList();
+ List classDirs = new ArrayList<>();
Resource webInfClasses = findWebInfClassesDir(context);
if (webInfClasses != null)
@@ -677,8 +614,7 @@ protected List findClassDirs(WebAppContext context)
protected List findJars(WebAppContext context)
throws Exception
{
- List jarResources = new ArrayList<>();
- jarResources.addAll(findWebInfLibJars(context));
+ List jarResources = new ArrayList<>(findWebInfLibJars(context));
List extraClasspathJars = findExtraClasspathJars(context);
if (extraClasspathJars != null)
jarResources.addAll(extraClasspathJars);
@@ -699,20 +635,23 @@ protected List findWebInfLibJars(WebAppContext context)
return List.of();
Resource webInf = context.getWebInf();
- if (Resources.isReadableDirectory(webInf))
- {
- Resource webInfLib = webInf.resolve("lib");
+ if (webInf == null || !webInf.exists() || !webInf.isDirectory())
+ return List.of();
- if (Resources.isReadableDirectory(webInfLib))
- {
- return webInfLib.list().stream()
- .filter((lib) -> FileID.isLibArchive(lib.getFileName()))
- .sorted(ResourceCollators.byName(true))
- .collect(Collectors.toList());
- }
- }
+ Resource webInfLib = webInf.resolve("lib");
- return List.of();
+ if (Resources.isReadableDirectory(webInfLib))
+ {
+ return webInfLib.list().stream()
+ .filter((lib) -> FileID.isLibArchive(lib.getFileName()))
+ .map(r -> ensureJarsAreOpened(context, r))
+ .sorted(ResourceCollators.byName(true))
+ .collect(Collectors.toList());
+ }
+ else
+ {
+ return List.of();
+ }
}
/**
@@ -720,10 +659,8 @@ protected List findWebInfLibJars(WebAppContext context)
*
* @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;
@@ -731,6 +668,7 @@ protected List findExtraClasspathJars(WebAppContext context)
return context.getExtraClasspath()
.stream()
.filter(this::isFileSupported)
+ .map(r -> ensureJarsAreOpened(context, r))
.collect(Collectors.toList());
}
@@ -748,14 +686,13 @@ protected Resource findWebInfClassesDir(WebAppContext context)
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;
+ Resource webInfClassesDir = webInf.resolve("classes/");
+ if (Resources.isReadableDirectory(webInfClassesDir))
+ return webInfClassesDir;
}
return null;
}
@@ -777,6 +714,17 @@ protected List findExtraClasspathDirs(WebAppContext context)
.collect(Collectors.toList());
}
+ private Resource ensureJarsAreOpened(WebAppContext context, Resource resource)
+ {
+ if (Resources.isReadable(resource) &&
+ FileID.isJavaArchive(resource.getURI()) &&
+ !"jar".equals(resource.getURI().getScheme()))
+ {
+ return context.getResourceFactory().newJarFileResource(resource.getURI());
+ }
+ return resource;
+ }
+
private boolean isFileSupported(Resource resource)
{
return FileID.isLibArchive(resource.getURI());
diff --git a/jetty-ee9/jetty-ee9-webapp/src/test/java/org/eclipse/jetty/ee9/webapp/MetaInfConfigurationTest.java b/jetty-ee9/jetty-ee9-webapp/src/test/java/org/eclipse/jetty/ee9/webapp/MetaInfConfigurationTest.java
index df1f8ca66bec..a1b89a615017 100644
--- a/jetty-ee9/jetty-ee9-webapp/src/test/java/org/eclipse/jetty/ee9/webapp/MetaInfConfigurationTest.java
+++ b/jetty-ee9/jetty-ee9-webapp/src/test/java/org/eclipse/jetty/ee9/webapp/MetaInfConfigurationTest.java
@@ -20,6 +20,7 @@
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.resource.FileSystemPool;
import org.eclipse.jetty.util.resource.Resource;
import org.junit.jupiter.api.AfterEach;
@@ -78,6 +79,18 @@ public void scanJars(WebAppContext context, Collection jars, boolean u
}
}
+ @BeforeEach
+ public void beforeEach()
+ {
+ assertThat(FileSystemPool.INSTANCE.mounts(), empty());
+ }
+
+ @AfterEach
+ public void tearDown()
+ {
+ assertThat(FileSystemPool.INSTANCE.mounts(), empty());
+ }
+
@Test
public void testScanTypes() throws Exception
{
@@ -154,12 +167,15 @@ public void testFindAndFilterContainerPathsJDK9() throws Exception
for (Resource r : containerResources)
{
String s = r.toString();
- assertTrue(s.endsWith("foo-bar-janb.jar") || s.contains("servlet-api"));
+ assertTrue(s.endsWith("foo-bar-janb.jar!/") || s.contains("servlet-api"));
}
}
finally
{
config.postConfigure(context);
+ // manually stop ResourceFactory.
+ // normally this would be done via WebAppContext.stop(), but we didn't start the context.
+ LifeCycle.stop(context.getResourceFactory()); // manu
}
}
}
diff --git a/tests/test-distribution/test-distribution-common/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java b/tests/test-distribution/test-distribution-common/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java
index cf5171f7b246..8dfbd0285918 100644
--- a/tests/test-distribution/test-distribution-common/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java
+++ b/tests/test-distribution/test-distribution-common/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java
@@ -284,9 +284,9 @@ public void testSimpleWebAppWithJSPAndJSTL(String env) throws Exception
@ValueSource(strings = {"ee10"})
public void testSimpleWebAppWithJSPOnModulePath(String env) throws Exception
{
- // Testing with env=ee9 is not possible because jakarta.transaction:1.x
- // does not have a proper module-info.java, so JPMS resolution will fail.
- // For env=ee10, jakarta.transaction:2.x has a proper module-info.java.
+ /* TODO Testing with env=ee9 is not possible because jakarta.transaction:1.x does not have a proper module-info.java, so JPMS resolution will fail.
+ * For env=ee10, jakarta.transaction:2.x has a proper module-info.java.
+ */
Path jettyBase = newTestJettyBaseDirectory();
String jettyVersion = System.getProperty("jettyVersion");
JettyHomeTester distribution = JettyHomeTester.Builder.newInstance()
@@ -294,12 +294,13 @@ public void testSimpleWebAppWithJSPOnModulePath(String env) throws Exception
.jettyBase(jettyBase)
.build();
+ // Initialize jetty base
String mods = String.join(",",
"resources", "server", "http", "jmx",
toEnvironment("webapp", env),
toEnvironment("deploy", env),
- toEnvironment("glassfish-jstl", env),
- toEnvironment("apache-jsp", env)
+ toEnvironment("jstl", env),
+ toEnvironment("jsp", env)
);
try (JettyHomeTester.Run run1 = distribution.start("--approve-all-licenses", "--add-modules=" + mods))
{
@@ -309,22 +310,32 @@ public void testSimpleWebAppWithJSPOnModulePath(String env) throws Exception
File war = distribution.resolveArtifact("org.eclipse.jetty." + env + ".demos:jetty-" + env + "-demo-jsp-webapp:war:" + jettyVersion);
distribution.installWarFile(war, "test");
- int port = distribution.freePort();
- try (JettyHomeTester.Run run2 = distribution.start("--jpms", "jetty.http.port=" + port))
- {
- assertTrue(run2.awaitConsoleLogsFor("Started oejs.Server@", START_TIMEOUT, TimeUnit.SECONDS));
-
- startHttpClient();
- ContentResponse response = client.GET("http://localhost:" + port + "/test/index.jsp");
- assertEquals(HttpStatus.OK_200, response.getStatus());
- assertThat(response.getContentAsString(), containsString("JSP Examples"));
- assertThat(response.getContentAsString(), not(containsString("<%")));
+ String logging = """
+ # Ensure that JettyHomeTester can find the Console Log it is looking for.
+ # The default behavior is usually sufficient.
+ # This also doubles as an example on how to customize jetty-logging for a DistributionTest
+ org.eclipse.jetty.LEVEL=INFO
+ """;
+ Files.writeString(jettyBase.resolve("resources/jetty-logging.properties"), logging, StandardCharsets.UTF_8);
+ }
- response = client.GET("http://localhost:" + port + "/test/jstl.jsp");
- assertEquals(HttpStatus.OK_200, response.getStatus());
- assertThat(response.getContentAsString(), containsString("JSTL Example"));
- assertThat(response.getContentAsString(), not(containsString("