Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions src/main/java/org/codehaus/plexus/classworlds/realm/ClassRealm.java
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,111 @@ public Enumeration<URL> loadResourcesFromParent(String name) {
return null;
}

// ---------------------------------------------------------------------------------------------
// Package visibility methods
// ---------------------------------------------------------------------------------------------

@Override
protected Package getPackage(String name) {
Package pkg = super.getPackage(name);

if (pkg == null) {
// Check imported packages from foreign imports
for (Entry entry : foreignImports) {
if (entry.matches(name + ".Dummy")) {
ClassLoader importClassLoader = entry.getClassLoader();
if (importClassLoader != null) {
pkg = getPackageFromClassLoader(importClassLoader, name);
if (pkg != null) {
return pkg;
}
}
}
}

// Check imported packages from parent
ClassLoader parent = getParentClassLoader();
if (parent != null && isImportedFromParent(name + ".Dummy")) {
pkg = getPackageFromClassLoader(parent, name);
}
}

return pkg;
}

@Override
protected Package[] getPackages() {
Collection<Package> packages = new LinkedHashSet<>();

// Add packages from parent first
Collections.addAll(packages, super.getPackages());

// Add packages from foreign imports
for (Entry entry : foreignImports) {
ClassLoader importClassLoader = entry.getClassLoader();
if (importClassLoader != null) {
Package[] importedPackages = getPackagesFromClassLoader(importClassLoader);
for (Package pkg : importedPackages) {
// Only include packages that match the import pattern
if (entry.matches(pkg.getName() + ".Dummy")) {
packages.add(pkg);
}
}
}
}

// Add packages from parent classloader
ClassLoader parent = getParentClassLoader();
if (parent != null) {
Package[] parentPackages = getPackagesFromClassLoader(parent);
for (Package pkg : parentPackages) {
if (isImportedFromParent(pkg.getName() + ".Dummy")) {
packages.add(pkg);
}
}
}

return packages.toArray(new Package[0]);
}

private static Package getPackageFromClassLoader(ClassLoader classLoader, String name) {
// Use reflection to call getDefinedPackage (Java 9+) or getPackage (pre-Java 9)
try {
java.lang.reflect.Method method = ClassLoader.class.getMethod("getDefinedPackage", String.class);
return (Package) method.invoke(classLoader, name);
} catch (NoSuchMethodException e) {
// Fall back to deprecated getPackage method (Java 8 and earlier)
try {
java.lang.reflect.Method method = ClassLoader.class.getDeclaredMethod("getPackage", String.class);
method.setAccessible(true);
return (Package) method.invoke(classLoader, name);
} catch (Exception ex) {
return null;
}
} catch (Exception e) {
return null;
}
}

private static Package[] getPackagesFromClassLoader(ClassLoader classLoader) {
// Use reflection to call getDefinedPackages (Java 9+) or getPackages (pre-Java 9)
try {
java.lang.reflect.Method method = ClassLoader.class.getMethod("getDefinedPackages");
return (Package[]) method.invoke(classLoader);
} catch (NoSuchMethodException e) {
// Fall back to deprecated getPackages method (Java 8 and earlier)
try {
java.lang.reflect.Method method = ClassLoader.class.getDeclaredMethod("getPackages");
method.setAccessible(true);
return (Package[]) method.invoke(classLoader);
} catch (Exception ex) {
return new Package[0];
}
} catch (Exception e) {
return new Package[0];
}
}

static {
registerAsParallelCapable();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package org.codehaus.plexus.classworlds.realm;

import java.lang.reflect.Method;

import org.codehaus.plexus.classworlds.AbstractClassWorldsTestCase;
import org.codehaus.plexus.classworlds.ClassWorld;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

/**
* Test that packages imported from other ClassLoaders are visible.
*/
class PackageVisibilityTest extends AbstractClassWorldsTestCase {

@Test
void testGetPackageForImportedPackage() throws Exception {
ClassWorld world = new ClassWorld();
ClassRealm realmA = world.newRealm("realmA");
ClassRealm realmB = world.newRealm("realmB");

// Add log4j to realmA (using absolute path since maven copies it to target/test-lib)
java.io.File log4jJar = new java.io.File("target/test-lib/log4j-api-2.23.1.jar");
if (!log4jJar.exists()) {
// Fallback if running tests outside maven
log4jJar = new java.io.File("../../target/test-lib/log4j-api-2.23.1.jar");
}
realmA.addURL(log4jJar.toURI().toURL());

// Import the package from realmA to realmB
realmB.importFrom("realmA", "org.apache.logging.log4j");

// Load a class to ensure the package is defined
Class<?> loggerClass = realmB.loadClass("org.apache.logging.log4j.Logger");
assertNotNull(loggerClass);

// The package should be visible through the class (this is what JEXL uses)
Package pkgViaClass = loggerClass.getPackage();
assertNotNull(pkgViaClass, "Package should be visible via Class.getPackage()");
assertEquals("org.apache.logging.log4j", pkgViaClass.getName());

// Try to test the protected getPackage() method we overrode (may fail on Java 9+ due to modules)
try {
Method getPackageMethod = ClassLoader.class.getDeclaredMethod("getPackage", String.class);
getPackageMethod.setAccessible(true);
Package pkgViaLoader = (Package) getPackageMethod.invoke(realmB, "org.apache.logging.log4j");
assertNotNull(pkgViaLoader, "Package should be visible via ClassLoader.getPackage()");
assertEquals("org.apache.logging.log4j", pkgViaLoader.getName());
} catch (Exception e) {
// Skip this check on Java 9+ if module system prevents access
System.out.println("Skipping direct getPackage() test due to module restrictions");
}
}

@Test
void testGetPackagesIncludesImportedPackages() throws Exception {
ClassWorld world = new ClassWorld();
ClassRealm realmA = world.newRealm("realmA");
ClassRealm realmB = world.newRealm("realmB");

// Add log4j to realmA
java.io.File log4jJar = new java.io.File("target/test-lib/log4j-api-2.23.1.jar");
if (!log4jJar.exists()) {
log4jJar = new java.io.File("../../target/test-lib/log4j-api-2.23.1.jar");
}
realmA.addURL(log4jJar.toURI().toURL());

// Import the package from realmA to realmB
realmB.importFrom("realmA", "org.apache.logging.log4j");

// Load a class to ensure the package is defined
realmB.loadClass("org.apache.logging.log4j.Logger");

// Try to test the protected getPackages() method we overrode (may fail on Java 9+ due to modules)
try {
Method getPackagesMethod = ClassLoader.class.getDeclaredMethod("getPackages");
getPackagesMethod.setAccessible(true);
Package[] packages = (Package[]) getPackagesMethod.invoke(realmB);

// Check if the imported package is included
boolean found = false;
for (Package pkg : packages) {
if ("org.apache.logging.log4j".equals(pkg.getName())) {
found = true;
break;
}
}
assertTrue(found, "Imported package should be included in getPackages()");
} catch (Exception e) {
// Skip this check on Java 9+ if module system prevents access
System.out.println("Skipping direct getPackages() test due to module restrictions");
}
}

@Test
void testGetPackageForParentImportedPackage() throws Exception {
ClassWorld world = new ClassWorld();
ClassRealm parent = world.newRealm("parent");
ClassRealm child = world.newRealm("child");

// Add log4j to parent
java.io.File log4jJar = new java.io.File("target/test-lib/log4j-api-2.23.1.jar");
if (!log4jJar.exists()) {
log4jJar = new java.io.File("../../target/test-lib/log4j-api-2.23.1.jar");
}
parent.addURL(log4jJar.toURI().toURL());

// Set parent and import from parent
child.setParentRealm(parent);
child.importFromParent("org.apache.logging.log4j");

// Load a class to ensure the package is defined
Class<?> loggerClass = child.loadClass("org.apache.logging.log4j.Logger");
assertNotNull(loggerClass);

// The package should be visible through the class
Package pkgViaClass = loggerClass.getPackage();
assertNotNull(pkgViaClass, "Package should be visible via Class.getPackage()");
assertEquals("org.apache.logging.log4j", pkgViaClass.getName());

// Try to test the protected getPackage() method (may fail on Java 9+ due to modules)
try {
Method getPackageMethod = ClassLoader.class.getDeclaredMethod("getPackage", String.class);
getPackageMethod.setAccessible(true);
Package pkgViaLoader = (Package) getPackageMethod.invoke(child, "org.apache.logging.log4j");
assertNotNull(pkgViaLoader, "Package should be visible from parent via ClassLoader.getPackage()");
assertEquals("org.apache.logging.log4j", pkgViaLoader.getName());
} catch (Exception e) {
// Skip this check on Java 9+ if module system prevents access
System.out.println("Skipping direct getPackage() test due to module restrictions");
}
}

@Test
void testMultipleImportedPackages() throws Exception {
ClassWorld world = new ClassWorld();
ClassRealm realmA = world.newRealm("realmA");
ClassRealm realmB = world.newRealm("realmB");
ClassRealm realmC = world.newRealm("realmC");

// Add different jars to different realms
java.io.File log4jJar = new java.io.File("target/test-lib/log4j-api-2.23.1.jar");
java.io.File jaxbJar = new java.io.File("target/test-lib/jakarta.xml.bind-api-4.0.2.jar");
if (!log4jJar.exists()) {
log4jJar = new java.io.File("../../target/test-lib/log4j-api-2.23.1.jar");
jaxbJar = new java.io.File("../../target/test-lib/jakarta.xml.bind-api-4.0.2.jar");
}
realmA.addURL(log4jJar.toURI().toURL());
realmB.addURL(jaxbJar.toURI().toURL());

// Import packages from both realms to realmC
realmC.importFrom("realmA", "org.apache.logging.log4j");
realmC.importFrom("realmB", "jakarta.xml.bind");

// Load classes from both imported packages
Class<?> loggerClass = realmC.loadClass("org.apache.logging.log4j.Logger");
Class<?> jaxbClass = realmC.loadClass("jakarta.xml.bind.JAXBContext");

// Both packages should be visible
Package log4jPkg = loggerClass.getPackage();
assertNotNull(log4jPkg);
assertEquals("org.apache.logging.log4j", log4jPkg.getName());

Package jaxbPkg = jaxbClass.getPackage();
assertNotNull(jaxbPkg);
assertEquals("jakarta.xml.bind", jaxbPkg.getName());
}
}
Loading