Skip to content

Commit

Permalink
Merge pull request #16782 from geoand/#16770
Browse files Browse the repository at this point in the history
Ensure that RunnerClassLoader returns unique entries
  • Loading branch information
gastaldi authored Apr 25, 2021
2 parents f923531 + 715ca5a commit ed1e899
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
import java.util.Objects;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
Expand Down Expand Up @@ -202,4 +203,19 @@ public String toString() {
jarPath.getFileName() +
'}';
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
JarResource that = (JarResource) o;
return Objects.equals(manifestInfo, that.manifestInfo) && jarPath.equals(that.jarPath);
}

@Override
public int hashCode() {
return Objects.hash(manifestInfo, jarPath);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
Expand All @@ -40,6 +41,9 @@ public class SerializedApplication {
private static final int MAGIC = 0XF0315432;
private static final int VERSION = 2;

private static final ClassLoadingResource[] EMPTY_ARRAY = new ClassLoadingResource[0];
private static final JarResource SENTINEL = new JarResource(null, Path.of("wqxehxivam"));

private final RunnerClassLoader runnerClassLoader;
private final String mainClass;

Expand Down Expand Up @@ -108,7 +112,7 @@ public static SerializedApplication read(InputStream inputStream, Path appRoot)
throw new RuntimeException("Wrong class path version");
}
String mainClass = in.readUTF();
Map<String, ClassLoadingResource[]> resourceDirectoryMap = new HashMap<>();
ResourceDirectoryTracker resourceDirectoryTracker = new ResourceDirectoryTracker();
Set<String> parentFirstPackages = new HashSet<>();
int numPaths = in.readUnsignedShort();
ClassLoadingResource[] allClassLoadingResources = new ClassLoadingResource[numPaths];
Expand All @@ -127,10 +131,10 @@ public static SerializedApplication read(InputStream inputStream, Path appRoot)
String dir = in.readUTF();
int j = dir.indexOf('/');
while (j >= 0) {
addResourceDir(dir.substring(0, j), resource, resourceDirectoryMap);
resourceDirectoryTracker.addResourceDir(dir.substring(0, j), resource);
j = dir.indexOf('/', j + 1);
}
addResourceDir(dir, resource, resourceDirectoryMap);
resourceDirectoryTracker.addResourceDir(dir, resource);
}
}
int packages = in.readUnsignedShort();
Expand All @@ -156,7 +160,7 @@ public static SerializedApplication read(InputStream inputStream, Path appRoot)
directlyIndexedResourcesIndexMap.put(resource, matchingResources);
}
RunnerClassLoader runnerClassLoader = new RunnerClassLoader(ClassLoader.getSystemClassLoader(),
resourceDirectoryMap, parentFirstPackages,
resourceDirectoryTracker.getResult(), parentFirstPackages,
nonExistentResources, FULLY_INDEXED_PATHS, directlyIndexedResourcesIndexMap);
for (ClassLoadingResource classLoadingResource : allClassLoadingResources) {
classLoadingResource.init(runnerClassLoader);
Expand All @@ -165,19 +169,6 @@ public static SerializedApplication read(InputStream inputStream, Path appRoot)
}
}

private static void addResourceDir(String dir, JarResource resource,
Map<String, ClassLoadingResource[]> resourceDirectoryMap) {
ClassLoadingResource[] existing = resourceDirectoryMap.get(dir);
if (existing == null) {
resourceDirectoryMap.put(dir, new ClassLoadingResource[] { resource });
} else {
ClassLoadingResource[] newResources = new ClassLoadingResource[existing.length + 1];
System.arraycopy(existing, 0, newResources, 0, existing.length);
newResources[existing.length] = resource;
resourceDirectoryMap.put(dir, newResources);
}
}

private static String readNullableString(DataInputStream in) throws IOException {
if (in.readBoolean()) {
return in.readUTF();
Expand Down Expand Up @@ -321,4 +312,58 @@ private static void writeNullableString(DataOutputStream out, String string) thr
}
}

/**
* This class is to build up the final directory to Resource map
* The idea here is that directories will only be contained by a single resource, so we optimistically
* create a single item array when we encounter a directory for the first time.
* However, when a resource is added for the same directory, we switch to using a Set to track the resources
* of that directory and then at the end use those sets to override to create the final array of resources.
* The reason for doing it this way to only create Sets when needed (which is only a fraction of the cases)
*/
private static class ResourceDirectoryTracker {
private final Map<String, ClassLoadingResource[]> result = new HashMap<>();
private final Map<String, Set<ClassLoadingResource>> overrides = new HashMap<>();

void addResourceDir(String dir, JarResource resource) {
ClassLoadingResource[] existing = result.get(dir);
if (existing == null) {
// this is the first the dir was ever tracked
result.put(dir, new JarResource[] { resource });
} else {
ClassLoadingResource existingResource = existing[0];
if (existingResource.equals(resource)) {
// we don't need to do anything as the resource has already been tracked and an attempt
// to add it again was made
} else {
Set<ClassLoadingResource> dirOverrides = overrides.get(dir);
if (dirOverrides == null) {
// we need to create the override set as this is the first time we find a resource for the dir
// that is not the same as the one in the array
dirOverrides = new LinkedHashSet<>(2);
dirOverrides.add(existingResource);
dirOverrides.add(resource);
overrides.put(dir, dirOverrides);

//replace the value in the original array with a sentinel in order to allow for quick comparisons
existing[0] = SENTINEL;
} else {
// in this case, overrides has already been created in a previous invocation so all we need to
// do is add the new resource
dirOverrides.add(resource);
}
}
}
}

Map<String, ClassLoadingResource[]> getResult() {
overrides.forEach(new BiConsumer<String, Set<? extends ClassLoadingResource>>() {
@Override
public void accept(String dir, Set<? extends ClassLoadingResource> jarResources) {
result.put(dir, jarResources.toArray(EMPTY_ARRAY));
}
});
return result;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-engine</artifactId>
<version>7.51.0.Final</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
Expand All @@ -32,7 +33,8 @@ public String readClassPathResources() {
() -> assertCorrectExactFileLocation(),
() -> assertInvalidDirectory(),
() -> assertCorrectDirectory(),
() -> assertMultiRelease()
() -> assertMultiRelease(),
() -> assertUniqueDirectories()
);
}

Expand Down Expand Up @@ -160,6 +162,22 @@ private String assertCorrectDirectory() {
}
}

private String assertUniqueDirectories() {
final String testType = "unique-directories";
try {
Enumeration<URL> resources = this.getClass().getClassLoader().getResources("META-INF/kie.conf");
List<URL> resourcesList = Collections.list(resources);
// 'META-INF/kie.conf' should be present in 'kie-internal', 'drools-core', 'drools-compiler' and 'drools-model-compiler'
if (resourcesList.size() != 4) {
return errorResult(testType, "wrong number of directory urls");
}
return SUCCESS;
} catch (Exception e) {
e.printStackTrace();
return errorResult(testType, "exception during resolution of resource");
}
}

private List<URL> urlList(Enumeration<URL> enumeration) {
if (enumeration == null) {
return Collections.emptyList();
Expand Down

0 comments on commit ed1e899

Please sign in to comment.