diff --git a/pom.xml b/pom.xml index 088f13e8..c889ab7b 100644 --- a/pom.xml +++ b/pom.xml @@ -207,7 +207,7 @@ summary index dependencies - issue-tracking + issue-management scm diff --git a/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java b/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java index bea3c73e..eab7a51d 100644 --- a/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java +++ b/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java @@ -23,9 +23,11 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Predicate; import org.codehaus.plexus.classworlds.realm.ClassRealm; import org.codehaus.plexus.classworlds.realm.DuplicateRealmException; +import org.codehaus.plexus.classworlds.realm.FilteredClassRealm; import org.codehaus.plexus.classworlds.realm.NoSuchRealmException; /** @@ -64,7 +66,40 @@ public ClassRealm newRealm( String id ) return newRealm( id, getClass().getClassLoader() ); } - public synchronized ClassRealm newRealm( String id, ClassLoader classLoader ) + public ClassRealm newRealm( String id, ClassLoader classLoader ) + throws DuplicateRealmException + { + return newRealm( id, classLoader, null ); + } + + /** + * Shortcut for {@link #newRealm(String, ClassLoader, Predicate)} with the class loader of the current class. + * @param id The identifier for this realm, must not be null. + * @param filter a predicate to apply to each resource name to determine if it should be loaded through this class loader + * @return the created class realm + * @throws DuplicateRealmException in case a realm with the given id does already exist + * @since 2.7.0 + * @see FilteredClassRealm + */ + public synchronized ClassRealm newRealm( String id, Predicate filter ) + throws DuplicateRealmException + { + return newRealm( id, getClass().getClassLoader(), filter ); + } + + /** + * Adds a class realm with filtering. + * Only resources/classes whose name matches a given predicate are exposed. + * @param id The identifier for this realm, must not be null. + * @param classLoader The base class loader for this realm, may be null to use the bootstrap class + * loader. + * @param filter a predicate to apply to each resource name to determine if it should be loaded through this class loader + * @return the created class realm + * @throws DuplicateRealmException in case a realm with the given id does already exist + * @since 2.7.0 + * @see FilteredClassRealm + */ + public synchronized ClassRealm newRealm( String id, ClassLoader classLoader, Predicate filter ) throws DuplicateRealmException { if ( realms.containsKey( id ) ) @@ -74,8 +109,14 @@ public synchronized ClassRealm newRealm( String id, ClassLoader classLoader ) ClassRealm realm; - realm = new ClassRealm( this, id, classLoader ); - + if ( filter == null ) + { + realm = new ClassRealm( this, id, classLoader ); + } + else + { + realm = new FilteredClassRealm( filter, this, id, classLoader ); + } realms.put( id, realm ); for ( ClassWorldListener listener : listeners ) @@ -85,7 +126,7 @@ public synchronized ClassRealm newRealm( String id, ClassLoader classLoader ) return realm; } - + public synchronized void disposeRealm( String id ) throws NoSuchRealmException { diff --git a/src/main/java/org/codehaus/plexus/classworlds/realm/ClassRealm.java b/src/main/java/org/codehaus/plexus/classworlds/realm/ClassRealm.java index 1a7731e7..e9591902 100644 --- a/src/main/java/org/codehaus/plexus/classworlds/realm/ClassRealm.java +++ b/src/main/java/org/codehaus/plexus/classworlds/realm/ClassRealm.java @@ -200,7 +200,7 @@ public ClassRealm getParentRealm() public ClassRealm createChildRealm( String id ) throws DuplicateRealmException { - ClassRealm childRealm = getWorld().newRealm( id, null ); + ClassRealm childRealm = getWorld().newRealm( id, (ClassLoader) null ); childRealm.setParentRealm( this ); @@ -272,7 +272,8 @@ private Class unsynchronizedLoadClass( String name, boolean resolve ) } } - // java11 + // overwrites https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/ClassLoader.html#findClass(java.lang.String,java.lang.String) + // introduced in Java9 protected Class findClass( String moduleName, String name ) { if ( moduleName != null ) @@ -281,7 +282,7 @@ protected Class findClass( String moduleName, String name ) } try { - return super.findClass( name ); + return findClassInternal( name ); } catch ( ClassNotFoundException e ) { @@ -306,6 +307,12 @@ protected Class findClass( String name ) throw new ClassNotFoundException( name ); } + protected Class findClassInternal( String name ) + throws ClassNotFoundException + { + return super.findClass( name ); + } + public URL getResource( String name ) { URL resource = super.getResource( name ); @@ -422,7 +429,7 @@ public Class loadClassFromSelf( String name ) if ( clazz == null ) { - clazz = super.findClass( name ); + clazz = findClassInternal( name ); } return clazz; @@ -495,7 +502,7 @@ public URL loadResourceFromImport( String name ) public URL loadResourceFromSelf( String name ) { - return super.findResource( name ); + return findResource( name ); } public URL loadResourceFromParent( String name ) @@ -539,7 +546,7 @@ public Enumeration loadResourcesFromSelf( String name ) { try { - return super.findResources( name ); + return findResources( name ); } catch ( IOException e ) { diff --git a/src/main/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealm.java b/src/main/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealm.java new file mode 100644 index 00000000..2fac1bea --- /dev/null +++ b/src/main/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealm.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.codehaus.plexus.classworlds.realm; + +import java.io.IOException; +import java.net.URL; +import java.util.Collections; +import java.util.Enumeration; +import java.util.function.Predicate; + +import org.codehaus.plexus.classworlds.ClassWorld; + +/** + * Similar to {@link ClassRealm} but only exposing some resources of the underlying URL. + * Only supposed to be called from {@link ClassWorld}. + */ +public class FilteredClassRealm extends ClassRealm +{ + private final Predicate filter; + + /** + * Creates a new class realm. + * + * @param filter a predicate to apply to each resource name to determine if it should be loaded through this class loader + * @param world The class world this realm belongs to, must not be null. + * @param id The identifier for this realm, must not be null. + * @param baseClassLoader The base class loader for this realm, may be null to use the bootstrap class + * loader. + */ + public FilteredClassRealm( Predicate filter, ClassWorld world, String id, ClassLoader baseClassLoader ) + { + super( world, id, baseClassLoader ); + this.filter = filter; + } + + @Override + protected Class findClassInternal( String name ) + throws ClassNotFoundException + { + String resourceName = name.replace( '.', '/' ).concat( ".class" ); + if ( !filter.test( resourceName ) ) + { + throw new ClassNotFoundException(name); + } + return super.findClassInternal( name ); + } + + @Override + public URL findResource( String name ) + { + if ( !filter.test( name ) ) + { + return null; + } + return super.findResource( name ); + } + + @Override + public Enumeration findResources( String name ) + throws IOException + { + if ( !filter.test( name ) ) + { + return Collections.emptyEnumeration(); + } + return super.findResources( name ); + } +} diff --git a/src/site/xdoc/apiusage.xml b/src/site/xdoc/apiusage.xml index a686977c..30793c27 100644 --- a/src/site/xdoc/apiusage.xml +++ b/src/site/xdoc/apiusage.xml @@ -32,7 +32,7 @@ ClassWorld world = new ClassWorld();

Once a ClassWorld is created, realms within it can be created. These realms effectively only allow loading - of the core JVM classes. + of the core JVM classes initially.

- In order to make each ClassRealm useful, constituent - must be added to that each can provide certain classes. + In order to make each ClassRealm useful, constituents + in form of URLs must be added to it where each can provide certain classes. + The URL must return either a JAR or a directory on the default file system.

+

+ ClassRealms can optionally be filtered to further restrict which classes/resources + are exposed. The filter is provided as additional argument to world.newRealm( "filteredcontainer", myPredicate ); +

+

Now, links between the various realms need to be created to allow classes loaded from one to be available to classes loaded in another. diff --git a/src/test/java/org/codehaus/plexus/classworlds/realm/ClassRealmImplTest.java b/src/test/java/org/codehaus/plexus/classworlds/realm/ClassRealmImplTest.java index d689ab53..dfd14af2 100644 --- a/src/test/java/org/codehaus/plexus/classworlds/realm/ClassRealmImplTest.java +++ b/src/test/java/org/codehaus/plexus/classworlds/realm/ClassRealmImplTest.java @@ -443,7 +443,7 @@ public void testLoadClass_ClassWorldsClassRepeatedly() } @Test - public void testLoadClass_Java11() + public void testLoadClassWithModuleName_Java9() { final ExtendedClassRealm mainRealm = new ExtendedClassRealm( world ); mainRealm.addURL( getJarUrl( "a.jar" ) ); @@ -503,13 +503,17 @@ public void testGetResources_SelfBeforeParent() assertEquals( Arrays.asList( childUrl, parentUrl ), urls ); } - // simulate new loadClass(Module,String) from java11 - // it is reversed in terms of inheritance but enables to simulate the same behavior in these tests + /** + * Simulates new {@code java.lang.ClassLoader#findClass(String,String)} introduced with Java 9. + * It is reversed in terms of inheritance but enables to simulate the same behavior in these tests. + * @see ClassLoader#findClass(String,String) + */ private static class ExtendedClassRealm extends ClassRealm { + public ExtendedClassRealm(final ClassWorld world) { - super( world, "java11", Thread.currentThread().getContextClassLoader() ); + super( world, "java9", Thread.currentThread().getContextClassLoader() ); } public Class simulateLoadClassFromModule(final String name) diff --git a/src/test/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealmTest.java b/src/test/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealmTest.java new file mode 100644 index 00000000..79b01610 --- /dev/null +++ b/src/test/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealmTest.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.codehaus.plexus.classworlds.realm; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Predicate; + +import org.codehaus.plexus.classworlds.AbstractClassWorldsTestCase; +import org.codehaus.plexus.classworlds.ClassWorld; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +public class FilteredClassRealmTest extends AbstractClassWorldsTestCase +{ + private ClassWorld world; + private ClassRealm realmA; + + @Before + public void setUp() throws DuplicateRealmException + { + this.world = new ClassWorld(); + // only allow loading resources whose names start with "a." + Set allowedResourcePrefixes = new HashSet<>(); + allowedResourcePrefixes.add( "a." ); + allowedResourcePrefixes.add( "a/Aa" ); + realmA = this.world.newRealm( "realmA", s -> allowedResourcePrefixes.stream().anyMatch( s::startsWith ) ); + } + + @Test + public void testLoadResources() + throws Exception + { + realmA.addURL( getJarUrl( "a.jar" ) ); + assertNull( realmA.getResource( "common.properties" ) ); + assertFalse( realmA.getResources( "common.properties" ).hasMoreElements() ); + + assertNotNull( realmA.getResource( "a.properties" ) ); + assertTrue( realmA.getResources( "a.properties" ).hasMoreElements() ); + } + + @Test + public void testLoadClass() throws ClassNotFoundException + { + assertThrows( ClassNotFoundException.class, () -> realmA.loadClass( "a.Aa" ) ); + realmA.addURL( getJarUrl( "a.jar" ) ); + + assertNotNull( realmA.loadClass( "a.Aa" ) ); + assertThrows( ClassNotFoundException.class, () -> realmA.loadClass( "a.A" ) ); + + assertNotNull( realmA.loadClass( "a.Aa" ) ); + assertThrows( ClassNotFoundException.class, () -> realmA.loadClass( "a.A" ) ); + } + + @Test + public void testLoadClassWithModule() throws IOException + { + try ( ExtendedFilteredClassRealm realmA = new ExtendedFilteredClassRealm( world, s -> s.startsWith( "a/Aa" ) ) ) { + realmA.addURL( getJarUrl( "a.jar" ) ); + assertNotNull( realmA.simulateLoadClassFromModule( "a.Aa" ) ); + assertNull( realmA.simulateLoadClassFromModule( "a.A" ) ); + } + } + + /** + * Simulates new {@code java.lang.ClassLoader#findClass(String,String)} introduced with Java 9. + * It is reversed in terms of inheritance but enables to simulate the same behavior in these tests. + * @see ClassLoader#findClass(String,String) + * @see ClassRealmImplTest.ExtendedClassRealm + */ + static class ExtendedFilteredClassRealm extends FilteredClassRealm + { + + public ExtendedFilteredClassRealm( final ClassWorld world, Predicate filter ) + { + super( filter, world, "java9", Thread.currentThread().getContextClassLoader() ); + } + + public Class simulateLoadClassFromModule(final String name) + { + synchronized (getClassLoadingLock(name)) + { + Class c = findLoadedClass(name); + if (c == null) { + c = findClass(null, name); + } + return c; + } + } + } +}