8139982: Re-examine java.management dependency on java.util.logging.LoggingMXBean
authordfuchs
Tue, 17 May 2016 19:24:46 +0200
changeset 38370 61f46e1e7aee
parent 38369 73280b96de24
child 38371 da16c8571a28
8139982: Re-examine java.management dependency on java.util.logging.LoggingMXBean Summary: The logging MXBean implementation no longer implements java.util.logging.LoggingMXBean. java.lang.management.PlatformLoggingMXBean is used instead. java.util.logging.LoggingMXBean and java.util.logging.LogManager::getLoggingMXBean are now deprecated. Types and accessors defined in java.lang.management should be used instead. Reviewed-by: mchung
jdk/src/java.logging/share/classes/java/util/logging/LogManager.java
jdk/src/java.logging/share/classes/java/util/logging/Logging.java
jdk/src/java.logging/share/classes/java/util/logging/LoggingMXBean.java
jdk/src/java.management/share/classes/java/lang/management/ManagementFactory.java
jdk/src/java.management/share/classes/java/lang/management/PlatformLoggingMXBean.java
jdk/src/java.management/share/classes/sun/management/ManagementFactoryHelper.java
jdk/test/java/lang/management/PlatformLoggingMXBean/PlatformLoggingMXBeanTest.java
--- a/jdk/src/java.logging/share/classes/java/util/logging/LogManager.java	Tue May 17 07:43:45 2016 -0700
+++ b/jdk/src/java.logging/share/classes/java/util/logging/LogManager.java	Tue May 17 19:24:46 2016 +0200
@@ -2506,15 +2506,12 @@
         }
     }
 
-    // Management Support
-    private static LoggingMXBean loggingMXBean = null;
     /**
      * String representation of the
      * {@link javax.management.ObjectName} for the management interface
      * for the logging facility.
      *
      * @see java.lang.management.PlatformLoggingMXBean
-     * @see java.util.logging.LoggingMXBean
      *
      * @since 1.5
      */
@@ -2523,24 +2520,21 @@
 
     /**
      * Returns {@code LoggingMXBean} for managing loggers.
-     * An alternative way to manage loggers is through the
-     * {@link java.lang.management.PlatformLoggingMXBean} interface
-     * that can be obtained by calling:
-     * <pre>
-     *     PlatformLoggingMXBean logging = {@link java.lang.management.ManagementFactory#getPlatformMXBean(Class)
-     *         ManagementFactory.getPlatformMXBean}(PlatformLoggingMXBean.class);
-     * </pre>
      *
      * @return a {@link LoggingMXBean} object.
      *
+     * @deprecated {@code java.util.logging.LoggingMXBean} is deprecated and
+     *      replaced with {@code java.lang.management.PlatformLoggingMXBean}. Use
+     *      {@link java.lang.management.ManagementFactory#getPlatformMXBean(Class)
+     *      ManagementFactory.getPlatformMXBean}(PlatformLoggingMXBean.class)
+     *      instead.
+     *
      * @see java.lang.management.PlatformLoggingMXBean
      * @since 1.5
      */
+    @Deprecated(since="9")
     public static synchronized LoggingMXBean getLoggingMXBean() {
-        if (loggingMXBean == null) {
-            loggingMXBean =  new Logging();
-        }
-        return loggingMXBean;
+        return Logging.getInstance();
     }
 
     /**
--- a/jdk/src/java.logging/share/classes/java/util/logging/Logging.java	Tue May 17 07:43:45 2016 -0700
+++ b/jdk/src/java.logging/share/classes/java/util/logging/Logging.java	Tue May 17 19:24:46 2016 +0200
@@ -44,16 +44,18 @@
  * @see Logger
  * @see LogManager
  */
-class Logging implements LoggingMXBean {
+@SuppressWarnings("deprecation") // implements LoggingMXBean
+final class Logging implements LoggingMXBean {
 
     private static LogManager logManager = LogManager.getLogManager();
 
     /** Constructor of Logging which is the implementation class
      *  of LoggingMXBean.
      */
-    Logging() {
+    private Logging() {
     }
 
+    @Override
     public List<String> getLoggerNames() {
         Enumeration<String> loggers = logManager.getLoggerNames();
         ArrayList<String> array = new ArrayList<>();
@@ -65,6 +67,7 @@
     }
 
     private static String EMPTY_STRING = "";
+    @Override
     public String getLoggerLevel(String loggerName) {
         Logger l = logManager.getLogger(loggerName);
         if (l == null) {
@@ -79,6 +82,7 @@
         }
     }
 
+    @Override
     public void setLoggerLevel(String loggerName, String levelName) {
         if (loggerName == null) {
             throw new NullPointerException("loggerName is null");
@@ -102,6 +106,7 @@
         logger.setLevel(level);
     }
 
+    @Override
     public String getParentLoggerName( String loggerName ) {
         Logger l = logManager.getLogger( loggerName );
         if (l == null) {
@@ -116,4 +121,11 @@
             return p.getName();
         }
     }
+
+    static Logging getInstance() {
+        return INSTANCE;
+    }
+
+    private static final Logging INSTANCE = new Logging();
+
 }
--- a/jdk/src/java.logging/share/classes/java/util/logging/LoggingMXBean.java	Tue May 17 07:43:45 2016 -0700
+++ b/jdk/src/java.logging/share/classes/java/util/logging/LoggingMXBean.java	Tue May 17 19:24:46 2016 +0200
@@ -27,30 +27,23 @@
 
 
 /**
- * The management interface for the logging facility. It is recommended
- * to use the {@link java.lang.management.PlatformLoggingMXBean} management
- * interface that implements all attributes defined in this
- * {@code LoggingMXBean}.  The
- * {@link java.lang.management.ManagementFactory#getPlatformMXBean(Class)
- * ManagementFactory.getPlatformMXBean} method can be used to obtain
- * the {@code PlatformLoggingMXBean} object representing the management
- * interface for logging.
+ * The management interface for the logging facility.
  *
- * <p>There is a single global instance of the {@code LoggingMXBean}.
- * This instance is an {@link javax.management.MXBean MXBean} that
- * can be obtained by calling the {@link LogManager#getLoggingMXBean}
- * method or from the
- * {@linkplain java.lang.management.ManagementFactory#getPlatformMBeanServer
+ * {@link java.lang.management.PlatformLoggingMXBean
+ * java.lang.management.PlatformLoggingMXBean} is the management interface
+ * for logging facility registered in the {@link
+ * java.lang.management.ManagementFactory#getPlatformMBeanServer()
  * platform MBeanServer}.
- * <p>
- * The {@link javax.management.ObjectName ObjectName} that uniquely identifies
- * the management interface for logging within the {@code MBeanServer} is:
- * <pre>
- *    {@link LogManager#LOGGING_MXBEAN_NAME java.util.logging:type=Logging}
- * </pre>
- * <p>
- * The instance registered in the platform {@code MBeanServer}
- * is also a {@link java.lang.management.PlatformLoggingMXBean}.
+ * It is recommended to use the {@code PlatformLoggingMXBean} obtained via
+ * the {@link java.lang.management.ManagementFactory#getPlatformMXBean(Class)
+ * ManagementFactory.getPlatformMXBean(PlatformLoggingMXBean.class)} method.
+ *
+ * @deprecated {@code LoggingMXBean} is no longer a {@link
+ * java.lang.management.PlatformManagedObject platform MXBean} and is replaced
+ * with {@link java.lang.management.PlatformLoggingMXBean}.
+ * It will not register in the platform {@code MBeanServer}.
+ * Use {@code ManagementFactory.getPlatformMXBean(PlatformLoggingMXBean.class)}
+ * instead.
  *
  * @author  Ron Mann
  * @author  Mandy Chung
@@ -58,6 +51,7 @@
  *
  * @see java.lang.management.PlatformLoggingMXBean
  */
+@Deprecated(since="9")
 public interface LoggingMXBean {
 
     /**
--- a/jdk/src/java.management/share/classes/java/lang/management/ManagementFactory.java	Tue May 17 07:43:45 2016 -0700
+++ b/jdk/src/java.management/share/classes/java/lang/management/ManagementFactory.java	Tue May 17 19:24:46 2016 +0200
@@ -598,9 +598,8 @@
 
         try {
             final ObjectName objName = new ObjectName(mxbeanName);
-            // skip the isInstanceOf check for LoggingMXBean
             String intfName = mxbeanInterface.getName();
-            if (!connection.isInstanceOf(objName, intfName)) {
+            if (!isInstanceOf(connection, objName, intfName)) {
                 throw new IllegalArgumentException(mxbeanName +
                     " is not an instance of " + mxbeanInterface);
             }
@@ -616,6 +615,33 @@
         }
     }
 
+    // This makes it possible to obtain an instance of LoggingMXBean
+    // using newPlatformMXBeanProxy(mbs, on, LoggingMXBean.class)
+    // even though the underlying MXBean no longer implements
+    // java.util.logging.LoggingMXBean.
+    // Altough java.util.logging.LoggingMXBean is deprecated, an application
+    // that uses newPlatformMXBeanProxy(mbs, on, LoggingMXBean.class) will
+    // continue to work.
+    //
+    private static boolean isInstanceOf(MBeanServerConnection connection,
+            ObjectName objName, String intfName)
+            throws InstanceNotFoundException, IOException
+    {
+        // special case for java.util.logging.LoggingMXBean.
+        // java.util.logging.LoggingMXBean is deprecated and
+        // replaced with java.lang.management.PlatformLoggingMXBean,
+        // so we will consider that any MBean implementing
+        // java.lang.management.PlatformLoggingMXBean also implements
+        // java.util.logging.LoggingMXBean.
+        if ("java.util.logging.LoggingMXBean".equals(intfName)) {
+            if (connection.isInstanceOf(objName,
+                    PlatformLoggingMXBean.class.getName())) {
+                return true;
+            }
+        }
+        return connection.isInstanceOf(objName, intfName);
+    }
+
     /**
      * Returns the platform MXBean implementing
      * the given {@code mxbeanInterface} which is specified
--- a/jdk/src/java.management/share/classes/java/lang/management/PlatformLoggingMXBean.java	Tue May 17 07:43:45 2016 -0700
+++ b/jdk/src/java.management/share/classes/java/lang/management/PlatformLoggingMXBean.java	Tue May 17 19:24:46 2016 +0200
@@ -44,10 +44,6 @@
  *      {@link java.util.logging.LogManager#LOGGING_MXBEAN_NAME java.util.logging:type=Logging}
  * </pre>
  *
- * <p>The instance registered in the platform {@code MBeanServer} with
- * this {@code ObjectName} implements all attributes defined by
- * {@link java.util.logging.LoggingMXBean}.
- *
  * @since   1.7
  */
 public interface PlatformLoggingMXBean extends PlatformManagedObject {
--- a/jdk/src/java.management/share/classes/sun/management/ManagementFactoryHelper.java	Tue May 17 07:43:45 2016 -0700
+++ b/jdk/src/java.management/share/classes/sun/management/ManagementFactoryHelper.java	Tue May 17 19:24:46 2016 +0200
@@ -26,6 +26,8 @@
 package sun.management;
 
 import java.lang.management.*;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import javax.management.InstanceAlreadyExistsException;
 import javax.management.InstanceNotFoundException;
 import javax.management.MBeanServer;
@@ -43,9 +45,13 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Module;
+import java.lang.reflect.UndeclaredThrowableException;
 import java.security.PrivilegedAction;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
 
 /**
  * ManagementFactoryHelper provides static factory methods to create
@@ -66,6 +72,7 @@
         return jvm;
     }
 
+    static final String LOGGING_MXBEAN_NAME = "java.util.logging:type=Logging";
     private static ClassLoadingImpl    classMBean = null;
     private static MemoryImpl          memoryMBean = null;
     private static ThreadImpl          threadMBean = null;
@@ -145,74 +152,138 @@
     }
 
     public static PlatformLoggingMXBean getPlatformLoggingMXBean() {
-        if (LoggingMXBeanSupport.isAvailable()) {
-            return PlatformLoggingImpl.instance;
+        if (LoggingMXBeanAccess.isAvailable()) {
+            return PlatformLoggingImpl.MBEAN;
         } else {
             return null;
         }
     }
 
     public static boolean isPlatformLoggingMXBeanAvailable() {
-        return LoggingMXBeanSupport.isAvailable();
+        return LoggingMXBeanAccess.isAvailable();
     }
 
-    /**
-     * The logging MXBean object is an instance of
-     * PlatformLoggingMXBean and java.util.logging.LoggingMXBean
-     * but it can't directly implement two MXBean interfaces
-     * as a compliant MXBean implements exactly one MXBean interface,
-     * or if it implements one interface that is a subinterface of
-     * all the others; otherwise, it is a non-compliant MXBean
-     * and MBeanServer will throw NotCompliantMBeanException.
-     * See the Definition of an MXBean section in javax.management.MXBean spec.
-     *
-     * To create a compliant logging MXBean, define a LoggingMXBean interface
-     * that extend PlatformLoggingMXBean and j.u.l.LoggingMXBean
-    */
-    public interface LoggingMXBean
-        extends PlatformLoggingMXBean, java.util.logging.LoggingMXBean {
-    }
-
-    // This is a trick: if java.util.logging is not present then
-    // attempting to access something that implements
-    // java.util.logging.LoggingMXBean will trigger a CNFE.
-    // So we cannot directly call any static method or access any static field
-    // on PlatformLoggingImpl, as we would risk raising a CNFE.
-    // Instead we use this intermediate LoggingMXBeanSupport class to determine
+    // The LoggingMXBeanAccess class uses reflection to determine
     // whether java.util.logging is present, and load the actual LoggingMXBean
     // implementation.
     //
-    static final class LoggingMXBeanSupport {
-        final static Object loggingImpl =
-                AccessController.doPrivileged(new PrivilegedAction<Object>() {
-            @Override
-            public Object run() {
-                try {
-                    // create a LoggingProxyImpl instance when
-                    // java.util.logging classes exist
-                    Class<?> c = Class.forName("java.util.logging.Logging", true, null);
-                    Constructor<?> cons = c.getDeclaredConstructor();
-                    cons.setAccessible(true);
-                    return cons.newInstance();
-                } catch (ClassNotFoundException cnf) {
-                    return null;
-                } catch (NoSuchMethodException | InstantiationException
-                        | IllegalAccessException | InvocationTargetException e) {
-                    throw new AssertionError(e);
-                }
-            }});
+    static final class LoggingMXBeanAccess {
+
+        final static String LOG_MANAGER_CLASS_NAME = "java.util.logging.LogManager";
+        final static String LOGGING_MXBEAN_CLASS_NAME = "java.util.logging.LoggingMXBean";
+        final static Class<?> LOG_MANAGER_CLASS = loadLoggingClass(LOG_MANAGER_CLASS_NAME);
 
         static boolean isAvailable() {
-            return loggingImpl != null;
+            return LOG_MANAGER_CLASS != null;
+        }
+
+        private static Class<?> loadLoggingClass(String className) {
+            return AccessController.doPrivileged(new PrivilegedAction<>() {
+                @Override
+                public Class<?> run() {
+                    Optional<Module> logging = java.lang.reflect.Layer.boot()
+                        .findModule("java.logging");
+                    if (logging.isPresent()) {
+                        return Class.forName(logging.get(), className);
+                    }
+                    return null;
+                }
+            });
+        }
+
+        private Map<String, Method> initMethodMap(Object impl) {
+            if (impl == null) {
+                return Collections.emptyMap();
+            }
+            Class<?> intfClass = loadLoggingClass(LOGGING_MXBEAN_CLASS_NAME);
+            final Map<String, Method> methodsMap = new HashMap<>();
+            for (Method m : intfClass.getMethods()) {
+                try {
+                    // Sanity checking: all public methods present in
+                    // java.util.logging.LoggingMXBean should
+                    // also be in PlatformLoggingMXBean
+                    Method specMethod = PlatformLoggingMXBean.class
+                             .getMethod(m.getName(), m.getParameterTypes());
+                    if (specMethod.getReturnType().isAssignableFrom(m.getReturnType())) {
+                        if (methodsMap.putIfAbsent(m.getName(), m) != null) {
+                            throw new RuntimeException("unexpected polymorphic method: "
+                                     + m.getName());
+                        }
+                    }
+                } catch (NoSuchMethodException x) {
+                    // All methods in java.util.logging.LoggingMXBean should
+                    // also be in PlatformLoggingMXBean
+                    throw new InternalError(x);
+                }
+            }
+            return Collections.unmodifiableMap(methodsMap);
         }
+
+        private static Object getMXBeanImplementation() {
+            if (!isAvailable()) {
+                // should not happen
+                throw new NoClassDefFoundError(LOG_MANAGER_CLASS_NAME);
+            }
+            try {
+                final Method m = LOG_MANAGER_CLASS.getMethod("getLoggingMXBean");
+                return m.invoke(null);
+            } catch (NoSuchMethodException
+                    | IllegalAccessException
+                    | InvocationTargetException x) {
+                throw new ExceptionInInitializerError(x);
+            }
+         }
+
+        // The implementation object, which will be invoked through
+        // reflection. The implementation does not need to implement
+        // PlatformLoggingMXBean, but must declare the same methods
+        // with same signatures, and they must be public, with one
+        // exception:
+        // getObjectName will not be called on the implementation object,
+        // so the implementation object does not need to declare such
+        // a method.
+        final Object impl = getMXBeanImplementation();
+        final Map<String, Method> methods = initMethodMap(impl);
+
+        LoggingMXBeanAccess() {
+        }
+
+        <T> T invoke(String methodName, Object... args) {
+            Method m = methods.get(methodName);
+            if (m == null) {
+                throw new UnsupportedOperationException(methodName);
+            }
+            try {
+                @SuppressWarnings("unchecked")
+                T result = (T) m.invoke(impl, args);
+                return result;
+            } catch (IllegalAccessException ex) {
+                throw new UnsupportedOperationException(ex);
+            } catch (InvocationTargetException ex) {
+                throw unwrap(ex);
+            }
+        }
+
+        private static RuntimeException unwrap(InvocationTargetException x) {
+            Throwable t = x.getCause();
+            if (t instanceof RuntimeException) {
+                return (RuntimeException)t;
+            }
+            if (t instanceof Error) {
+                throw (Error)t;
+            }
+            return new UndeclaredThrowableException(t == null ? x : t);
+        }
+
+
     }
 
-    static class PlatformLoggingImpl implements LoggingMXBean
-    {
-        final static java.util.logging.LoggingMXBean impl =
-                (java.util.logging.LoggingMXBean) LoggingMXBeanSupport.loggingImpl;
-        final static PlatformLoggingMXBean instance = new PlatformLoggingImpl();
-        final static String LOGGING_MXBEAN_NAME = "java.util.logging:type=Logging";
+    static final class PlatformLoggingImpl implements PlatformLoggingMXBean {
+
+        private final LoggingMXBeanAccess loggingAccess;
+        private PlatformLoggingImpl(LoggingMXBeanAccess loggingAccess) {
+            this.loggingAccess = loggingAccess;
+        }
 
         private volatile ObjectName objname;  // created lazily
         @Override
@@ -232,23 +303,29 @@
 
         @Override
         public java.util.List<String> getLoggerNames() {
-            return impl.getLoggerNames();
+            return loggingAccess.invoke("getLoggerNames");
         }
 
         @Override
         public String getLoggerLevel(String loggerName) {
-            return impl.getLoggerLevel(loggerName);
+            return loggingAccess.invoke("getLoggerLevel", loggerName);
         }
 
         @Override
         public void setLoggerLevel(String loggerName, String levelName) {
-            impl.setLoggerLevel(loggerName, levelName);
+            loggingAccess.invoke("setLoggerLevel", loggerName, levelName);
         }
 
         @Override
         public String getParentLoggerName(String loggerName) {
-            return impl.getParentLoggerName(loggerName);
+            return loggingAccess.invoke("getParentLoggerName", loggerName);
         }
+
+        private static PlatformLoggingImpl getInstance() {
+            return new PlatformLoggingImpl(new LoggingMXBeanAccess());
+         }
+
+        static final PlatformLoggingMXBean MBEAN = getInstance();
     }
 
     private static List<BufferPoolMXBean> bufferPools = null;
--- a/jdk/test/java/lang/management/PlatformLoggingMXBean/PlatformLoggingMXBeanTest.java	Tue May 17 07:43:45 2016 -0700
+++ b/jdk/test/java/lang/management/PlatformLoggingMXBean/PlatformLoggingMXBeanTest.java	Tue May 17 19:24:46 2016 +0200
@@ -270,8 +270,7 @@
         // Calling getMBeanInfo will throw exception if not found.
         platformMBS.getMBeanInfo(objName);
 
-        if (!platformMBS.isInstanceOf(objName, "java.lang.management.PlatformLoggingMXBean") ||
-            !platformMBS.isInstanceOf(objName, "java.util.logging.LoggingMXBean")) {
+        if (!platformMBS.isInstanceOf(objName, "java.lang.management.PlatformLoggingMXBean")) {
             throw new RuntimeException(objName + " is of unexpected type");
         }