Issue #2191 - JPMS Support.

Introduced --jpms option in jetty-start to run Jetty from the module-path.

Introduced [jpms] sections in *.mod files, to specify JPMS command line
options that needs to be added to the command line generated by jetty-start.

Bumped java.transaction-api to 1.3 because it has Automatic-Module-Name.

Fixed ASM version lookup using ManifestUtils.

Fixed WebInfConfiguration.findAndFilterContainerPaths() to properly
scan the module-path, which may contain both files and directories.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2018-09-25 22:11:25 +02:00
parent 78d529b13a
commit e4ff653295
15 changed files with 274 additions and 80 deletions

View File

@ -13,3 +13,6 @@ lib/annotations/*.jar
[xml]
# Enable annotation scanning webapp configurations
etc/jetty-annotations.xml
[jpms]
add-modules:org.objectweb.asm

View File

@ -24,7 +24,6 @@ import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@ -36,6 +35,7 @@ import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jetty.util.JavaVersion;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.ManifestUtils;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.MultiReleaseJarFile;
import org.eclipse.jetty.util.log.Log;
@ -69,9 +69,8 @@ import org.objectweb.asm.Opcodes;
public class AnnotationParser
{
private static final Logger LOG = Log.getLogger(AnnotationParser.class);
protected static int ASM_OPCODE_VERSION = Opcodes.ASM6; //compatibility of api
protected static String ASM_OPCODE_VERSION_STR = "ASM6";
private static final int ASM_OPCODE_VERSION = Opcodes.ASM6; //compatibility of api
/**
* Map of classnames scanned and the first location from which scan occurred
*/
@ -86,48 +85,44 @@ public class AnnotationParser
public static int asmVersion ()
{
int asmVersion = ASM_OPCODE_VERSION;
Package asm = Opcodes.class.getPackage();
if (asm == null)
LOG.warn("Unknown asm runtime version, assuming version {}", ASM_OPCODE_VERSION_STR);
String version = ManifestUtils.getVersion(Opcodes.class).orElse(null);
if (version == null)
{
LOG.warn("Unknown ASM version, assuming {}", ASM_OPCODE_VERSION);
}
else
{
String s = asm.getImplementationVersion();
if (s==null)
LOG.info("Unknown asm implementation version, assuming version {}", ASM_OPCODE_VERSION_STR);
else
int dot = version.indexOf('.');
version = version.substring(0, (dot < 0 ? version.length() : dot)).trim();
try
{
int dot = s.indexOf('.');
s = s.substring(0, (dot < 0 ? s.length() : dot)).trim();
try
int v = Integer.parseInt(version);
switch (v)
{
int v = Integer.parseInt(s);
switch (v)
case 4:
{
case 4:
{
asmVersion = Opcodes.ASM4;
break;
}
case 5:
{
asmVersion = Opcodes.ASM5;
break;
}
case 6:
{
asmVersion = Opcodes.ASM6;
break;
}
default:
{
LOG.warn("Unrecognized runtime asm version, assuming {}", ASM_OPCODE_VERSION_STR);
}
asmVersion = Opcodes.ASM4;
break;
}
case 5:
{
asmVersion = Opcodes.ASM5;
break;
}
case 6:
{
asmVersion = Opcodes.ASM6;
break;
}
default:
{
LOG.warn("Unrecognized ASM version, assuming {}", ASM_OPCODE_VERSION);
}
}
catch (NumberFormatException e)
{
LOG.warn("Unable to parse runtime asm version, assuming version {}", ASM_OPCODE_VERSION_STR);
}
}
catch (NumberFormatException e)
{
LOG.warn("Unable to parse ASM version, assuming {}", ASM_OPCODE_VERSION);
}
}
return asmVersion;

View File

@ -170,7 +170,7 @@ Note: order presented here is how they would appear on the classpath.
13: {VERSION} | ${jetty.home}/lib/jetty-jndi-{VERSION}.jar
14: 1.1.0.v201105071233 | ${jetty.home}/lib/jndi/javax.activation-1.1.0.v201105071233.jar
15: 1.4.1.v201005082020 | ${jetty.home}/lib/jndi/javax.mail.glassfish-1.4.1.v201005082020.jar
16: 1.2 | ${jetty.home}/lib/jndi/javax.transaction-api-1.2.jar
16: 1.3 | ${jetty.home}/lib/jndi/javax.transaction-api-1.3.jar
17: {VERSION} | ${jetty.home}/lib/jetty-rewrite-{VERSION}.jar
18: {VERSION} | ${jetty.home}/lib/jetty-security-{VERSION}.jar
19: {VERSION} | ${jetty.home}/lib/jetty-servlet-{VERSION}.jar

View File

@ -80,7 +80,7 @@ Note: order presented here is how they would appear on the classpath.
13: {VERSION} | ${jetty.home}/lib/jetty-jndi-{VERSION}.jar
14: 1.1.0.v201105071233 | ${jetty.home}/lib/jndi/javax.activation-1.1.0.v201105071233.jar
15: 1.4.1.v201005082020 | ${jetty.home}/lib/jndi/javax.mail.glassfish-1.4.1.v201005082020.jar
16: 1.2 | ${jetty.home}/lib/jndi/javax.transaction-api-1.2.jar
16: 1.3 | ${jetty.home}/lib/jndi/javax.transaction-api-1.3.jar
17: {VERSION} | ${jetty.home}/lib/jetty-rewrite-{VERSION}.jar
18: {VERSION} | ${jetty.home}/lib/jetty-security-{VERSION}.jar
19: {VERSION} | ${jetty.home}/lib/jetty-servlet-{VERSION}.jar

View File

@ -43,7 +43,7 @@
<extensions>true</extensions>
<configuration>
<instructions>
<Import-Package>javax.transaction*;version="[1.1,1.3)",*</Import-Package>
<Import-Package>javax.transaction*;version="[1.3)",*</Import-Package>
</instructions>
</configuration>
</plugin>

View File

@ -37,7 +37,6 @@
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>javax.transaction-api</artifactId>
<version>1.2</version>
<scope>compile</scope>
</dependency>
<dependency>
@ -49,7 +48,6 @@
<dependency>
<groupId>org.eclipse.jetty.orbit</groupId>
<artifactId>javax.mail.glassfish</artifactId>
<version>1.4.1.v201005082020</version>
<scope>test</scope>
</dependency>
<dependency>

View File

@ -18,11 +18,6 @@
package org.eclipse.jetty.start;
import static org.eclipse.jetty.start.UsageException.ERR_BAD_STOP_PROPS;
import static org.eclipse.jetty.start.UsageException.ERR_INVOKE_MAIN;
import static org.eclipse.jetty.start.UsageException.ERR_NOT_STOPPED;
import static org.eclipse.jetty.start.UsageException.ERR_UNKNOWN;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
@ -44,6 +39,11 @@ import org.eclipse.jetty.start.Props.Prop;
import org.eclipse.jetty.start.config.CommandLineConfigSource;
import org.eclipse.jetty.start.config.ConfigSource;
import static org.eclipse.jetty.start.UsageException.ERR_BAD_STOP_PROPS;
import static org.eclipse.jetty.start.UsageException.ERR_INVOKE_MAIN;
import static org.eclipse.jetty.start.UsageException.ERR_NOT_STOPPED;
import static org.eclipse.jetty.start.UsageException.ERR_UNKNOWN;
/**
* Main start class.
* <p>
@ -353,9 +353,13 @@ public class Main
// ------------------------------------------------------------
// 6) Resolve Extra XMLs
args.resolveExtraXmls();
// ------------------------------------------------------------
// 9) Resolve Property Files
// 7) JPMS Expansion
args.expandJPMS(activeModules);
// ------------------------------------------------------------
// 8) Resolve Property Files
args.resolvePropertyFiles();
return args;

View File

@ -89,6 +89,9 @@ public class Module implements Comparable<Module>
/** List of library options for this Module */
private final List<String> _libs=new ArrayList<>();
/** List of JPMS options for this Module */
private final List<String> _jpms=new ArrayList<>();
/** List of files for this Module */
private final List<String> _files=new ArrayList<>();
@ -229,6 +232,11 @@ public class Module implements Comparable<Module>
{
return _xmls;
}
public List<String> getJPMS()
{
return _jpms;
}
public Version getVersion()
{
@ -350,6 +358,9 @@ public class Module implements Comparable<Module>
case "LIBS":
_libs.add(line);
break;
case "JPMS":
_jpms.add(line);
break;
case "LICENSE":
case "LICENSES":
case "LICENCE":

View File

@ -154,6 +154,10 @@ public class Modules implements Iterable<Module>
{
System.out.printf(" XML: %s%n",xml);
}
for (String jpms : module.getJPMS())
{
System.out.printf(" JPMS: %s%n",jpms);
}
for (String jvm : module.getJvmArgs())
{
System.out.printf(" JVM: %s%n",jvm);

View File

@ -27,9 +27,12 @@ import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
@ -37,6 +40,8 @@ import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.eclipse.jetty.start.Props.Prop;
import org.eclipse.jetty.start.config.ConfigSource;
@ -109,7 +114,8 @@ public class StartArgs
System.setProperty("jetty.tag.version", tag);
}
private static final String SERVER_MAIN = "org.eclipse.jetty.xml.XmlConfiguration";
private static final String MAIN_CLASS = "org.eclipse.jetty.xml.XmlConfiguration";
private static final String MODULE_MAIN_CLASS = "org.eclipse.jetty.xml/org.eclipse.jetty.xml.XmlConfiguration";
private final BaseHome baseHome;
@ -131,6 +137,13 @@ public class StartArgs
/** List of all active [xml] sections from enabled modules */
private List<Path> xmls = new ArrayList<>();
/** List of all active [jpms] sections for enabled modules */
private Set<String> jmodAdds = new LinkedHashSet<>();
private Map<String, Set<String>> jmodPatch = new LinkedHashMap<>();
private Map<String, Set<String>> jmodOpens = new LinkedHashMap<>();
private Map<String, Set<String>> jmodExports = new LinkedHashMap<>();
private Map<String, Set<String>> jmodReads = new LinkedHashMap<>();
/** JVM arguments, found via command line and in all active [exec] sections from enabled modules */
private List<String> jvmArgs = new ArrayList<>();
@ -173,6 +186,7 @@ public class StartArgs
private boolean listConfig = false;
private boolean version = false;
private boolean dryRun = false;
private boolean jpms = false;
private boolean createStartd = false;
private boolean updateIni = false;
private String mavenBaseUri;
@ -540,6 +554,57 @@ public class StartArgs
}
}
void expandJPMS(List<Module> activeModules)
{
for (Module module : activeModules)
{
for (String line : module.getJPMS())
{
line = properties.expand(line);
String directive;
if (line.startsWith(directive = "add-modules:"))
{
String[] names = line.substring(directive.length()).split(",");
Arrays.stream(names).map(String::trim).collect(Collectors.toCollection(() -> jmodAdds));
}
else if (line.startsWith(directive = "patch-module:"))
{
parseJPMSKeyValue(module, line, directive, File.pathSeparator, jmodPatch);
}
else if (line.startsWith(directive = "add-opens:"))
{
parseJPMSKeyValue(module, line, directive, ",", jmodOpens);
}
else if (line.startsWith(directive = "add-exports:"))
{
parseJPMSKeyValue(module, line, directive, ",", jmodExports);
}
else if (line.startsWith(directive = "add-reads:"))
{
parseJPMSKeyValue(module, line, directive, ",", jmodReads);
}
else
{
throw new IllegalArgumentException("Invalid [jpms] directive " + directive + " in module " + module.getName() + ": " + line);
}
}
}
StartLog.debug("Expanded JPMS directives:%nadd-modules: %s%npatch-modules: %s%nadd-opens: %s%nadd-exports: %s%nadd-reads: %s",
jmodAdds, jmodPatch, jmodOpens, jmodExports, jmodReads);
}
private void parseJPMSKeyValue(Module module, String line, String directive, String delimiter, Map<String, Set<String>> output)
{
String value = line.substring(directive.length());
int equals = value.indexOf('=');
if (equals <= 0)
throw new IllegalArgumentException("Invalid [jpms] directive " + directive + " in module " + module.getName() + ": " + line);
String key = value.substring(0, equals).trim();
List<String> values = Arrays.asList(value.substring(equals + 1).split(delimiter));
values = values.stream().map(String::trim).collect(Collectors.toList());
output.computeIfAbsent(key, k -> new LinkedHashSet<>()).addAll(values);
}
public List<String> getStartModules()
{
return startModules;
@ -611,9 +676,76 @@ public class StartArgs
cmd.addEqualsArg("-D" + propKey,value);
}
cmd.addRawArg("-cp");
cmd.addRawArg(classpath.toString());
cmd.addRawArg(getMainClassname());
if (isJPMS())
{
Map<Boolean, List<File>> dirsAndFiles = StreamSupport.stream(classpath.spliterator(), false)
.collect(Collectors.groupingBy(File::isDirectory));
List<File> files = dirsAndFiles.get(false);
if (!files.isEmpty())
{
cmd.addRawArg("--module-path");
String modules = files.stream()
.map(File::getAbsolutePath)
.collect(Collectors.joining(File.pathSeparator));
cmd.addRawArg(modules);
}
List<File> dirs = dirsAndFiles.get(true);
if (!dirs.isEmpty())
{
cmd.addRawArg("--class-path");
String directories = dirs.stream()
.map(File::getAbsolutePath)
.collect(Collectors.joining(File.pathSeparator));
cmd.addRawArg(directories);
}
if (!jmodAdds.isEmpty())
{
cmd.addRawArg("--add-modules");
cmd.addRawArg(String.join(",", jmodAdds));
}
if (!jmodPatch.isEmpty())
{
for (Map.Entry<String, Set<String>> entry : jmodPatch.entrySet())
{
cmd.addRawArg("--patch-module");
cmd.addRawArg(entry.getKey() + "=" + String.join(File.pathSeparator, entry.getValue()));
}
}
if (!jmodOpens.isEmpty())
{
for (Map.Entry<String, Set<String>> entry : jmodOpens.entrySet())
{
cmd.addRawArg("--add-opens");
cmd.addRawArg(entry.getKey() + "=" + String.join(",", entry.getValue()));
}
}
if (!jmodExports.isEmpty())
{
for (Map.Entry<String, Set<String>> entry : jmodExports.entrySet())
{
cmd.addRawArg("--add-exports");
cmd.addRawArg(entry.getKey() + "=" + String.join(",", entry.getValue()));
}
}
if (!jmodReads.isEmpty())
{
for (Map.Entry<String, Set<String>> entry : jmodReads.entrySet())
{
cmd.addRawArg("--add-reads");
cmd.addRawArg(entry.getKey() + "=" + String.join(",", entry.getValue()));
}
}
cmd.addRawArg("--module");
cmd.addRawArg(getMainClassname());
}
else
{
cmd.addRawArg("-cp");
cmd.addRawArg(classpath.toString());
cmd.addRawArg(getMainClassname());
}
}
@ -656,8 +788,8 @@ public class StartArgs
public String getMainClassname()
{
String mainclass = System.getProperty("jetty.server",SERVER_MAIN);
return System.getProperty("main.class",mainclass);
String mainClass = System.getProperty("jetty.server", isJPMS() ? MODULE_MAIN_CLASS : MAIN_CLASS);
return System.getProperty("main.class", mainClass);
}
public String getMavenLocalRepoDir()
@ -764,6 +896,11 @@ public class StartArgs
return createFiles;
}
public boolean isJPMS()
{
return jpms;
}
public boolean isDryRun()
{
return dryRun;
@ -781,7 +918,7 @@ public class StartArgs
public boolean isNormalMainClass()
{
return SERVER_MAIN.equals(getMainClassname());
return MAIN_CLASS.equals(getMainClassname());
}
public boolean isHelp()
@ -973,6 +1110,14 @@ public class StartArgs
return;
}
if ("--jpms".equals(arg))
{
jpms = true;
// Need to fork because we cannot use JDK 9 Module APIs.
exec = true;
return;
}
if ("--dry-run".equals(arg) || "--exec-print".equals(arg))
{
dryRun = true;

View File

@ -54,4 +54,32 @@ public class ManifestUtils
return Optional.empty();
}
}
/**
* <p>Attempts to return the version of the jar/module for the given class.</p>
* <p>First, retrieves the {@code Implementation-Version} main attribute of the manifest;
* if that is missing, retrieves the JPMS module version (via reflection);
* if that is missing, returns an empty Optional.</p>
*
* @param klass the class of the jar/module to retrieve the version
* @return the jar/module version, or an empty Optional
*/
public static Optional<String> getVersion(Class<?> klass)
{
Optional<String> version = getManifest(klass).map(Manifest::getMainAttributes)
.map(attributes -> attributes.getValue("Implementation-Version"));
if (version.isPresent())
return version;
try
{
Object module = klass.getClass().getMethod("getModule").invoke(klass);
Object descriptor = module.getClass().getMethod("getDescriptor").invoke(module);
return (Optional<String>)descriptor.getClass().getMethod("rawVersion").invoke(descriptor);
}
catch (Throwable x)
{
return Optional.empty();
}
}
}

View File

@ -30,3 +30,6 @@ lib/jetty-webapp-${jetty.version}.jar
##
#jetty.webapp.addSystemClasses+=,org.example.
#jetty.webapp.addServerClasses+=,org.example.
[jpms]
add-modules:java.instrument

View File

@ -159,8 +159,6 @@ public class WebInfConfiguration extends AbstractConfiguration
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
@ -177,15 +175,14 @@ public class WebInfConfiguration extends AbstractConfiguration
* @param context the WebAppContext being deployed
* @throws Exception if unable to apply optional filtering on the container's classpath
*/
public void findAndFilterContainerPaths (final WebAppContext context)
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();
targetPlatform = Integer.parseInt(target.toString());
//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.
@ -199,7 +196,7 @@ public class WebInfConfiguration extends AbstractConfiguration
List<URI> containerUris = new ArrayList<>();
while (loader != null && (loader instanceof URLClassLoader))
while (loader instanceof URLClassLoader)
{
URL[] urls = ((URLClassLoader)loader).getURLs();
if (urls != null)
@ -219,12 +216,13 @@ public class WebInfConfiguration extends AbstractConfiguration
loader = loader.getParent();
}
if (LOG.isDebugEnabled()) LOG.debug("Matching container urls {}", containerUris);
if (LOG.isDebugEnabled())
LOG.debug("Matching container urls {}", containerUris);
containerPathNameMatcher.match(containerUris);
//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)
if (targetPlatform >= 9)
{
tmp = System.getProperty("java.class.path");
if (tmp != null)
@ -236,7 +234,8 @@ public class WebInfConfiguration extends AbstractConfiguration
File f = new File(entry);
cpUris.add(f.toURI());
}
if (LOG.isDebugEnabled()) LOG.debug("Matching java.class.path {}", cpUris);
if (LOG.isDebugEnabled())
LOG.debug("Matching java.class.path {}", cpUris);
containerPathNameMatcher.match(cpUris);
}
}
@ -253,28 +252,33 @@ public class WebInfConfiguration extends AbstractConfiguration
{
List<URI> moduleUris = new ArrayList<>();
String[] entries = tmp.split(File.pathSeparator);
for (String entry:entries)
for (String entry : entries)
{
File dir = new File(entry);
File[] files = dir.listFiles();
if (files != null)
File file = new File(entry);
if (file.isDirectory())
{
for (File f:files)
File[] files = file.listFiles();
if (files != null)
{
moduleUris.add(f.toURI());
for (File f : files)
moduleUris.add(f.toURI());
}
}
else
{
moduleUris.add(file.toURI());
}
}
if (LOG.isDebugEnabled()) LOG.debug("Matching jdk.module.path {}", moduleUris);
if (LOG.isDebugEnabled())
LOG.debug("Matching jdk.module.path {}", moduleUris);
containerPathNameMatcher.match(moduleUris);
}
}
if (LOG.isDebugEnabled()) LOG.debug("Container paths selected:{}", context.getMetaData().getContainerResources());
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

View File

@ -1020,7 +1020,7 @@
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>javax.transaction-api</artifactId>
<version>1.2</version>
<version>1.3</version>
<scope>provided</scope>
</dependency>
<!-- maven deps -->

View File

@ -44,7 +44,6 @@
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>javax.transaction-api</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.tests</groupId>