1 /*
2  * Copyright (c) 2002, 2013, 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.util.logging.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.FINER)) {
152             MBEANSERVER_LOGGER.logp(Level.FINER,
153                     ClassLoaderRepositorySupport.class.getName(),
154                     "loadClassWithout", className + " without " + without);
155         }
156 
157         // without is null => just behave as loadClass
158         //
159         if (without == null)
160             return loadClass(loaders, className, null, null);
161 
162         // We must try to load the class without the given loader.
163         //
164         startValidSearch(without, className);
165         try {
166             return loadClass(loaders, className, without, null);
167         } finally {
168             stopValidSearch(without, className);
169         }
170     }
171 
172 
loadClassBefore(ClassLoader stop, String className)173     public final Class<?> loadClassBefore(ClassLoader stop, String className)
174             throws ClassNotFoundException {
175         if (MBEANSERVER_LOGGER.isLoggable(Level.FINER)) {
176             MBEANSERVER_LOGGER.logp(Level.FINER,
177                     ClassLoaderRepositorySupport.class.getName(),
178                     "loadClassBefore", className + " before " + stop);
179         }
180 
181         if (stop == null)
182             return loadClass(loaders, className, null, null);
183 
184         startValidSearch(stop, className);
185         try {
186             return loadClass(loaders, className, null, stop);
187         } finally {
188             stopValidSearch(stop, className);
189         }
190     }
191 
192 
loadClass(final LoaderEntry list[], final String className, final ClassLoader without, final ClassLoader stop)193     private Class<?> loadClass(final LoaderEntry list[],
194                                final String className,
195                                final ClassLoader without,
196                                final ClassLoader stop)
197             throws ClassNotFoundException {
198         ReflectUtil.checkPackageAccess(className);
199         final int size = list.length;
200         for(int i=0; i<size; i++) {
201             try {
202                 final ClassLoader cl = list[i].loader;
203                 if (cl == null) // bootstrap class loader
204                     return Class.forName(className, false, null);
205                 if (cl == without)
206                     continue;
207                 if (cl == stop)
208                     break;
209                 if (MBEANSERVER_LOGGER.isLoggable(Level.FINER)) {
210                     MBEANSERVER_LOGGER.logp(Level.FINER,
211                             ClassLoaderRepositorySupport.class.getName(),
212                             "loadClass", "Trying loader = " + cl);
213                 }
214                 /* We used to have a special case for "instanceof
215                    MLet" here, where we invoked the method
216                    loadClass(className, null) to prevent infinite
217                    recursion.  But the rule whereby the MLet only
218                    consults loaders that precede it in the CLR (via
219                    loadClassBefore) means that the recursion can't
220                    happen, and the test here caused some legitimate
221                    classloading to fail.  For example, if you have
222                    dependencies C->D->E with loaders {E D C} in the
223                    CLR in that order, you would expect to be able to
224                    load C.  The problem is that while resolving D, CLR
225                    delegation is disabled, so it can't find E.  */
226                 return Class.forName(className, false, cl);
227             } catch (ClassNotFoundException e) {
228                 // OK: continue with next class
229             }
230         }
231 
232         throw new ClassNotFoundException(className);
233     }
234 
startValidSearch(ClassLoader aloader, String className)235     private synchronized void startValidSearch(ClassLoader aloader,
236                                                String className)
237         throws ClassNotFoundException {
238         // Check if we have such a current search
239         //
240         List<ClassLoader> excluded = search.get(className);
241         if ((excluded!= null) && (excluded.contains(aloader))) {
242             if (MBEANSERVER_LOGGER.isLoggable(Level.FINER)) {
243                 MBEANSERVER_LOGGER.logp(Level.FINER,
244                         ClassLoaderRepositorySupport.class.getName(),
245                         "startValidSearch", "Already requested loader = " +
246                         aloader + " class = " + className);
247             }
248             throw new ClassNotFoundException(className);
249         }
250 
251         // Add an entry
252         //
253         if (excluded == null) {
254             excluded = new ArrayList<ClassLoader>(1);
255             search.put(className, excluded);
256         }
257         excluded.add(aloader);
258         if (MBEANSERVER_LOGGER.isLoggable(Level.FINER)) {
259             MBEANSERVER_LOGGER.logp(Level.FINER,
260                     ClassLoaderRepositorySupport.class.getName(),
261                     "startValidSearch",
262                     "loader = " + aloader + " class = " + className);
263         }
264     }
265 
stopValidSearch(ClassLoader aloader, String className)266     private synchronized void stopValidSearch(ClassLoader aloader,
267                                               String className) {
268 
269         // Retrieve the search.
270         //
271         List<ClassLoader> excluded = search.get(className);
272         if (excluded != null) {
273             excluded.remove(aloader);
274             if (MBEANSERVER_LOGGER.isLoggable(Level.FINER)) {
275                 MBEANSERVER_LOGGER.logp(Level.FINER,
276                         ClassLoaderRepositorySupport.class.getName(),
277                         "stopValidSearch",
278                         "loader = " + aloader + " class = " + className);
279             }
280         }
281     }
282 
addClassLoader(ClassLoader loader)283     public final void addClassLoader(ClassLoader loader) {
284         add(null, loader);
285     }
286 
removeClassLoader(ClassLoader loader)287     public final void removeClassLoader(ClassLoader loader) {
288         remove(null, loader);
289     }
290 
addClassLoader(ObjectName name, ClassLoader loader)291     public final synchronized void addClassLoader(ObjectName name,
292                                                   ClassLoader loader) {
293         loadersWithNames.put(name, loader);
294         if (!(loader instanceof PrivateClassLoader))
295             add(name, loader);
296     }
297 
removeClassLoader(ObjectName name)298     public final synchronized void removeClassLoader(ObjectName name) {
299         ClassLoader loader = loadersWithNames.remove(name);
300         if (!(loader instanceof PrivateClassLoader))
301             remove(name, loader);
302     }
303 
getClassLoader(ObjectName name)304     public final ClassLoader getClassLoader(ObjectName name) {
305         ClassLoader instance = loadersWithNames.get(name);
306         if (instance != null) {
307             SecurityManager sm = System.getSecurityManager();
308             if (sm != null) {
309                 Permission perm =
310                         new MBeanPermission(instance.getClass().getName(),
311                         null,
312                         name,
313                         "getClassLoader");
314                 sm.checkPermission(perm);
315             }
316         }
317         return instance;
318     }
319 
320 }
321