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