Issue #1797
Converted jetty-util to be a multi release jar Added org/eclipse/jetty/util/MultiReleaseJarFile as botha java8 and java9 version deprecated jarScanner updated AnnotationParser to use MultiReleaseJarFile
This commit is contained in:
parent
e00182323b
commit
5de895af88
|
@ -29,11 +29,13 @@ import java.util.List;
|
|||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarInputStream;
|
||||
|
||||
import org.eclipse.jetty.util.ConcurrentHashSet;
|
||||
import org.eclipse.jetty.util.Loader;
|
||||
import org.eclipse.jetty.util.MultiException;
|
||||
import org.eclipse.jetty.util.MultiReleaseJarFile;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
|
@ -764,36 +766,10 @@ public class AnnotationParser
|
|||
* @param resolver the class name resolver
|
||||
* @throws Exception if unable to parse
|
||||
*/
|
||||
public void parse (final Set<? extends Handler> handlers, ClassLoader loader, boolean visitParents, boolean nullInclusive, final ClassNameResolver resolver)
|
||||
throws Exception
|
||||
@Deprecated
|
||||
public void parse (final Set<? extends Handler> handlers, ClassLoader loader, boolean visitParents, boolean nullInclusive) throws Exception
|
||||
{
|
||||
if (loader==null)
|
||||
return;
|
||||
|
||||
if (!(loader instanceof URLClassLoader))
|
||||
return; //can't extract classes?
|
||||
|
||||
final MultiException me = new MultiException();
|
||||
|
||||
JarScanner scanner = new JarScanner()
|
||||
{
|
||||
@Override
|
||||
public void processEntry(URI jarUri, JarEntry entry)
|
||||
{
|
||||
try
|
||||
{
|
||||
parseJarEntry(handlers, Resource.newResource(jarUri), entry, resolver);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
me.add(new RuntimeException("Error parsing entry "+entry.getName()+" from jar "+ jarUri, e));
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
scanner.scan(null, loader, nullInclusive, visitParents);
|
||||
me.ifExceptionThrow();
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
|
@ -905,33 +881,20 @@ public class AnnotationParser
|
|||
{
|
||||
if (LOG.isDebugEnabled()) {LOG.debug("Scanning jar {}", jarResource);};
|
||||
|
||||
//treat it as a jar that we need to open and scan all entries from
|
||||
InputStream in = jarResource.getInputStream();
|
||||
if (in==null)
|
||||
return;
|
||||
|
||||
MultiException me = new MultiException();
|
||||
JarInputStream jar_in = new JarInputStream(in);
|
||||
try
|
||||
{
|
||||
JarEntry entry = jar_in.getNextJarEntry();
|
||||
while (entry!=null)
|
||||
JarFile jarFile = MultiReleaseJarFile.open(jarResource.getFile());
|
||||
MultiReleaseJarFile.stream(jarFile).forEach(e->
|
||||
{
|
||||
try
|
||||
{
|
||||
parseJarEntry(handlers, jarResource, entry, resolver);
|
||||
parseJarEntry(handlers, jarResource, jarFile, e,resolver);
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Exception ex)
|
||||
{
|
||||
me.add(new RuntimeException("Error scanning entry "+entry.getName()+" from jar "+jarResource, e));
|
||||
}
|
||||
entry = jar_in.getNextJarEntry();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
me.add(new RuntimeException("Error scanning jar "+jarResource, e));
|
||||
me.add(new RuntimeException("Error scanning entry " + e.getName() + " from jar " + jarResource, ex));
|
||||
}
|
||||
});
|
||||
|
||||
finally
|
||||
{
|
||||
jar_in.close();
|
||||
|
@ -945,15 +908,14 @@ public class AnnotationParser
|
|||
* Parse a single entry in a jar file
|
||||
*
|
||||
* @param handlers the handlers to look for classes in
|
||||
* @param jar the jar resource to parse
|
||||
* @param jarFile the jar resource being parses
|
||||
* @param entry the entry in the jar resource to parse
|
||||
* @param resolver the class name resolver
|
||||
* @throws Exception if unable to parse
|
||||
*/
|
||||
protected void parseJarEntry (Set<? extends Handler> handlers, Resource jar, JarEntry entry, final ClassNameResolver resolver)
|
||||
throws Exception
|
||||
protected void parseJarEntry (Set<? extends Handler> handlers, Resource jar, JarFile jarFile, JarEntry entry,final ClassNameResolver resolver) throws Exception
|
||||
{
|
||||
if (jar == null || entry == null)
|
||||
if (jarFile == null || entry == null)
|
||||
return;
|
||||
|
||||
//skip directories
|
||||
|
@ -973,7 +935,7 @@ public class AnnotationParser
|
|||
{
|
||||
Resource clazz = Resource.newResource("jar:"+jar.getURI()+"!/"+name);
|
||||
if (LOG.isDebugEnabled()) {LOG.debug("Scanning class from jar {}", clazz);};
|
||||
try (InputStream is = clazz.getInputStream())
|
||||
try (InputStream is = jarFile.getInputStream(entry))
|
||||
{
|
||||
scanClass(handlers, jar, is);
|
||||
}
|
||||
|
@ -1055,57 +1017,6 @@ public class AnnotationParser
|
|||
if (path == null || path.length()==0)
|
||||
return false;
|
||||
|
||||
if (path.startsWith("META-INF/versions/"))
|
||||
{
|
||||
// Handle JEP 238 - Multi-Release Jars
|
||||
if (JVM_MAJOR_VER < 9)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("JEP-238 Multi-Release JAR not supported on Java " +
|
||||
System.getProperty("java.version") + ": " + path);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Safety check for ASM bytecode support level.
|
||||
// When ASM 6.0 is integrated, the below will start to work.
|
||||
if (ASM_OPCODE_VERSION <= Opcodes.ASM5)
|
||||
{
|
||||
// Cannot scan Java 9 classes with ASM version 5
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("Unable to scan newer Java bytecode (Java 9?) with ASM 5 (skipping): " + path);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int idxStart = "META-INF/versions/".length();
|
||||
int idxEnd = path.indexOf('/', idxStart + 1);
|
||||
try
|
||||
{
|
||||
int pathVersion = Integer.parseInt(path.substring(idxStart, idxEnd));
|
||||
if (pathVersion < JVM_MAJOR_VER)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("JEP-238 Multi-Release JAR version " + pathVersion +
|
||||
" not supported on Java " + System.getProperty("java.version") +
|
||||
": " + path);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("Not a valid JEP-238 Multi-Release path: " + path);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// skip any classfiles that are in a hidden directory
|
||||
if (path.startsWith(".") || path.contains("/."))
|
||||
{
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
<url>http://www.eclipse.org/jetty</url>
|
||||
<properties>
|
||||
<bundle-symbolic-name>${project.groupId}.util</bundle-symbolic-name>
|
||||
<java9.sourceDirectory>${project.basedir}/src/main/java9</java9.sourceDirectory>
|
||||
<java9.build.outputDirectory>${project.build.directory}/classes-java9</java9.build.outputDirectory>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
|
@ -57,4 +59,69 @@
|
|||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>jdk9 MR Jar</id>
|
||||
<activation>
|
||||
<jdk>[1.9,)</jdk>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>compile-java9</id>
|
||||
<phase>compile</phase>
|
||||
<configuration>
|
||||
<tasks>
|
||||
<mkdir dir="${java9.build.outputDirectory}" />
|
||||
<javac target="1.9" srcdir="${java9.sourceDirectory}" destdir="${java9.build.outputDirectory}"
|
||||
classpath="${project.build.directory}/classes" includeantruntime="false" />
|
||||
</tasks>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-resources</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.outputDirectory}/META-INF/versions/9</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>${java9.build.outputDirectory}</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<Multi-Release>true</Multi-Release>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* <p>Utility class to create a stream of Multi Release {@link JarEntry}s</p>
|
||||
* <p>This is the java 8 version of this class.
|
||||
* A java 9 version of this class is included as a Multi Release class in the
|
||||
* jetty-util jar, that uses java 9 APIs to correctly handle Multi Release jars.</p>
|
||||
*/
|
||||
public class MultiReleaseJarFile
|
||||
{
|
||||
private static final String META_INF_VERSIONS = "META-INF/versions/";
|
||||
|
||||
public static JarFile open(File file) throws IOException
|
||||
{
|
||||
return new JarFile(file);
|
||||
}
|
||||
|
||||
public static Stream<VersionedJarEntry> streamVersioned(JarFile jf)
|
||||
{
|
||||
return jf.stream()
|
||||
.map(VersionedJarEntry::new);
|
||||
}
|
||||
|
||||
public static Stream<JarEntry> stream(JarFile jf)
|
||||
{
|
||||
// Java 8 version of this class, ignores all versioned entries.
|
||||
return streamVersioned(jf)
|
||||
.filter(e->!e.isVersioned())
|
||||
.map(e->e.resolve(jf));
|
||||
}
|
||||
|
||||
public static class VersionedJarEntry
|
||||
{
|
||||
final JarEntry entry;
|
||||
final String name;
|
||||
final int version;
|
||||
|
||||
VersionedJarEntry(JarEntry entry)
|
||||
{
|
||||
int v = 0;
|
||||
String name = entry.getName();
|
||||
if (name.startsWith(META_INF_VERSIONS))
|
||||
{
|
||||
v=-1;
|
||||
int index = name.indexOf('/', META_INF_VERSIONS.length());
|
||||
if (index >= 0 && index < name.length())
|
||||
{
|
||||
try
|
||||
{
|
||||
v = Integer.parseInt(name.substring(META_INF_VERSIONS.length(), index), 10);
|
||||
name = name.substring(index + 1);
|
||||
}
|
||||
catch (NumberFormatException x)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.entry = entry;
|
||||
this.name = name;
|
||||
this.version = v;
|
||||
}
|
||||
|
||||
public int version()
|
||||
{
|
||||
return version;
|
||||
}
|
||||
|
||||
public boolean isVersioned()
|
||||
{
|
||||
return version > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return entry.toString() + (version==0?"[base]":("["+version+"]"));
|
||||
}
|
||||
|
||||
public JarEntry resolve(JarFile jf)
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* <p>Utility class to create a stream of Multi Release {@link JarEntry}s</p>
|
||||
* <p>This is the java 9 version of this class.
|
||||
* A java 8 version of this class is included as the base version in a Multi Release
|
||||
* jetty-util jar, that uses does not use java 9 APIs.</p>
|
||||
*/
|
||||
public class MultiReleaseJarFile
|
||||
{
|
||||
private static final String META_INF_VERSIONS = "META-INF/versions/";
|
||||
|
||||
public static JarFile open(File file) throws IOException
|
||||
{
|
||||
return new JarFile(file,true,JarFile.OPEN_READ,Runtime.version());
|
||||
}
|
||||
|
||||
public static Stream<VersionedJarEntry> streamVersioned(JarFile jf)
|
||||
{
|
||||
return jf.stream()
|
||||
.map(VersionedJarEntry::new);
|
||||
}
|
||||
|
||||
public static Stream<JarEntry> stream(JarFile jf)
|
||||
{
|
||||
if (!jf.isMultiRelease())
|
||||
return jf.stream();
|
||||
|
||||
// Map to hold unversioned name to VersionedJarEntry
|
||||
Map<String,VersionedJarEntry> entries = new TreeMap<>();
|
||||
|
||||
// Fill the map, removing non applicable entries and replacing versions with later versions.
|
||||
streamVersioned(jf)
|
||||
.filter(e->e.isApplicable(jf.getVersion().major()))
|
||||
.forEach(e->entries.compute(e.name, (k, v) -> v==null || v.isReplacedBy(e) ? e : v));
|
||||
|
||||
// filter the values to remove non applicable inner classes and map to versioned entry
|
||||
return entries.values().stream()
|
||||
.filter(e-> {
|
||||
if (!e.inner)
|
||||
return true;
|
||||
VersionedJarEntry outer = entries.get(e.outer);
|
||||
return outer != null && outer.version == e.version;
|
||||
})
|
||||
.map(e->e.resolve(jf));
|
||||
}
|
||||
|
||||
public static class VersionedJarEntry
|
||||
{
|
||||
final JarEntry entry;
|
||||
final String name;
|
||||
final int version;
|
||||
final boolean inner;
|
||||
final String outer;
|
||||
|
||||
VersionedJarEntry(JarEntry entry)
|
||||
{
|
||||
int v = 0;
|
||||
String name = entry.getName();
|
||||
if (name.startsWith(META_INF_VERSIONS))
|
||||
{
|
||||
v=-1;
|
||||
int index = name.indexOf('/', META_INF_VERSIONS.length());
|
||||
if (index >= 0 && index < name.length())
|
||||
{
|
||||
try
|
||||
{
|
||||
v = Integer.parseInt(name, META_INF_VERSIONS.length(), index, 10);
|
||||
name = name.substring(index + 1);
|
||||
}
|
||||
catch (NumberFormatException x)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.entry = entry;
|
||||
this.name = name;
|
||||
this.version = v;
|
||||
this.inner = name.contains("$") && name.toLowerCase().endsWith(".class");
|
||||
this.outer = inner?name.substring(0,name.indexOf('$'))+name.substring(name.length()-6,name.length()):null;
|
||||
}
|
||||
|
||||
public int version()
|
||||
{
|
||||
return version;
|
||||
}
|
||||
|
||||
public boolean isVersioned()
|
||||
{
|
||||
return version > 0;
|
||||
}
|
||||
|
||||
boolean isApplicable(int version)
|
||||
{
|
||||
return this.version>=0 && this.version <= version;
|
||||
}
|
||||
|
||||
boolean isReplacedBy(VersionedJarEntry entry)
|
||||
{
|
||||
return this.name.equals(entry.name) && entry.version>version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return entry.toString() + (version==0?"[base]":("["+version+"]"));
|
||||
}
|
||||
|
||||
public JarEntry resolve(JarFile jf)
|
||||
{
|
||||
if (version>0)
|
||||
return jf.getJarEntry(name);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -43,7 +43,9 @@ import org.eclipse.jetty.util.resource.Resource;
|
|||
* Subclasses should implement the processEntry(URL jarUrl, JarEntry entry)
|
||||
* method to handle entries in jar files whose names match the supplied
|
||||
* pattern.
|
||||
* @deprecated Does not handle MR Jars
|
||||
*/
|
||||
@Deprecated()
|
||||
public abstract class JarScanner extends org.eclipse.jetty.util.PatternMatcher
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(JarScanner.class);
|
||||
|
|
Loading…
Reference in New Issue