View Javadoc
1   package org.codehaus.plexus.classworlds.realm;
2   
3   /*
4    * Copyright 2001-2006 Codehaus Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * 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, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import java.io.IOException;
20  import java.io.PrintStream;
21  import java.net.MalformedURLException;
22  import java.net.URL;
23  import java.net.URLClassLoader;
24  import java.util.Collection;
25  import java.util.Collections;
26  import java.util.Enumeration;
27  import java.util.HashSet;
28  import java.util.LinkedHashSet;
29  import java.util.SortedSet;
30  import java.util.TreeSet;
31  import java.util.concurrent.ConcurrentHashMap;
32  import java.util.concurrent.ConcurrentMap;
33  
34  import org.codehaus.plexus.classworlds.ClassWorld;
35  import org.codehaus.plexus.classworlds.strategy.Strategy;
36  import org.codehaus.plexus.classworlds.strategy.StrategyFactory;
37  
38  /**
39   * The class loading gateway. Each class realm has access to a base class loader, imports form zero or more other class
40   * loaders, an optional parent class loader and of course its own class path. When queried for a class/resource, a class
41   * realm will always query its base class loader first before it delegates to a pluggable strategy. The strategy in turn
42   * controls the order in which imported class loaders, the parent class loader and the realm itself are searched. The
43   * base class loader is assumed to be capable of loading of the bootstrap classes.
44   *
45   * @author <a href="mailto:bob@eng.werken.com">bob mcwhirter</a>
46   * @author Jason van Zyl
47   */
48  public class ClassRealm extends URLClassLoader {
49  
50      private final ClassWorld world;
51  
52      private final String id;
53  
54      private final SortedSet<Entry> foreignImports;
55  
56      private SortedSet<Entry> parentImports;
57  
58      private final Strategy strategy;
59  
60      private ClassLoader parentClassLoader;
61  
62      private final ConcurrentMap<String, Object> lockMap;
63  
64      /**
65       * Creates a new class realm.
66       *
67       * @param world           The class world this realm belongs to, must not be <code>null</code>.
68       * @param id              The identifier for this realm, must not be <code>null</code>.
69       * @param baseClassLoader The base class loader for this realm, may be <code>null</code> to use the bootstrap class
70       *                        loader.
71       */
72      public ClassRealm(ClassWorld world, String id, ClassLoader baseClassLoader) {
73          super(new URL[0], baseClassLoader);
74  
75          this.world = world;
76  
77          this.id = id;
78  
79          foreignImports = new TreeSet<>();
80  
81          strategy = StrategyFactory.getStrategy(this);
82  
83          lockMap = new ConcurrentHashMap<>();
84          // We must call super.getClassLoadingLock at least once
85          // to avoid NPE in super.loadClass.
86          super.getClassLoadingLock(getClass().getName());
87      }
88  
89      public String getId() {
90          return this.id;
91      }
92  
93      public ClassWorld getWorld() {
94          return this.world;
95      }
96  
97      public void importFromParent(String packageName) {
98          if (parentImports == null) {
99              parentImports = new TreeSet<>();
100         }
101 
102         parentImports.add(new Entry(null, packageName));
103     }
104 
105     boolean isImportedFromParent(String name) {
106         if (parentImports != null && !parentImports.isEmpty()) {
107             for (Entry entry : parentImports) {
108                 if (entry.matches(name)) {
109                     return true;
110                 }
111             }
112 
113             return false;
114         }
115 
116         return true;
117     }
118 
119     public void importFrom(String realmId, String packageName) throws NoSuchRealmException {
120         importFrom(getWorld().getRealm(realmId), packageName);
121     }
122 
123     public void importFrom(ClassLoader classLoader, String packageName) {
124         foreignImports.add(new Entry(classLoader, packageName));
125     }
126 
127     public ClassLoader getImportClassLoader(String name) {
128         for (Entry entry : foreignImports) {
129             if (entry.matches(name)) {
130                 return entry.getClassLoader();
131             }
132         }
133 
134         return null;
135     }
136 
137     public Collection<ClassRealm> getImportRealms() {
138         Collection<ClassRealm> importRealms = new HashSet<>();
139 
140         for (Entry entry : foreignImports) {
141             if (entry.getClassLoader() instanceof ClassRealm) {
142                 importRealms.add((ClassRealm) entry.getClassLoader());
143             }
144         }
145 
146         return importRealms;
147     }
148 
149     public Strategy getStrategy() {
150         return strategy;
151     }
152 
153     public void setParentClassLoader(ClassLoader parentClassLoader) {
154         this.parentClassLoader = parentClassLoader;
155     }
156 
157     public ClassLoader getParentClassLoader() {
158         return parentClassLoader;
159     }
160 
161     public void setParentRealm(ClassRealm realm) {
162         this.parentClassLoader = realm;
163     }
164 
165     public ClassRealm getParentRealm() {
166         return (parentClassLoader instanceof ClassRealm) ? (ClassRealm) parentClassLoader : null;
167     }
168 
169     public ClassRealm createChildRealm(String id) throws DuplicateRealmException {
170         ClassRealm childRealm = getWorld().newRealm(id, null);
171         childRealm.setParentRealm(this);
172         return childRealm;
173     }
174 
175     public void addURL(URL url) {
176         String urlStr = url.toExternalForm();
177 
178         if (urlStr.startsWith("jar:") && urlStr.endsWith("!/")) {
179             urlStr = urlStr.substring(4, urlStr.length() - 2);
180 
181             try {
182                 url = new URL(urlStr);
183             } catch (MalformedURLException e) {
184                 //noinspection CallToPrintStackTrace
185                 e.printStackTrace();
186             }
187         }
188 
189         super.addURL(url);
190     }
191 
192     // ----------------------------------------------------------------------
193     // We delegate to the Strategy here so that we can change the behavior
194     // of any existing ClassRealm.
195     // ----------------------------------------------------------------------
196 
197     public Class<?> loadClass(String name) throws ClassNotFoundException {
198         return loadClass(name, false);
199     }
200 
201     protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
202         return unsynchronizedLoadClass(name, resolve);
203     }
204 
205     private Class<?> unsynchronizedLoadClass(String name, boolean resolve) throws ClassNotFoundException {
206         try {
207             // first, try loading bootstrap classes
208             return super.loadClass(name, resolve);
209         } catch (ClassNotFoundException e) {
210             // next, try loading via imports, self and parent as controlled by strategy
211             return strategy.loadClass(name);
212         }
213     }
214 
215     // overwrites
216     // https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/ClassLoader.html#findClass(java.lang.String,java.lang.String)
217     // introduced in Java9
218     @SuppressWarnings("Since15")
219     protected Class<?> findClass(String moduleName, String name) {
220         if (moduleName != null) {
221             return null;
222         }
223         try {
224             return findClassInternal(name);
225         } catch (ClassNotFoundException e) {
226             try {
227                 return strategy.getRealm().findClass(name);
228             } catch (ClassNotFoundException nestedException) {
229                 return null;
230             }
231         }
232     }
233 
234     protected Class<?> findClass(String name) throws ClassNotFoundException {
235         /*
236          * NOTE: This gets only called from ClassLoader.loadClass(Class, boolean) while we try to check for bootstrap
237          * stuff. Don't scan our class path yet, loadClassFromSelf() will do this later when called by the strategy.
238          */
239         throw new ClassNotFoundException(name);
240     }
241 
242     protected Class<?> findClassInternal(String name) throws ClassNotFoundException {
243         return super.findClass(name);
244     }
245 
246     public URL getResource(String name) {
247         URL resource = super.getResource(name);
248         return resource != null ? resource : strategy.getResource(name);
249     }
250 
251     public URL findResource(String name) {
252         return super.findResource(name);
253     }
254 
255     public Enumeration<URL> getResources(String name) throws IOException {
256         Collection<URL> resources = new LinkedHashSet<>(Collections.list(super.getResources(name)));
257         resources.addAll(Collections.list(strategy.getResources(name)));
258         return Collections.enumeration(resources);
259     }
260 
261     public Enumeration<URL> findResources(String name) throws IOException {
262         return super.findResources(name);
263     }
264 
265     // ----------------------------------------------------------------------------
266     // Display methods
267     // ----------------------------------------------------------------------------
268 
269     public void display() {
270         display(System.out);
271     }
272 
273     public void display(PrintStream out) {
274         out.println("-----------------------------------------------------");
275 
276         for (ClassRealm cr = this; cr != null; cr = cr.getParentRealm()) {
277             out.println("realm =    " + cr.getId());
278             out.println("strategy = " + cr.getStrategy().getClass().getName());
279 
280             showUrls(cr, out);
281 
282             out.println();
283         }
284 
285         out.println("-----------------------------------------------------");
286     }
287 
288     private static void showUrls(ClassRealm classRealm, PrintStream out) {
289         URL[] urls = classRealm.getURLs();
290 
291         for (int i = 0; i < urls.length; i++) {
292             out.println("urls[" + i + "] = " + urls[i]);
293         }
294 
295         out.println("Number of foreign imports: " + classRealm.foreignImports.size());
296 
297         for (Entry entry : classRealm.foreignImports) {
298             out.println("import: " + entry);
299         }
300 
301         if (classRealm.parentImports != null) {
302             out.println("Number of parent imports: " + classRealm.parentImports.size());
303 
304             for (Entry entry : classRealm.parentImports) {
305                 out.println("import: " + entry);
306             }
307         }
308     }
309 
310     public String toString() {
311         return "ClassRealm[" + getId() + ", parent: " + getParentClassLoader() + "]";
312     }
313 
314     // ---------------------------------------------------------------------------------------------
315     // Search methods that can be ordered by strategies to load a class
316     // ---------------------------------------------------------------------------------------------
317 
318     public Class<?> loadClassFromImport(String name) {
319         ClassLoader importClassLoader = getImportClassLoader(name);
320 
321         if (importClassLoader != null) {
322             try {
323                 return importClassLoader.loadClass(name);
324             } catch (ClassNotFoundException e) {
325                 return null;
326             }
327         }
328 
329         return null;
330     }
331 
332     public Class<?> loadClassFromSelf(String name) {
333         synchronized (getClassRealmLoadingLock(name)) {
334             try {
335                 Class<?> clazz = findLoadedClass(name);
336 
337                 if (clazz == null) {
338                     clazz = findClassInternal(name);
339                 }
340 
341                 return clazz;
342             } catch (ClassNotFoundException e) {
343                 return null;
344             }
345         }
346     }
347 
348     private Object getClassRealmLoadingLock(String name) {
349         return getClassLoadingLock(name);
350     }
351 
352     @Override
353     protected Object getClassLoadingLock(String name) {
354         Object newLock = new Object();
355         Object lock = lockMap.putIfAbsent(name, newLock);
356         return (lock == null) ? newLock : lock;
357     }
358 
359     public Class<?> loadClassFromParent(String name) {
360         ClassLoader parent = getParentClassLoader();
361 
362         if (parent != null && isImportedFromParent(name)) {
363             try {
364                 return parent.loadClass(name);
365             } catch (ClassNotFoundException e) {
366                 return null;
367             }
368         }
369 
370         return null;
371     }
372 
373     // ---------------------------------------------------------------------------------------------
374     // Search methods that can be ordered by strategies to get a resource
375     // ---------------------------------------------------------------------------------------------
376 
377     public URL loadResourceFromImport(String name) {
378         ClassLoader importClassLoader = getImportClassLoader(name);
379 
380         if (importClassLoader != null) {
381             return importClassLoader.getResource(name);
382         }
383 
384         return null;
385     }
386 
387     public URL loadResourceFromSelf(String name) {
388         return findResource(name);
389     }
390 
391     public URL loadResourceFromParent(String name) {
392         ClassLoader parent = getParentClassLoader();
393 
394         if (parent != null && isImportedFromParent(name)) {
395             return parent.getResource(name);
396         } else {
397             return null;
398         }
399     }
400 
401     // ---------------------------------------------------------------------------------------------
402     // Search methods that can be ordered by strategies to get resources
403     // ---------------------------------------------------------------------------------------------
404 
405     public Enumeration<URL> loadResourcesFromImport(String name) {
406         ClassLoader importClassLoader = getImportClassLoader(name);
407 
408         if (importClassLoader != null) {
409             try {
410                 return importClassLoader.getResources(name);
411             } catch (IOException e) {
412                 return null;
413             }
414         }
415 
416         return null;
417     }
418 
419     public Enumeration<URL> loadResourcesFromSelf(String name) {
420         try {
421             return findResources(name);
422         } catch (IOException e) {
423             return null;
424         }
425     }
426 
427     public Enumeration<URL> loadResourcesFromParent(String name) {
428         ClassLoader parent = getParentClassLoader();
429 
430         if (parent != null && isImportedFromParent(name)) {
431             try {
432                 return parent.getResources(name);
433             } catch (IOException e) {
434                 // eat it
435             }
436         }
437 
438         return null;
439     }
440 
441     static {
442         registerAsParallelCapable();
443     }
444 }