diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java index cc453c4b46c..22571e6f2cd 100644 --- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java +++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java @@ -45,6 +45,7 @@ import javax.servlet.annotation.HandlesTypes; import org.eclipse.jetty.annotations.AnnotationParser.Handler; import org.eclipse.jetty.plus.annotation.ContainerInitializer; +import org.eclipse.jetty.util.JavaVersion; import org.eclipse.jetty.util.MultiException; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.TypeUtil; @@ -72,7 +73,7 @@ public class AnnotationConfiguration extends AbstractConfiguration public static final String CONTAINER_INITIALIZER_STARTER = "org.eclipse.jetty.containerInitializerStarter"; public static final String MULTI_THREADED = "org.eclipse.jetty.annotations.multiThreaded"; public static final String MAX_SCAN_WAIT = "org.eclipse.jetty.annotations.maxWait"; - public static final String JAVA_TARGET_PLATFORM = "org.eclipse.jetty.javaTargetPlatform"; + public static final int DEFAULT_MAX_SCAN_WAIT = 60; /* time in sec */ public static final boolean DEFAULT_MULTI_THREADED = true; @@ -420,7 +421,7 @@ public class AnnotationConfiguration extends AbstractConfiguration throws Exception { int javaPlatform = 0; - Object target = context.getAttribute(JAVA_TARGET_PLATFORM); + Object target = context.getAttribute(JavaVersion.JAVA_TARGET_PLATFORM); if (target!=null) javaPlatform = Integer.valueOf(target.toString()); AnnotationParser parser = createAnnotationParser(javaPlatform); diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/JavaVersion.java b/jetty-util/src/main/java/org/eclipse/jetty/util/JavaVersion.java index 6dd630251ec..4c977801162 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/JavaVersion.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/JavaVersion.java @@ -27,6 +27,13 @@ import java.util.regex.Pattern; */ public class JavaVersion { + + /** + * Context attribute that can be set to target a different version of the jvm than the current runtime. + * Acceptable values should correspond to those returned by JavaVersion.getPlatform(). + */ + public static final String JAVA_TARGET_PLATFORM = "org.eclipse.jetty.javaTargetPlatform"; + // Copy of version in jetty-start private static final Pattern PRE_JDK9 = Pattern.compile("1\\.(\\d)(\\.(\\d+)(_(\\d+))?)?(-.+)?"); diff --git a/jetty-webapp/pom.xml b/jetty-webapp/pom.xml index db037264636..48b44c577ca 100644 --- a/jetty-webapp/pom.xml +++ b/jetty-webapp/pom.xml @@ -38,6 +38,7 @@ org.apache.maven.plugins maven-surefire-plugin + false org.eclipse.jetty.webapp.WebAppClassLoaderUrlStreamTest @@ -70,4 +71,22 @@ true + + + jdk9 + + [1.9,) + + + + + maven-surefire-plugin + + @{argLine} --module-path src/test/resources/mods + + + + + + diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java index 05238c03513..9e6d92f2698 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java @@ -24,6 +24,8 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -36,6 +38,7 @@ import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.NetworkConnector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.JavaVersion; import org.eclipse.jetty.util.PatternMatcher; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.log.Log; @@ -61,6 +64,84 @@ public class WebInfConfiguration extends AbstractConfiguration protected Resource _preUnpackBaseResource; + /** + * ContainerPathNameMatcher + * + * Matches names of jars on the container classpath + * against a pattern. If no pattern is specified, no + * jars match. + */ + public class ContainerPathNameMatcher extends PatternMatcher + { + protected final WebAppContext _context; + protected final Pattern _pattern; + + public ContainerPathNameMatcher(WebAppContext context, Pattern pattern) + { + if (context == null) + throw new IllegalArgumentException("Context null"); + _context = context; + _pattern = pattern; + } + + + public void match (List uris) + throws Exception + { + if (uris == null) + return; + match(_pattern, uris.toArray(new URI[uris.size()]), false); + } + + + + /** + * @see org.eclipse.jetty.util.PatternMatcher#matched(java.net.URI) + */ + @Override + public void matched(URI uri) throws Exception + { + _context.getMetaData().addContainerResource(Resource.newResource(uri)); + } + } + + + /** + * WebAppPathNameMatcher + * + * Matches names of jars or dirs on the webapp classpath + * against a pattern. If there is no pattern, all jars or dirs + * will match. + */ + public class WebAppPathNameMatcher extends PatternMatcher + { + protected final WebAppContext _context; + protected final Pattern _pattern; + + public WebAppPathNameMatcher (WebAppContext context, Pattern pattern) + { + if (context == null) + throw new IllegalArgumentException("Context null"); + _context=context; + _pattern=pattern; + } + + public void match (List uris) + throws Exception + { + match(_pattern, uris.toArray(new URI[uris.size()]), true); + } + + /** + * @see org.eclipse.jetty.util.PatternMatcher#matched(java.net.URI) + */ + @Override + public void matched(URI uri) throws Exception + { + _context.getMetaData().addWebInfJar(Resource.newResource(uri)); + } + + } @Override @@ -72,79 +153,165 @@ public class WebInfConfiguration extends AbstractConfiguration //Extract webapp if necessary unpack (context); + findAndFilterContainerPaths(context); - //Apply an initial ordering to the jars which governs which will be scanned for META-INF - //info and annotations. The ordering is based on inclusion patterns. - String tmp = (String)context.getAttribute(WEBINF_JAR_PATTERN); - Pattern webInfPattern = (tmp==null?null:Pattern.compile(tmp)); - tmp = (String)context.getAttribute(CONTAINER_JAR_PATTERN); + findAndFilterWebAppPaths(context); + + //No pattern to appy to classes, just add to metadata + context.getMetaData().setWebInfClassesDirs(findClassDirs(context)); + } + + + + /** + * Find jars and directories that are on the container's classpath + * and apply an optional filter. The filter is a pattern applied to the + * full jar or directory names. If there is no pattern, then no jar + * or dir is considered to match. + * + * Those jars that do match will be later examined for META-INF + * information and annotations. + * + * To find them, examine the classloaders in the hierarchy above the + * webapp classloader that are URLClassLoaders. For jdk-9 we also + * look at the java.class.path, and the jdk.module.path. + * + * @param context the WebAppContext being deployed + * @throws Exception + */ + public void findAndFilterContainerPaths (final WebAppContext context) + throws Exception + { + //assume the target jvm is the same as that running + int targetPlatform = JavaVersion.VERSION.getPlatform(); + //allow user to specify target jvm different to current runtime + Object target = context.getAttribute(JavaVersion.JAVA_TARGET_PLATFORM); + if (target!=null) + targetPlatform = Integer.valueOf(target.toString()).intValue(); + + //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. + String tmp = (String)context.getAttribute(CONTAINER_JAR_PATTERN); Pattern containerPattern = (tmp==null?null:Pattern.compile(tmp)); - - //Apply ordering to container jars - if no pattern is specified, we won't - //match any of the container jars - PatternMatcher containerJarNameMatcher = new PatternMatcher () - { - public void matched(URI uri) throws Exception - { - context.getMetaData().addContainerResource(Resource.newResource(uri)); - } - }; + ContainerPathNameMatcher containerPathNameMatcher = new ContainerPathNameMatcher(context, containerPattern); + ClassLoader loader = null; if (context.getClassLoader() != null) loader = context.getClassLoader().getParent(); + List containerUris = new ArrayList<>(); + while (loader != null && (loader instanceof URLClassLoader)) { URL[] urls = ((URLClassLoader)loader).getURLs(); if (urls != null) { - URI[] containerUris = new URI[urls.length]; - int i=0; for (URL u : urls) { try { - containerUris[i] = u.toURI(); + containerUris.add(u.toURI()); } catch (URISyntaxException e) { - containerUris[i] = new URI(u.toString().replaceAll(" ", "%20")); + containerUris.add(new URI(u.toString().replaceAll(" ", "%20"))); } - i++; } - containerJarNameMatcher.match(containerPattern, containerUris, false); } loader = loader.getParent(); } + + if (LOG.isDebugEnabled()) LOG.debug("Matching container urls {}", containerUris); + containerPathNameMatcher.match(containerUris); - //Apply ordering to WEB-INF/lib jars - PatternMatcher webInfJarNameMatcher = new PatternMatcher () + //if running on jvm 9 or above, we we won't be able to look at the application classloader + //to extract urls, so we need to examine the classpath instead. + if (JavaVersion.VERSION.getPlatform() >= 9) { - @Override - public void matched(URI uri) throws Exception + tmp = System.getProperty("java.class.path"); + if (tmp != null) { - context.getMetaData().addWebInfJar(Resource.newResource(uri)); + List cpUris = new ArrayList<>(); + String[] entries = tmp.split(File.pathSeparator); + for (String entry:entries) + { + File f = new File(entry); + cpUris.add(f.toURI()); + } + if (LOG.isDebugEnabled()) LOG.debug("Matching java.class.path {}", cpUris); + containerPathNameMatcher.match(cpUris); } - }; + } + + //if we're targetting jdk 9 or above, we also need to examine the + //module path + if (targetPlatform >= 9) + { + //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. + tmp = System.getProperty("jdk.module.path"); + if (tmp != null) + { + List moduleUris = new ArrayList<>(); + String[] entries = tmp.split(File.pathSeparator); + for (String entry:entries) + { + File dir = new File(entry); + File[] files = dir.listFiles(); + if (files != null) + { + for (File f:files) + { + moduleUris.add(f.toURI()); + } + } + + } + if (LOG.isDebugEnabled()) LOG.debug("Matching jdk.module.path {}", moduleUris); + containerPathNameMatcher.match(moduleUris); + } + } + + if (LOG.isDebugEnabled()) LOG.debug("Container paths selected:{}", context.getMetaData().getContainerResources()); + } + + + /** + * Finds the jars that are either physically or virtually in + * WEB-INF/lib, and applies an optional filter to their full + * pathnames. + * + * The filter selects which jars will later be examined for META-INF + * information and annotations. If there is no pattern, then + * all jars are considered selected. + * + * @param context the WebAppContext being deployed + * @throws Exception + */ + public void findAndFilterWebAppPaths (WebAppContext context) + throws Exception + { + String tmp = (String)context.getAttribute(WEBINF_JAR_PATTERN); + Pattern webInfPattern = (tmp==null?null:Pattern.compile(tmp)); + //Apply filter to WEB-INF/lib jars + WebAppPathNameMatcher matcher = new WebAppPathNameMatcher(context, webInfPattern); + List jars = findJars(context); //Convert to uris for matching - URI[] uris = null; if (jars != null) { - uris = new URI[jars.size()]; + List uris = new ArrayList<>(); int i=0; for (Resource r: jars) { - uris[i++] = r.getURI(); + uris.add(r.getURI()); } + matcher.match(uris); } - webInfJarNameMatcher.match(webInfPattern, uris, true); //null is inclusive, no pattern == all jars match - - //No pattern to appy to classes, just add to metadata - context.getMetaData().setWebInfClassesDirs(findClassDirs(context)); } - + @Override public void configure(WebAppContext context) throws Exception diff --git a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebInfConfigurationTest.java b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebInfConfigurationTest.java new file mode 100644 index 00000000000..ea9f6919b5d --- /dev/null +++ b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebInfConfigurationTest.java @@ -0,0 +1,113 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + + +package org.eclipse.jetty.webapp; + +import static org.junit.Assert.*; + +import java.util.List; + +import org.eclipse.jetty.util.JavaVersion; +import org.eclipse.jetty.util.resource.Resource; +import org.junit.Assume; +import org.junit.Test; + +/** + * WebInfConfigurationTest + * + * + */ +public class WebInfConfigurationTest +{ + + /** + * Assume target < jdk9. In this case, we should be able to extract + * the urls from the application classloader, and we should not look + * at the java.class.path property. + * @throws Exception + */ + @Test + public void testFindAndFilterContainerPaths() + throws Exception + { + Assume.assumeTrue(JavaVersion.VERSION.getMajor() < 9); + WebInfConfiguration config = new WebInfConfiguration(); + WebAppContext context = new WebAppContext(); + context.setAttribute(WebInfConfiguration.CONTAINER_JAR_PATTERN, ".*/jetty-util-[^/]*\\.jar$|.*/jetty-util/target/classes/"); + WebAppClassLoader loader = new WebAppClassLoader(context); + context.setClassLoader(loader); + config.findAndFilterContainerPaths(context); + List containerResources = context.getMetaData().getContainerResources(); + assertEquals(1, containerResources.size()); + assertTrue(containerResources.get(0).toString().contains("jetty-util")); + } + + /** + * Assume target jdk9 or above. In this case we should extract what we need + * from the java.class.path. We should also examine the module path. + * @throws Exception + */ + @Test + public void testFindAndFilterContainerPathsJDK9() + throws Exception + { + Assume.assumeTrue(JavaVersion.VERSION.getMajor() >= 9); + Assume.assumeTrue(System.getProperty("jdk.module.path") != null); + WebInfConfiguration config = new WebInfConfiguration(); + WebAppContext context = new WebAppContext(); + context.setAttribute(WebInfConfiguration.CONTAINER_JAR_PATTERN, ".*/jetty-util-[^/]*\\.jar$|.*/jetty-util/target/classes/$|.*/foo-bar-janb.jar"); + WebAppClassLoader loader = new WebAppClassLoader(context); + context.setClassLoader(loader); + config.findAndFilterContainerPaths(context); + List containerResources = context.getMetaData().getContainerResources(); + assertEquals(2, containerResources.size()); + for (Resource r:containerResources) + { + String s = r.toString(); + assertTrue(s.endsWith("foo-bar-janb.jar") || s.contains("jetty-util")); + } + } + + + /** + * Assume runtime is jdk9 or above. Target is jdk 8. In this + * case we must extract from the java.class.path (because jdk 9 + * has no url based application classloader), but we should + * ignore the module path. + * @throws Exception + */ + @Test + public void testFindAndFilterContainerPathsTarget8() + throws Exception + { + Assume.assumeTrue(JavaVersion.VERSION.getMajor() >= 9); + Assume.assumeTrue(System.getProperty("jdk.module.path") != null); + WebInfConfiguration config = new WebInfConfiguration(); + WebAppContext context = new WebAppContext(); + context.setAttribute(JavaVersion.JAVA_TARGET_PLATFORM, "8"); + context.setAttribute(WebInfConfiguration.CONTAINER_JAR_PATTERN, ".*/jetty-util-[^/]*\\.jar$|.*/jetty-util/target/classes/$|.*/foo-bar-janb.jar"); + WebAppClassLoader loader = new WebAppClassLoader(context); + context.setClassLoader(loader); + config.findAndFilterContainerPaths(context); + List containerResources = context.getMetaData().getContainerResources(); + assertEquals(1, containerResources.size()); + assertTrue(containerResources.get(0).toString().contains("jetty-util")); + } + +} diff --git a/jetty-webapp/src/test/resources/mods/com-acme-janb.jar b/jetty-webapp/src/test/resources/mods/com-acme-janb.jar new file mode 100644 index 00000000000..a4d2b89004d Binary files /dev/null and b/jetty-webapp/src/test/resources/mods/com-acme-janb.jar differ diff --git a/jetty-webapp/src/test/resources/mods/foo-bar-janb.jar b/jetty-webapp/src/test/resources/mods/foo-bar-janb.jar new file mode 100644 index 00000000000..702ff7b996b Binary files /dev/null and b/jetty-webapp/src/test/resources/mods/foo-bar-janb.jar differ