Author: markt
Date: Wed Oct 18 20:53:55 2017
New Revision: 1812581

URL: http://svn.apache.org/viewvc?rev=1812581&view=rev
Log:
Fix https://bz.apache.org/bugzilla/show_bug.cgi?id=61601
Handle multi-release JARs for packed and unpacked web applications

Modified:
    
tomcat/tc7.0.x/trunk/java/org/apache/catalina/loader/WebappClassLoaderBase.java
    tomcat/tc7.0.x/trunk/java/org/apache/catalina/loader/WebappLoader.java
    tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/compat/Jre7Compat.java
    tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/compat/Jre8Compat.java
    tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/compat/Jre9Compat.java
    tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/compat/JreCompat.java
    tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/scan/FileUrlJar.java
    tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/scan/UrlJar.java
    tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml

Modified: 
tomcat/tc7.0.x/trunk/java/org/apache/catalina/loader/WebappClassLoaderBase.java
URL: 
http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/loader/WebappClassLoaderBase.java?rev=1812581&r1=1812580&r2=1812581&view=diff
==============================================================================
--- 
tomcat/tc7.0.x/trunk/java/org/apache/catalina/loader/WebappClassLoaderBase.java 
(original)
+++ 
tomcat/tc7.0.x/trunk/java/org/apache/catalina/loader/WebappClassLoaderBase.java 
Wed Oct 18 20:53:55 2017
@@ -3006,7 +3006,7 @@ public abstract class WebappClassLoaderB
             if (jarFiles[0] == null) {
                 for (int i = 0; i < jarFiles.length; i++) {
                     try {
-                        jarFiles[i] = new JarFile(jarRealFiles[i]);
+                        jarFiles[i] = 
JreCompat.getInstance().jarFileNewInstance(jarRealFiles[i]);
                     } catch (IOException e) {
                         log.warn(sm.getString("webappClassLoader.jarOpenFail", 
jarFiles[i]), e);
                         closeJARs(true);
@@ -3695,7 +3695,7 @@ public abstract class WebappClassLoaderB
 
         JarFile jarFile = null;
         try {
-            jarFile = new JarFile(file);
+            jarFile = JreCompat.getInstance().jarFileNewInstance(file);
             for (int i = 0; i < triggers.length; i++) {
                 Class<?> clazz = null;
                 try {

Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/loader/WebappLoader.java
URL: 
http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/loader/WebappLoader.java?rev=1812581&r1=1812580&r2=1812581&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/catalina/loader/WebappLoader.java 
(original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/loader/WebappLoader.java Wed 
Oct 18 20:53:55 2017
@@ -59,6 +59,7 @@ import org.apache.naming.resources.DirCo
 import org.apache.naming.resources.DirContextURLStreamHandlerFactory;
 import org.apache.naming.resources.Resource;
 import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.compat.JreCompat;
 import org.apache.tomcat.util.modeler.Registry;
 import org.apache.tomcat.util.res.StringManager;
 
@@ -999,7 +1000,7 @@ public class WebappLoader extends Lifecy
                 }
 
                 try {
-                    JarFile jarFile = new JarFile(destFile);
+                    JarFile jarFile = 
JreCompat.getInstance().jarFileNewInstance(destFile);
                     classLoader.addJar(filename, jarFile, destFile);
                 } catch (Exception ex) {
                     // Catch the exception if there is an empty jar file

Modified: 
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/compat/Jre7Compat.java
URL: 
http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/compat/Jre7Compat.java?rev=1812581&r1=1812580&r2=1812581&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/compat/Jre7Compat.java 
(original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/compat/Jre7Compat.java Wed 
Oct 18 20:53:55 2017
@@ -22,6 +22,8 @@ import java.util.Locale;
 
 class Jre7Compat extends JreCompat {
 
+    private static final int RUNTIME_MAJOR_VERSION = 7;
+
     private static final Method forLanguageTagMethod;
 
 
@@ -55,4 +57,10 @@ class Jre7Compat extends JreCompat {
             return null;
         }
     }
+
+
+    @Override
+    public int jarFileRuntimeMajorVersion() {
+        return RUNTIME_MAJOR_VERSION;
+    }
 }

Modified: 
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/compat/Jre8Compat.java
URL: 
http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/compat/Jre8Compat.java?rev=1812581&r1=1812580&r2=1812581&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/compat/Jre8Compat.java 
(original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/compat/Jre8Compat.java Wed 
Oct 18 20:53:55 2017
@@ -25,6 +25,8 @@ import javax.net.ssl.SSLServerSocket;
 
 class Jre8Compat extends Jre7Compat {
 
+    private static final int RUNTIME_MAJOR_VERSION = 8;
+
     private static final Method getSSLParametersMethod;
     private static final Method setUseCipherSuitesOrderMethod;
     private static final Method setSSLParametersMethod;
@@ -93,4 +95,10 @@ class Jre8Compat extends Jre7Compat {
             throw new UnsupportedOperationException(e);
         }
     }
+
+
+    @Override
+    public int jarFileRuntimeMajorVersion() {
+        return RUNTIME_MAJOR_VERSION;
+    }
 }

Modified: 
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/compat/Jre9Compat.java
URL: 
http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/compat/Jre9Compat.java?rev=1812581&r1=1812580&r2=1812581&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/compat/Jre9Compat.java 
(original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/compat/Jre9Compat.java Wed 
Oct 18 20:53:55 2017
@@ -16,7 +16,9 @@
  */
 package org.apache.tomcat.util.compat;
 
+import java.io.File;
 import java.io.IOException;
+import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.net.MalformedURLException;
@@ -25,6 +27,8 @@ import java.net.URL;
 import java.net.URLConnection;
 import java.util.Deque;
 import java.util.Set;
+import java.util.jar.JarFile;
+import java.util.zip.ZipFile;
 
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
@@ -44,14 +48,13 @@ class Jre9Compat extends Jre8Compat {
     private static final Method locationMethod;
     private static final Method isPresentMethod;
     private static final Method getMethod;
+    private static final Constructor<JarFile> jarFileConstructor;
+    private static final Method isMultiReleaseMethod;
 
-    static {
-        Class<?> moduleLayerClazz = null;
-        Class<?> configurationClazz = null;
-        Class<?> resolvedModuleClazz = null;
-        Class<?> moduleReferenceClazz = null;
-        Class<?> optionalClazz = null;
+    private static final Object RUNTIME_VERSION;
+    private static final int RUNTIME_MAJOR_VERSION;
 
+    static {
         Class<?> c1 = null;
         Method m4 = null;
         Method m5 = null;
@@ -61,13 +64,20 @@ class Jre9Compat extends Jre8Compat {
         Method m9 = null;
         Method m10 = null;
         Method m11 = null;
+        Constructor<JarFile> c12 = null;
+        Method m13 = null;
+        Object o14 = null;
+        Object o15 = null;
 
         try {
-            moduleLayerClazz = Class.forName("java.lang.ModuleLayer");
-            configurationClazz = 
Class.forName("java.lang.module.Configuration");
-            resolvedModuleClazz = 
Class.forName("java.lang.module.ResolvedModule");
-            moduleReferenceClazz = 
Class.forName("java.lang.module.ModuleReference");
-            optionalClazz = Class.forName("java.util.Optional");
+            Class<?> moduleLayerClazz = Class.forName("java.lang.ModuleLayer");
+            Class<?> configurationClazz = 
Class.forName("java.lang.module.Configuration");
+            Class<?> resolvedModuleClazz = 
Class.forName("java.lang.module.ResolvedModule");
+            Class<?> moduleReferenceClazz = 
Class.forName("java.lang.module.ModuleReference");
+            Class<?> optionalClazz = Class.forName("java.util.Optional");
+            Class<?> versionClazz = Class.forName("java.lang.Runtime$Version");
+            Method runtimeVersionMethod = 
JarFile.class.getMethod("runtimeVersion");
+            Method majorMethod = versionClazz.getMethod("major");
 
             c1 = 
Class.forName("java.lang.reflect.InaccessibleObjectException");
             m4 = URLConnection.class.getMethod("setDefaultUseCaches", 
String.class, boolean.class);
@@ -78,13 +88,25 @@ class Jre9Compat extends Jre8Compat {
             m9 = moduleReferenceClazz.getMethod("location");
             m10 = optionalClazz.getMethod("isPresent");
             m11 = optionalClazz.getMethod("get");
+            c12 = JarFile.class.getConstructor(File.class, boolean.class, 
int.class, versionClazz);
+            m13 = JarFile.class.getMethod("isMultiRelease");
+            o14 = runtimeVersionMethod.invoke(null);
+            o15 = majorMethod.invoke(o14);
+
         } catch (SecurityException e) {
             // Should never happen
         } catch (NoSuchMethodException e) {
             // Should never happen
         } catch (ClassNotFoundException e) {
             // Must be Java 8
+        } catch (IllegalArgumentException e) {
+            // Should never happen
+        } catch (IllegalAccessException e) {
+            // Should never happen
+        } catch (InvocationTargetException e) {
+            // Should never happen
         }
+
         inaccessibleObjectExceptionClazz = c1;
         setDefaultUseCachesMethod = m4;
         bootMethod = m5;
@@ -94,6 +116,16 @@ class Jre9Compat extends Jre8Compat {
         locationMethod = m9;
         isPresentMethod = m10;
         getMethod = m11;
+        jarFileConstructor = c12;
+        isMultiReleaseMethod = m13;
+
+        RUNTIME_VERSION = o14;
+        if (o15 != null) {
+            RUNTIME_MAJOR_VERSION = ((Integer) o15).intValue();
+        } else {
+            // Must be Java 8
+            RUNTIME_MAJOR_VERSION = 8;
+        }
     }
 
 
@@ -154,4 +186,41 @@ class Jre9Compat extends Jre8Compat {
             throw new UnsupportedOperationException(e);
         }
     }
+
+
+    @Override
+    public JarFile jarFileNewInstance(File f) throws IOException {
+        try {
+            return jarFileConstructor.newInstance(
+                    f, Boolean.TRUE, Integer.valueOf(ZipFile.OPEN_READ), 
RUNTIME_VERSION);
+        } catch (IllegalArgumentException e) {
+            throw new IOException(e);
+        } catch (InstantiationException e) {
+            throw new IOException(e);
+        } catch (IllegalAccessException e) {
+            throw new IOException(e);
+        } catch (InvocationTargetException e) {
+            throw new IOException(e);
+        }
+    }
+
+
+    @Override
+    public boolean jarFileIsMultiRelease(JarFile jarFile) {
+        try {
+            return ((Boolean) 
isMultiReleaseMethod.invoke(jarFile)).booleanValue();
+        } catch (IllegalArgumentException e) {
+            return false;
+        } catch (IllegalAccessException e) {
+            return false;
+        } catch (InvocationTargetException e) {
+            return false;
+        }
+    }
+
+
+    @Override
+    public int jarFileRuntimeMajorVersion() {
+        return RUNTIME_MAJOR_VERSION;
+    }
 }

Modified: tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/compat/JreCompat.java
URL: 
http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/compat/JreCompat.java?rev=1812581&r1=1812580&r2=1812581&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/compat/JreCompat.java 
(original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/compat/JreCompat.java Wed 
Oct 18 20:53:55 2017
@@ -16,11 +16,13 @@
  */
 package org.apache.tomcat.util.compat;
 
+import java.io.File;
 import java.io.IOException;
 import java.net.URL;
 import java.net.URLConnection;
 import java.util.Deque;
 import java.util.Locale;
+import java.util.jar.JarFile;
 
 import javax.net.ssl.SSLEngine;
 import javax.net.ssl.SSLServerSocket;
@@ -34,6 +36,8 @@ import org.apache.tomcat.util.res.String
  */
 public class JreCompat {
 
+    private static final int RUNTIME_MAJOR_VERSION = 6;
+
     private static final JreCompat instance;
     private static StringManager sm =
             StringManager.getManager(JreCompat.class.getPackage().getName());
@@ -190,7 +194,40 @@ public class JreCompat {
      *                                  added
      */
     public void addBootModulePath(Deque<URL> classPathUrlsToProcess) {
-        // NO-OP for Java 8. There is no module path.
+        // NO-OP. There is no module path prior to Java 9.
+    }
+
+
+    /**
+     * Creates a new JarFile instance. When running on Java 9 and later, the
+     * JarFile will be multi-release JAR aware.
+     *
+     * @param f The JAR file to open
+     *
+     * @return A JarFile instance based on the provided file
+     *
+     * @throws IOException  If an I/O error occurs creating the JarFile 
instance
+     */
+    public JarFile jarFileNewInstance(File f) throws IOException {
+        return new JarFile(f);
     }
 
+
+    /**
+     * Is this JarFile a multi-release JAR file.
+     *
+     * @param jarFile   The JarFile to test
+     *
+     * @return {@code true} If it is a multi-release JAR file and is configured
+     *         to behave as such.
+     */
+    public boolean jarFileIsMultiRelease(JarFile jarFile) {
+        // There is no multi-release JAR support prior to Java 9
+        return false;
+    }
+
+
+    public int jarFileRuntimeMajorVersion() {
+        return RUNTIME_MAJOR_VERSION;
+    }
 }

Modified: tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/scan/FileUrlJar.java
URL: 
http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/scan/FileUrlJar.java?rev=1812581&r1=1812580&r2=1812581&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/scan/FileUrlJar.java 
(original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/scan/FileUrlJar.java Wed 
Oct 18 20:53:55 2017
@@ -21,10 +21,14 @@ import java.io.InputStream;
 import java.net.JarURLConnection;
 import java.net.URL;
 import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
 import java.util.zip.ZipEntry;
 
+import org.apache.tomcat.util.compat.JreCompat;
+
 /**
  * Implementation of {@link Jar} that is optimised for file based JAR URLs (e.g
  * URLs of the form jar:file:...).
@@ -32,13 +36,17 @@ import java.util.zip.ZipEntry;
 public class FileUrlJar implements Jar {
 
     private JarFile jarFile;
+    private final boolean multiRelease;
     private Enumeration<JarEntry> entries;
+    private Set<String> entryNamesSeen;
     private JarEntry entry = null;
 
     public FileUrlJar(URL url) throws IOException {
         JarURLConnection jarConn = (JarURLConnection) url.openConnection();
         jarConn.setUseCaches(false);
+        // JarFile returned will be multi-release aware if the OS supports it.
         jarFile = jarConn.getJarFile();
+        multiRelease = JreCompat.getInstance().jarFileIsMultiRelease(jarFile);
     }
 
     @Override
@@ -49,6 +57,7 @@ public class FileUrlJar implements Jar {
 
     @Override
     public InputStream getInputStream(String name) throws IOException {
+        // JarFile#getEntry() is multi-release aware
         ZipEntry entry = jarFile.getEntry(name);
         if (entry == null) {
             return null;
@@ -70,13 +79,58 @@ public class FileUrlJar implements Jar {
 
     @Override
     public void nextEntry() {
+        // JarFile#entries() is NOT multi-release aware
         if (entries == null) {
             entries = jarFile.entries();
+            if (multiRelease) {
+                entryNamesSeen = new HashSet<String>();
+            }
         }
-        if (entries.hasMoreElements()) {
-            entry = entries.nextElement();
+        if (multiRelease) {
+            // Need to ensure that:
+            // - the one, correct entry is returned where multiple versions
+            //   are available
+            // - that the order of entries in the JAR doesn't prevent the
+            //   correct entries being returned
+            // - the case where an entry appears in the versions location
+            //   but not in the the base location is handled correctly
+
+            // Enumerate the entries until one is reached that represents an
+            // entry that has not been seen before.
+            String name = null;
+            while (true) {
+                if (entries.hasMoreElements()) {
+                    entry = entries.nextElement();
+                    name = entry.getName();
+                    // Get 'base' name
+                    if (name.startsWith("META-INF/versions/")) {
+                        int i = name.indexOf('/', 18);
+                        if (i == -1) {
+                            continue;
+                        }
+                        name = name.substring(i + 1);
+                    }
+                    if (name.length() == 0 || entryNamesSeen.contains(name)) {
+                        continue;
+                    }
+
+                    entryNamesSeen.add(name);
+
+                    // JarFile.getJarEntry is version aware so use it
+                    entry = jarFile.getJarEntry(entry.getName());
+                    break;
+                } else {
+                    entry = null;
+                    break;
+                }
+            }
+
         } else {
-            entry = null;
+            if (entries.hasMoreElements()) {
+                entry = entries.nextElement();
+            } else {
+                entry = null;
+            }
         }
     }
 
@@ -101,6 +155,7 @@ public class FileUrlJar implements Jar {
     @Override
     public void reset() throws IOException {
         entries = null;
+        entryNamesSeen = null;
         entry = null;
     }
 }

Modified: tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/scan/UrlJar.java
URL: 
http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/scan/UrlJar.java?rev=1812581&r1=1812580&r2=1812581&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/scan/UrlJar.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/scan/UrlJar.java Wed Oct 
18 20:53:55 2017
@@ -21,7 +21,13 @@ import java.io.InputStream;
 import java.net.JarURLConnection;
 import java.net.URL;
 import java.net.URLConnection;
+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.util.compat.JreCompat;
 
 /**
  * Implementation of {@link Jar} that is optimised for non-file based JAR URLs
@@ -32,10 +38,23 @@ public class UrlJar implements Jar {
     private NonClosingJarInputStream jarInputStream = null;
     private URL url = null;
     private JarEntry entry = null;
+    private Map<String,String> mrMap = null;
 
     public UrlJar(URL url) throws IOException {
         this.url = url;
         this.jarInputStream = createJarInputStream();
+
+        boolean multiRelease = false;
+        if (JreCompat.isJre9Available()) {
+            Manifest manifest = jarInputStream.getManifest();
+            String mrValue = 
manifest.getMainAttributes().getValue("Multi-Release");
+            if (mrValue != null) {
+                multiRelease = Boolean.valueOf(mrValue).booleanValue();
+            }
+        }
+        if (multiRelease) {
+            populateMrMap();
+        }
     }
 
     @Override
@@ -84,7 +103,22 @@ public class UrlJar implements Jar {
     public void nextEntry() {
         try {
             entry = jarInputStream.getNextJarEntry();
-        } catch (IOException ioe) {
+            if (mrMap != null) {
+                // 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;
         }
     }
@@ -106,6 +140,60 @@ public class UrlJar implements Jar {
     @Override
     public void reset() throws IOException {
         close();
+        entry = null;
+        jarInputStream = createJarInputStream();
+    }
+
+
+    private void populateMrMap() throws IOException {
+        int targetVersion = 
JreCompat.getInstance().jarFileRuntimeMajorVersion();
+
+        Map<String,Integer> mrVersions = new HashMap<String,Integer>();
+
+        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<String,String>();
+
+        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
+        close();
         jarInputStream = createJarInputStream();
     }
 }

Modified: tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml
URL: 
http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml?rev=1812581&r1=1812580&r2=1812581&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml Wed Oct 18 20:53:55 2017
@@ -80,6 +80,10 @@
         Fix the JMX descriptor for <code>Wrapper.findInitParameter()</code>.
         (rjung)
       </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="Jasper">



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to