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 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 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

- * 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("