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
26package com.sun.jmx.mbeanserver;
27
28
29import static com.sun.jmx.defaults.JmxProperties.MBEANSERVER_LOGGER;
30import java.security.Permission;
31import java.util.ArrayList;
32import java.util.Arrays;
33import java.util.Hashtable;
34import java.util.List;
35import java.util.Map;
36import java.lang.System.Logger.Level;
37import javax.management.MBeanPermission;
38
39import javax.management.ObjectName;
40import javax.management.loading.PrivateClassLoader;
41import 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 */
50final 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
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     **/
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     **/
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
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
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
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
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
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
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
274    public final void addClassLoader(ClassLoader loader) {
275        add(null, loader);
276    }
277
278    public final void removeClassLoader(ClassLoader loader) {
279        remove(null, loader);
280    }
281
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
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
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