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