You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2017/10/16 19:29:38 UTC
svn commit: r1812315 - in /tomcat/trunk:
java/org/apache/catalina/webresources/ java/org/apache/tomcat/util/compat/
java/org/apache/tomcat/util/scan/ webapps/docs/
Author: markt
Date: Mon Oct 16 19:29:37 2017
New Revision: 1812315
URL: http://svn.apache.org/viewvc?rev=1812315&view=rev
Log:
Complete the fix for https://bz.apache.org/bugzilla/show_bug.cgi?id=61601
Handle multi-release JARs for packed web applications
Modified:
tomcat/trunk/java/org/apache/catalina/webresources/JarWarResourceSet.java
tomcat/trunk/java/org/apache/tomcat/util/compat/Jre9Compat.java
tomcat/trunk/java/org/apache/tomcat/util/compat/JreCompat.java
tomcat/trunk/java/org/apache/tomcat/util/scan/AbstractInputStreamJar.java
tomcat/trunk/webapps/docs/changelog.xml
Modified: tomcat/trunk/java/org/apache/catalina/webresources/JarWarResourceSet.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/webresources/JarWarResourceSet.java?rev=1812315&r1=1812314&r2=1812315&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/webresources/JarWarResourceSet.java (original)
+++ tomcat/trunk/java/org/apache/catalina/webresources/JarWarResourceSet.java Mon Oct 16 19:29:37 2017
@@ -21,7 +21,9 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
@@ -31,6 +33,7 @@ import org.apache.catalina.LifecycleExce
import org.apache.catalina.WebResource;
import org.apache.catalina.WebResourceRoot;
import org.apache.tomcat.util.buf.UriUtil;
+import org.apache.tomcat.util.compat.JreCompat;
/**
* Represents a {@link org.apache.catalina.WebResourceSet} based on a JAR file
@@ -101,6 +104,7 @@ public class JarWarResourceSet extends A
JarFile warFile = null;
InputStream jarFileIs = null;
archiveEntries = new HashMap<>();
+ boolean multiRelease = false;
try {
warFile = openJarFile();
JarEntry jarFileInWar = warFile.getJarEntry(archivePath);
@@ -112,7 +116,14 @@ public class JarWarResourceSet extends A
archiveEntries.put(entry.getName(), entry);
entry = jarIs.getNextJarEntry();
}
- setManifest(jarIs.getManifest());
+ Manifest m = jarIs.getManifest();
+ setManifest(m);
+ if (m != null && JreCompat.isJre9Available()) {
+ String value = m.getMainAttributes().getValue("Multi-Release");
+ if (value != null) {
+ multiRelease = Boolean.parseBoolean(value);
+ }
+ }
// Hack to work-around JarInputStream swallowing these
// entries. TomcatJarInputStream is used above which
// extends JarInputStream and the method that creates
@@ -128,6 +139,9 @@ public class JarWarResourceSet extends A
archiveEntries.put(entry.getName(), entry);
}
}
+ if (multiRelease) {
+ processArchivesEntriesForMultiRelease();
+ }
} catch (IOException ioe) {
// Should never happen
archiveEntries = null;
@@ -150,6 +164,56 @@ public class JarWarResourceSet extends A
}
+ protected void processArchivesEntriesForMultiRelease() {
+
+ int targetVersion = JreCompat.getInstance().jarFileRuntimeMajorVersion();
+
+ Map<String,VersionedJarEntry> versionedEntries = new HashMap<>();
+ Iterator<Entry<String,JarEntry>> iter = archiveEntries.entrySet().iterator();
+ while (iter.hasNext()) {
+ Entry<String,JarEntry> entry = iter.next();
+ String name = entry.getKey();
+ if (name.startsWith("META-INF/versions/")) {
+ // Remove the multi-release version
+ iter.remove();
+
+ // Get the base name and version for this versioned entry
+ int i = name.indexOf('/', 18);
+ if (i > 0) {
+ String baseName = name.substring(i + 1);
+ int version = Integer.parseInt(name.substring(18, i));
+
+ // Ignore any entries targeting for a later version than
+ // the target for this runtime
+ if (version <= targetVersion) {
+ VersionedJarEntry versionedJarEntry = versionedEntries.get(baseName);
+ if (versionedJarEntry == null) {
+ // No versioned entry found for this name. Create
+ // one.
+ versionedEntries.put(baseName,
+ new VersionedJarEntry(version, entry.getValue()));
+ } else {
+ // Ignore any entry for which we have already found
+ // a later version
+ if (version > versionedJarEntry.getVersion()) {
+ // Replace the entry targeted at an earlier
+ // version
+ versionedEntries.put(baseName,
+ new VersionedJarEntry(version, entry.getValue()));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ for (Entry<String,VersionedJarEntry> versionedJarEntry : versionedEntries.entrySet()) {
+ archiveEntries.put(versionedJarEntry.getKey(),
+ versionedJarEntry.getValue().getJarEntry());
+ }
+ }
+
+
/**
* {@inheritDoc}
* <p>
@@ -164,7 +228,8 @@ public class JarWarResourceSet extends A
@Override
protected boolean isMultiRelease() {
- // TODO: multi-release support for packed WAR files
+ // This always returns false otherwise the superclass will call
+ // #getArchiveEntry(String)
return false;
}
@@ -190,4 +255,25 @@ public class JarWarResourceSet extends A
throw new IllegalArgumentException(e);
}
}
+
+
+ private static final class VersionedJarEntry {
+ private final int version;
+ private final JarEntry jarEntry;
+
+ public VersionedJarEntry(int version, JarEntry jarEntry) {
+ this.version = version;
+ this.jarEntry = jarEntry;
+ }
+
+
+ public int getVersion() {
+ return version;
+ }
+
+
+ public JarEntry getJarEntry() {
+ return jarEntry;
+ }
+ }
}
Modified: tomcat/trunk/java/org/apache/tomcat/util/compat/Jre9Compat.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/compat/Jre9Compat.java?rev=1812315&r1=1812314&r2=1812315&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/util/compat/Jre9Compat.java (original)
+++ tomcat/trunk/java/org/apache/tomcat/util/compat/Jre9Compat.java Mon Oct 16 19:29:37 2017
@@ -57,6 +57,7 @@ class Jre9Compat extends JreCompat {
private static final Method isMultiReleaseMethod;
private static final Object RUNTIME_VERSION;
+ private static final int RUNTIME_MAJOR_VERSION;
static {
Class<?> c1 = null;
@@ -73,6 +74,7 @@ class Jre9Compat extends JreCompat {
Constructor<JarFile> c12 = null;
Method m13 = null;
Object o14 = null;
+ Object o15 = null;
try {
Class<?> moduleLayerClazz = Class.forName("java.lang.ModuleLayer");
@@ -81,7 +83,8 @@ class Jre9Compat extends JreCompat {
Class<?> moduleReferenceClazz = Class.forName("java.lang.module.ModuleReference");
Class<?> optionalClazz = Class.forName("java.util.Optional");
Class<?> versionClazz = Class.forName("java.lang.Runtime$Version");
- Method versionMethod = JarFile.class.getMethod("runtimeVersion");
+ Method runtimeVersionMethod = JarFile.class.getMethod("runtimeVersion");
+ Method majorMethod = versionClazz.getMethod("major");
c1 = Class.forName("java.lang.reflect.InaccessibleObjectException");
m2 = SSLParameters.class.getMethod("setApplicationProtocols", String[].class);
@@ -96,7 +99,9 @@ class Jre9Compat extends JreCompat {
m11 = optionalClazz.getMethod("get");
c12 = JarFile.class.getConstructor(File.class, boolean.class, int.class, versionClazz);
m13 = JarFile.class.getMethod("isMultiRelease");
- o14 = versionMethod.invoke(null);
+ o14 = runtimeVersionMethod.invoke(null);
+ o15 = majorMethod.invoke(o14);
+
} catch (ClassNotFoundException e) {
// Must be Java 8
} catch (ReflectiveOperationException | IllegalArgumentException e) {
@@ -118,6 +123,12 @@ class Jre9Compat extends JreCompat {
isMultiReleaseMethod = m13;
RUNTIME_VERSION = o14;
+ if (o15 != null) {
+ RUNTIME_MAJOR_VERSION = ((Integer) o15).intValue();
+ } else {
+ // Must be Java 8
+ RUNTIME_MAJOR_VERSION = 8;
+ }
}
@@ -211,4 +222,10 @@ class Jre9Compat extends JreCompat {
return false;
}
}
+
+
+ @Override
+ public int jarFileRuntimeMajorVersion() {
+ return RUNTIME_MAJOR_VERSION;
+ }
}
Modified: tomcat/trunk/java/org/apache/tomcat/util/compat/JreCompat.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/compat/JreCompat.java?rev=1812315&r1=1812314&r2=1812315&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/util/compat/JreCompat.java (original)
+++ tomcat/trunk/java/org/apache/tomcat/util/compat/JreCompat.java Mon Oct 16 19:29:37 2017
@@ -35,6 +35,8 @@ import org.apache.tomcat.util.res.String
*/
public class JreCompat {
+ private static final int RUNTIME_MAJOR_VERSION = 8;
+
private static final JreCompat instance;
private static final boolean jre9Available;
private static final StringManager sm = StringManager.getManager(JreCompat.class);
@@ -175,4 +177,9 @@ public class JreCompat {
// Java 8 doesn't support multi-release so default to false
return false;
}
+
+
+ public int jarFileRuntimeMajorVersion() {
+ return RUNTIME_MAJOR_VERSION;
+ }
}
Modified: tomcat/trunk/java/org/apache/tomcat/util/scan/AbstractInputStreamJar.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/scan/AbstractInputStreamJar.java?rev=1812315&r1=1812314&r2=1812315&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/util/scan/AbstractInputStreamJar.java (original)
+++ tomcat/trunk/java/org/apache/tomcat/util/scan/AbstractInputStreamJar.java Mon Oct 16 19:29:37 2017
@@ -19,10 +19,14 @@ package org.apache.tomcat.util.scan;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
import java.util.jar.JarEntry;
import java.util.jar.Manifest;
import org.apache.tomcat.Jar;
+import org.apache.tomcat.util.compat.JreCompat;
/**
* Base implementation of Jar for implementations that use a JarInputStream to
@@ -34,6 +38,8 @@ public abstract class AbstractInputStrea
private NonClosingJarInputStream jarInputStream = null;
private JarEntry entry = null;
+ private Boolean multiRelease = null;
+ private Map<String,String> mrMap = null;
public AbstractInputStreamJar(URL jarFileUrl) {
this.jarFileURL = jarFileUrl;
@@ -50,7 +56,7 @@ public abstract class AbstractInputStrea
public void nextEntry() {
if (jarInputStream == null) {
try {
- jarInputStream = createJarInputStream();
+ reset();
} catch (IOException e) {
entry = null;
return;
@@ -58,6 +64,21 @@ public abstract class AbstractInputStrea
}
try {
entry = jarInputStream.getNextJarEntry();
+ if (multiRelease.booleanValue()) {
+ // Skip base entries where there is a multi-release entry
+ // Skip multi-release entries that are not being used
+ while (entry != null &&
+ (mrMap.keySet().contains(entry.getName()) ||
+ entry.getName().startsWith("META-INF/versions/") &&
+ !mrMap.values().contains(entry.getName()))) {
+ entry = jarInputStream.getNextJarEntry();
+ }
+ } else {
+ // Skip multi-release entries
+ while (entry != null && entry.getName().startsWith("META-INF/versions/")) {
+ entry = jarInputStream.getNextJarEntry();
+ }
+ }
} catch (IOException ioe) {
entry = null;
}
@@ -66,6 +87,8 @@ public abstract class AbstractInputStrea
@Override
public String getEntryName() {
+ // Given how the entry name is used, there is no requirement to convert
+ // the name for a multi-release entry to the corresponding base name.
if (entry == null) {
return null;
} else {
@@ -129,6 +152,25 @@ public abstract class AbstractInputStrea
closeStream();
entry = null;
jarInputStream = createJarInputStream();
+ // Only perform multi-release processing on first access
+ if (multiRelease == null) {
+ if (JreCompat.isJre9Available()) {
+ Manifest manifest = jarInputStream.getManifest();
+ String mrValue = manifest.getMainAttributes().getValue("Multi-Release");
+ if (mrValue == null) {
+ multiRelease = Boolean.FALSE;
+ } else {
+ multiRelease = Boolean.valueOf(mrValue);
+ }
+ } else {
+ multiRelease = Boolean.FALSE;
+ }
+ if (multiRelease.booleanValue()) {
+ if (mrMap == null) {
+ populateMrMap();
+ }
+ }
+ }
}
@@ -147,10 +189,25 @@ public abstract class AbstractInputStrea
private void gotoEntry(String name) throws IOException {
+ if (multiRelease == null) {
+ reset();
+ }
+
+ // Need to convert requested name to multi-release name (if one exists)
+ if (multiRelease.booleanValue()) {
+ String mrName = mrMap.get(name);
+ if (mrName != null) {
+ name = mrName;
+ }
+ } else if (name.startsWith("META-INF/versions/")) {
+ entry = null;
+ return;
+ }
+
if (entry != null && name.equals(entry.getName())) {
return;
}
- reset();
+
JarEntry jarEntry = jarInputStream.getNextJarEntry();
while (jarEntry != null) {
if (name.equals(jarEntry.getName())) {
@@ -160,4 +217,57 @@ public abstract class AbstractInputStrea
jarEntry = jarInputStream.getNextJarEntry();
}
}
+
+
+ private void populateMrMap() throws IOException {
+ int targetVersion = JreCompat.getInstance().jarFileRuntimeMajorVersion();
+
+ Map<String,Integer> mrVersions = new HashMap<>();
+
+ JarEntry jarEntry = jarInputStream.getNextJarEntry();
+
+ // Tracking the base name and the latest valid version found is
+ // sufficient to be able to create the renaming map required
+ while (jarEntry != null) {
+ String name = jarEntry.getName();
+ if (name.startsWith("META-INF/versions/") && name.endsWith(".class")) {
+
+ // Get the base name and version for this versioned entry
+ int i = name.indexOf('/', 18);
+ if (i > 0) {
+ String baseName = name.substring(i + 1);
+ int version = Integer.parseInt(name.substring(18, i));
+
+ // Ignore any entries targeting for a later version than
+ // the target for this runtime
+ if (version <= targetVersion) {
+ Integer mappedVersion = mrVersions.get(baseName);
+ if (mappedVersion == null) {
+ // No version found for this name. Create one.
+ mrVersions.put(baseName, Integer.valueOf(version));
+ } else {
+ // Ignore any entry for which we have already found
+ // a later version
+ if (version > mappedVersion.intValue()) {
+ // Replace the earlier version
+ mrVersions.put(baseName, Integer.valueOf(version));
+ }
+ }
+ }
+ }
+ }
+ jarEntry = jarInputStream.getNextJarEntry();
+ }
+
+ mrMap = new HashMap<>();
+
+ for (Entry<String,Integer> mrVersion : mrVersions.entrySet()) {
+ mrMap.put(mrVersion.getKey() , "META-INF/versions/" + mrVersion.getValue().toString() + ""
+ + "/" + mrVersion.getKey());
+ }
+
+ // Reset stream back to the beginning of the JAR
+ closeStream();
+ jarInputStream = createJarInputStream();
+ }
}
Modified: tomcat/trunk/webapps/docs/changelog.xml
URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1812315&r1=1812314&r2=1812315&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/trunk/webapps/docs/changelog.xml Mon Oct 16 19:29:37 2017
@@ -56,6 +56,10 @@
JARs on the module path when running on Java 9 and class path scanning
is enabled. (markt)
</fix>
+ <fix>
+ <bug>61601</bug>: Add support for multi-release JARs in JAR scanning and
+ web application class loading. (markt)
+ </fix>
</changelog>
</subsection>
<subsection name="Coyote">
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org