1 /*
2  * Copyright (c) 2002, 2017, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.sun.jmx.mbeanserver;
27 
28 
29 import static com.sun.jmx.defaults.JmxProperties.MBEANSERVER_LOGGER;
30 import java.security.Permission;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Hashtable;
34 import java.util.List;
35 import java.util.Map;
36 import java.lang.System.Logger.Level;
37 import javax.management.MBeanPermission;
38 
39 import javax.management.ObjectName;
40 import javax.management.loading.PrivateClassLoader;
41 import sun.reflect.misc.ReflectUtil;
42 
43 /**
44  * This class keeps the list of Class Loaders registered in the MBean Server.
45  * It provides the necessary methods to load classes using the
46  * registered Class Loaders.
47  *
48  * @since 1.5
49  */
50 final class ClassLoaderRepositorySupport
51     implements ModifiableClassLoaderRepository {
52 
53     /* We associate an optional ObjectName with each entry so that
54        we can remove the correct entry when unregistering an MBean
55        that is a ClassLoader.  The same object could be registered
56        under two different names (even though this is not recommended)
57        so if we did not do this we could disturb the defined
58        semantics for the order of ClassLoaders in the repository.  */
59     private static class LoaderEntry {
60         ObjectName name; // can be null
61         ClassLoader loader;
62 
LoaderEntry(ObjectName name, ClassLoader loader)63         LoaderEntry(ObjectName name,  ClassLoader loader) {
64             this.name = name;
65             this.loader = loader;
66         }
67     }
68 
69     private static final LoaderEntry[] EMPTY_LOADER_ARRAY = new LoaderEntry[0];
70 
71     /**
72      * List of class loaders
73      * Only read-only actions should be performed on this object.
74      *
75      * We do O(n) operations on this array, e.g. when removing
76      * a ClassLoader.  The assumption is that the number of elements
77      * is small, probably less than ten, and that the vast majority
78      * of operations are searches (loadClass) which are by definition
79      * linear.
80      */
81     private LoaderEntry[] loaders = EMPTY_LOADER_ARRAY;
82 
83     /**
84      * Same behavior as add(Object o) in {@link java.util.List}.
85      * Replace the loader list with a new one in which the new
86      * loader has been added.
87      **/
add(ObjectName name, ClassLoader cl)88     private synchronized boolean add(ObjectName name, ClassLoader cl) {
89         List<LoaderEntry> l =
90             new ArrayList<LoaderEntry>(Arrays.asList(loaders));
91         l.add(new LoaderEntry(name, cl));
92         loaders = l.toArray(EMPTY_LOADER_ARRAY);
93         return true;
94     }
95 
96     /**
97      * Same behavior as remove(Object o) in {@link java.util.List}.
98      * Replace the loader list with a new one in which the old loader
99      * has been removed.
100      *
101      * The ObjectName may be null, in which case the entry to
102      * be removed must also have a null ObjectName and the ClassLoader
103      * values must match.  If the ObjectName is not null, then
104      * the first entry with a matching ObjectName is removed,
105      * regardless of whether ClassLoader values match.  (In fact,
106      * the ClassLoader parameter will usually be null in this case.)
107      **/
remove(ObjectName name, ClassLoader cl)108     private synchronized boolean remove(ObjectName name, ClassLoader cl) {
109         final int size = loaders.length;
110         for (int i = 0; i < size; i++) {
111             LoaderEntry entry = loaders[i];
112             boolean match =
113                 (name == null) ?
114                 cl == entry.loader :
115                 name.equals(entry.name);
116             if (match) {
117                 LoaderEntry[] newloaders = new LoaderEntry[size - 1];
118                 System.arraycopy(loaders, 0, newloaders, 0, i);
119                 System.arraycopy(loaders, i + 1, newloaders, i,
120                                  size - 1 - i);
121                 loaders = newloaders;
122                 return true;
123             }
124         }
125         return false;
126     }
127 
128 
129     /**
130      * List of valid search
131      */
132     private final Map<String,List<ClassLoader>> search =
133         new Hashtable<String,List<ClassLoader>>(10);
134 
135     /**
136      * List of named class loaders.
137      */
138     private final Map<ObjectName,ClassLoader> loadersWithNames =
139         new Hashtable<ObjectName,ClassLoader>(10);
140 
141     // from javax.management.loading.DefaultLoaderRepository
loadClass(String className)142     public final Class<?> loadClass(String className)
143         throws ClassNotFoundException {
144         return  loadClass(loaders, className, null, null);
145     }
146 
147 
148     // from javax.management.loading.DefaultLoaderRepository
loadClassWithout(ClassLoader without, String className)149     public final Class<?> loadClassWithout(ClassLoader without, String className)
150             throws ClassNotFoundException {
151         if (MBEANSERVER_LOGGER.isLoggable(Level.TRACE)) {
152             MBEANSERVER_LOGGER.log(Level.TRACE,
153                     className + " without " + without);
154         }
155 
156         // without is null => just behave as loadClass
157         //
158         if (without == null)
159             return loadClass(loaders, className, null, null);
160 
161         // We must try to load the class without the given loader.
162         //
163         startValidSearch(without, className);
164         try {
165             return loadClass(loaders, className, without, null);
166         } finally {
167             stopValidSearch(without, className);
168         }
169     }
170 
171 
loadClassBefore(ClassLoader stop, String className)172     public final Class<?> loadClassBefore(ClassLoader stop, String className)
173             throws ClassNotFoundException {
174         if (MBEANSERVER_LOGGER.isLoggable(Level.TRACE)) {
175             MBEANSERVER_LOGGER.log(Level.TRACE,
176                     className + " before " + stop);
177         }
178 
179         if (stop == null)
180             return loadClass(loaders, className, null, null);
181 
182         startValidSearch(stop, className);
183         try {
184             return loadClass(loaders, className, null, stop);
185         } finally {
186             stopValidSearch(stop, className);
187         }
188     }
189 
190 
loadClass(final LoaderEntry list[], final String className, final ClassLoader without, final ClassLoader stop)191     private Class<?> loadClass(final LoaderEntry list[],
192                                final String className,
193                                final ClassLoader without,
194                                final ClassLoader stop)
195             throws ClassNotFoundException {
196         ReflectUtil.checkPackageAccess(className);
197         final int size = list.length;
198         for(int i=0; i<size; i++) {
199             try {
200                 final ClassLoader cl = list[i].loader;
201                 if (cl == null) // bootstrap class loader
202                     return Class.forName(className, false, null);
203                 if (cl == without)
204                     continue;
205                 if (cl == stop)
206                     break;
207                 if (MBEANSERVER_LOGGER.isLoggable(Level.TRACE)) {
208                     MBEANSERVER_LOGGER.log(Level.TRACE, "Trying loader = " + cl);
209                 }
210                 /* We used to have a special case for "instanceof
211                    MLet" here, where we invoked the method
212                    loadClass(className, null) to prevent infinite
213                    recursion.  But the rule whereby the MLet only
214                    consults loaders that precede it in the CLR (via
215                    loadClassBefore) means that the recursion can't
216                    happen, and the test here caused some legitimate
217                    classloading to fail.  For example, if you have
218                    dependencies C->D->E with loaders {E D C} in the
219                    CLR in that order, you would expect to be able to
220                    load C.  The problem is that while resolving D, CLR
221                    delegation is disabled, so it can't find E.  */
222                 return Class.forName(className, false, cl);
223             } catch (ClassNotFoundException e) {
224                 // OK: continue with next class
225             }
226         }
227 
228         throw new ClassNotFoundException(className);
229     }
230 
startValidSearch(ClassLoader aloader, String className)231     private synchronized void startValidSearch(ClassLoader aloader,
232                                                String className)
233         throws ClassNotFoundException {
234         // Check if we have such a current search
235         //
236         List<ClassLoader> excluded = search.get(className);
237         if ((excluded!= null) && (excluded.contains(aloader))) {
238             if (MBEANSERVER_LOGGER.isLoggable(Level.TRACE)) {
239                 MBEANSERVER_LOGGER.log(Level.TRACE,
240                         "Already requested loader = " +
241                         aloader + " class = " + className);
242             }
243             throw new ClassNotFoundException(className);
244         }
245 
246         // Add an entry
247         //
248         if (excluded == null) {
249             excluded = new ArrayList<ClassLoader>(1);
250             search.put(className, excluded);
251         }
252         excluded.add(aloader);
253         if (MBEANSERVER_LOGGER.isLoggable(Level.TRACE)) {
254             MBEANSERVER_LOGGER.log(Level.TRACE,
255                     "loader = " + aloader + " class = " + className);
256         }
257     }
258 
stopValidSearch(ClassLoader aloader, String className)259     private synchronized void stopValidSearch(ClassLoader aloader,
260                                               String className) {
261 
262         // Retrieve the search.
263         //
264         List<ClassLoader> excluded = search.get(className);
265         if (excluded != null) {
266             excluded.remove(aloader);
267             if (MBEANSERVER_LOGGER.isLoggable(Level.TRACE)) {
268                 MBEANSERVER_LOGGER.log(Level.TRACE,
269                         "loader = " + aloader + " class = " + className);
270             }
271         }
272     }
273 
addClassLoader(ClassLoader loader)274     public final void addClassLoader(ClassLoader loader) {
275         add(null, loader);
276     }
277 
removeClassLoader(ClassLoader loader)278     public final void removeClassLoader(ClassLoader loader) {
279         remove(null, loader);
280     }
281 
addClassLoader(ObjectName name, ClassLoader loader)282     public final synchronized void addClassLoader(ObjectName name,
283                                                   ClassLoader loader) {
284         loadersWithNames.put(name, loader);
285         if (!(loader instanceof PrivateClassLoader))
286             add(name, loader);
287     }
288 
removeClassLoader(ObjectName name)289     public final synchronized void removeClassLoader(ObjectName name) {
290         ClassLoader loader = loadersWithNames.remove(name);
291         if (!(loader instanceof PrivateClassLoader))
292             remove(name, loader);
293     }
294 
getClassLoader(ObjectName name)295     public final ClassLoader getClassLoader(ObjectName name) {
296         ClassLoader instance = loadersWithNames.get(name);
297         if (instance != null) {
298             SecurityManager sm = System.getSecurityManager();
299             if (sm != null) {
300                 Permission perm =
301                         new MBeanPermission(instance.getClass().getName(),
302                         null,
303                         name,
304                         "getClassLoader");
305                 sm.checkPermission(perm);
306             }
307         }
308         return instance;
309     }
310 
311 }
312