--- old/src/java.logging/share/classes/java/util/logging/LogManager.java 2014-09-15 17:58:06.000000000 +0200 +++ new/src/java.logging/share/classes/java/util/logging/LogManager.java 2014-09-15 17:58:06.000000000 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -169,6 +169,9 @@ // True if JVM death is imminent and the exit hook has been called. private boolean deathImminent; + private final Map listeners = + Collections.synchronizedMap(new IdentityHashMap<>()); + static { manager = AccessController.doPrivileged(new PrivilegedAction() { @Override @@ -1168,7 +1171,8 @@ * Any log level definitions in the new configuration file will be * applied using Logger.setLevel(), if the target Logger exists. *

- * A PropertyChangeEvent will be fired after the properties are read. + * Any {@linkplain #addConfigurationListener registered configuration + * listener} will be invoked after the properties are read. * * @exception SecurityException if a security manager exists and if * the caller does not have LoggingPermission("control"). @@ -1302,7 +1306,8 @@ /** * Reinitialize the logging properties and reread the logging configuration * from the given stream, which should be in java.util.Properties format. - * A PropertyChangeEvent will be fired after the properties are read. + * Any {@linkplain #addConfigurationListener registered configuration + * listener} will be invoked after the properties are read. *

* Any log level definitions in the new configuration file will be * applied using Logger.setLevel(), if the target Logger exists. @@ -1335,10 +1340,14 @@ // Set levels on any pre-existing loggers, based on the new properties. setLevelsOnExistingLoggers(); - // Note that we need to reinitialize global handles when - // they are first referenced. - synchronized (this) { - initializedGlobalHandlers = false; + try { + invokeConfigurationListeners(); + } finally { + // Note that we need to reinitialize global handles when + // they are first referenced. + synchronized (this) { + initializedGlobalHandlers = false; + } } } @@ -1620,4 +1629,82 @@ } return loggingMXBean; } + + /** + * Adds a configuration listener to be invoked each time the logging + * configuration is read. + * If the listener is already registered the method does nothing. + *

+ * The listener is invoked with privileges that are restricted by the + * calling context of this method. + * The order in which the listeners are invoked is unspecified. + *

+ * It is recommended that listeners do not throw errors or exceptions. + * + * If a listener terminates with an uncaught error or exception then + * the first exception that was raised will be propagated to the caller of + * {@link #readConfiguration()} (or {@link #readConfiguration(java.io.InputStream)}) + * after all listeners have been invoked. + * + * @param listener A configuration listener that will be invoked after the + * configuration changed. + * @return This LogManager. + * @throws SecurityException if a security manager exists and if the + * caller does not have LoggingPermission("control"). + * @throws NullPointerException if the listener is null. + * + * @since 1.9 + */ + public LogManager addConfigurationListener(Runnable listener) { + final Runnable r = Objects.requireNonNull(listener); + checkPermission(); + final SecurityManager sm = System.getSecurityManager(); + final AccessControlContext acc = + sm == null ? null : AccessController.getContext(); + final PrivilegedAction pa = + acc == null ? null : () -> { r.run() ; return null; }; + final Runnable pr = + acc == null ? r : () -> AccessController.doPrivileged(pa, acc); + // Will do nothing if already registered. + listeners.putIfAbsent(r, pr); + return this; + } + + /** + * Removes a previously registered configuration listener. + * + * Returns silently if the listener is not found. + * + * @param listener the configuration listener to remove. + * @throws NullPointerException if the listener is null. + * @throws SecurityException if a security manager exists and if the + * caller does not have LoggingPermission("control"). + * + * @since 1.9 + */ + public void removeConfigurationListener(Runnable listener) { + final Runnable key = Objects.requireNonNull(listener); + checkPermission(); + listeners.remove(key); + } + + private void invokeConfigurationListeners() { + Throwable t = null; + for (Runnable c : listeners.values().toArray(new Runnable[0])) { + try { + c.run(); + } catch (ThreadDeath death) { + throw death; + } catch (Error | RuntimeException x) { + if (t == null) t = x; + else t.addSuppressed(x); + } + } + // Listeners are not supposed to throw exceptions, but if that + // happens, we will rethrow the first error or exception that is raised + // after all listeners have been invoked. + if (t instanceof Error) throw (Error)t; + if (t instanceof RuntimeException) throw (RuntimeException)t; + } + }