--- old/src/java.logging/share/classes/java/util/logging/LogManager.java 2015-09-14 18:10:46.000000000 +0200 +++ new/src/java.logging/share/classes/java/util/logging/LogManager.java 2015-09-14 18:10:46.000000000 +0200 @@ -32,8 +32,15 @@ import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.util.concurrent.ConcurrentHashMap; +import java.nio.file.Paths; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; import sun.misc.JavaAWTAccess; import sun.misc.ManagedLocalsThread; import sun.misc.SharedSecrets; @@ -788,7 +795,7 @@ // instantiation of the handler is done in the LogManager.addLogger // implementation as a handler class may be only visible to LogManager // subclass for the custom log manager case - processParentHandlers(logger, name); + processParentHandlers(logger, name, VisitedLoggers.of(null)); // Find the new node and its parent. LogNode node = getNode(name); @@ -836,7 +843,8 @@ // If logger.getUseParentHandlers() returns 'true' and any of the logger's // parents have levels or handlers defined, make sure they are instantiated. - private void processParentHandlers(final Logger logger, final String name) { + private void processParentHandlers(final Logger logger, final String name, + BiPredicate visited) { final LogManager owner = getOwner(); AccessController.doPrivileged(new PrivilegedAction() { @Override @@ -862,7 +870,9 @@ owner.getProperty(pname + ".handlers") != null) { // This pname has a level/handlers definition. // Make sure it exists. - demandLogger(pname, null, null); + if (visited.test(demandLogger(pname, null, null), pname)) { + break; + } } ix = ix2+1; } @@ -942,46 +952,62 @@ private void loadLoggerHandlers(final Logger logger, final String name, final String handlersPropertyName) { - AccessController.doPrivileged(new PrivilegedAction() { + AccessController.doPrivileged(new PrivilegedAction() { @Override - public Object run() { - String names[] = parseClassNames(handlersPropertyName); - final boolean ensureCloseOnReset = names.length > 0 + public Void run() { + setLoggerHandlers(logger, name, handlersPropertyName, + createLoggerHandlers(name, handlersPropertyName)); + return null; + } + }); + } + + private void setLoggerHandlers(final Logger logger, final String name, + final String handlersPropertyName, + List handlers) + { + final boolean ensureCloseOnReset = ! handlers.isEmpty() && getBooleanProperty(handlersPropertyName + ".ensureCloseOnReset",true); + int count = 0; + for (Handler hdl : handlers) { + logger.addHandler(hdl); + if (++count == 1 && ensureCloseOnReset) { + // add this logger to the closeOnResetLoggers list. + closeOnResetLoggers.addIfAbsent(CloseOnReset.create(logger)); + } + } + } - int count = 0; - for (String type : names) { - try { - Class clz = ClassLoader.getSystemClassLoader().loadClass(type); - Handler hdl = (Handler) clz.newInstance(); - // Check if there is a property defining the - // this handler's level. - String levs = getProperty(type + ".level"); - if (levs != null) { - Level l = Level.findLevel(levs); - if (l != null) { - hdl.setLevel(l); - } else { - // Probably a bad level. Drop through. - System.err.println("Can't set level for " + type); - } - } - // Add this Handler to the logger - logger.addHandler(hdl); - if (++count == 1 && ensureCloseOnReset) { - // add this logger to the closeOnResetLoggers list. - closeOnResetLoggers.addIfAbsent(CloseOnReset.create(logger)); - } - } catch (Exception ex) { - System.err.println("Can't load log handler \"" + type + "\""); - System.err.println("" + ex); - ex.printStackTrace(); + private List createLoggerHandlers(final String name, final String handlersPropertyName) + { + String names[] = parseClassNames(handlersPropertyName); + List handlers = new ArrayList<>(names.length); + for (String type : names) { + try { + Class clz = ClassLoader.getSystemClassLoader().loadClass(type); + Handler hdl = (Handler) clz.newInstance(); + // Check if there is a property defining the + // this handler's level. + String levs = getProperty(type + ".level"); + if (levs != null) { + Level l = Level.findLevel(levs); + if (l != null) { + hdl.setLevel(l); + } else { + // Probably a bad level. Drop through. + System.err.println("Can't set level for " + type); } } - - return null; + // Add this Handler to the logger + handlers.add(hdl); + } catch (Exception ex) { + System.err.println("Can't load log handler \"" + type + "\""); + System.err.println("" + ex); + ex.printStackTrace(); } - }); + } + + return handlers; } @@ -1254,6 +1280,17 @@ * Any {@linkplain #addConfigurationListener registered configuration * listener} will be invoked after the properties are read. * + * @apiNote {@code readConfiguration} is principally useful for + * establishing the LogManager primordial configuration. + *

+ * Calling this method directly from the application code after the + * LogManager has been initialized is discouraged. + * + * Applications that wish to update the LogManager + * configuration after the LogManager has been initialized should + * call {@link #updateConfiguration(java.util.function.Function)} + * instead. + * * @exception SecurityException if a security manager exists and if * the caller does not have LoggingPermission("control"). * @exception IOException if there are IO problems reading the configuration. @@ -1284,20 +1321,24 @@ } } + String fname = getConfigurationFileName(); + try (final InputStream in = new FileInputStream(fname)) { + final BufferedInputStream bin = new BufferedInputStream(in); + readConfiguration(bin); + } + } + + String getConfigurationFileName() throws IOException { String fname = System.getProperty("java.util.logging.config.file"); if (fname == null) { fname = System.getProperty("java.home"); if (fname == null) { throw new Error("Can't find java.home ??"); } - File f = new File(fname, "conf"); - f = new File(f, "logging.properties"); - fname = f.getCanonicalPath(); - } - try (final InputStream in = new FileInputStream(fname)) { - final BufferedInputStream bin = new BufferedInputStream(in); - readConfiguration(bin); + fname = Paths.get(fname, "conf", "logging.properties") + .toAbsolutePath().normalize().toString(); } + return fname; } /** @@ -1429,6 +1470,17 @@ * Any log level definitions in the new configuration file will be * applied using Logger.setLevel(), if the target Logger exists. * + * @apiNote {@code readConfiguration} is principally useful for applications + * which use the {@code "java.util.logging.config.class"} property + * to establish their own custom configuration. + *

+ * Calling this method directly from the application code after the + * LogManager has been initialized is discouraged. + * Applications that wish to update the LogManager configuration after + * the LogManager has been initialized should call + * {@link #updateConfiguration(java.io.InputStream, java.util.function.Function)} + * instead. + * * @param ins stream to read properties from * @exception SecurityException if a security manager exists and if * the caller does not have LoggingPermission("control"). @@ -1506,6 +1558,562 @@ invokeConfigurationListeners(); } + // This enum enumerate the configuration properties that will be + // re-established on existing loggers when the configuration is updated + // with LogManager.updateConfiguration(). + // + // Note that this works properly only for the global LogManager - as + // Handler and its subclasses get their configuration from + // LogManager.getLogManager(). + // + static enum ConfigurationProperties implements Predicate { + LEVEL(".level"), HANDLERS(".handlers"), USEPARENT(".useParentHandlers"); + final String suffix; + final int length; + private ConfigurationProperties(String suffix) { + this.suffix = Objects.requireNonNull(suffix); + length = suffix.length(); + } + @Override + public boolean test(String key) { + if (this == HANDLERS && suffix.substring(1).equals(key)) return true; + if (this == HANDLERS && suffix.equals(key)) return false; + return key.endsWith(suffix); + } + String key(String loggerName) { + if (this == HANDLERS && (loggerName == null || loggerName.isEmpty())) { + return suffix.substring(1); + } + return loggerName + suffix; + } + String loggerName(String key) { + assert key.equals(suffix.substring(1)) && this == HANDLERS || key.endsWith(suffix); + if (this == HANDLERS && suffix.substring(1).equals(key)) return ""; + return key.substring(0, key.length() - length); + } + + /** + * If the property is one that should be reestablished by + * updateConfiguration, returns the name of the logger for which the + * property is configured. Otherwise, returns null. + * @param property a property key in 'props' + * @return the name of the logger on which the property is to be set, + * if the property is one that we should reestablish. + * {@code null} otherwise. + */ + static String getLoggerName(String property) { + for (ConfigurationProperties p : ConfigurationProperties.ALL) { + if (p.test(property)) { + return p.loggerName(property); + } + } + return null; // Not a property that should be reestablished. + } + + /** + * If the property is one that should be reestablished by + * updateConfiguration, returns the corresponding + * ConfigurationProperties object. Otherwise, returns null. + * @param property a property key in 'props' + * @return the corresponding ConfigurationProperties object, + * if the property is one that we should reestablish. + * {@code null} otherwise. + */ + static ConfigurationProperties of(String property) { + for (ConfigurationProperties p : ConfigurationProperties.ALL) { + if (p.test(property)) { + return p; + } + } + return null; // Not a property that should be reestablished. + } + + /** + * Returns true if the given property is one that should be reestablished. + * Used to filter property name streams. + * @param property a property key from the configuration. + * @return true if this property is of interest for updateConfiguration. + */ + static boolean isOf(String property) { + return of(property) != null; + } + + /** + * Returns true if the new property value is different from the old. + * @param k a property key in the configuration + * @param previous the old configuration + * @param next the new configuration + * @return true if the property is changing value between the two + * configurations. + */ + static boolean isChanging(String k, Properties previous, Properties next) { + final String p = trim(previous.getProperty(k, null)); + final String n = trim(next.getProperty(k, null)); + return ! Objects.equals(p,n); + } + + /** + * Applies the remapping function for the given key to the next + * configuration. + * If the remapping function is null then this method does nothing. + * Otherwise, it calls the remapping function to compute the value + * that should be associated with {@code key} in the resulting + * configuration, and applies it to {@code next}. + * If the remapping function returns {@code null} the key is removed + * from {@code next}. + * + * @param k a property key in the configuration + * @param previous the old configuration + * @param next the new configuration (modified by this function) + * @param remappingFunction the remapping function. + */ + static void merge(String k, Properties previous, Properties next, + BiFunction remappingFunction) { + if (remappingFunction != null) { + String p = trim(previous.getProperty(k, null)); + String n = trim(next.getProperty(k, null)); + String mapped = trim(remappingFunction.apply(p,n)); + if (!Objects.equals(n, mapped)) { + if (mapped == null) { + next.remove(k); + } else { + next.setProperty(k, mapped); + } + } + } + } + + private static final EnumSet ALL = + EnumSet.allOf(ConfigurationProperties.class); + } + + // trim the value if not null. + private static String trim(String value) { + return value == null ? null : value.trim(); + } + + /** + * An object that keep track of loggers we have already visited. + * Used when updating configuration, to avoid processing the same logger + * twice. + */ + static final class VisitedLoggers implements BiPredicate { + final Map visited; + private VisitedLoggers(Map visited) { + this.visited = visited; + } + @Override + public boolean test(Logger logger, String name) { + return visited != null && visited.put(logger, name) != null; + } + public void clear() { + if (visited != null) visited.clear(); + } + + public static VisitedLoggers of(IdentityHashMap visited) { + return visited == null ? NEVER : new VisitedLoggers(visited); + } + + // An object that considers that no logger has ever been visited. + // This is used when processParentHandlers is called from + // LoggerContext.addLocalLogger + static final VisitedLoggers NEVER = new VisitedLoggers(null); + } + + + /** + * Type of the modification for a given property. One of ADDED, CHANGED, + * or REMOVED. + */ + static enum ModType { + ADDED, // property had no value in the old conf, but has one in the new. + CHANGED, // property has a different value in the old conf and the new conf. + REMOVED; // property has no value in the new conf, but had one in the old. + static ModType of(String previous, String next) { + if (previous == null && next != null) { + return ADDED; + } + if (next == null && previous != null) { + return REMOVED; + } + if (!Objects.equals(trim(previous), trim(next))) { + return CHANGED; + } + return null; + } + } + + /** + * Updates an existing configuration. + *

+ * @implSpec + * This is equivalent to calling: + *

+     *   try (final InputStream in = new FileInputStream(<logging.properties>)) {
+     *       final BufferedInputStream bin = new BufferedInputStream(in);
+     *       updateConfiguration(bin, mapper);
+     *   }
+     * 
+ * where {@code } is the logging configuration file path. + *

+ * Note that this method not take into account the value of the + * {@code "java.util.logging.config.class"} property. + * The {@code "java.util.logging.config.file"} system property is read + * to find the logging properties file, whether the + * {@code "java.util.logging.config.class"} property is set or not. + * If the {@code "java.util.logging.config.file"} system property is not + * defined then the default configuration is used. + * The default configuration is typically loaded from the properties file + * "{@code conf/logging.properties}" in the Java installation directory. + * + * @param mapper Used to control how the old configuration and + * candidate configuration will be mapped into the new configuration. + * See {@link + * #updateConfiguration(java.io.InputStream, java.util.function.Function)} + * for more details. + * + * @throws SecurityException if a security manager exists and if + * the caller does not have LoggingPermission("control"), or + * does not have the permissions required to set up the + * configuration (e.g. open file specified for FileHandlers + * etc...) + * + * @throws IOException if there are problems reading from the + * logging configuration file. + */ + public void updateConfiguration(Function> mapper) + throws IOException { + checkPermission(); + String fname = getConfigurationFileName(); + try (final InputStream in = new FileInputStream(fname)) { + final BufferedInputStream bin = new BufferedInputStream(in); + updateConfiguration(bin, mapper); + } + } + + /** + * Update the logging configuration. The given {@code mapper} function + * is invoked for each configuration key in order to produce a resulting + * logging configuration to to be applied. The result of applying that new + * configuration is detailed in the table below. + * The registered {@linkplain #addConfigurationListener configuration + * listener} will be invoked after the configuration is successfully updated. + *
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Updating configuration properties
PropertyResulting Behavior
{@code .level} + *
    + *
  • If the new configuration defines a level for a logger and + * if the new level is different than the level specified in the + * the old configuration, or not specified in + * the old configuration, then the level for that logger will be + * updated, and the change propagated to the existing logger children, + * if necessary. + *
  • + *
  • If the old configuration defined a level for a logger, and the new + * configuration doesn't, then the logger level remains unchanged. + * To completely replace a configuration - the caller should therefore + * call {@link #reset() reset} to empty the current configuration, before + * calling {@code updateConfiguration}. + *
  • + *
+ *
{@code .useParentHandlers} + *
    + *
  • If either the new or the old value for the useParentHandlers property + * is not null, then if the logger exists or if children for + * that logger exist, that logger will be updated to the new value. + * The value of the useParentHandlers property is the value specified + * in the configuration; if not specified, the default is true. + *
  • + *
+ *
{@code .handlers} + *
    + *
  • If the new configuration defines a list of handlers for a logger, + * and if the new list is different than the list specified in the old + * configuration for that logger (that could be empty), if the logger + * exists or whose children exist, the handlers associated with that logger + * are closed and removed and the new handlers will be created per the new + * configuration and added to that logger. + *
  • + *
  • If the old configuration defined some handlers for a logger, and + * the new configuration doesn't, if that logger exists, + * its handlers will be removed and closed. + *
  • + *
  • Changing the list of handlers on an existing logger will cause all + * its previous handlers to be removed and closed, regardless of whether + * they had been created from the configuration or programmatically. + * The old handlers will be replaced by new handlers, if any. + *
  • + *
+ *
{@code .*} + *
    + *
  • Properties configured/changed on handler classes will only affect + * newly created handlers. If a node is configured with the same list + * of handlers in the old and the new configuration, then these handlers + * will remain unchanged. + *
  • + *
+ *
+ *

+ * To completely reestablish a configuration, an application can first call + * {@link #reset() reset} to fully remove the old configuration, followed by + * {@code updateConfiguration} to establish the new configuration. + *

+ * Note that this method has no special handling for the "config" property. + * The new value provided by the mapper function will be stored in the + * LogManager properties, but {@code updateConfiguration} will not parse its + * value nor instantiate the classes it points to. + * + * @param ins stream to read properties from + * @param mapper a functional interface that takes a configuration + * key and returns a remapping function for values associated + * with that key. The remapping function takes the old value + * and the candidate new value as parameters and produces the + * actual value to be applied. If the {@code mapper} is null, + * or if the remapping function it returns is null, then the + * candidate value will be the new value. + * A {@code null} value passed as parameter to the remapping + * function indicates that no value was present in the + * corresponding configuration. + *

+ * Examples of {@code mapper} are: + *

  • {@code (k) -> ((o, n) -> n)}: always take the new value. + * equivalent to passing null for {@code mapper}
  • + *
  • {@code (k) -> ((o, n) -> n == null ? o : n)}: keep the + * old value if the candidate configuration has no value + * for that key, otherwise take the new value. + * This is equivalent to adding the new + * configuration to the old, letting new values override + * old values, as what would be obtained with + * {@code result.putAll(old); result.putAll(candidate)}. + *
  • + *
  • {@code (k) -> ((o, n) -> o == null ? n : o)}: only + * take the new value if the old configuration didn't + * have a value for that key. + * This is equivalent to appending the new + * configuration to the old, without letting new values + * override old values, as what would be obtained with + * {@code result.putAll(candidate); result.putAll(old)}. + *
  • + *
  • + *
    {@code (k) -> k.endsWith(".handlers")}
    +     *      {@code     ? ((o, n) -> (o == null ? n : o))}
    +     *      {@code     : ((o, n) -> n)}
    do not let the new configuration + * override existing per logger handlers. Only root handlers + * can be overridden.
  • + *
+ * @throws SecurityException if a security manager exists and if + * the caller does not have LoggingPermission("control"), or + * does not have the permissions required to set up the + * configuration (e.g. open files specified for FileHandlers) + * + * @throws IOException if there are problems reading from the + * logging configuration file. + */ + public void updateConfiguration(InputStream ins, + Function> mapper) + throws IOException { + checkPermission(); + ensureLogManagerInitialized(); + drainLoggerRefQueueBounded(); + + final Properties previous; + final Set updatePropertyNames; + Properties next = new Properties(); + next.load(ins); + List cxs = Collections.emptyList(); + final VisitedLoggers visited = VisitedLoggers.of(new IdentityHashMap<>()); + + if (globalHandlersState == STATE_SHUTDOWN) return; + + // exclusive lock: readConfiguration/reset/updateConfiguration can't + // run concurrently. + // configurationLock.writeLock().lock(); + configurationLock.lock(); + try { + if (globalHandlersState == STATE_SHUTDOWN) return; + previous = props; + + // Builds a TreeSet of all (old and new) property names. + updatePropertyNames = + Stream.concat(previous.stringPropertyNames().stream(), + next.stringPropertyNames().stream()) + .collect(Collectors.toCollection(TreeSet::new)); + + if (mapper != null) { + // mapper will potentially modify the content of + // 'next', so we need to call it before affecting props=next. + // give a chance to the mapper to control all + // properties - not just those we will reset. + updatePropertyNames.stream() + .forEachOrdered(k -> ConfigurationProperties + .merge(k, previous, next, mapper.apply(k))); + } + + props = next; + + // allKeys will contain all keys: + // - which correspond to a configuration property we are interested in + // (first filter) + // - whose value is changing (new, removed, different) in the configuration + // (second filter) + final Stream allKeys = updatePropertyNames.stream() + .filter(ConfigurationProperties::isOf) + .filter(k -> ConfigurationProperties.isChanging(k, previous, next)); + + // Group configuration properties by logger name + // We use a TreeMap so that parent loggers will be visited before + // child loggers. + final Map> loggerConfigs = + allKeys.collect( + Collectors.groupingBy( + ConfigurationProperties::getLoggerName, + TreeMap::new, + Collectors.toCollection(TreeSet::new))); + + if (!loggerConfigs.isEmpty()) { + cxs = contexts(); + } + final List tmp = cxs.isEmpty() + ? Collections.emptyList() : new ArrayList<>(cxs.size()); + for (Map.Entry> e : loggerConfigs.entrySet()) { + // This can be a logger name, or something else... + // The only thing we know is that we found a property + // we are interested in. + // For instance, if we found x.y.z.level, then x.y.z could be + // a logger, but it could also be a handler class... + // Anyway... + final String name = e.getKey(); + final Set properties = e.getValue(); + tmp.clear(); + for (LoggerContext cx : cxs) { + Logger l = cx.findLogger(name); + if (l == null) continue; + if (!visited.test(l, name)) { + tmp.add(l); + } + } + if (tmp.isEmpty()) continue; + for (String pk : properties) { + ConfigurationProperties cp = ConfigurationProperties.of(pk); + String p = previous.getProperty(pk, null); + String n = next.getProperty(pk, null); + + // Determines the type of modification. + ModType mod = ModType.of(p, n); + + // mod == null means that the two values are equals, there + // is nothing to do. Usually, this should not happen as such + // properties should have been filtered above. + // It could happen however if the properties had + // trailing/leading whitespaces. + if (mod == null) continue; + + switch (cp) { + case LEVEL: + if (mod == ModType.REMOVED) continue; + Level level = Level.findLevel(trim(n)); + if (level == null) { + if (name.isEmpty()) { + rootLogger.setLevel(level); + } + for (Logger l : tmp) { + if (!name.isEmpty() || l != rootLogger) { + l.setLevel(level); + } + } + } + break; + case USEPARENT: + if (!name.isEmpty()) { + boolean useParent = getBooleanProperty(pk, true); + if (n != null || p != null) { + // reset the flag only if the previous value + // or the new value are not null. + for (Logger l : tmp) { + l.setUseParentHandlers(useParent); + } + } + } + break; + case HANDLERS: + List hdls = null; + if (name.isEmpty()) { + // special handling for the root logger. + globalHandlersState = STATE_READING_CONFIG; + try { + closeHandlers(rootLogger); + globalHandlersState = STATE_UNINITIALIZED; + } catch (Throwable t) { + globalHandlersState = STATE_INITIALIZED; + throw t; + } + } + for (Logger l : tmp) { + if (l == rootLogger) continue; + closeHandlers(l); + if (mod == ModType.REMOVED) { + closeOnResetLoggers.removeIf(c -> c.logger == l); + continue; + } + if (hdls == null) { + hdls = name.isEmpty() + ? Arrays.asList(rootLogger.getHandlers()) + : createLoggerHandlers(name, pk); + } + setLoggerHandlers(l, name, pk, hdls); + } + break; + default: break; + } + } + } + } finally { + configurationLock.unlock(); + visited.clear(); + } + + // Now ensure that if an existing logger has acquired a new parent + // in the configuration, this new parent will be created - if needed, + // and added to the context of the existing child. + // + drainLoggerRefQueueBounded(); + for (LoggerContext cx : cxs) { + for (Enumeration names = cx.getLoggerNames() ; names.hasMoreElements();) { + String name = names.nextElement(); + if (name.isEmpty()) continue; // don't need to process parents on root. + Logger l = cx.findLogger(name); + if (l != null && !visited.test(l, name)) { + // should pass visited here to cut the processing when + // reaching a logger already visited. + cx.processParentHandlers(l, name, visited); + } + } + } + + // We changed the configuration: invoke configuration listeners + invokeConfigurationListeners(); + } + /** * Get the value of a logging property. * The method returns null if the property is not found. --- /dev/null 2015-09-14 18:10:47.000000000 +0200 +++ new/test/java/util/logging/LogManager/Configuration/updateConfiguration/HandlersOnComplexResetUpdate.java 2015-09-14 18:10:47.000000000 +0200 @@ -0,0 +1,548 @@ +/* + * Copyright (c) 2015, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FilePermission; +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.CodeSource; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.Permissions; +import java.security.Policy; +import java.security.ProtectionDomain; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.Properties; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.logging.FileHandler; +import java.util.logging.LogManager; +import java.util.logging.Logger; +import java.util.logging.LoggingPermission; + +/** + * @test + * @bug 8033661 + * @summary tests that FileHandlers configured on abstract nodes in logging.properties + * will be closed on reset and reopened on updateConfiguration(). + * Test a complex reconfiguration where a logger with handlers + * suddenly appears in the hierarchy between a child logger and the + * root logger. + * @run main/othervm HandlersOnComplexResetUpdate UNSECURE + * @run main/othervm HandlersOnComplexResetUpdate SECURE + * @author danielfuchs + */ +public class HandlersOnComplexResetUpdate { + + /** + * We will test the handling of abstract logger nodes with file handlers in + * two configurations: + * UNSECURE: No security manager. + * SECURE: With the security manager present - and the required + * permissions granted. + */ + public static enum TestCase { + UNSECURE, SECURE; + public void run(List properties) throws Exception { + System.out.println("Running test case: " + name()); + Configure.setUp(this, properties.get(0)); + test(this.name(), properties); + } + } + + + private static final String PREFIX = + "FileHandler-" + UUID.randomUUID() + ".log"; + private static final String userDir = System.getProperty("user.dir", "."); + private static final boolean userDirWritable = Files.isWritable(Paths.get(userDir)); + + private static final List properties; + static { + // The test will call reset() and updateConfiguration() with each of these + // properties in sequence. The child logger is not released between each + // configuration. What is interesting here is mostly what happens between + // props4 and props5: + // + // In step 4 (props4) the configuration defines a handler for the + // logger com.foo (the direct parent of com.foo.child - which is the + // logger we hold on to). + // + // In step 5 (props5) the configuration has nothing concerning + // 'com.foo', but the handler has been migrated to 'com'. + // Since there doesn't exist any logger for 'com' (the previous + // configuration didn't have any configuration for 'com'), then + // 'com' will not be found when we process the existing loggers named + // in the configuration. + // + // So if we didn't also process the existing loggers not named in the + // configuration (such as com.foo.child) then no logger for 'com' + // would be created, which means that com.foo.child would not be + // able to inherit its configuration for 'com' until someone explicitely + // creates a logger for 'com'. + // + // This test check that a logger for 'com' will be created because + // 'com.foo.child' still exists when updateConfiguration() is called. + + Properties props1 = new Properties(); + props1.setProperty("test.name", "parent logger with handler"); + props1.setProperty(FileHandler.class.getName() + ".pattern", PREFIX); + props1.setProperty(FileHandler.class.getName() + ".limit", String.valueOf(Integer.MAX_VALUE)); + props1.setProperty(FileHandler.class.getName() + ".level", "ALL"); + props1.setProperty(FileHandler.class.getName() + ".formatter", "java.util.logging.SimpleFormatter"); + props1.setProperty("com.foo.handlers", FileHandler.class.getName()); + props1.setProperty("test.checkHandlersOnParent", "true"); + props1.setProperty("test.checkHandlersOn", "com.foo"); + props1.setProperty("com.bar.level", "FINEST"); + + Properties props2 = new Properties(); + props2.setProperty("java.util.logging.LogManager.reconfigureHandlers", "true"); + props2.setProperty(FileHandler.class.getName() + ".pattern", PREFIX); + props2.setProperty(FileHandler.class.getName() + ".limit", String.valueOf(Integer.MAX_VALUE)); + props2.setProperty(FileHandler.class.getName() + ".level", "ALL"); + props2.setProperty(FileHandler.class.getName() + ".formatter", "java.util.logging.SimpleFormatter"); + props2.setProperty("com.foo.handlers", FileHandler.class.getName()); + props2.setProperty("test.checkHandlersOnParent", "true"); + props2.setProperty("test.checkHandlersOn", "com.foo"); + props2.setProperty("com.bar.level", "FINEST"); + + Properties props3 = new Properties(); + props3.setProperty("test.name", "parent logger with handler"); + props3.setProperty(FileHandler.class.getName() + ".pattern", PREFIX); + props3.setProperty(FileHandler.class.getName() + ".limit", String.valueOf(Integer.MAX_VALUE)); + props3.setProperty(FileHandler.class.getName() + ".level", "ALL"); + props3.setProperty(FileHandler.class.getName() + ".formatter", "java.util.logging.SimpleFormatter"); + props3.setProperty("com.foo.handlers", FileHandler.class.getName()); + props3.setProperty("test.checkHandlersOnParent", "true"); + props3.setProperty("test.checkHandlersOn", "com.foo"); + props3.setProperty("com.bar.level", "FINEST"); + + Properties props4 = new Properties(); + props4.setProperty("java.util.logging.LogManager.reconfigureHandlers", "true"); + props4.setProperty(FileHandler.class.getName() + ".pattern", PREFIX); + props4.setProperty(FileHandler.class.getName() + ".limit", String.valueOf(Integer.MAX_VALUE)); + props4.setProperty(FileHandler.class.getName() + ".level", "ALL"); + props4.setProperty(FileHandler.class.getName() + ".formatter", "java.util.logging.SimpleFormatter"); + props4.setProperty("test.checkHandlersOnParent", "true"); + props4.setProperty("test.checkHandlersOn", "com.foo"); + props4.setProperty("com.foo.handlers", FileHandler.class.getName()); + + Properties props5 = new Properties(); + props5.setProperty("test.name", "parent logger with handler"); + props5.setProperty(FileHandler.class.getName() + ".pattern", PREFIX); + props5.setProperty(FileHandler.class.getName() + ".limit", String.valueOf(Integer.MAX_VALUE)); + props5.setProperty(FileHandler.class.getName() + ".level", "ALL"); + props5.setProperty(FileHandler.class.getName() + ".formatter", "java.util.logging.SimpleFormatter"); + props5.setProperty("test.checkHandlersOnParent", "false"); + props5.setProperty("test.checkHandlersOn", "com"); + props5.setProperty("com.handlers", FileHandler.class.getName()); + + properties = Collections.unmodifiableList(Arrays.asList( + props1, props2, props3, props4, props5)); + } + + /** + * This is the main test method. The rest is infrastructure. + * Creates a child of the 'com.foo' logger (com.foo.child) and holds on to + * it. + *

+ * Then applies all given configurations in sequence and verifies assumptions + * about the handlers that com.foo should have, or not have. + * In the last configuration (props5) it also verifies that the + * logger 'com' has been created and has now the expected handler. + *

+ * Finally releases the child logger after all configurations have been + * applied. + * + * @param name + * @param properties + * @throws Exception + */ + static void test(String name, List properties) + throws Exception { + + System.out.println("\nTesting: " + name); + if (!userDirWritable) { + throw new RuntimeException("Not writable: "+userDir); + } + + // Then create a child of the com.foo logger. + Logger fooChild = Logger.getLogger("com.foo.child"); + fooChild.info("hello world"); + Logger barChild = Logger.getLogger("com.bar.child"); + barChild.info("hello world"); + + ReferenceQueue queue = new ReferenceQueue(); + WeakReference fooRef = new WeakReference<>(Logger.getLogger("com.foo"), queue); + if (fooRef.get() != fooChild.getParent()) { + throw new RuntimeException("Unexpected parent logger: " + + fooChild.getParent() +"\n\texpected: " + fooRef.get()); + } + WeakReference barRef = new WeakReference<>(Logger.getLogger("com.bar"), queue); + if (barRef.get() != barChild.getParent()) { + throw new RuntimeException("Unexpected parent logger: " + + barChild.getParent() +"\n\texpected: " + barRef.get()); + } + Reference ref2; + int max = 3; + barChild = null; + while ((ref2 = queue.poll()) == null) { + System.gc(); + Thread.sleep(100); + if (--max == 0) break; + } + + Throwable failed = null; + try { + if (ref2 != null) { + String refName = ref2 == fooRef ? "fooRef" : ref2 == barRef ? "barRef" : "unknown"; + if (ref2 != barRef) { + throw new RuntimeException("Unexpected logger reference cleared: " + refName); + } else { + System.out.println("Reference " + refName + " cleared as expected"); + } + } else if (ref2 == null) { + throw new RuntimeException("Expected 'barRef' to be cleared"); + } + // Now lets try to reset, check that ref2 has no handlers, and + // attempt to configure again. + Properties previousProps = properties.get(0); + int expectedHandlersCount = 1; + boolean checkHandlersOnParent = Boolean.parseBoolean( + previousProps.getProperty("test.checkHandlersOnParent", "true")); + String checkHandlersOn = previousProps.getProperty("test.checkHandlersOn", null); + for (int i=1; i LogManager.getLogManager().reset()); + assertEquals(0, fooChild.getParent().getHandlers().length, "fooChild.getParent().getHandlers().length"); + if (checkHandlersOn != null) { + Logger loggerWithHandlers = LogManager.getLogManager().getLogger(checkHandlersOn); + if (loggerWithHandlers == null) { + throw new RuntimeException("Logger with handlers not found: " + checkHandlersOn); + } + assertEquals(0, loggerWithHandlers.getHandlers().length, + checkHandlersOn + ".getHandlers().length"); + } + + if (i == 4) { + System.out.println("Last configuration..."); + } + // Read configuration + Configure.doPrivileged(() -> Configure.updateConfigurationWith(nextProps, false)); + + expectedHandlersCount = reconfigureHandlers ? 1 : 0; + checkHandlersOnParent = Boolean.parseBoolean( + nextProps.getProperty("test.checkHandlersOnParent", "true")); + checkHandlersOn = nextProps.getProperty("test.checkHandlersOn", null); + + if (checkHandlersOnParent) { + assertEquals(expectedHandlersCount, + fooChild.getParent().getHandlers().length, + "fooChild.getParent().getHandlers().length"); + } else { + assertEquals(0, + fooChild.getParent().getHandlers().length, + "fooChild.getParent().getHandlers().length"); + } + if (checkHandlersOn != null) { + Logger loggerWithHandlers = LogManager.getLogManager().getLogger(checkHandlersOn); + if (loggerWithHandlers == null) { + throw new RuntimeException("Logger with handlers not found: " + checkHandlersOn); + } + assertEquals(expectedHandlersCount, + loggerWithHandlers.getHandlers().length, + checkHandlersOn + ".getHandlers().length"); + } + } + } catch (Throwable t) { + failed = t; + } finally { + final Throwable suppressed = failed; + Configure.doPrivileged(() -> LogManager.getLogManager().reset()); + Configure.doPrivileged(() -> { + try { + StringBuilder builder = new StringBuilder(); + Files.list(Paths.get(userDir)) + .filter((f) -> f.toString().contains(PREFIX)) + .filter((f) -> f.toString().endsWith(".lck")) + .forEach((f) -> { + builder.append(f.toString()).append('\n'); + }); + if (!builder.toString().isEmpty()) { + throw new RuntimeException("Lock files not cleaned:\n" + + builder.toString()); + } + } catch(RuntimeException | Error x) { + if (suppressed != null) x.addSuppressed(suppressed); + throw x; + } catch(Exception x) { + if (suppressed != null) x.addSuppressed(suppressed); + throw new RuntimeException(x); + } + }); + fooChild = null; + System.out.println("Setting fooChild to: " + fooChild); + while ((ref2 = queue.poll()) == null) { + System.gc(); + Thread.sleep(1000); + } + if (ref2 != fooRef) { + throw new RuntimeException("Unexpected reference: " + + ref2 +"\n\texpected: " + fooRef); + } + if (ref2.get() != null) { + throw new RuntimeException("Referent not cleared: " + ref2.get()); + } + System.out.println("Got fooRef after reset(), fooChild is " + fooChild); + + } + if (failed != null) { + // should rarely happen... + throw new RuntimeException(failed); + } + + } + + public static void main(String... args) throws Exception { + + + if (args == null || args.length == 0) { + args = new String[] { + TestCase.UNSECURE.name(), + TestCase.SECURE.name(), + }; + } + + try { + for (String testName : args) { + TestCase test = TestCase.valueOf(testName); + test.run(properties); + } + } finally { + if (userDirWritable) { + Configure.doPrivileged(() -> { + // cleanup - delete files that have been created + try { + Files.list(Paths.get(userDir)) + .filter((f) -> f.toString().contains(PREFIX)) + .forEach((f) -> { + try { + System.out.println("deleting " + f); + Files.delete(f); + } catch(Throwable t) { + System.err.println("Failed to delete " + f + ": " + t); + } + }); + } catch(Throwable t) { + System.err.println("Cleanup failed to list files: " + t); + t.printStackTrace(); + } + }); + } + } + } + + static class Configure { + static Policy policy = null; + static final ThreadLocal allowAll = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static void setUp(TestCase test, Properties propertyFile) { + switch (test) { + case SECURE: + if (policy == null && System.getSecurityManager() != null) { + throw new IllegalStateException("SecurityManager already set"); + } else if (policy == null) { + policy = new SimplePolicy(TestCase.SECURE, allowAll); + Policy.setPolicy(policy); + System.setSecurityManager(new SecurityManager()); + } + if (System.getSecurityManager() == null) { + throw new IllegalStateException("No SecurityManager."); + } + if (policy == null) { + throw new IllegalStateException("policy not configured"); + } + break; + case UNSECURE: + if (System.getSecurityManager() != null) { + throw new IllegalStateException("SecurityManager already set"); + } + break; + default: + new InternalError("No such testcase: " + test); + } + doPrivileged(() -> { + updateConfigurationWith(propertyFile, false); + }); + } + + static void updateConfigurationWith(Properties propertyFile, boolean append) { + try { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + propertyFile.store(bytes, propertyFile.getProperty("test.name")); + ByteArrayInputStream bais = new ByteArrayInputStream(bytes.toByteArray()); + Function> remapper = + append ? (x) -> ((o, n) -> n == null ? o : n) + : (x) -> ((o, n) -> n); + LogManager.getLogManager().updateConfiguration(bais, remapper); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + static void doPrivileged(Runnable run) { + final boolean old = allowAll.get().getAndSet(true); + try { + run.run(); + } finally { + allowAll.get().set(old); + } + } + static T callPrivileged(Callable call) throws Exception { + final boolean old = allowAll.get().getAndSet(true); + try { + return call.call(); + } finally { + allowAll.get().set(old); + } + } + } + + @FunctionalInterface + public static interface FileHandlerSupplier { + public FileHandler test() throws Exception; + } + + static final class TestAssertException extends RuntimeException { + TestAssertException(String msg) { + super(msg); + } + } + + private static void assertEquals(long expected, long received, String msg) { + if (expected != received) { + throw new TestAssertException("Unexpected result for " + msg + + ".\n\texpected: " + expected + + "\n\tactual: " + received); + } else { + System.out.println("Got expected " + msg + ": " + received); + } + } + + final static class PermissionsBuilder { + final Permissions perms; + public PermissionsBuilder() { + this(new Permissions()); + } + public PermissionsBuilder(Permissions perms) { + this.perms = perms; + } + public PermissionsBuilder add(Permission p) { + perms.add(p); + return this; + } + public PermissionsBuilder addAll(PermissionCollection col) { + if (col != null) { + for (Enumeration e = col.elements(); e.hasMoreElements(); ) { + perms.add(e.nextElement()); + } + } + return this; + } + public Permissions toPermissions() { + final PermissionsBuilder builder = new PermissionsBuilder(); + builder.addAll(perms); + return builder.perms; + } + } + + public static class SimplePolicy extends Policy { + + final Permissions permissions; + final Permissions allPermissions; + final ThreadLocal allowAll; // actually: this should be in a thread locale + public SimplePolicy(TestCase test, ThreadLocal allowAll) { + this.allowAll = allowAll; + permissions = new Permissions(); + permissions.add(new LoggingPermission("control", null)); + permissions.add(new FilePermission(PREFIX+".lck", "read,write,delete")); + permissions.add(new FilePermission(PREFIX, "read,write")); + + // these are used for configuring the test itself... + allPermissions = new Permissions(); + allPermissions.add(new java.security.AllPermission()); + + } + + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + if (allowAll.get().get()) return allPermissions.implies(permission); + return permissions.implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll(allowAll.get().get() + ? allPermissions : permissions).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll(allowAll.get().get() + ? allPermissions : permissions).toPermissions(); + } + } + +} --- /dev/null 2015-09-14 18:10:48.000000000 +0200 +++ new/test/java/util/logging/LogManager/Configuration/updateConfiguration/HandlersOnComplexUpdate.java 2015-09-14 18:10:48.000000000 +0200 @@ -0,0 +1,547 @@ +/* + * Copyright (c) 2015, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FilePermission; +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.CodeSource; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.Permissions; +import java.security.Policy; +import java.security.ProtectionDomain; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.Properties; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.logging.FileHandler; +import java.util.logging.LogManager; +import java.util.logging.Logger; +import java.util.logging.LoggingPermission; + +/** + * @test + * @bug 8033661 + * @summary tests that FileHandlers configured on abstract nodes in logging.properties + * will be properly closed and reopened on updateConfiguration(). + * Test a complex reconfiguration where a logger with handlers + * suddenly appears in the hierarchy between a child logger and the + * root logger. + * @run main/othervm HandlersOnComplexUpdate UNSECURE + * @run main/othervm HandlersOnComplexUpdate SECURE + * @author danielfuchs + */ +public class HandlersOnComplexUpdate { + + /** + * We will test the handling of abstract logger nodes with file handlers in + * two configurations: + * UNSECURE: No security manager. + * SECURE: With the security manager present - and the required + * permissions granted. + */ + public static enum TestCase { + UNSECURE, SECURE; + public void run(List properties) throws Exception { + System.out.println("Running test case: " + name()); + Configure.setUp(this, properties.get(0)); + test(this.name(), properties); + } + } + + + private static final String PREFIX = + "FileHandler-" + UUID.randomUUID() + ".log"; + private static final String userDir = System.getProperty("user.dir", "."); + private static final boolean userDirWritable = Files.isWritable(Paths.get(userDir)); + + private static final List properties; + static { + // The test will call updateConfiguration() with each of these + // properties in sequence. The child logger is not released between each + // configuration. What is interesting here is mostly what happens between + // props4 and props5: + // + // In step 4 (props4) the configuration defines a handler for the + // logger com.foo (the direct parent of com.foo.child - which is the + // logger we hold on to). + // + // In step 5 (props5) the configuration has nothing concerning + // 'com.foo', but the handler has been migrated to 'com'. + // Since there doesn't exist any logger for 'com' (the previous + // configuration didn't have any configuration for 'com'), then + // 'com' will not be found when we process the existing loggers named + // in the configuration. + // + // So if we didn't also process the existing loggers not named in the + // configuration (such as com.foo.child) then no logger for 'com' + // would be created, which means that com.foo.child would not be + // able to inherit its configuration for 'com' until someone explicitely + // creates a logger for 'com'. + // + // This test check that a logger for 'com' will be created because + // 'com.foo.child' still exists when updateConfiguration() is called. + + Properties props1 = new Properties(); + props1.setProperty("test.name", "parent logger with handler"); + props1.setProperty(FileHandler.class.getName() + ".pattern", PREFIX); + props1.setProperty(FileHandler.class.getName() + ".limit", String.valueOf(Integer.MAX_VALUE)); + props1.setProperty(FileHandler.class.getName() + ".level", "ALL"); + props1.setProperty(FileHandler.class.getName() + ".formatter", "java.util.logging.SimpleFormatter"); + props1.setProperty("com.foo.handlers", FileHandler.class.getName()); + props1.setProperty("test.checkHandlersOnParent", "true"); + props1.setProperty("test.checkHandlersOn", "com.foo"); + props1.setProperty("com.bar.level", "FINEST"); + + Properties props2 = new Properties(); + props2.setProperty("test.name", "parent logger with handler"); + props2.setProperty(FileHandler.class.getName() + ".pattern", PREFIX); + props2.setProperty(FileHandler.class.getName() + ".limit", String.valueOf(Integer.MAX_VALUE)); + props2.setProperty(FileHandler.class.getName() + ".level", "ALL"); + props2.setProperty(FileHandler.class.getName() + ".formatter", "java.util.logging.SimpleFormatter"); + props2.setProperty("com.foo.handlers", FileHandler.class.getName()); + props2.setProperty("test.checkHandlersOnParent", "true"); + props2.setProperty("test.checkHandlersOn", "com.foo"); + props2.setProperty("com.bar.level", "FINEST"); + + Properties props3 = new Properties(); + props3.setProperty("test.name", "parent logger with handler"); + props3.setProperty(FileHandler.class.getName() + ".pattern", PREFIX); + props3.setProperty(FileHandler.class.getName() + ".limit", String.valueOf(Integer.MAX_VALUE)); + props3.setProperty(FileHandler.class.getName() + ".level", "ALL"); + props3.setProperty(FileHandler.class.getName() + ".formatter", "java.util.logging.SimpleFormatter"); + props3.setProperty("com.foo.handlers", FileHandler.class.getName()); + props3.setProperty("test.checkHandlersOnParent", "true"); + props3.setProperty("test.checkHandlersOn", "com.foo"); + props3.setProperty("com.bar.level", "FINEST"); + + Properties props4 = new Properties(); + props4.setProperty("test.name", "parent logger with handler"); + props4.setProperty(FileHandler.class.getName() + ".pattern", PREFIX); + props4.setProperty(FileHandler.class.getName() + ".limit", String.valueOf(Integer.MAX_VALUE)); + props4.setProperty(FileHandler.class.getName() + ".level", "ALL"); + props4.setProperty(FileHandler.class.getName() + ".formatter", "java.util.logging.SimpleFormatter"); + props4.setProperty("test.checkHandlersOnParent", "true"); + props4.setProperty("test.checkHandlersOn", "com.foo"); + props4.setProperty("com.foo.handlers", FileHandler.class.getName()); + + Properties props5 = new Properties(); + props5.setProperty("test.name", "parent logger with handler"); + props5.setProperty(FileHandler.class.getName() + ".pattern", PREFIX); + props5.setProperty(FileHandler.class.getName() + ".limit", String.valueOf(Integer.MAX_VALUE)); + props5.setProperty(FileHandler.class.getName() + ".level", "ALL"); + props5.setProperty(FileHandler.class.getName() + ".formatter", "java.util.logging.SimpleFormatter"); + props5.setProperty("test.checkHandlersOnParent", "false"); + props5.setProperty("test.checkHandlersOn", "com"); + props5.setProperty("com.handlers", FileHandler.class.getName()); + + properties = Collections.unmodifiableList(Arrays.asList( + props1, props2, props3, props4, props5)); + } + + /** + * This is the main test method. The rest is infrastructure. + * Creates a child of the 'com.foo' logger (com.foo.child) and holds on to + * it. + *

+ * Then applies all given configurations in sequence and verifies assumptions + * about the handlers that com.foo should have, or not have. + * In the last configuration (props5) it also verifies that the + * logger 'com' has been created and has now the expected handler. + *

+ * Finally releases the child logger after all configurations have been + * applied. + * + * @param name + * @param properties + * @throws Exception + */ + static void test(String name, List properties) + throws Exception { + + System.out.println("\nTesting: " + name); + if (!userDirWritable) { + throw new RuntimeException("Not writable: "+userDir); + } + + // Then create a child of the com.foo logger. + Logger fooChild = Logger.getLogger("com.foo.child"); + fooChild.info("hello world"); + Logger barChild = Logger.getLogger("com.bar.child"); + barChild.info("hello world"); + + ReferenceQueue queue = new ReferenceQueue(); + WeakReference fooRef = new WeakReference<>(Logger.getLogger("com.foo"), queue); + if (fooRef.get() != fooChild.getParent()) { + throw new RuntimeException("Unexpected parent logger: " + + fooChild.getParent() +"\n\texpected: " + fooRef.get()); + } + WeakReference barRef = new WeakReference<>(Logger.getLogger("com.bar"), queue); + if (barRef.get() != barChild.getParent()) { + throw new RuntimeException("Unexpected parent logger: " + + barChild.getParent() +"\n\texpected: " + barRef.get()); + } + Reference ref2; + int max = 3; + barChild = null; + while ((ref2 = queue.poll()) == null) { + System.gc(); + Thread.sleep(100); + if (--max == 0) break; + } + + Throwable failed = null; + try { + if (ref2 != null) { + String refName = ref2 == fooRef ? "fooRef" : ref2 == barRef ? "barRef" : "unknown"; + if (ref2 != barRef) { + throw new RuntimeException("Unexpected logger reference cleared: " + refName); + } else { + System.out.println("Reference " + refName + " cleared as expected"); + } + } else if (ref2 == null) { + throw new RuntimeException("Expected 'barRef' to be cleared"); + } + // Now lets try to check handlers, and + // attempt to update the configuration again. + Properties previousProps = properties.get(0); + int expectedHandlersCount = 1; + boolean checkHandlersOnParent = Boolean.parseBoolean( + previousProps.getProperty("test.checkHandlersOnParent", "true")); + String checkHandlersOn = previousProps.getProperty("test.checkHandlersOn", null); + for (int i=1; i Configure.updateConfigurationWith(nextProps, false)); + + expectedHandlersCount = reconfigureHandlers ? 1 : 0; + checkHandlersOnParent = Boolean.parseBoolean( + nextProps.getProperty("test.checkHandlersOnParent", "true")); + checkHandlersOn = nextProps.getProperty("test.checkHandlersOn", null); + + if (checkHandlersOnParent) { + assertEquals(expectedHandlersCount, + fooChild.getParent().getHandlers().length, + "fooChild.getParent().getHandlers().length"); + } else { + assertEquals(0, + fooChild.getParent().getHandlers().length, + "fooChild.getParent().getHandlers().length"); + } + if (checkHandlersOn != null) { + Logger loggerWithHandlers = LogManager.getLogManager().getLogger(checkHandlersOn); + if (loggerWithHandlers == null) { + throw new RuntimeException("Logger with handlers not found: " + checkHandlersOn); + } + assertEquals(expectedHandlersCount, + loggerWithHandlers.getHandlers().length, + checkHandlersOn + ".getHandlers().length"); + } + } + } catch (Throwable t) { + failed = t; + } finally { + final Throwable suppressed = failed; + Configure.doPrivileged(() -> LogManager.getLogManager().reset()); + Configure.doPrivileged(() -> { + try { + StringBuilder builder = new StringBuilder(); + Files.list(Paths.get(userDir)) + .filter((f) -> f.toString().contains(PREFIX)) + .filter((f) -> f.toString().endsWith(".lck")) + .forEach((f) -> { + builder.append(f.toString()).append('\n'); + }); + if (!builder.toString().isEmpty()) { + throw new RuntimeException("Lock files not cleaned:\n" + + builder.toString()); + } + } catch(RuntimeException | Error x) { + if (suppressed != null) x.addSuppressed(suppressed); + throw x; + } catch(Exception x) { + if (suppressed != null) x.addSuppressed(suppressed); + throw new RuntimeException(x); + } + }); + fooChild = null; + System.out.println("Setting fooChild to: " + fooChild); + while ((ref2 = queue.poll()) == null) { + System.gc(); + Thread.sleep(1000); + } + if (ref2 != fooRef) { + throw new RuntimeException("Unexpected reference: " + + ref2 +"\n\texpected: " + fooRef); + } + if (ref2.get() != null) { + throw new RuntimeException("Referent not cleared: " + ref2.get()); + } + System.out.println("Got fooRef after reset(), fooChild is " + fooChild); + + } + if (failed != null) { + // should rarely happen... + throw new RuntimeException(failed); + } + + } + + public static void main(String... args) throws Exception { + + + if (args == null || args.length == 0) { + args = new String[] { + TestCase.UNSECURE.name(), + TestCase.SECURE.name(), + }; + } + + try { + for (String testName : args) { + TestCase test = TestCase.valueOf(testName); + test.run(properties); + } + } finally { + if (userDirWritable) { + Configure.doPrivileged(() -> { + // cleanup - delete files that have been created + try { + Files.list(Paths.get(userDir)) + .filter((f) -> f.toString().contains(PREFIX)) + .forEach((f) -> { + try { + System.out.println("deleting " + f); + Files.delete(f); + } catch(Throwable t) { + System.err.println("Failed to delete " + f + ": " + t); + } + }); + } catch(Throwable t) { + System.err.println("Cleanup failed to list files: " + t); + t.printStackTrace(); + } + }); + } + } + } + + static class Configure { + static Policy policy = null; + static final ThreadLocal allowAll = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static void setUp(TestCase test, Properties propertyFile) { + switch (test) { + case SECURE: + if (policy == null && System.getSecurityManager() != null) { + throw new IllegalStateException("SecurityManager already set"); + } else if (policy == null) { + policy = new SimplePolicy(TestCase.SECURE, allowAll); + Policy.setPolicy(policy); + System.setSecurityManager(new SecurityManager()); + } + if (System.getSecurityManager() == null) { + throw new IllegalStateException("No SecurityManager."); + } + if (policy == null) { + throw new IllegalStateException("policy not configured"); + } + break; + case UNSECURE: + if (System.getSecurityManager() != null) { + throw new IllegalStateException("SecurityManager already set"); + } + break; + default: + new InternalError("No such testcase: " + test); + } + doPrivileged(() -> { + configureWith(propertyFile); + }); + } + + static void configureWith(Properties propertyFile) { + try { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + propertyFile.store(bytes, propertyFile.getProperty("test.name")); + ByteArrayInputStream bais = new ByteArrayInputStream(bytes.toByteArray()); + LogManager.getLogManager().readConfiguration(bais); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + static void updateConfigurationWith(Properties propertyFile, boolean append) { + try { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + propertyFile.store(bytes, propertyFile.getProperty("test.name")); + ByteArrayInputStream bais = new ByteArrayInputStream(bytes.toByteArray()); + Function> remapper = + append ? (x) -> ((o, n) -> n == null ? o : n) + : (x) -> ((o, n) -> n); + LogManager.getLogManager().updateConfiguration(bais, remapper); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + static void doPrivileged(Runnable run) { + final boolean old = allowAll.get().getAndSet(true); + try { + run.run(); + } finally { + allowAll.get().set(old); + } + } + static T callPrivileged(Callable call) throws Exception { + final boolean old = allowAll.get().getAndSet(true); + try { + return call.call(); + } finally { + allowAll.get().set(old); + } + } + } + + @FunctionalInterface + public static interface FileHandlerSupplier { + public FileHandler test() throws Exception; + } + + static final class TestAssertException extends RuntimeException { + TestAssertException(String msg) { + super(msg); + } + } + + private static void assertEquals(long expected, long received, String msg) { + if (expected != received) { + throw new TestAssertException("Unexpected result for " + msg + + ".\n\texpected: " + expected + + "\n\tactual: " + received); + } else { + System.out.println("Got expected " + msg + ": " + received); + } + } + + final static class PermissionsBuilder { + final Permissions perms; + public PermissionsBuilder() { + this(new Permissions()); + } + public PermissionsBuilder(Permissions perms) { + this.perms = perms; + } + public PermissionsBuilder add(Permission p) { + perms.add(p); + return this; + } + public PermissionsBuilder addAll(PermissionCollection col) { + if (col != null) { + for (Enumeration e = col.elements(); e.hasMoreElements(); ) { + perms.add(e.nextElement()); + } + } + return this; + } + public Permissions toPermissions() { + final PermissionsBuilder builder = new PermissionsBuilder(); + builder.addAll(perms); + return builder.perms; + } + } + + public static class SimplePolicy extends Policy { + + final Permissions permissions; + final Permissions allPermissions; + final ThreadLocal allowAll; // actually: this should be in a thread locale + public SimplePolicy(TestCase test, ThreadLocal allowAll) { + this.allowAll = allowAll; + permissions = new Permissions(); + permissions.add(new LoggingPermission("control", null)); + permissions.add(new FilePermission(PREFIX+".lck", "read,write,delete")); + permissions.add(new FilePermission(PREFIX, "read,write")); + + // these are used for configuring the test itself... + allPermissions = new Permissions(); + allPermissions.add(new java.security.AllPermission()); + + } + + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + if (allowAll.get().get()) return allPermissions.implies(permission); + return permissions.implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll(allowAll.get().get() + ? allPermissions : permissions).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll(allowAll.get().get() + ? allPermissions : permissions).toPermissions(); + } + } + +} --- /dev/null 2015-09-14 18:10:49.000000000 +0200 +++ new/test/java/util/logging/LogManager/Configuration/updateConfiguration/UpdateConfigurationTest.java 2015-09-14 18:10:49.000000000 +0200 @@ -0,0 +1,608 @@ +/* + * Copyright (c) 2015, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FilePermission; +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.CodeSource; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.Permissions; +import java.security.Policy; +import java.security.ProtectionDomain; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.logging.FileHandler; +import java.util.logging.LogManager; +import java.util.logging.Logger; +import java.util.logging.LoggingPermission; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @test + * @bug 8033661 + * @summary tests LogManager.updateConfiguration(bin) + * @run main/othervm UpdateConfigurationTest UNSECURE + * @run main/othervm UpdateConfigurationTest SECURE + * @author danielfuchs + */ +public class UpdateConfigurationTest { + + /** + * We will test the handling of abstract logger nodes with file handlers in + * two configurations: + * UNSECURE: No security manager. + * SECURE: With the security manager present - and the required + * permissions granted. + */ + public static enum TestCase { + UNSECURE, SECURE; + public void run(Properties propertyFile, boolean last) throws Exception { + System.out.println("Running test case: " + name()); + Configure.setUp(this); + test(this.name() + " " + propertyFile.getProperty("test.name"), + propertyFile, last); + } + } + + + private static final String PREFIX = + "FileHandler-" + UUID.randomUUID() + ".log"; + private static final String userDir = System.getProperty("user.dir", "."); + private static final boolean userDirWritable = Files.isWritable(Paths.get(userDir)); + + static enum ConfigMode { APPEND, REPLACE, DEFAULT; + boolean append() { return this == APPEND; } + Function> remapper() { + switch(this) { + case APPEND: + return (k) -> ((o,n) -> (n == null ? o : n)); + case REPLACE: + return (k) -> ((o,n) -> n); + } + return null; + } + } + + private static final List properties; + static { + // The test will be run with each of the configurations below. + // The 'child' logger is forgotten after each test + + Properties props1 = new Properties(); + props1.setProperty("test.name", "props1"); + props1.setProperty("test.config.mode", ConfigMode.REPLACE.name()); + props1.setProperty(FileHandler.class.getName() + ".pattern", PREFIX); + props1.setProperty(FileHandler.class.getName() + ".limit", String.valueOf(Integer.MAX_VALUE)); + props1.setProperty(FileHandler.class.getName() + ".level", "ALL"); + props1.setProperty(FileHandler.class.getName() + ".formatter", "java.util.logging.SimpleFormatter"); + props1.setProperty("com.foo.handlers", FileHandler.class.getName()); + props1.setProperty("com.bar.level", "FINEST"); + + Properties props2 = new Properties(); + props2.setProperty("test.name", "props2"); + props2.setProperty("test.config.mode", ConfigMode.DEFAULT.name()); + props2.setProperty(FileHandler.class.getName() + ".pattern", PREFIX); + props2.setProperty(FileHandler.class.getName() + ".limit", String.valueOf(Integer.MAX_VALUE)); + props2.setProperty(FileHandler.class.getName() + ".level", "ALL"); + props2.setProperty(FileHandler.class.getName() + ".formatter", "java.util.logging.SimpleFormatter"); + props2.setProperty("com.foo.handlers", FileHandler.class.getName()); + props2.setProperty("com.foo.handlers.ensureCloseOnReset", "true"); + props2.setProperty("com.level", "FINE"); + + Properties props3 = new Properties(); + props3.setProperty("test.name", "props3"); + props3.setProperty("test.config.mode", ConfigMode.APPEND.name()); + props3.setProperty(FileHandler.class.getName() + ".pattern", PREFIX); + props3.setProperty(FileHandler.class.getName() + ".limit", String.valueOf(Integer.MAX_VALUE)); + props3.setProperty(FileHandler.class.getName() + ".level", "ALL"); + props3.setProperty(FileHandler.class.getName() + ".formatter", "java.util.logging.SimpleFormatter"); + props3.setProperty("com.foo.handlers", ""); // specify "" to override the value in the previous conf + props3.setProperty("com.foo.handlers.ensureCloseOnReset", "false"); + props3.setProperty("com.bar.level", "FINER"); + + + properties = Collections.unmodifiableList(Arrays.asList( + props1, props2, props3, props1)); + } + + static Properties previous; + static Properties current; + static final Field propsField; + static { + LogManager manager = LogManager.getLogManager(); + try { + propsField = LogManager.class.getDeclaredField("props"); + propsField.setAccessible(true); + previous = current = (Properties) propsField.get(manager); + } catch (NoSuchFieldException | IllegalAccessException ex) { + throw new ExceptionInInitializerError(ex); + } + } + + static Properties getProperties() { + try { + return (Properties) propsField.get(LogManager.getLogManager()); + } catch (IllegalAccessException x) { + throw new RuntimeException(x); + } + } + + static String trim(String value) { + return value == null ? null : value.trim(); + } + + + /** + * Tests one of the configuration defined above. + *

+ * This is the main test method (the rest is infrastructure). + *

+ * Creates a child of the com.foo logger (com.foo.child), resets + * the configuration, and verifies that com.foo has no handler. + * Then reapplies the configuration and verifies that the handler + * for com.foo has been reestablished, depending on whether + * java.util.logging.LogManager.reconfigureHandlers is present and + * true. + *

+ * Finally releases the logger com.foo.child, so that com.foo can + * be garbage collected, and the next configuration can be + * tested. + */ + static void test(ConfigMode mode, String name, Properties props, boolean last) + throws Exception { + + // Then create a child of the com.foo logger. + Logger fooChild = Logger.getLogger("com.foo.child"); + fooChild.info("hello world"); + Logger barChild = Logger.getLogger("com.bar.child"); + barChild.info("hello world"); + + ReferenceQueue queue = new ReferenceQueue(); + WeakReference fooRef = new WeakReference<>(Logger.getLogger("com.foo"), queue); + if (fooRef.get() != fooChild.getParent()) { + throw new RuntimeException("Unexpected parent logger: " + + fooChild.getParent() +"\n\texpected: " + fooRef.get()); + } + WeakReference barRef = new WeakReference<>(Logger.getLogger("com.bar"), queue); + if (barRef.get() != barChild.getParent()) { + throw new RuntimeException("Unexpected parent logger: " + + barChild.getParent() +"\n\texpected: " + barRef.get()); + } + Reference ref2; + int max = 3; + barChild = null; + while ((ref2 = queue.poll()) == null) { + System.gc(); + Thread.sleep(100); + if (--max == 0) break; + } + + Throwable failed = null; + try { + if (ref2 != null) { + String refName = ref2 == fooRef ? "fooRef" : ref2 == barRef ? "barRef" : "unknown"; + if (ref2 != barRef) { + throw new RuntimeException("Unexpected logger reference cleared: " + refName); + } else { + System.out.println("Reference " + refName + " cleared as expected"); + } + } else if (ref2 == null) { + throw new RuntimeException("Expected 'barRef' to be cleared"); + } + // Now lets try to check that ref2 has expected handlers, and + // attempt to configure again. + String p = current.getProperty("com.foo.handlers", "").trim(); + assertEquals(p.isEmpty() ? 0 : 1, fooChild.getParent().getHandlers().length, + "["+name+"] fooChild.getParent().getHandlers().length"); + Configure.doPrivileged(() -> Configure.updateConfigurationWith(props, mode.remapper())); + String p2 = previous.getProperty("com.foo.handlers", "").trim(); + assertEquals(p, p2, "["+name+"] com.foo.handlers"); + String n = trim(props.getProperty("com.foo.handlers", null)); + boolean hasHandlers = mode.append() + ? (n == null ? !p.isEmpty() : !n.isEmpty()) + : n != null && !n.isEmpty(); + assertEquals( hasHandlers ? 1 : 0, + fooChild.getParent().getHandlers().length, + "["+name+"] fooChild.getParent().getHandlers().length" + + "[p=\""+p+"\", n=" + (n==null?null:"\""+n+"\"") + "]"); + + checkProperties(mode, previous, current, props); + + } catch (Throwable t) { + failed = t; + } finally { + if (last || failed != null) { + final Throwable suppressed = failed; + Configure.doPrivileged(LogManager.getLogManager()::reset); + Configure.doPrivileged(() -> { + try { + StringBuilder builder = new StringBuilder(); + Files.list(Paths.get(userDir)) + .filter((f) -> f.toString().contains(PREFIX)) + .filter((f) -> f.toString().endsWith(".lck")) + .forEach((f) -> { + builder.append(f.toString()).append('\n'); + }); + if (!builder.toString().isEmpty()) { + throw new RuntimeException("Lock files not cleaned:\n" + + builder.toString()); + } + } catch(RuntimeException | Error x) { + if (suppressed != null) x.addSuppressed(suppressed); + throw x; + } catch(Exception x) { + if (suppressed != null) x.addSuppressed(suppressed); + throw new RuntimeException(x); + } + }); + + // Now we need to forget the child, so that loggers are released, + // and so that we can run the test with the next configuration... + + fooChild = null; + System.out.println("Setting fooChild to: " + fooChild); + while ((ref2 = queue.poll()) == null) { + System.gc(); + Thread.sleep(1000); + } + if (ref2 != fooRef) { + throw new RuntimeException("Unexpected reference: " + + ref2 +"\n\texpected: " + fooRef); + } + if (ref2.get() != null) { + throw new RuntimeException("Referent not cleared: " + ref2.get()); + } + System.out.println("Got fooRef after reset(), fooChild is " + fooChild); + + } + } + if (failed != null) { + // should rarely happen... + throw new RuntimeException(failed); + } + + } + + private static void checkProperties(ConfigMode mode, + Properties previous, Properties current, Properties props) { + Set set = new HashSet<>(); + + // Check that all property names from 'props' are in current. + set.addAll(props.stringPropertyNames()); + set.removeAll(current.keySet()); + if (!set.isEmpty()) { + throw new RuntimeException("Missing properties in current: " + set); + } + set.clear(); + set.addAll(current.stringPropertyNames()); + set.removeAll(previous.keySet()); + set.removeAll(props.keySet()); + if (!set.isEmpty()) { + throw new RuntimeException("Superfluous properties in current: " + set); + } + set.clear(); + Stream allnames = + Stream.concat( + Stream.concat(previous.stringPropertyNames().stream(), + props.stringPropertyNames().stream()), + current.stringPropertyNames().stream()) + .collect(Collectors.toCollection(TreeSet::new)) + .stream(); + if (mode.append()) { + // Check that all previous property names are in current. + set.addAll(previous.stringPropertyNames()); + set.removeAll(current.keySet()); + if (!set.isEmpty()) { + throw new RuntimeException("Missing properties in current: " + set + + "\n\tprevious: " + previous + + "\n\tcurrent: " + current + + "\n\tprops: " + props); + + } + allnames.forEach((k) -> { + String p = previous.getProperty(k, "").trim(); + String n = current.getProperty(k, "").trim(); + if (props.containsKey(k)) { + assertEquals(props.getProperty(k), n, k); + } else { + assertEquals(p, n, k); + } + }); + } else { + // Check that only properties from 'props' are in current. + set.addAll(current.stringPropertyNames()); + set.removeAll(props.keySet()); + if (!set.isEmpty()) { + throw new RuntimeException("Superfluous properties in current: " + set); + } + allnames.forEach((k) -> { + String p = previous.getProperty(k, ""); + String n = current.getProperty(k, ""); + if (props.containsKey(k)) { + assertEquals(props.getProperty(k), n, k); + } else { + assertEquals("", n, k); + } + }); + } + + } + + public static void main(String... args) throws Exception { + + + if (args == null || args.length == 0) { + args = new String[] { + TestCase.UNSECURE.name(), + TestCase.SECURE.name(), + }; + } + + try { + for (String testName : args) { + TestCase test = TestCase.valueOf(testName); + for (int i=0; i { + // cleanup - delete files that have been created + try { + Files.list(Paths.get(userDir)) + .filter((f) -> f.toString().contains(PREFIX)) + .forEach((f) -> { + try { + System.out.println("deleting " + f); + Files.delete(f); + } catch(Throwable t) { + System.err.println("Failed to delete " + f + ": " + t); + } + }); + } catch(Throwable t) { + System.err.println("Cleanup failed to list files: " + t); + t.printStackTrace(); + } + }); + } + } + } + + static class Configure { + static Policy policy = null; + static final ThreadLocal allowAll = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static void setUp(TestCase test) { + switch (test) { + case SECURE: + if (policy == null && System.getSecurityManager() != null) { + throw new IllegalStateException("SecurityManager already set"); + } else if (policy == null) { + policy = new SimplePolicy(TestCase.SECURE, allowAll); + Policy.setPolicy(policy); + System.setSecurityManager(new SecurityManager()); + } + if (System.getSecurityManager() == null) { + throw new IllegalStateException("No SecurityManager."); + } + if (policy == null) { + throw new IllegalStateException("policy not configured"); + } + break; + case UNSECURE: + if (System.getSecurityManager() != null) { + throw new IllegalStateException("SecurityManager already set"); + } + break; + default: + new InternalError("No such testcase: " + test); + } + } + + static void updateConfigurationWith(Properties propertyFile, + Function> remapper) { + try { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + propertyFile.store(bytes, propertyFile.getProperty("test.name")); + ByteArrayInputStream bais = new ByteArrayInputStream(bytes.toByteArray()); + LogManager.getLogManager().updateConfiguration(bais, remapper); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + static void doPrivileged(Runnable run) { + final boolean old = allowAll.get().getAndSet(true); + try { + Properties before = getProperties(); + try { + run.run(); + } finally { + Properties after = getProperties(); + if (before != after) { + previous = before; + current = after; + } + } + } finally { + allowAll.get().set(old); + } + } + static T callPrivileged(Callable call) throws Exception { + final boolean old = allowAll.get().getAndSet(true); + try { + Properties before = getProperties(); + try { + return call.call(); + } finally { + Properties after = getProperties(); + if (before != after) { + previous = before; + current = after; + } + } + } finally { + allowAll.get().set(old); + } + } + } + + @FunctionalInterface + public static interface FileHandlerSupplier { + public FileHandler test() throws Exception; + } + + static final class TestAssertException extends RuntimeException { + TestAssertException(String msg) { + super(msg); + } + } + + private static void assertEquals(long expected, long received, String msg) { + if (expected != received) { + throw new TestAssertException("Unexpected result for " + msg + + ".\n\texpected: " + expected + + "\n\tactual: " + received); + } else { + System.out.println("Got expected " + msg + ": " + received); + } + } + + private static void assertEquals(String expected, String received, String msg) { + if (!Objects.equals(expected, received)) { + throw new TestAssertException("Unexpected result for " + msg + + ".\n\texpected: " + expected + + "\n\tactual: " + received); + } else { + System.out.println("Got expected " + msg + ": " + received); + } + } + + + public static void test(String name, Properties props, boolean last) throws Exception { + ConfigMode configMode = ConfigMode.valueOf(props.getProperty("test.config.mode")); + System.out.println("\nTesting: " + name + " mode=" + configMode); + if (!userDirWritable) { + throw new RuntimeException("Not writable: "+userDir); + } + switch(configMode) { + case REPLACE: + case APPEND: + case DEFAULT: + test(configMode, name, props, last); break; + default: + throw new RuntimeException("Unknwown mode: " + configMode); + } + } + + final static class PermissionsBuilder { + final Permissions perms; + public PermissionsBuilder() { + this(new Permissions()); + } + public PermissionsBuilder(Permissions perms) { + this.perms = perms; + } + public PermissionsBuilder add(Permission p) { + perms.add(p); + return this; + } + public PermissionsBuilder addAll(PermissionCollection col) { + if (col != null) { + for (Enumeration e = col.elements(); e.hasMoreElements(); ) { + perms.add(e.nextElement()); + } + } + return this; + } + public Permissions toPermissions() { + final PermissionsBuilder builder = new PermissionsBuilder(); + builder.addAll(perms); + return builder.perms; + } + } + + public static class SimplePolicy extends Policy { + + final Permissions permissions; + final Permissions allPermissions; + final ThreadLocal allowAll; // actually: this should be in a thread locale + public SimplePolicy(TestCase test, ThreadLocal allowAll) { + this.allowAll = allowAll; + permissions = new Permissions(); + permissions.add(new LoggingPermission("control", null)); + permissions.add(new FilePermission(PREFIX+".lck", "read,write,delete")); + permissions.add(new FilePermission(PREFIX, "read,write")); + + // these are used for configuring the test itself... + allPermissions = new Permissions(); + allPermissions.add(new java.security.AllPermission()); + + } + + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + if (allowAll.get().get()) return allPermissions.implies(permission); + return permissions.implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll(allowAll.get().get() + ? allPermissions : permissions).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll(allowAll.get().get() + ? allPermissions : permissions).toPermissions(); + } + } + +}