From 917a2ee3e1b962e65991d7a698aaf744c023a9f1 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Wed, 19 Oct 2022 09:42:00 +0200 Subject: [PATCH 1/4] Allows to register a filtered class realm Only those classes/resoures which match one of the given prefixes are exposed through the filtered class realm This closes #69 --- .../plexus/classworlds/ClassWorld.java | 48 +++++++++- .../plexus/classworlds/realm/ClassRealm.java | 14 ++- .../classworlds/realm/FilteredClassRealm.java | 92 +++++++++++++++++++ .../realm/FilteredClassRealmTest.java | 69 ++++++++++++++ 4 files changed, 215 insertions(+), 8 deletions(-) create mode 100644 src/main/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealm.java create mode 100644 src/test/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealmTest.java diff --git a/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java b/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java index bea3c73e..dd892251 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.Set; 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,39 @@ 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, Collections.emptySet() ); + } + + /** + * Shortcut for {@link #newRealm(String, ClassLoader, Set)} with the class loader of the current class. + * @param id + * @param allowedResourceNamePrefixes + * @return the created class realm + * @throws DuplicateRealmException + * @since 2.7.0 + * @see FilteredClassRealm + */ + public synchronized ClassRealm newRealm( String id, Set allowedResourceNamePrefixes ) + throws DuplicateRealmException + { + return newRealm( id, getClass().getClassLoader(), allowedResourceNamePrefixes ); + } + + /** + * Adds a class realm with filtering. + * Only resources/classes starting with one of the given prefixes are exposed. + * @param id + * @param classLoader + * @param allowedResourceNamePrefixes the prefixes of resource names which should be exposed. Separator '/' is used here (even for classes). + * @return the created class realm + * @throws DuplicateRealmException + * @since 2.7.0 + * @see FilteredClassRealm + */ + public synchronized ClassRealm newRealm( String id, ClassLoader classLoader, Set allowedResourceNamePrefixes ) throws DuplicateRealmException { if ( realms.containsKey( id ) ) @@ -74,8 +108,14 @@ public synchronized ClassRealm newRealm( String id, ClassLoader classLoader ) ClassRealm realm; - realm = new ClassRealm( this, id, classLoader ); - + if ( allowedResourceNamePrefixes.isEmpty() ) + { + realm = new ClassRealm( this, id, classLoader ); + } + else + { + realm = new FilteredClassRealm( allowedResourceNamePrefixes, this, id, classLoader ); + } realms.put( id, realm ); for ( ClassWorldListener listener : listeners ) @@ -85,7 +125,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..fc6380c7 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 ); @@ -306,6 +306,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 +428,7 @@ public Class loadClassFromSelf( String name ) if ( clazz == null ) { - clazz = super.findClass( name ); + clazz = findClassInternal( name ); } return clazz; @@ -495,7 +501,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 +545,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..f44a0541 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealm.java @@ -0,0 +1,92 @@ +package org.codehaus.plexus.classworlds.realm; + +/* + * 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. + */ + +import java.io.IOException; +import java.net.URL; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Set; + +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 +{ + + // no regular expressions for performance reasons + private final Set allowedResourceNamePrefixes; + + /** + * Creates a new class realm. + * + * @param allowedResourceNamePrefixes all resources not starting with one of the given prefixes are never exposed 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( Set allowedResourceNamePrefixes, ClassWorld world, String id, ClassLoader baseClassLoader ) + { + super( world, id, baseClassLoader ); + this.allowedResourceNamePrefixes = allowedResourceNamePrefixes; + } + + @Override + protected Class findClassInternal( String name ) + throws ClassNotFoundException + { + String resourceName = name.replace( '.', '/' ).concat( ".class" ); + if ( !isAllowedName( resourceName )) + { + throw new ClassNotFoundException(name); + } + return super.findClassInternal( name ); + } + + @Override + public URL findResource( String name ) + { + if ( !isAllowedName( name )) + { + return null; + } + return super.findResource( name ); + } + + @Override + public Enumeration findResources( String name ) + throws IOException + { + if ( !isAllowedName( name )) + { + return Collections.emptyEnumeration(); + } + return super.findResources( name ); + } + + private boolean isAllowedName( String name ) + { + return allowedResourceNamePrefixes.stream().anyMatch( name::startsWith ); + } +} 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..f7099882 --- /dev/null +++ b/src/test/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealmTest.java @@ -0,0 +1,69 @@ +package org.codehaus.plexus.classworlds.realm; + +/* + * 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. + */ + +import java.util.HashSet; +import java.util.Set; + +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; + + @Before + public void setUp() + { + this.world = new ClassWorld(); + } + + @Test + public void testLoadClassAndResourcesFiltered() + throws Exception + { + // only allow loading resources whose names start with "a." + Set allowedResourcePrefixes = new HashSet<>(); + allowedResourcePrefixes.add( "a." ); + allowedResourcePrefixes.add( "a/Aa" ); + ClassRealm realmA = this.world.newRealm( "realmA", allowedResourcePrefixes ); + + assertThrows( ClassNotFoundException.class, () -> realmA.loadClass( "a.Aa" ) ); + realmA.addURL( getJarUrl( "a.jar" ) ); + + assertNotNull( realmA.loadClass( "a.Aa" ) ); + assertThrows( ClassNotFoundException.class, () -> realmA.loadClass( "a.A" ) ); + + assertNull( realmA.getResource( "common.properties" ) ); + assertFalse( realmA.getResources( "common.properties" ).hasMoreElements() ); + + assertNotNull( realmA.getResource( "a.properties" ) ); + assertTrue( realmA.getResources( "a.properties" ).hasMoreElements() ); + } + +} From be7515470d29b4cb012eda0c9a530d634bc73532 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Wed, 19 Oct 2022 13:07:09 +0200 Subject: [PATCH 2/4] support JPMS classloading clean up --- .../plexus/classworlds/ClassWorld.java | 13 ++-- .../plexus/classworlds/realm/ClassRealm.java | 5 +- .../classworlds/realm/FilteredClassRealm.java | 3 +- .../classworlds/realm/ClassRealmImplTest.java | 12 ++- .../realm/FilteredClassRealmTest.java | 73 +++++++++++++++---- 5 files changed, 78 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java b/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java index dd892251..fd8c942a 100644 --- a/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java +++ b/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java @@ -74,10 +74,10 @@ public ClassRealm newRealm( String id, ClassLoader classLoader ) /** * Shortcut for {@link #newRealm(String, ClassLoader, Set)} with the class loader of the current class. - * @param id - * @param allowedResourceNamePrefixes + * @param id The identifier for this realm, must not be null. + * @param allowedResourceNamePrefixes the prefixes of resource names which should be exposed. Separator '/' is used here (even for classes). * @return the created class realm - * @throws DuplicateRealmException + * @throws DuplicateRealmException in case a realm with the given id does already exist * @since 2.7.0 * @see FilteredClassRealm */ @@ -90,11 +90,12 @@ public synchronized ClassRealm newRealm( String id, Set allowedResourceN /** * Adds a class realm with filtering. * Only resources/classes starting with one of the given prefixes are exposed. - * @param id - * @param classLoader + * @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. * @param allowedResourceNamePrefixes the prefixes of resource names which should be exposed. Separator '/' is used here (even for classes). * @return the created class realm - * @throws DuplicateRealmException + * @throws DuplicateRealmException in case a realm with the given id does already exist * @since 2.7.0 * @see FilteredClassRealm */ 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 fc6380c7..e9591902 100644 --- a/src/main/java/org/codehaus/plexus/classworlds/realm/ClassRealm.java +++ b/src/main/java/org/codehaus/plexus/classworlds/realm/ClassRealm.java @@ -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 ) { diff --git a/src/main/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealm.java b/src/main/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealm.java index f44a0541..af31f35f 100644 --- a/src/main/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealm.java +++ b/src/main/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealm.java @@ -1,5 +1,3 @@ -package org.codehaus.plexus.classworlds.realm; - /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file @@ -18,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +package org.codehaus.plexus.classworlds.realm; import java.io.IOException; import java.net.URL; 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 index f7099882..4fbb5e9a 100644 --- a/src/test/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealmTest.java +++ b/src/test/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealmTest.java @@ -1,5 +1,3 @@ -package org.codehaus.plexus.classworlds.realm; - /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file @@ -18,7 +16,10 @@ * specific language governing permissions and limitations * under the License. */ +package org.codehaus.plexus.classworlds.realm; +import java.io.IOException; +import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -36,34 +37,78 @@ public class FilteredClassRealmTest extends AbstractClassWorldsTestCase { private ClassWorld world; + private ClassRealm realmA; @Before - public void setUp() + 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", allowedResourcePrefixes ); } @Test - public void testLoadClassAndResourcesFiltered() + public void testLoadResources() throws Exception { - // only allow loading resources whose names start with "a." - Set allowedResourcePrefixes = new HashSet<>(); - allowedResourcePrefixes.add( "a." ); - allowedResourcePrefixes.add( "a/Aa" ); - ClassRealm realmA = this.world.newRealm( "realmA", allowedResourcePrefixes ); + 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" ) ); - assertNull( realmA.getResource( "common.properties" ) ); - assertFalse( realmA.getResources( "common.properties" ).hasMoreElements() ); - - assertNotNull( realmA.getResource( "a.properties" ) ); - assertTrue( realmA.getResources( "a.properties" ).hasMoreElements() ); + assertNotNull( realmA.loadClass( "a.Aa" ) ); + assertThrows( ClassNotFoundException.class, () -> realmA.loadClass( "a.A" ) ); } + @Test + public void testLoadClassWithModule() throws IOException + { + try (ExtendedFilteredClassRealm realmA = new ExtendedFilteredClassRealm( world, Collections.singleton( "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, Set allowedResourcePrefixes ) + { + super( allowedResourcePrefixes, 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; + } + } + } } From 3ff12a4b2334009cf1ae4161f5065387ab5c1f9c Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Wed, 19 Oct 2022 13:10:48 +0200 Subject: [PATCH 3/4] fix javadoc --- src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java b/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java index fd8c942a..841c8558 100644 --- a/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java +++ b/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java @@ -91,7 +91,7 @@ public synchronized ClassRealm newRealm( String id, Set allowedResourceN * Adds a class realm with filtering. * Only resources/classes starting with one of the given prefixes are exposed. * @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 + * @param classLoader The base class loader for this realm, may be null to use the bootstrap class * loader. * @param allowedResourceNamePrefixes the prefixes of resource names which should be exposed. Separator '/' is used here (even for classes). * @return the created class realm From eff80f7b7830b21c92a0b1f5df8a062aa4da9950 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Sat, 5 Nov 2022 10:47:57 +0100 Subject: [PATCH 4/4] add documentation --- pom.xml | 2 +- .../plexus/classworlds/ClassWorld.java | 22 +++++++++--------- .../classworlds/realm/FilteredClassRealm.java | 23 +++++++------------ src/site/xdoc/apiusage.xml | 12 +++++++--- .../realm/FilteredClassRealmTest.java | 10 ++++---- 5 files changed, 34 insertions(+), 35 deletions(-) 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 841c8558..eab7a51d 100644 --- a/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java +++ b/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java @@ -23,7 +23,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Set; +import java.util.function.Predicate; import org.codehaus.plexus.classworlds.realm.ClassRealm; import org.codehaus.plexus.classworlds.realm.DuplicateRealmException; @@ -69,37 +69,37 @@ public ClassRealm newRealm( String id ) public ClassRealm newRealm( String id, ClassLoader classLoader ) throws DuplicateRealmException { - return newRealm( id, classLoader, Collections.emptySet() ); + return newRealm( id, classLoader, null ); } /** - * Shortcut for {@link #newRealm(String, ClassLoader, Set)} with the class loader of the current class. + * 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 allowedResourceNamePrefixes the prefixes of resource names which should be exposed. Separator '/' is used here (even for classes). + * @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, Set allowedResourceNamePrefixes ) + public synchronized ClassRealm newRealm( String id, Predicate filter ) throws DuplicateRealmException { - return newRealm( id, getClass().getClassLoader(), allowedResourceNamePrefixes ); + return newRealm( id, getClass().getClassLoader(), filter ); } /** * Adds a class realm with filtering. - * Only resources/classes starting with one of the given prefixes are exposed. + * 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 allowedResourceNamePrefixes the prefixes of resource names which should be exposed. Separator '/' is used here (even for classes). + * @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, Set allowedResourceNamePrefixes ) + public synchronized ClassRealm newRealm( String id, ClassLoader classLoader, Predicate filter ) throws DuplicateRealmException { if ( realms.containsKey( id ) ) @@ -109,13 +109,13 @@ public synchronized ClassRealm newRealm( String id, ClassLoader classLoader, Set ClassRealm realm; - if ( allowedResourceNamePrefixes.isEmpty() ) + if ( filter == null ) { realm = new ClassRealm( this, id, classLoader ); } else { - realm = new FilteredClassRealm( allowedResourceNamePrefixes, this, id, classLoader ); + realm = new FilteredClassRealm( filter, this, id, classLoader ); } realms.put( id, realm ); diff --git a/src/main/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealm.java b/src/main/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealm.java index af31f35f..2fac1bea 100644 --- a/src/main/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealm.java +++ b/src/main/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealm.java @@ -22,7 +22,7 @@ import java.net.URL; import java.util.Collections; import java.util.Enumeration; -import java.util.Set; +import java.util.function.Predicate; import org.codehaus.plexus.classworlds.ClassWorld; @@ -32,23 +32,21 @@ */ public class FilteredClassRealm extends ClassRealm { - - // no regular expressions for performance reasons - private final Set allowedResourceNamePrefixes; + private final Predicate filter; /** * Creates a new class realm. * - * @param allowedResourceNamePrefixes all resources not starting with one of the given prefixes are never exposed through this class loader + * @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( Set allowedResourceNamePrefixes, ClassWorld world, String id, ClassLoader baseClassLoader ) + public FilteredClassRealm( Predicate filter, ClassWorld world, String id, ClassLoader baseClassLoader ) { super( world, id, baseClassLoader ); - this.allowedResourceNamePrefixes = allowedResourceNamePrefixes; + this.filter = filter; } @Override @@ -56,7 +54,7 @@ protected Class findClassInternal( String name ) throws ClassNotFoundException { String resourceName = name.replace( '.', '/' ).concat( ".class" ); - if ( !isAllowedName( resourceName )) + if ( !filter.test( resourceName ) ) { throw new ClassNotFoundException(name); } @@ -66,7 +64,7 @@ protected Class findClassInternal( String name ) @Override public URL findResource( String name ) { - if ( !isAllowedName( name )) + if ( !filter.test( name ) ) { return null; } @@ -77,15 +75,10 @@ public URL findResource( String name ) public Enumeration findResources( String name ) throws IOException { - if ( !isAllowedName( name )) + if ( !filter.test( name ) ) { return Collections.emptyEnumeration(); } return super.findResources( name ); } - - private boolean isAllowedName( String name ) - { - return allowedResourceNamePrefixes.stream().anyMatch( name::startsWith ); - } } 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/FilteredClassRealmTest.java b/src/test/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealmTest.java index 4fbb5e9a..79b01610 100644 --- a/src/test/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealmTest.java +++ b/src/test/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealmTest.java @@ -19,9 +19,9 @@ package org.codehaus.plexus.classworlds.realm; import java.io.IOException; -import java.util.Collections; 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; @@ -47,7 +47,7 @@ public void setUp() throws DuplicateRealmException Set allowedResourcePrefixes = new HashSet<>(); allowedResourcePrefixes.add( "a." ); allowedResourcePrefixes.add( "a/Aa" ); - realmA = this.world.newRealm( "realmA", allowedResourcePrefixes ); + realmA = this.world.newRealm( "realmA", s -> allowedResourcePrefixes.stream().anyMatch( s::startsWith ) ); } @Test @@ -78,7 +78,7 @@ public void testLoadClass() throws ClassNotFoundException @Test public void testLoadClassWithModule() throws IOException { - try (ExtendedFilteredClassRealm realmA = new ExtendedFilteredClassRealm( world, Collections.singleton( "a/Aa" ) )) { + 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" ) ); @@ -94,9 +94,9 @@ public void testLoadClassWithModule() throws IOException static class ExtendedFilteredClassRealm extends FilteredClassRealm { - public ExtendedFilteredClassRealm( final ClassWorld world, Set allowedResourcePrefixes ) + public ExtendedFilteredClassRealm( final ClassWorld world, Predicate filter ) { - super( allowedResourcePrefixes, world, "java9", Thread.currentThread().getContextClassLoader() ); + super( filter, world, "java9", Thread.currentThread().getContextClassLoader() ); } public Class simulateLoadClassFromModule(final String name)