Skip to content

Commit

Permalink
Multi release jar ClassLoader fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
stuartwdouglas committed Aug 18, 2020
1 parent 55ff54c commit b54f0e7
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 7 deletions.
1 change: 1 addition & 0 deletions core/runtime/pom.xml
Expand Up @@ -118,6 +118,7 @@
<configuration>
<parentFirstArtifacts>
<parentFirstArtifact>io.quarkus:quarkus-bootstrap-runner</parentFirstArtifact>
<parentFirstArtifact>io.smallrye.common:smallrye-common-io</parentFirstArtifact>
<parentFirstArtifact>org.wildfly.common:wildfly-common</parentFirstArtifact>
<parentFirstArtifact>org.graalvm.sdk:graal-sdk</parentFirstArtifact>
<parentFirstArtifact>org.graalvm.nativeimage:svm</parentFirstArtifact>
Expand Down
Expand Up @@ -8,6 +8,7 @@
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.UncheckedIOException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
Expand All @@ -18,6 +19,7 @@
import java.security.cert.Certificate;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.jar.JarEntry;
Expand All @@ -30,7 +32,23 @@
*/
public class JarClassPathElement implements ClassPathElement {

private static final int JAVA_VERSION;

static {
int version = 8;
try {
Method versionMethod = Runtime.class.getMethod("version");
Object v = versionMethod.invoke(null);
List<Integer> list = (List<Integer>) v.getClass().getMethod("version").invoke(v);
version = list.get(0);
} catch (Exception e) {
//version 8
}
JAVA_VERSION = version;
}

private static final Logger log = Logger.getLogger(JarClassPathElement.class);
public static final String META_INF_VERSIONS = "META-INF/versions/";
private final File file;
private final URL jarPath;
private final Path root;
Expand Down Expand Up @@ -147,6 +165,26 @@ public Set<String> apply(JarFile jarFile) {
paths.add(entry.getName());
}
}
//multi release jars can add additional entries
if (JarFiles.isMultiRelease(jarFile)) {
Set<String> copy = new HashSet<>(paths);
for (String i : copy) {
if (i.startsWith(META_INF_VERSIONS)) {
String part = i.substring(META_INF_VERSIONS.length());
int slash = part.indexOf("/");
if (slash != -1) {
try {
int ver = Integer.parseInt(part.substring(0, slash));
if (ver <= JAVA_VERSION) {
paths.add(part.substring(slash + 1));
}
} catch (NumberFormatException e) {
log.debug("Failed to parse META-INF/versions entry", e);
}
}
}
}
}
return paths;
}
}));
Expand Down
4 changes: 4 additions & 0 deletions independent-projects/bootstrap/runner/pom.xml
Expand Up @@ -19,6 +19,10 @@
</description>

<dependencies>
<dependency>
<groupId>io.smallrye.common</groupId>
<artifactId>smallrye-common-io</artifactId>
</dependency>
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>graal-sdk</artifactId>
Expand Down
@@ -1,5 +1,7 @@
package io.quarkus.bootstrap.runner;

import io.smallrye.common.io.jar.JarEntries;
import io.smallrye.common.io.jar.JarFiles;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
Expand All @@ -10,6 +12,8 @@
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

Expand All @@ -20,7 +24,7 @@ public class JarResource implements ClassLoadingResource {

private final ManifestInfo manifestInfo;
private final Path jarPath;
private volatile ZipFile zipFile;
private volatile JarFile zipFile;

public JarResource(ManifestInfo manifestInfo, Path jarPath) {
this.manifestInfo = manifestInfo;
Expand Down Expand Up @@ -54,12 +58,17 @@ public byte[] getResourceData(String resource) {

@Override
public URL getResourceURL(String resource) {
ZipFile zipFile = file();
ZipEntry entry = zipFile.getEntry(resource);
JarFile zipFile = file();
JarEntry entry = zipFile.getJarEntry(resource);
if (entry == null) {
return null;
}
try {
String realName = JarEntries.getRealName(entry);
// Avoid ending the URL with / to avoid breaking compatibility
if (realName.endsWith("/")) {
realName = realName.substring(0, realName.length() - 1);
}
URI jarUri = jarPath.toUri();
return new URL("jar", null, jarUri.getScheme() + ":" + jarUri.getPath() + "!/" + resource);
} catch (MalformedURLException e) {
Expand Down Expand Up @@ -90,12 +99,12 @@ public ProtectionDomain getProtectionDomain(ClassLoader classLoader) {
return protectionDomain;
}

private ZipFile file() {
private JarFile file() {
if (zipFile == null) {
synchronized (this) {
if (zipFile == null) {
try {
return zipFile = new ZipFile(jarPath.toFile());
return zipFile = JarFiles.create(jarPath.toFile());
} catch (IOException e) {
throw new RuntimeException("Failed to open " + jarPath, e);
}
Expand Down
Expand Up @@ -29,6 +29,8 @@
*/
public class SerializedApplication {

public static final String META_INF_VERSIONS = "META-INF/versions/";

private static final int MAGIC = 0XF0315432;
private static final int VERSION = 1;

Expand Down Expand Up @@ -160,6 +162,18 @@ private static void writeJar(DataOutputStream out, Path jar) throws IOException
//looking at you h2
final int index = entry.getName().lastIndexOf('/');
dirs.add(entry.getName().substring(0, index));

if (entry.getName().startsWith(META_INF_VERSIONS)) {
//multi release jar
//we add all packages here
//they may no be relevant for some versions, but that is fine
String part = entry.getName().substring(META_INF_VERSIONS.length());
int slash = part.indexOf("/");
if (slash != -1) {
final int subIndex = part.lastIndexOf('/');
dirs.add(part.substring(slash + 1, subIndex));
}
}
}
}
if (hasDefaultPackge) {
Expand Down
Expand Up @@ -32,6 +32,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-context-propagation</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-undertow-websockets</artifactId>
Expand Down
Expand Up @@ -31,7 +31,8 @@ public String readClassPathResources() {
() -> assertInvalidExactFileLocation(),
() -> assertCorrectExactFileLocation(),
() -> assertInvalidDirectory(),
() -> assertCorrectDirectory()
() -> assertCorrectDirectory(),
() -> assertMultiRelease()
);
}

Expand Down Expand Up @@ -61,6 +62,24 @@ private String assertInvalidExactFileLocation() {
}
}

private String assertMultiRelease() {
final String testType = "assert-multi-release-jar";
if (System.getProperty("java.version").startsWith("8.")) {
return SUCCESS;
}
try {
//this class is only present in multi release jars
//for fast-jar we need to make sure it is loaded correctly
Class<?> clazz = this.getClass().getClassLoader().loadClass("io.smallrye.context.Jdk9CompletableFutureWrapper");
if (clazz.getClassLoader() == getClass().getClassLoader()) {
return SUCCESS;
}
return errorResult(testType, "Incorrect ClassLoader for " + clazz);
} catch (Exception e) {
e.printStackTrace();
return errorResult(testType, "exception during resolution of resource");
}
}
private String assertCorrectExactFileLocation() {
final String testType = "correct-exact-location";
try {
Expand Down
Expand Up @@ -121,7 +121,7 @@ public static void assertThatOutputWorksCorrectly(String logs) {
assertThat(logs.contains(infoLogLevel)).isTrue();
Predicate<String> datePattern = Pattern.compile("\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2},\\d{3}").asPredicate();
assertThat(datePattern.test(logs)).isTrue();
assertThat(logs.contains("cdi, resteasy, servlet, undertow-websockets")).isTrue();
assertThat(logs.contains("cdi, resteasy, servlet, smallrye-context-propagation, undertow-websockets")).isTrue();
assertThat(logs.contains("JBoss Threads version")).isFalse();
}

Expand Down

0 comments on commit b54f0e7

Please sign in to comment.