1/*
2 * Copyright (c) 1999, 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 javax.management;
27
28import com.sun.jmx.defaults.JmxProperties;
29import static com.sun.jmx.defaults.JmxProperties.JMX_INITIAL_BUILDER;
30import static com.sun.jmx.defaults.JmxProperties.MBEANSERVER_LOGGER;
31import com.sun.jmx.mbeanserver.GetPropertyAction;
32import java.security.AccessController;
33import java.security.Permission;
34import java.util.ArrayList;
35import java.lang.System.Logger.Level;
36import javax.management.loading.ClassLoaderRepository;
37import sun.reflect.misc.ReflectUtil;
38
39
40/**
41 * <p>Provides MBean server references.  There are no instances of
42 * this class.</p>
43 *
44 * <p>Since JMX 1.2 this class makes it possible to replace the default
45 * MBeanServer implementation. This is done using the
46 * {@link javax.management.MBeanServerBuilder} class.
47 * The class of the initial MBeanServerBuilder to be
48 * instantiated can be specified through the
49 * <b>javax.management.builder.initial</b> system property.
50 * The specified class must be a public subclass of
51 * {@link javax.management.MBeanServerBuilder}, and must have a public
52 * empty constructor.
53 * <p>By default, if no value for that property is specified, an instance of
54 * {@link
55 * javax.management.MBeanServerBuilder javax.management.MBeanServerBuilder}
56 * is created. Otherwise, the MBeanServerFactory attempts to load the
57 * specified class using
58 * {@link java.lang.Thread#getContextClassLoader()
59 *   Thread.currentThread().getContextClassLoader()}, or if that is null,
60 * {@link java.lang.Class#forName(java.lang.String) Class.forName()}. Then
61 * it creates an initial instance of that Class using
62 * {@link java.lang.Class#newInstance()}. If any checked exception
63 * is raised during this process (e.g.
64 * {@link java.lang.ClassNotFoundException},
65 * {@link java.lang.InstantiationException}) the MBeanServerFactory
66 * will propagate this exception from within a RuntimeException.</p>
67 *
68 * <p>The <b>javax.management.builder.initial</b> system property is
69 * consulted every time a new MBeanServer needs to be created, and the
70 * class pointed to by that property is loaded. If that class is different
71 * from that of the current MBeanServerBuilder, then a new MBeanServerBuilder
72 * is created. Otherwise, the MBeanServerFactory may create a new
73 * MBeanServerBuilder or reuse the current one.</p>
74 *
75 * <p>If the class pointed to by the property cannot be
76 * loaded, or does not correspond to a valid subclass of MBeanServerBuilder
77 * then an exception is propagated, and no MBeanServer can be created until
78 * the <b>javax.management.builder.initial</b> system property is reset to
79 * valid value.</p>
80 *
81 * <p>The MBeanServerBuilder makes it possible to wrap the MBeanServers
82 * returned by the default MBeanServerBuilder implementation, for the purpose
83 * of e.g. adding an additional security layer.</p>
84 *
85 * @since 1.5
86 */
87public class MBeanServerFactory {
88
89    /*
90     * There are no instances of this class so don't generate the
91     * default public constructor.
92     */
93    private MBeanServerFactory() {
94
95    }
96
97    /**
98     * The builder that will be used to construct MBeanServers.
99     *
100     **/
101    private static MBeanServerBuilder builder = null;
102
103    /**
104     * Provide a new {@link javax.management.MBeanServerBuilder}.
105     * @param builder The new MBeanServerBuilder that will be used to
106     *        create {@link javax.management.MBeanServer}s.
107     * @exception IllegalArgumentException if the given builder is null.
108     *
109     * @exception SecurityException if there is a SecurityManager and
110     * the caller's permissions do not include or imply <code>{@link
111     * MBeanServerPermission}("setMBeanServerBuilder")</code>.
112     *
113     **/
114    // public static synchronized void
115    //    setMBeanServerBuilder(MBeanServerBuilder builder) {
116    //    checkPermission("setMBeanServerBuilder");
117    //    MBeanServerFactory.builder = builder;
118    // }
119
120    /**
121     * Get the current {@link javax.management.MBeanServerBuilder}.
122     *
123     * @return the current {@link javax.management.MBeanServerBuilder}.
124     *
125     * @exception SecurityException if there is a SecurityManager and
126     * the caller's permissions do not include or imply <code>{@link
127     * MBeanServerPermission}("getMBeanServerBuilder")</code>.
128     *
129     **/
130    // public static synchronized MBeanServerBuilder getMBeanServerBuilder() {
131    //     checkPermission("getMBeanServerBuilder");
132    //     return builder;
133    // }
134
135    /**
136     * Remove internal MBeanServerFactory references to a created
137     * MBeanServer. This allows the garbage collector to remove the
138     * MBeanServer object.
139     *
140     * @param mbeanServer the MBeanServer object to remove.
141     *
142     * @exception java.lang.IllegalArgumentException if
143     * <code>mbeanServer</code> was not generated by one of the
144     * <code>createMBeanServer</code> methods, or if
145     * <code>releaseMBeanServer</code> was already called on it.
146     *
147     * @exception SecurityException if there is a SecurityManager and
148     * the caller's permissions do not include or imply <code>{@link
149     * MBeanServerPermission}("releaseMBeanServer")</code>.
150     */
151    public static void releaseMBeanServer(MBeanServer mbeanServer) {
152        checkPermission("releaseMBeanServer");
153
154        removeMBeanServer(mbeanServer);
155    }
156
157    /**
158     * <p>Return a new object implementing the MBeanServer interface
159     * with a standard default domain name.  The default domain name
160     * is used as the domain part in the ObjectName of MBeans when the
161     * domain is specified by the user is null.</p>
162     *
163     * <p>The standard default domain name is
164     * <code>DefaultDomain</code>.</p>
165     *
166     * <p>The MBeanServer reference is internally kept. This will
167     * allow <CODE>findMBeanServer</CODE> to return a reference to
168     * this MBeanServer object.</p>
169     *
170     * <p>This method is equivalent to <code>createMBeanServer(null)</code>.
171     *
172     * @return the newly created MBeanServer.
173     *
174     * @exception SecurityException if there is a SecurityManager and the
175     * caller's permissions do not include or imply <code>{@link
176     * MBeanServerPermission}("createMBeanServer")</code>.
177     *
178     * @exception JMRuntimeException if the property
179     * <code>javax.management.builder.initial</code> exists but the
180     * class it names cannot be instantiated through a public
181     * no-argument constructor; or if the instantiated builder returns
182     * null from its {@link MBeanServerBuilder#newMBeanServerDelegate
183     * newMBeanServerDelegate} or {@link
184     * MBeanServerBuilder#newMBeanServer newMBeanServer} methods.
185     *
186     * @exception ClassCastException if the property
187     * <code>javax.management.builder.initial</code> exists and can be
188     * instantiated but is not assignment compatible with {@link
189     * MBeanServerBuilder}.
190     */
191    public static MBeanServer createMBeanServer() {
192        return createMBeanServer(null);
193    }
194
195    /**
196     * <p>Return a new object implementing the {@link MBeanServer}
197     * interface with the specified default domain name.  The given
198     * domain name is used as the domain part in the ObjectName of
199     * MBeans when the domain is specified by the user is null.</p>
200     *
201     * <p>The MBeanServer reference is internally kept. This will
202     * allow <CODE>findMBeanServer</CODE> to return a reference to
203     * this MBeanServer object.</p>
204     *
205     * @param domain the default domain name for the created
206     * MBeanServer.  This is the value that will be returned by {@link
207     * MBeanServer#getDefaultDomain}.
208     *
209     * @return the newly created MBeanServer.
210     *
211     * @exception SecurityException if there is a SecurityManager and
212     * the caller's permissions do not include or imply <code>{@link
213     * MBeanServerPermission}("createMBeanServer")</code>.
214     *
215     * @exception JMRuntimeException if the property
216     * <code>javax.management.builder.initial</code> exists but the
217     * class it names cannot be instantiated through a public
218     * no-argument constructor; or if the instantiated builder returns
219     * null from its {@link MBeanServerBuilder#newMBeanServerDelegate
220     * newMBeanServerDelegate} or {@link
221     * MBeanServerBuilder#newMBeanServer newMBeanServer} methods.
222     *
223     * @exception ClassCastException if the property
224     * <code>javax.management.builder.initial</code> exists and can be
225     * instantiated but is not assignment compatible with {@link
226     * MBeanServerBuilder}.
227     */
228    public static MBeanServer createMBeanServer(String domain)  {
229        checkPermission("createMBeanServer");
230
231        final MBeanServer mBeanServer = newMBeanServer(domain);
232        addMBeanServer(mBeanServer);
233        return mBeanServer;
234    }
235
236    /**
237     * <p>Return a new object implementing the MBeanServer interface
238     * with a standard default domain name, without keeping an
239     * internal reference to this new object.  The default domain name
240     * is used as the domain part in the ObjectName of MBeans when the
241     * domain is specified by the user is null.</p>
242     *
243     * <p>The standard default domain name is
244     * <code>DefaultDomain</code>.</p>
245     *
246     * <p>No reference is kept. <CODE>findMBeanServer</CODE> will not
247     * be able to return a reference to this MBeanServer object, but
248     * the garbage collector will be able to remove the MBeanServer
249     * object when it is no longer referenced.</p>
250     *
251     * <p>This method is equivalent to <code>newMBeanServer(null)</code>.</p>
252     *
253     * @return the newly created MBeanServer.
254     *
255     * @exception SecurityException if there is a SecurityManager and the
256     * caller's permissions do not include or imply <code>{@link
257     * MBeanServerPermission}("newMBeanServer")</code>.
258     *
259     * @exception JMRuntimeException if the property
260     * <code>javax.management.builder.initial</code> exists but the
261     * class it names cannot be instantiated through a public
262     * no-argument constructor; or if the instantiated builder returns
263     * null from its {@link MBeanServerBuilder#newMBeanServerDelegate
264     * newMBeanServerDelegate} or {@link
265     * MBeanServerBuilder#newMBeanServer newMBeanServer} methods.
266     *
267     * @exception ClassCastException if the property
268     * <code>javax.management.builder.initial</code> exists and can be
269     * instantiated but is not assignment compatible with {@link
270     * MBeanServerBuilder}.
271     */
272    public static MBeanServer newMBeanServer() {
273        return newMBeanServer(null);
274    }
275
276    /**
277     * <p>Return a new object implementing the MBeanServer interface
278     * with the specified default domain name, without keeping an
279     * internal reference to this new object.  The given domain name
280     * is used as the domain part in the ObjectName of MBeans when the
281     * domain is specified by the user is null.</p>
282     *
283     * <p>No reference is kept. <CODE>findMBeanServer</CODE> will not
284     * be able to return a reference to this MBeanServer object, but
285     * the garbage collector will be able to remove the MBeanServer
286     * object when it is no longer referenced.</p>
287     *
288     * @param domain the default domain name for the created
289     * MBeanServer.  This is the value that will be returned by {@link
290     * MBeanServer#getDefaultDomain}.
291     *
292     * @return the newly created MBeanServer.
293     *
294     * @exception SecurityException if there is a SecurityManager and the
295     * caller's permissions do not include or imply <code>{@link
296     * MBeanServerPermission}("newMBeanServer")</code>.
297     *
298     * @exception JMRuntimeException if the property
299     * <code>javax.management.builder.initial</code> exists but the
300     * class it names cannot be instantiated through a public
301     * no-argument constructor; or if the instantiated builder returns
302     * null from its {@link MBeanServerBuilder#newMBeanServerDelegate
303     * newMBeanServerDelegate} or {@link
304     * MBeanServerBuilder#newMBeanServer newMBeanServer} methods.
305     *
306     * @exception ClassCastException if the property
307     * <code>javax.management.builder.initial</code> exists and can be
308     * instantiated but is not assignment compatible with {@link
309     * MBeanServerBuilder}.
310     */
311    public static MBeanServer newMBeanServer(String domain)  {
312        checkPermission("newMBeanServer");
313
314        // Get the builder. Creates a new one if necessary.
315        //
316        final MBeanServerBuilder mbsBuilder = getNewMBeanServerBuilder();
317        // Returned value cannot be null.  NullPointerException if violated.
318
319        synchronized(mbsBuilder) {
320            final MBeanServerDelegate delegate  =
321                    mbsBuilder.newMBeanServerDelegate();
322            if (delegate == null) {
323                final String msg =
324                        "MBeanServerBuilder.newMBeanServerDelegate() " +
325                        "returned null";
326                throw new JMRuntimeException(msg);
327            }
328            final MBeanServer mbeanServer =
329                    mbsBuilder.newMBeanServer(domain,null,delegate);
330            if (mbeanServer == null) {
331                final String msg =
332                        "MBeanServerBuilder.newMBeanServer() returned null";
333                throw new JMRuntimeException(msg);
334            }
335            return mbeanServer;
336        }
337    }
338
339    /**
340     * <p>Return a list of registered MBeanServer objects.  A
341     * registered MBeanServer object is one that was created by one of
342     * the <code>createMBeanServer</code> methods and not subsequently
343     * released with <code>releaseMBeanServer</code>.</p>
344     *
345     * @param agentId The agent identifier of the MBeanServer to
346     * retrieve.  If this parameter is null, all registered
347     * MBeanServers in this JVM are returned.  Otherwise, only
348     * MBeanServers whose id is equal to <code>agentId</code> are
349     * returned.  The id of an MBeanServer is the
350     * <code>MBeanServerId</code> attribute of its delegate MBean.
351     *
352     * @return A list of MBeanServer objects.
353     *
354     * @exception SecurityException if there is a SecurityManager and the
355     * caller's permissions do not include or imply <code>{@link
356     * MBeanServerPermission}("findMBeanServer")</code>.
357     */
358    public synchronized static
359            ArrayList<MBeanServer> findMBeanServer(String agentId) {
360
361        checkPermission("findMBeanServer");
362
363        if (agentId == null)
364            return new ArrayList<MBeanServer>(mBeanServerList);
365
366        ArrayList<MBeanServer> result = new ArrayList<MBeanServer>();
367        for (MBeanServer mbs : mBeanServerList) {
368            String name = mBeanServerId(mbs);
369            if (agentId.equals(name))
370                result.add(mbs);
371        }
372        return result;
373    }
374
375    /**
376     * Return the ClassLoaderRepository used by the given MBeanServer.
377     * This method is equivalent to {@link
378     * MBeanServer#getClassLoaderRepository() server.getClassLoaderRepository()}.
379     * @param server The MBeanServer under examination. Since JMX 1.2,
380     * if <code>server</code> is <code>null</code>, the result is a
381     * {@link NullPointerException}.  This behavior differs from what
382     * was implemented in JMX 1.1 - where the possibility to use
383     * <code>null</code> was deprecated.
384     * @return The Class Loader Repository used by the given MBeanServer.
385     * @exception SecurityException if there is a SecurityManager and
386     * the caller's permissions do not include or imply <code>{@link
387     * MBeanPermission}("getClassLoaderRepository")</code>.
388     *
389     * @exception NullPointerException if <code>server</code> is null.
390     *
391     **/
392    public static ClassLoaderRepository getClassLoaderRepository(
393            MBeanServer server) {
394        return server.getClassLoaderRepository();
395    }
396
397    private static String mBeanServerId(MBeanServer mbs) {
398        try {
399            return (String) mbs.getAttribute(MBeanServerDelegate.DELEGATE_NAME,
400                    "MBeanServerId");
401        } catch (JMException e) {
402            JmxProperties.MISC_LOGGER.log(Level.TRACE,
403                    "Ignoring exception while getting MBeanServerId: "+e);
404            return null;
405        }
406    }
407
408    private static void checkPermission(String action)
409    throws SecurityException {
410        SecurityManager sm = System.getSecurityManager();
411        if (sm != null) {
412            Permission perm = new MBeanServerPermission(action);
413            sm.checkPermission(perm);
414        }
415    }
416
417    private static synchronized void addMBeanServer(MBeanServer mbs) {
418        mBeanServerList.add(mbs);
419    }
420
421    private static synchronized void removeMBeanServer(MBeanServer mbs) {
422        boolean removed = mBeanServerList.remove(mbs);
423        if (!removed) {
424            MBEANSERVER_LOGGER.log(Level.TRACE,
425                    "MBeanServer was not in list!");
426            throw new IllegalArgumentException("MBeanServer was not in list!");
427        }
428    }
429
430    private static final ArrayList<MBeanServer> mBeanServerList =
431            new ArrayList<MBeanServer>();
432
433    /**
434     * Load the builder class through the context class loader.
435     * @param builderClassName The name of the builder class.
436     **/
437    private static Class<?> loadBuilderClass(String builderClassName)
438    throws ClassNotFoundException {
439        final ClassLoader loader =
440                Thread.currentThread().getContextClassLoader();
441
442        if (loader != null) {
443            // Try with context class loader
444            return loader.loadClass(builderClassName);
445        }
446
447        // No context class loader? Try with Class.forName()
448        return ReflectUtil.forName(builderClassName);
449    }
450
451    /**
452     * Creates the initial builder according to the
453     * javax.management.builder.initial System property - if specified.
454     * If any checked exception needs to be thrown, it is embedded in
455     * a JMRuntimeException.
456     **/
457    private static MBeanServerBuilder newBuilder(Class<?> builderClass) {
458        try {
459            @SuppressWarnings("deprecation")
460            final Object abuilder = builderClass.newInstance();
461            return (MBeanServerBuilder)abuilder;
462        } catch (RuntimeException x) {
463            throw x;
464        } catch (Exception x) {
465            final String msg =
466                    "Failed to instantiate a MBeanServerBuilder from " +
467                    builderClass + ": " + x;
468            throw new JMRuntimeException(msg, x);
469        }
470    }
471
472    /**
473     * Instantiate a new builder according to the
474     * javax.management.builder.initial System property - if needed.
475     **/
476    private static synchronized void checkMBeanServerBuilder() {
477        try {
478            GetPropertyAction act =
479                    new GetPropertyAction(JMX_INITIAL_BUILDER);
480            String builderClassName = AccessController.doPrivileged(act);
481
482            try {
483                final Class<?> newBuilderClass;
484                if (builderClassName == null || builderClassName.length() == 0)
485                    newBuilderClass = MBeanServerBuilder.class;
486                else
487                    newBuilderClass = loadBuilderClass(builderClassName);
488
489                // Check whether a new builder needs to be created
490                if (builder != null) {
491                    final Class<?> builderClass = builder.getClass();
492                    if (newBuilderClass == builderClass)
493                        return; // no need to create a new builder...
494                }
495
496                // Create a new builder
497                builder = newBuilder(newBuilderClass);
498            } catch (ClassNotFoundException x) {
499                final String msg =
500                        "Failed to load MBeanServerBuilder class " +
501                        builderClassName + ": " + x;
502                throw new JMRuntimeException(msg, x);
503            }
504        } catch (RuntimeException x) {
505            if (MBEANSERVER_LOGGER.isLoggable(Level.DEBUG)) {
506                StringBuilder strb = new StringBuilder()
507                .append("Failed to instantiate MBeanServerBuilder: ").append(x)
508                .append("\n\t\tCheck the value of the ")
509                .append(JMX_INITIAL_BUILDER).append(" property.");
510                MBEANSERVER_LOGGER.log(Level.DEBUG, strb::toString);
511            }
512            throw x;
513        }
514    }
515
516    /**
517     * Get the current {@link javax.management.MBeanServerBuilder},
518     * as specified by the current value of the
519     * javax.management.builder.initial property.
520     *
521     * This method consults the property and instantiates a new builder
522     * if needed.
523     *
524     * @return the new current {@link javax.management.MBeanServerBuilder}.
525     *
526     * @exception SecurityException if there is a SecurityManager and
527     * the caller's permissions do not make it possible to instantiate
528     * a new builder.
529     * @exception JMRuntimeException if the builder instantiation
530     *   fails with a checked exception -
531     *   {@link java.lang.ClassNotFoundException} etc...
532     *
533     **/
534    private static synchronized MBeanServerBuilder getNewMBeanServerBuilder() {
535        checkMBeanServerBuilder();
536        return builder;
537    }
538
539}
540