Skip to content

Commit 596d83d

Browse files
authored
Support filtered class realms (#70)
This closes #69
1 parent 1862fc6 commit 596d83d

File tree

7 files changed

+274
-18
lines changed

7 files changed

+274
-18
lines changed

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@
207207
<report>summary</report>
208208
<report>index</report>
209209
<report>dependencies</report>
210-
<report>issue-tracking</report>
210+
<report>issue-management</report>
211211
<report>scm</report>
212212
</reports>
213213
</reportSet>

src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java

+45-4
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@
2323
import java.util.LinkedHashMap;
2424
import java.util.List;
2525
import java.util.Map;
26+
import java.util.function.Predicate;
2627

2728
import org.codehaus.plexus.classworlds.realm.ClassRealm;
2829
import org.codehaus.plexus.classworlds.realm.DuplicateRealmException;
30+
import org.codehaus.plexus.classworlds.realm.FilteredClassRealm;
2931
import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;
3032

3133
/**
@@ -64,7 +66,40 @@ public ClassRealm newRealm( String id )
6466
return newRealm( id, getClass().getClassLoader() );
6567
}
6668

67-
public synchronized ClassRealm newRealm( String id, ClassLoader classLoader )
69+
public ClassRealm newRealm( String id, ClassLoader classLoader )
70+
throws DuplicateRealmException
71+
{
72+
return newRealm( id, classLoader, null );
73+
}
74+
75+
/**
76+
* Shortcut for {@link #newRealm(String, ClassLoader, Predicate)} with the class loader of the current class.
77+
* @param id The identifier for this realm, must not be <code>null</code>.
78+
* @param filter a predicate to apply to each resource name to determine if it should be loaded through this class loader
79+
* @return the created class realm
80+
* @throws DuplicateRealmException in case a realm with the given id does already exist
81+
* @since 2.7.0
82+
* @see FilteredClassRealm
83+
*/
84+
public synchronized ClassRealm newRealm( String id, Predicate<String> filter )
85+
throws DuplicateRealmException
86+
{
87+
return newRealm( id, getClass().getClassLoader(), filter );
88+
}
89+
90+
/**
91+
* Adds a class realm with filtering.
92+
* Only resources/classes whose name matches a given predicate are exposed.
93+
* @param id The identifier for this realm, must not be <code>null</code>.
94+
* @param classLoader The base class loader for this realm, may be <code>null</code> to use the bootstrap class
95+
* loader.
96+
* @param filter a predicate to apply to each resource name to determine if it should be loaded through this class loader
97+
* @return the created class realm
98+
* @throws DuplicateRealmException in case a realm with the given id does already exist
99+
* @since 2.7.0
100+
* @see FilteredClassRealm
101+
*/
102+
public synchronized ClassRealm newRealm( String id, ClassLoader classLoader, Predicate<String> filter )
68103
throws DuplicateRealmException
69104
{
70105
if ( realms.containsKey( id ) )
@@ -74,8 +109,14 @@ public synchronized ClassRealm newRealm( String id, ClassLoader classLoader )
74109

75110
ClassRealm realm;
76111

77-
realm = new ClassRealm( this, id, classLoader );
78-
112+
if ( filter == null )
113+
{
114+
realm = new ClassRealm( this, id, classLoader );
115+
}
116+
else
117+
{
118+
realm = new FilteredClassRealm( filter, this, id, classLoader );
119+
}
79120
realms.put( id, realm );
80121

81122
for ( ClassWorldListener listener : listeners )
@@ -85,7 +126,7 @@ public synchronized ClassRealm newRealm( String id, ClassLoader classLoader )
85126

86127
return realm;
87128
}
88-
129+
89130
public synchronized void disposeRealm( String id )
90131
throws NoSuchRealmException
91132
{

src/main/java/org/codehaus/plexus/classworlds/realm/ClassRealm.java

+13-6
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ public ClassRealm getParentRealm()
200200
public ClassRealm createChildRealm( String id )
201201
throws DuplicateRealmException
202202
{
203-
ClassRealm childRealm = getWorld().newRealm( id, null );
203+
ClassRealm childRealm = getWorld().newRealm( id, (ClassLoader) null );
204204

205205
childRealm.setParentRealm( this );
206206

@@ -272,7 +272,8 @@ private Class<?> unsynchronizedLoadClass( String name, boolean resolve )
272272
}
273273
}
274274

275-
// java11
275+
// overwrites https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/ClassLoader.html#findClass(java.lang.String,java.lang.String)
276+
// introduced in Java9
276277
protected Class<?> findClass( String moduleName, String name )
277278
{
278279
if ( moduleName != null )
@@ -281,7 +282,7 @@ protected Class<?> findClass( String moduleName, String name )
281282
}
282283
try
283284
{
284-
return super.findClass( name );
285+
return findClassInternal( name );
285286
}
286287
catch ( ClassNotFoundException e )
287288
{
@@ -306,6 +307,12 @@ protected Class<?> findClass( String name )
306307
throw new ClassNotFoundException( name );
307308
}
308309

310+
protected Class<?> findClassInternal( String name )
311+
throws ClassNotFoundException
312+
{
313+
return super.findClass( name );
314+
}
315+
309316
public URL getResource( String name )
310317
{
311318
URL resource = super.getResource( name );
@@ -422,7 +429,7 @@ public Class<?> loadClassFromSelf( String name )
422429

423430
if ( clazz == null )
424431
{
425-
clazz = super.findClass( name );
432+
clazz = findClassInternal( name );
426433
}
427434

428435
return clazz;
@@ -495,7 +502,7 @@ public URL loadResourceFromImport( String name )
495502

496503
public URL loadResourceFromSelf( String name )
497504
{
498-
return super.findResource( name );
505+
return findResource( name );
499506
}
500507

501508
public URL loadResourceFromParent( String name )
@@ -539,7 +546,7 @@ public Enumeration<URL> loadResourcesFromSelf( String name )
539546
{
540547
try
541548
{
542-
return super.findResources( name );
549+
return findResources( name );
543550
}
544551
catch ( IOException e )
545552
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.codehaus.plexus.classworlds.realm;
20+
21+
import java.io.IOException;
22+
import java.net.URL;
23+
import java.util.Collections;
24+
import java.util.Enumeration;
25+
import java.util.function.Predicate;
26+
27+
import org.codehaus.plexus.classworlds.ClassWorld;
28+
29+
/**
30+
* Similar to {@link ClassRealm} but only exposing some resources of the underlying URL.
31+
* Only supposed to be called from {@link ClassWorld}.
32+
*/
33+
public class FilteredClassRealm extends ClassRealm
34+
{
35+
private final Predicate<String> filter;
36+
37+
/**
38+
* Creates a new class realm.
39+
*
40+
* @param filter a predicate to apply to each resource name to determine if it should be loaded through this class loader
41+
* @param world The class world this realm belongs to, must not be <code>null</code>.
42+
* @param id The identifier for this realm, must not be <code>null</code>.
43+
* @param baseClassLoader The base class loader for this realm, may be <code>null</code> to use the bootstrap class
44+
* loader.
45+
*/
46+
public FilteredClassRealm( Predicate<String> filter, ClassWorld world, String id, ClassLoader baseClassLoader )
47+
{
48+
super( world, id, baseClassLoader );
49+
this.filter = filter;
50+
}
51+
52+
@Override
53+
protected Class<?> findClassInternal( String name )
54+
throws ClassNotFoundException
55+
{
56+
String resourceName = name.replace( '.', '/' ).concat( ".class" );
57+
if ( !filter.test( resourceName ) )
58+
{
59+
throw new ClassNotFoundException(name);
60+
}
61+
return super.findClassInternal( name );
62+
}
63+
64+
@Override
65+
public URL findResource( String name )
66+
{
67+
if ( !filter.test( name ) )
68+
{
69+
return null;
70+
}
71+
return super.findResource( name );
72+
}
73+
74+
@Override
75+
public Enumeration<URL> findResources( String name )
76+
throws IOException
77+
{
78+
if ( !filter.test( name ) )
79+
{
80+
return Collections.emptyEnumeration();
81+
}
82+
return super.findResources( name );
83+
}
84+
}

src/site/xdoc/apiusage.xml

+9-3
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ ClassWorld world = new ClassWorld();
3232
<p>
3333
Once a <code>ClassWorld</code> is created, realms within it
3434
can be created. These realms effectively only allow loading
35-
of the core JVM classes.
35+
of the core JVM classes initially.
3636
</p>
3737

3838
<source><![CDATA[
@@ -42,15 +42,21 @@ ClassRealm logComponentRealm = world.newRealm( "logComponent" );
4242
]]></source>
4343

4444
<p>
45-
In order to make each <code>ClassRealm</code> useful, constituent
46-
must be added to that each can provide certain classes.
45+
In order to make each <code>ClassRealm</code> useful, constituents
46+
in form of URLs must be added to it where each can provide certain classes.
47+
The URL must return either a JAR or a directory on the default file system.
4748
</p>
4849

4950
<source><![CDATA[
5051
containerRealm.addURL( containerJarUrl );
5152
logComponentRealm.addURL( logComponentJarUrl );
5253
]]></source>
5354

55+
<p>
56+
<code>ClassRealm</code>s can optionally be filtered to further restrict which classes/resources
57+
are exposed. The filter is provided as additional argument to <code>world.newRealm( "filteredcontainer", myPredicate );</code>
58+
</p>
59+
5460
<p>
5561
Now, links between the various realms need to be created to allow
5662
classes loaded from one to be available to classes loaded in another.

src/test/java/org/codehaus/plexus/classworlds/realm/ClassRealmImplTest.java

+8-4
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ public void testLoadClass_ClassWorldsClassRepeatedly()
443443
}
444444

445445
@Test
446-
public void testLoadClass_Java11()
446+
public void testLoadClassWithModuleName_Java9()
447447
{
448448
final ExtendedClassRealm mainRealm = new ExtendedClassRealm( world );
449449
mainRealm.addURL( getJarUrl( "a.jar" ) );
@@ -503,13 +503,17 @@ public void testGetResources_SelfBeforeParent()
503503
assertEquals( Arrays.asList( childUrl, parentUrl ), urls );
504504
}
505505

506-
// simulate new loadClass(Module,String) from java11
507-
// it is reversed in terms of inheritance but enables to simulate the same behavior in these tests
506+
/**
507+
* Simulates new {@code java.lang.ClassLoader#findClass(String,String)} introduced with Java 9.
508+
* It is reversed in terms of inheritance but enables to simulate the same behavior in these tests.
509+
* @see <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/ClassLoader.html#findClass(java.lang.String,java.lang.String)">ClassLoader#findClass(String,String)</a>
510+
*/
508511
private static class ExtendedClassRealm extends ClassRealm
509512
{
513+
510514
public ExtendedClassRealm(final ClassWorld world)
511515
{
512-
super( world, "java11", Thread.currentThread().getContextClassLoader() );
516+
super( world, "java9", Thread.currentThread().getContextClassLoader() );
513517
}
514518

515519
public Class<?> simulateLoadClassFromModule(final String name)

0 commit comments

Comments
 (0)