--- old/./modules.xml 2015-11-20 17:43:59.000000000 +0100 +++ new/./modules.xml 2015-11-20 17:43:59.000000000 +0100 @@ -459,6 +459,10 @@ jdk.localedata + jdk.internal.logger + java.logging + + sun.util.logging java.desktop java.logging --- old/jdk/src/java.base/share/classes/java/lang/RuntimePermission.java 2015-11-20 17:44:00.000000000 +0100 +++ new/jdk/src/java.base/share/classes/java/lang/RuntimePermission.java 2015-11-20 17:44:00.000000000 +0100 @@ -348,6 +348,19 @@ * {@code java.util.spi.LocaleServiceProvider} for more * information. * + * + * + * loggerFinder + * This {@code RuntimePermission} is required to be granted to + * classes which subclass or call methods on + * {@code java.lang.System.LoggerFinder}. The permission is + * checked during invocation of the abstract base class constructor, as + * well as on the invocation of its public methods. + * This permission ensures trust in classes which provide loggers + * to system classes. + * See {@link java.lang.System.LoggerFinder java.lang.System.LoggerFinder} + * for more information. + * * * * @implNote --- old/jdk/src/java.base/share/classes/java/lang/System.java 2015-11-20 17:44:01.000000000 +0100 +++ new/jdk/src/java.base/share/classes/java/lang/System.java 2015-11-20 17:44:01.000000000 +0100 @@ -30,13 +30,14 @@ import java.security.AccessControlContext; import java.util.Properties; import java.util.PropertyPermission; -import java.util.StringTokenizer; import java.util.Map; import java.security.AccessController; import java.security.PrivilegedAction; -import java.security.AllPermission; import java.nio.channels.Channel; import java.nio.channels.spi.SelectorProvider; +import java.util.Objects; +import java.util.ResourceBundle; +import java.util.function.Supplier; import sun.nio.ch.Interruptible; import sun.reflect.CallerSensitive; import sun.reflect.Reflection; @@ -45,6 +46,9 @@ import jdk.internal.HotSpotIntrinsicCandidate; import jdk.internal.misc.JavaLangAccess;; import jdk.internal.misc.SharedSecrets;; +import jdk.internal.logger.LoggerFinderLoader; +import jdk.internal.logger.LazyLoggers; +import jdk.internal.logger.LocalizedLoggerWrapper; /** * The System class contains several useful class fields @@ -944,6 +948,648 @@ } /** + * {@code System.Logger} instances log messages that will be + * routed to the underlying logging framework the {@link System.LoggerFinder + * LoggerFinder} uses. + *

+ * {@code System.Logger} instances are typically obtained from + * the {@link java.lang.System System} class, by calling + * {@link java.lang.System#getLogger(java.lang.String) System.getLogger(loggerName)} + * or {@link java.lang.System#getLogger(java.lang.String, java.util.ResourceBundle) + * System.getLogger(loggerName, bundle)}. + * + * @see java.lang.System#getLogger(java.lang.String) + * @see java.lang.System#getLogger(java.lang.String, java.util.ResourceBundle) + * @see java.lang.System.LoggerFinder + * + * @since 9 + * + */ + public interface Logger { + + /** + * System {@linkplain Logger loggers} levels. + *

+ * A level has a {@linkplain #getName() name} and {@linkplain + * #getSeverity() severity}. + * Level values are {@link #ALL}, {@link #TRACE}, {@link #DEBUG}, + * {@link #INFO}, {@link #WARNING}, {@link #ERROR}, {@link #OFF}, + * by order of increasing severity. + *
+ * {@link #ALL} and {@link #OFF} + * are simple markers with severities mapped respectively to + * {@link java.lang.Integer#MIN_VALUE Integer.MIN_VALUE} and + * {@link java.lang.Integer#MAX_VALUE Integer.MAX_VALUE}. + *

+ * Severity values and Mapping to {@code java.util.logging.Level}. + *

+ * {@linkplain System.Logger.Level System logger levels} are mapped to + * {@linkplain java.util.logging.Level java.util.logging levels} + * of corresponding severity. + *
The mapping is as follows: + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
System.Logger Severity Level Mapping
System.Logger Levels{@link Logger.Level#ALL ALL}{@link Logger.Level#TRACE TRACE}{@link Logger.Level#DEBUG DEBUG}{@link Logger.Level#INFO INFO}{@link Logger.Level#WARNING WARNING}{@link Logger.Level#ERROR ERROR}{@link Logger.Level#OFF OFF}
java.util.logging Levels{@link java.util.logging.Level#ALL ALL}{@link java.util.logging.Level#FINER FINER}{@link java.util.logging.Level#FINE FINE}{@link java.util.logging.Level#INFO INFO}{@link java.util.logging.Level#WARNING WARNING}{@link java.util.logging.Level#SEVERE SEVERE}{@link java.util.logging.Level#OFF OFF}
+ * + * @since 9 + * + * @see java.lang.System.LoggerFinder + * @see java.lang.System.Logger + */ + public enum Level { + + // for convenience, we're reusing java.util.logging.Level int values + // the mapping logic in sun.util.logging.PlatformLogger depends + // on this. + /** + * A marker to indicate that all levels are enabled. + * This level {@linkplain #getSeverity() severity} is + * {@link Integer#MIN_VALUE}. + */ + ALL(Integer.MIN_VALUE), // typically mapped to/from j.u.l.Level.ALL + /** + * {@code TRACE} level: usually used to log diagnostic information. + * This level {@linkplain #getSeverity() severity} is + * {@code 400}. + */ + TRACE(400), // typically mapped to/from j.u.l.Level.FINER + /** + * {@code DEBUG} level: usually used to log debug information traces. + * This level {@linkplain #getSeverity() severity} is + * {@code 500}. + */ + DEBUG(500), // typically mapped to/from j.u.l.Level.FINEST/FINE/CONFIG + /** + * {@code INFO} level: usually used to log information messages. + * This level {@linkplain #getSeverity() severity} is + * {@code 800}. + */ + INFO(800), // typically mapped to/from j.u.l.Level.INFO + /** + * {@code WARNING} level: usually used to log warning messages. + * This level {@linkplain #getSeverity() severity} is + * {@code 900}. + */ + WARNING(900), // typically mapped to/from j.u.l.Level.WARNING + /** + * {@code ERROR} level: usually used to log error messages. + * This level {@linkplain #getSeverity() severity} is + * {@code 1000}. + */ + ERROR(1000), // typically mapped to/from j.u.l.Level.SEVERE + /** + * A marker to indicate that all levels are disabled. + * This level {@linkplain #getSeverity() severity} is + * {@link Integer#MAX_VALUE}. + */ + OFF(Integer.MAX_VALUE); // typically mapped to/from j.u.l.Level.OFF + + private final int severity; + + private Level(int severity) { + this.severity = severity; + } + + /** + * Returns the name of this level. + * @return this level {@linkplain #name()}. + */ + public final String getName() { + return name(); + } + + /** + * Returns the severity of this level. + * A higher severity means a more severe condition. + * @return this level severity. + */ + public final int getSeverity() { + return severity; + } + } + + /** + * Returns the name of this logger. + * + * @return the logger name. + */ + public String getName(); + + /** + * Checks if a message of the given level would be logged by + * this logger. + * + * @param level the log message level. + * @return {@code true} if the given log message level is currently + * being logged. + * + * @throws NullPointerException if {@code level} is {@code null}. + */ + public boolean isLoggable(Level level); + + /** + * Logs a message. + * + * @implSpec The default implementation for this method calls + * {@code this.log(level, (ResourceBundle)null, msg, (Object[])null);} + * + * @param level the log message level. + * @param msg the string message (or a key in the message catalog, if + * this logger is a {@link + * LoggerFinder#getLocalizedLogger(java.lang.String, java.util.ResourceBundle, java.lang.Class) + * localized logger}); can be {@code null}. + * + * @throws NullPointerException if {@code level} is {@code null}. + */ + public default void log(Level level, String msg) { + log(level, (ResourceBundle) null, msg, (Object[]) null); + } + + /** + * Logs a lazily supplied message. + *

+ * If the logger is currently enabled for the given log message level + * then a message is logged that is the result produced by the + * given supplier function. Otherwise, the supplier is not operated on. + * + * @implSpec When logging is enabled for the given level, the default + * implementation for this method calls + * {@code this.log(level, (ResourceBundle)null, msgSupplier.get(), (Object[])null);} + * + * @param level the log message level. + * @param msgSupplier a supplier function that produces a message. + * + * @throws NullPointerException if {@code level} is {@code null}, + * or {@code msgSupplier} is {@code null}. + */ + public default void log(Level level, Supplier msgSupplier) { + Objects.requireNonNull(msgSupplier); + if (isLoggable(Objects.requireNonNull(level))) { + log(level, (ResourceBundle) null, msgSupplier.get(), (Object[]) null); + } + } + + /** + * Logs a message produced from the given object. + *

+ * If the logger is currently enabled for the given log message level then + * a message is logged that, by default, is the result produced from + * calling toString on the given object. + * Otherwise, the object is not operated on. + * + * @implSpec When logging is enabled for the given level, the default + * implementation for this method calls + * {@code this.log(level, (ResourceBundle)null, obj.toString(), (Object[])null);} + * + * @param level the log message level. + * @param obj the object to log. + * + * @throws NullPointerException if {@code level} is {@code null}, or + * {@code obj} is {@code null}. + */ + public default void log(Level level, Object obj) { + Objects.requireNonNull(obj); + if (isLoggable(Objects.requireNonNull(level))) { + this.log(level, (ResourceBundle) null, obj.toString(), (Object[]) null); + } + } + + /** + * Logs a message associated with a given throwable. + * + * @implSpec The default implementation for this method calls + * {@code this.log(level, (ResourceBundle)null, msg, thrown);} + * + * @param level the log message level. + * @param msg the string message (or a key in the message catalog, if + * this logger is a {@link + * LoggerFinder#getLocalizedLogger(java.lang.String, java.util.ResourceBundle, java.lang.Class) + * localized logger}); can be {@code null}. + * @param thrown a {@code Throwable} associated with the log message; + * can be {@code null}. + * + * @throws NullPointerException if {@code level} is {@code null}. + */ + public default void log(Level level, String msg, Throwable thrown) { + this.log(level, null, msg, thrown); + } + + /** + * Logs a lazily supplied message associated with a given throwable. + *

+ * If the logger is currently enabled for the given log message level + * then a message is logged that is the result produced by the + * given supplier function. Otherwise, the supplier is not operated on. + * + * @implSpec When logging is enabled for the given level, the default + * implementation for this method calls + * {@code this.log(level, (ResourceBundle)null, msgSupplier.get(), thrown);} + * + * @param level one of the log message level identifiers. + * @param msgSupplier a supplier function that produces a message. + * @param thrown a {@code Throwable} associated with log message; + * can be {@code null}. + * + * @throws NullPointerException if {@code level} is {@code null}, or + * {@code msgSupplier} is {@code null}. + */ + public default void log(Level level, Supplier msgSupplier, + Throwable thrown) { + Objects.requireNonNull(msgSupplier); + if (isLoggable(Objects.requireNonNull(level))) { + this.log(level, null, msgSupplier.get(), thrown); + } + } + + /** + * Logs a message with an optional list of parameters. + * + * @implSpec The default implementation for this method calls + * {@code this.log(level, (ResourceBundle)null, format, params);} + * + * @param level one of the log message level identifiers. + * @param format the string message format in {@link + * java.text.MessageFormat} format, (or a key in the message + * catalog, if this logger is a {@link + * LoggerFinder#getLocalizedLogger(java.lang.String, java.util.ResourceBundle, java.lang.Class) + * localized logger}); can be {@code null}. + * @param params an optional list of parameters to the message (may be + * none). + * + * @throws NullPointerException if {@code level} is {@code null}. + */ + public default void log(Level level, String format, Object... params) { + this.log(level, null, format, params); + } + + /** + * Logs a localized message associated with a given throwable. + *

+ * If the given resource bundle is non-{@code null}, the {@code msg} + * string is localized using the given resource bundle. + * Otherwise the {@code msg} string is not localized. + * + * @param level the log message level. + * @param bundle a resource bundle to localize {@code msg}; can be + * {@code null}. + * @param msg the string message (or a key in the message catalog, + * if {@code bundle} is not {@code null}); can be {@code null}. + * @param thrown a {@code Throwable} associated with the log message; + * can be {@code null}. + * + * @throws NullPointerException if {@code level} is {@code null}. + */ + public void log(Level level, ResourceBundle bundle, String msg, + Throwable thrown); + + /** + * Logs a message with resource bundle and an optional list of + * parameters. + *

+ * If the given resource bundle is non-{@code null}, the {@code format} + * string is localized using the given resource bundle. + * Otherwise the {@code format} string is not localized. + * + * @param level the log message level. + * @param bundle a resource bundle to localize {@code format}; can be + * {@code null}. + * @param format the string message format in {@link + * java.text.MessageFormat} format, (or a key in the message + * catalog if {@code bundle} is not {@code null}); can be {@code null}. + * @param params an optional list of parameters to the message (may be + * none). + * + * @throws NullPointerException if {@code level} is {@code null}. + */ + public void log(Level level, ResourceBundle bundle, String format, + Object... params); + + + } + + /** + * The {@code LoggerFinder} service is responsible for creating, managing, + * and configuring loggers to the underlying framework it uses. + *

+ * A logger finder is a concrete implementation of this class that has a + * zero-argument constructor and implements the abstract methods defined + * by this class. + * The loggers returned from a logger finder are capable of routing log + * messages to the logging backend this provider supports. + * A given invocation of the Java Runtime maintains a single + * system-wide LoggerFinder instance that is loaded as follows: + *

+ *

+ * An application can replace the logging backend + * even when the java.logging module is present, by simply providing + * and declaring an implementation of the {@link LoggerFinder} service. + *

+ * Default Implementation + *

+ * The system default {@code LoggerFinder} implementation uses + * {@code java.util.logging} as the backend framework when the + * {@code java.logging} module is present. + * It returns a {@linkplain System.Logger logger} instance + * that will route log messages to a {@link java.util.logging.Logger + * java.util.logging.Logger}. Otherwise, if {@code java.logging} is not + * present, the default implementation will return a simple logger + * instance that will route log messages of {@code INFO} level and above to + * the console ({@code System.err}). + *

+ * Logging Configuration + *

+ * {@linkplain Logger Logger} instances obtained from the + * {@code LoggerFinder} factory methods are not directly configurable by + * the application. Configuration is the responsibility of the underlying + * logging backend, and usually requires using APIs specific to that backend. + *

For the default {@code LoggerFinder} implementation + * using {@code java.util.logging} as its backend, refer to + * {@link java.util.logging java.util.logging} for logging configuration. + * For the default {@code LoggerFinder} implementation returning simple loggers + * when the {@code java.logging} module is absent, the configuration + * is implementation dependent. + *

+ * Usually an application that uses a logging framework will log messages + * through a logger facade defined (or supported) by that framework. + * Applications that wish to use an external framework should log + * through the facade associated with that framework. + *

+ * A system class that needs to log messages will typically obtain + * a {@link System.Logger} instance to route messages to the logging + * framework selected by the application. + *

+ * Libraries and classes that only need loggers to produce log messages + * should not attempt to configure loggers by themselves, as that + * would make them dependent from a specific implementation of the + * {@code LoggerFinder} service. + *

+ * In addition, when a security manager is present, loggers provided to + * system classes should not be directly configurable through the logging + * backend without requiring permissions. + *
+ * It is the responsibility of the provider of + * the concrete {@code LoggerFinder} implementation to ensure that + * these loggers are not configured by untrusted code without proper + * permission checks, as configuration performed on such loggers usually + * affects all applications in the same Java Runtime. + *

+ * Message Levels and Mapping to backend levels + *

+ * A logger finder is responsible for mapping from a {@code + * System.Logger.Level} to a level supported by the logging backend it uses. + *
The default LoggerFinder using {@code java.util.logging} as the backend + * maps {@code System.Logger} levels to + * {@linkplain java.util.logging.Level java.util.logging} levels + * of corresponding severity - as described in {@link Logger.Level + * Logger.Level}. + * + * @see java.lang.System + * @see java.lang.System.Logger + * + * @since 9 + */ + public static abstract class LoggerFinder { + /** + * The {@code RuntimePermission("loggerFinder")} is + * necessary to subclass and instantiate the {@code LoggerFinder} class, + * as well as to obtain loggers from an instance of that class. + */ + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + + /** + * Creates a new instance of {@code LoggerFinder}. + * + * @implNote It is recommended that a {@code LoggerFinder} service + * implementation does not perform any heavy initialization in its + * constructor, in order to avoid possible risks of deadlock or class + * loading cycles during the instantiation of the service provider. + * + * @throws SecurityException if a security manager is present and its + * {@code checkPermission} method doesn't allow the + * {@code RuntimePermission("loggerFinder")}. + */ + protected LoggerFinder() { + this(checkPermission()); + } + + private LoggerFinder(Void unused) { + // nothing to do. + } + + private static Void checkPermission() { + final SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGERFINDER_PERMISSION); + } + return null; + } + + /** + * Returns an instance of {@link Logger Logger} + * for the given {@code caller}. + * + * @param name the name of the logger. + * @param caller the class for which the logger is being requested; + * can be {@code null}. + * + * @return a {@link Logger logger} suitable for the given caller's + * use. + * @throws NullPointerException if {@code name} is {@code null} or + * {@code caller} is {@code null}. + * @throws SecurityException if a security manager is present and its + * {@code checkPermission} method doesn't allow the + * {@code RuntimePermission("loggerFinder")}. + */ + public abstract Logger getLogger(String name, /* Module */ Class caller); + + /** + * Returns a localizable instance of {@link Logger Logger} + * for the given {@code caller}. + * The returned logger will use the provided resource bundle for + * message localization. + * + * @implSpec By default, this method calls {@link + * #getLogger(java.lang.String, java.lang.Class) + * this.getLogger(name, caller)} to obtain a logger, then wraps that + * logger in a {@link Logger} instance where all methods that do not + * take a {@link ResourceBundle} as parameter are redirected to one + * which does - passing the given {@code bundle} for + * localization. So for instance, a call to {@link + * Logger#log(Level, String) Logger.log(Level.INFO, msg)} + * will end up as a call to {@link + * Logger#log(Level, ResourceBundle, String, Object...) + * Logger.log(Level.INFO, bundle, msg, (Object[])null)} on the wrapped + * logger instance. + * Note however that by default, string messages returned by {@link + * java.util.function.Supplier Supplier<String>} will not be + * localized, as it is assumed that such strings are messages which are + * already constructed, rather than keys in a resource bundle. + *

+ * An implementation of {@code LoggerFinder} may override this method, + * for example, when the underlying logging backend provides its own + * mechanism for localizing log messages, then such a + * {@code LoggerFinder} would be free to return a logger + * that makes direct use of the mechanism provided by the backend. + * + * @param name the name of the logger. + * @param bundle a resource bundle; can be {@code null}. + * @param caller the class for which the logger is being requested. + * @return an instance of {@link Logger Logger} which will use the + * provided resource bundle for message localization. + * + * @throws NullPointerException if {@code name} is {@code null} or + * {@code caller} is {@code null}. + * @throws SecurityException if a security manager is present and its + * {@code checkPermission} method doesn't allow the + * {@code RuntimePermission("loggerFinder")}. + */ + public Logger getLocalizedLogger(String name, ResourceBundle bundle, + /* Module */ Class caller) { + return new LocalizedLoggerWrapper<>(getLogger(name, caller), bundle); + } + + /** + * Returns the {@code LoggerFinder} instance. There is one + * single system-wide {@code LoggerFinder} instance in + * the Java Runtime. See the class specification of how the + * {@link LoggerFinder LoggerFinder} implementation is located and + * loaded. + + * @return the {@link LoggerFinder LoggerFinder} instance. + * @throws SecurityException if a security manager is present and its + * {@code checkPermission} method doesn't allow the + * {@code RuntimePermission("loggerFinder")}. + */ + public static LoggerFinder getLoggerFinder() { + final SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGERFINDER_PERMISSION); + } + return accessProvider(); + } + + + private static volatile LoggerFinder service; + static LoggerFinder accessProvider() { + // We do not need to synchronize: LoggerFinderLoader will + // always return the same instance, so if we don't have it, + // just fetch it again. + if (service == null) { + PrivilegedAction pa = + () -> LoggerFinderLoader.getLoggerFinder(); + service = AccessController.doPrivileged(pa, null, + LOGGERFINDER_PERMISSION); + } + return service; + } + + } + + + /** + * Returns an instance of {@link Logger Logger} for the caller's + * use. + * + * @implSpec + * Instances returned by this method route messages to loggers + * obtained by calling {@link LoggerFinder#getLogger(java.lang.String, java.lang.Class) + * LoggerFinder.getLogger(name, caller)}. + * + * @apiNote + * This method may defer calling the {@link + * LoggerFinder#getLogger(java.lang.String, java.lang.Class) + * LoggerFinder.getLogger} method to create an actual logger supplied by + * the logging backend, for instance, to allow loggers to be obtained during + * the system initialization time. + * + * @param name the name of the logger. + * @return an instance of {@link Logger} that can be used by the calling + * class. + * @throws NullPointerException if {@code name} is {@code null}. + */ + @CallerSensitive + public static Logger getLogger(String name) { + Objects.requireNonNull(name); + final Class caller = Reflection.getCallerClass(); + return LazyLoggers.getLogger(name, caller); + } + + /** + * Returns a localizable instance of {@link Logger + * Logger} for the caller's use. + * The returned logger will use the provided resource bundle for message + * localization. + * + * @implSpec + * The returned logger will perform message localization as specified + * by {@link LoggerFinder#getLocalizedLogger(java.lang.String, + * java.util.ResourceBundle, java.lang.Class) + * LoggerFinder.getLocalizedLogger(name, bundle, caller}. + * + * @apiNote + * This method is intended to be used after the system is fully initialized. + * This method may trigger the immediate loading and initialization + * of the {@link LoggerFinder} service, which may cause issues if the + * Java Runtime is not ready to initialize the concrete service + * implementation yet. + * System classes which may be loaded early in the boot sequence and + * need to log localized messages should create a logger using + * {@link #getLogger(java.lang.String)} and then use the log methods that + * take a resource bundle as parameter. + * + * @param name the name of the logger. + * @param bundle a resource bundle. + * @return an instance of {@link Logger} which will use the provided + * resource bundle for message localization. + * @throws NullPointerException if {@code name} is {@code null} or + * {@code bundle} is {@code null}. + */ + @CallerSensitive + public static Logger getLogger(String name, ResourceBundle bundle) { + final ResourceBundle rb = Objects.requireNonNull(bundle); + Objects.requireNonNull(name); + final Class caller = Reflection.getCallerClass(); + final SecurityManager sm = System.getSecurityManager(); + // We don't use LazyLoggers if a resource bundle is specified. + // Bootstrap sensitive classes in the JDK do not use resource bundles + // when logging. This could be revisited later, if it needs to. + if (sm != null) { + return AccessController.doPrivileged((PrivilegedAction) + () -> LoggerFinder.accessProvider().getLocalizedLogger(name, rb, caller), + null, + LoggerFinder.LOGGERFINDER_PERMISSION); + } + return LoggerFinder.accessProvider().getLocalizedLogger(name, rb, caller); + } + + /** * Terminates the currently running Java Virtual Machine. The * argument serves as a status code; by convention, a nonzero status * code indicates abnormal termination. --- old/jdk/src/java.base/share/classes/sun/util/logging/PlatformLogger.java 2015-11-20 17:44:02.000000000 +0100 +++ new/jdk/src/java.base/share/classes/sun/util/logging/PlatformLogger.java 2015-11-20 17:44:01.000000000 +0100 @@ -27,20 +27,13 @@ package sun.util.logging; import java.lang.ref.WeakReference; -import java.io.PrintStream; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.time.Clock; -import java.time.Instant; -import java.time.ZoneId; -import java.time.ZonedDateTime; import java.util.Arrays; import java.util.HashMap; import java.util.Map; -import jdk.internal.misc.JavaLangAccess; -import jdk.internal.misc.SharedSecrets; +import java.util.ResourceBundle; +import java.util.function.Supplier; +import jdk.internal.logger.LazyLoggers; +import jdk.internal.logger.LoggerWrapper; /** * Platform logger provides an API for the JRE components to log @@ -56,18 +49,28 @@ * the stack frame information issuing the log message. * * When the logging facility is enabled (at startup or runtime), - * the java.util.logging.Logger will be created for each platform + * the backend logger will be created for each platform * logger and all log messages will be forwarded to the Logger * to handle. * + * The PlatformLogger uses an underlying PlatformLogger.Bridge instance + * obtained by calling {@link PlatformLogger.Bridge#convert PlatformLogger.Bridge.convert(} + * {@link jdk.internal.logger.LazyLoggers#getLazyLogger(java.lang.String, java.lang.Class) + * jdk.internal.logger.LazyLoggers#getLazyLogger(name, PlatformLogger.class))}. + * * Logging facility is "enabled" when one of the following * conditions is met: - * 1) a system property "java.util.logging.config.class" or - * "java.util.logging.config.file" is set - * 2) java.util.logging.LogManager or java.util.logging.Logger - * is referenced that will trigger the logging initialization. + * 1) ServiceLoader.load({@link java.lang.System.LoggerFinder LoggerFinder.class}, + * ClassLoader.getSystemClassLoader()).iterator().hasNext(). + * 2) ServiceLoader.loadInstalled({@link jdk.internal.logger.DefaultLoggerFinder}).iterator().hasNext(), + * and 2.1) a system property "java.util.logging.config.class" or + * "java.util.logging.config.file" is set + * or 2.2) java.util.logging.LogManager or java.util.logging.Logger + * is referenced that will trigger the logging initialization. * * Default logging configuration: + * + * No LoggerFinder service implementation declared * global logging level = INFO * handlers = java.util.logging.ConsoleHandler * java.util.logging.ConsoleHandler.level = INFO @@ -84,71 +87,84 @@ * The platform loggers are designed for JDK developers use and * this limitation can be workaround with setting * -Djava.util.logging.config.file system property. + *
+ * Calling PlatformLogger.setLevel will not work when there is a custom + * LoggerFinder installed - and as a consequence {@link #setLevel setLevel} + * is now deprecated. * * @since 1.7 */ public class PlatformLogger { - // The integer values must match that of {@code java.util.logging.Level} - // objects. - private static final int OFF = Integer.MAX_VALUE; - private static final int SEVERE = 1000; - private static final int WARNING = 900; - private static final int INFO = 800; - private static final int CONFIG = 700; - private static final int FINE = 500; - private static final int FINER = 400; - private static final int FINEST = 300; - private static final int ALL = Integer.MIN_VALUE; - /** * PlatformLogger logging levels. */ public static enum Level { // The name and value must match that of {@code java.util.logging.Level}s. // Declare in ascending order of the given value for binary search. - ALL, - FINEST, - FINER, - FINE, - CONFIG, - INFO, - WARNING, - SEVERE, - OFF; - - /** - * Associated java.util.logging.Level lazily initialized in - * JavaLoggerProxy's static initializer only once - * when java.util.logging is available and enabled. - * Only accessed by JavaLoggerProxy. - */ - /* java.util.logging.Level */ Object javaLevel; + ALL(System.Logger.Level.ALL), + FINEST(System.Logger.Level.TRACE), + FINER(System.Logger.Level.TRACE), + FINE(System.Logger.Level.DEBUG), + CONFIG(System.Logger.Level.DEBUG), + INFO(System.Logger.Level.INFO), + WARNING(System.Logger.Level.WARNING), + SEVERE(System.Logger.Level.ERROR), + OFF(System.Logger.Level.OFF); + + final System.Logger.Level systemLevel; + Level(System.Logger.Level systemLevel) { + this.systemLevel = systemLevel; + } + + // The integer values must match that of {@code java.util.logging.Level} + // objects. + private static final int SEVERITY_OFF = Integer.MAX_VALUE; + private static final int SEVERITY_SEVERE = 1000; + private static final int SEVERITY_WARNING = 900; + private static final int SEVERITY_INFO = 800; + private static final int SEVERITY_CONFIG = 700; + private static final int SEVERITY_FINE = 500; + private static final int SEVERITY_FINER = 400; + private static final int SEVERITY_FINEST = 300; + private static final int SEVERITY_ALL = Integer.MIN_VALUE; // ascending order for binary search matching the list of enum constants private static final int[] LEVEL_VALUES = new int[] { - PlatformLogger.ALL, PlatformLogger.FINEST, PlatformLogger.FINER, - PlatformLogger.FINE, PlatformLogger.CONFIG, PlatformLogger.INFO, - PlatformLogger.WARNING, PlatformLogger.SEVERE, PlatformLogger.OFF + SEVERITY_ALL, SEVERITY_FINEST, SEVERITY_FINER, + SEVERITY_FINE, SEVERITY_CONFIG, SEVERITY_INFO, + SEVERITY_WARNING, SEVERITY_SEVERE, SEVERITY_OFF }; + public System.Logger.Level systemLevel() { + return systemLevel; + } + public int intValue() { return LEVEL_VALUES[this.ordinal()]; } - static Level valueOf(int level) { + /** + * Maps a severity value to an effective logger level. + * @param level The severity of the messages that should be + * logged with a logger set to the returned level. + * @return The effective logger level, which is the nearest Level value + * whose severity is greater or equal to the given level. + * For level > SEVERE (OFF excluded), return SEVERE. + */ + public static Level valueOf(int level) { switch (level) { // ordering per the highest occurrences in the jdk source // finest, fine, finer, info first - case PlatformLogger.FINEST : return Level.FINEST; - case PlatformLogger.FINE : return Level.FINE; - case PlatformLogger.FINER : return Level.FINER; - case PlatformLogger.INFO : return Level.INFO; - case PlatformLogger.WARNING : return Level.WARNING; - case PlatformLogger.CONFIG : return Level.CONFIG; - case PlatformLogger.SEVERE : return Level.SEVERE; - case PlatformLogger.OFF : return Level.OFF; - case PlatformLogger.ALL : return Level.ALL; + case SEVERITY_FINEST : return Level.FINEST; + case SEVERITY_FINE : return Level.FINE; + case SEVERITY_FINER : return Level.FINER; + case SEVERITY_INFO : return Level.INFO; + case SEVERITY_WARNING : return Level.WARNING; + case SEVERITY_CONFIG : return Level.CONFIG; + case SEVERITY_SEVERE : return Level.SEVERE; + case SEVERITY_OFF : return Level.OFF; + case SEVERITY_ALL : return Level.ALL; } // return the nearest Level value >= the given level, // for level > SEVERE, return SEVERE and exclude OFF @@ -157,39 +173,110 @@ } } - private static final Level DEFAULT_LEVEL = Level.INFO; - private static boolean loggingEnabled; - static { - loggingEnabled = AccessController.doPrivileged( - new PrivilegedAction<>() { - public Boolean run() { - String cname = System.getProperty("java.util.logging.config.class"); - String fname = System.getProperty("java.util.logging.config.file"); - return (cname != null || fname != null); - } - }); - - // force loading of all JavaLoggerProxy (sub)classes to make JIT de-optimizations - // less probable. Don't initialize JavaLoggerProxy class since - // java.util.logging may not be enabled. - try { - Class.forName("sun.util.logging.PlatformLogger$DefaultLoggerProxy", - false, - PlatformLogger.class.getClassLoader()); - Class.forName("sun.util.logging.PlatformLogger$JavaLoggerProxy", - false, // do not invoke class initializer - PlatformLogger.class.getClassLoader()); - } catch (ClassNotFoundException ex) { - throw new InternalError(ex); + /** + * + * The PlatformLogger.Bridge interface is implemented by the System.Logger + * objects returned by our default JUL provider - so that JRE classes using + * PlatformLogger see no difference when JUL is the actual backend. + * + * PlatformLogger is now only a thin adaptation layer over the same + * loggers than returned by java.lang.System.getLogger(String name). + * + * The recommendation for JRE classes going forward is to use + * java.lang.System.getLogger(String name), which will + * use Lazy Loggers when possible and necessary. + * + */ + public static interface Bridge { + + /** + * Gets the name for this platform logger. + * @return the name of the platform logger. + */ + public String getName(); + + /** + * Returns true if a message of the given level would actually + * be logged by this logger. + * @param level the level + * @return whether a message of that level would be logged + */ + public boolean isLoggable(Level level); + public boolean isEnabled(); + + public void log(Level level, String msg); + public void log(Level level, String msg, Throwable thrown); + public void log(Level level, String msg, Object... params); + public void log(Level level, Supplier msgSupplier); + public void log(Level level, Throwable thrown, Supplier msgSupplier); + public void logp(Level level, String sourceClass, String sourceMethod, String msg); + public void logp(Level level, String sourceClass, String sourceMethod, + Supplier msgSupplier); + public void logp(Level level, String sourceClass, String sourceMethod, + String msg, Object... params); + public void logp(Level level, String sourceClass, String sourceMethod, + String msg, Throwable thrown); + public void logp(Level level, String sourceClass, String sourceMethod, + Throwable thrown, Supplier msgSupplier); + public void logrb(Level level, String sourceClass, String sourceMethod, + ResourceBundle bundle, String msg, Object... params); + public void logrb(Level level, String sourceClass, String sourceMethod, + ResourceBundle bundle, String msg, Throwable thrown); + public void logrb(Level level, ResourceBundle bundle, String msg, + Object... params); + public void logrb(Level level, ResourceBundle bundle, String msg, + Throwable thrown); + + + public static Bridge convert(System.Logger logger) { + if (logger instanceof PlatformLogger.Bridge) { + return (Bridge) logger; + } else { + return new LoggerWrapper<>(logger); + } + } + } + + /** + * The {@code PlatformLogger.ConfigurableBridge} interface is used to + * implement the deprecated {@link PlatformLogger#setLevel} method. + * + * PlatformLogger is now only a thin adaptation layer over the same + * loggers than returned by java.lang.System.getLogger(String name). + * + * The recommendation for JRE classes going forward is to use + * java.lang.System.getLogger(String name), which will + * use Lazy Loggers when possible and necessary. + * + */ + public static interface ConfigurableBridge { + + public abstract class LoggerConfiguration { + public abstract Level getPlatformLevel(); + public abstract void setPlatformLevel(Level level); + } + + public default LoggerConfiguration getLoggerConfiguration() { + return null; + } + + public static LoggerConfiguration getLoggerConfiguration(PlatformLogger.Bridge logger) { + if (logger instanceof PlatformLogger.ConfigurableBridge) { + return ((ConfigurableBridge) logger).getLoggerConfiguration(); + } else { + return null; + } } } // Table of known loggers. Maps names to PlatformLoggers. - private static Map> loggers = + private static final Map> loggers = new HashMap<>(); /** * Returns a PlatformLogger of a given name. + * @param name the name of the logger + * @return a PlatformLogger */ public static synchronized PlatformLogger getLogger(String name) { PlatformLogger log = null; @@ -198,56 +285,31 @@ log = ref.get(); } if (log == null) { - log = new PlatformLogger(name); + log = new PlatformLogger(PlatformLogger.Bridge.convert( + // We pass PlatformLogger.class rather than the actual caller + // because we want PlatformLoggers to be system loggers: we + // won't need to resolve any resource bundles anyway. + // Note: Many unit tests depend on the fact that + // PlatformLogger.getLoggerFromFinder is not caller sensitive. + LazyLoggers.getLazyLogger(name, PlatformLogger.class))); loggers.put(name, new WeakReference<>(log)); } return log; } - /** - * Initialize java.util.logging.Logger objects for all platform loggers. - * This method is called from LogManager.readPrimordialConfiguration(). - */ - public static synchronized void redirectPlatformLoggers() { - if (loggingEnabled || !LoggingSupport.isAvailable()) return; - - loggingEnabled = true; - for (Map.Entry> entry : loggers.entrySet()) { - WeakReference ref = entry.getValue(); - PlatformLogger plog = ref.get(); - if (plog != null) { - plog.redirectToJavaLoggerProxy(); - } - } - } - - /** - * Creates a new JavaLoggerProxy and redirects the platform logger to it - */ - private void redirectToJavaLoggerProxy() { - DefaultLoggerProxy lp = DefaultLoggerProxy.class.cast(this.loggerProxy); - JavaLoggerProxy jlp = new JavaLoggerProxy(lp.name, lp.level); - // the order of assignments is important - this.javaLoggerProxy = jlp; // isLoggable checks javaLoggerProxy if set - this.loggerProxy = jlp; - } - - // DefaultLoggerProxy may be replaced with a JavaLoggerProxy object - // when the java.util.logging facility is enabled - private volatile LoggerProxy loggerProxy; - // javaLoggerProxy is only set when the java.util.logging facility is enabled - private volatile JavaLoggerProxy javaLoggerProxy; - private PlatformLogger(String name) { - if (loggingEnabled) { - this.loggerProxy = this.javaLoggerProxy = new JavaLoggerProxy(name); - } else { - this.loggerProxy = new DefaultLoggerProxy(name); - } + // The system loggerProxy returned by LazyLoggers + // This may be a lazy logger - see jdk.internal.logger.LazyLoggers, + // or may be a Logger instance (or a wrapper thereof). + // + private final PlatformLogger.Bridge loggerProxy; + private PlatformLogger(PlatformLogger.Bridge loggerProxy) { + this.loggerProxy = loggerProxy; } /** * A convenience method to test if the logger is turned off. * (i.e. its level is OFF). + * @return whether the logger is turned off. */ public boolean isEnabled() { return loggerProxy.isEnabled(); @@ -255,22 +317,24 @@ /** * Gets the name for this platform logger. + * @return the name of the platform logger. */ public String getName() { - return loggerProxy.name; + return loggerProxy.getName(); } /** * Returns true if a message of the given level would actually * be logged by this logger. + * @param level the level + * @return whether a message of that level would be logged */ public boolean isLoggable(Level level) { if (level == null) { throw new NullPointerException(); } - // performance-sensitive method: use two monomorphic call-sites - JavaLoggerProxy jlp = javaLoggerProxy; - return jlp != null ? jlp.isLoggable(level) : loggerProxy.isLoggable(level); + + return loggerProxy.isLoggable(level); } /** @@ -281,13 +345,15 @@ * @return this PlatformLogger's level */ public Level level() { - return loggerProxy.getLevel(); + final ConfigurableBridge.LoggerConfiguration spi = + PlatformLogger.ConfigurableBridge.getLoggerConfiguration(loggerProxy); + return spi == null ? null : spi.getPlatformLevel(); } /** * Set the log level specifying which message levels will be * logged by this logger. Message levels lower than this - * value will be discarded. The level value {@link #OFF} + * value will be discarded. The level value {@link Level#OFF} * can be used to turn off logging. *

* If the new level is null, it means that this node should @@ -295,366 +361,153 @@ * (non-null) level value. * * @param newLevel the new value for the log level (may be null) + * @deprecated Platform Loggers should not be configured programmatically. + * This method will not work if a custom {@link + * java.lang.System.LoggerFinder} is installed. */ + @Deprecated public void setLevel(Level newLevel) { - loggerProxy.setLevel(newLevel); + final ConfigurableBridge.LoggerConfiguration spi = + PlatformLogger.ConfigurableBridge.getLoggerConfiguration(loggerProxy);; + if (spi != null) { + spi.setPlatformLevel(newLevel); + } } /** * Logs a SEVERE message. + * @param msg the message */ public void severe(String msg) { - loggerProxy.doLog(Level.SEVERE, msg); + loggerProxy.log(Level.SEVERE, msg, (Object[])null); } public void severe(String msg, Throwable t) { - loggerProxy.doLog(Level.SEVERE, msg, t); + loggerProxy.log(Level.SEVERE, msg, t); } public void severe(String msg, Object... params) { - loggerProxy.doLog(Level.SEVERE, msg, params); + loggerProxy.log(Level.SEVERE, msg, params); } /** * Logs a WARNING message. + * @param msg the message */ public void warning(String msg) { - loggerProxy.doLog(Level.WARNING, msg); + loggerProxy.log(Level.WARNING, msg, (Object[])null); } public void warning(String msg, Throwable t) { - loggerProxy.doLog(Level.WARNING, msg, t); + loggerProxy.log(Level.WARNING, msg, t); } public void warning(String msg, Object... params) { - loggerProxy.doLog(Level.WARNING, msg, params); + loggerProxy.log(Level.WARNING, msg, params); } /** * Logs an INFO message. + * @param msg the message */ public void info(String msg) { - loggerProxy.doLog(Level.INFO, msg); + loggerProxy.log(Level.INFO, msg, (Object[])null); } public void info(String msg, Throwable t) { - loggerProxy.doLog(Level.INFO, msg, t); + loggerProxy.log(Level.INFO, msg, t); } public void info(String msg, Object... params) { - loggerProxy.doLog(Level.INFO, msg, params); + loggerProxy.log(Level.INFO, msg, params); } /** * Logs a CONFIG message. + * @param msg the message */ public void config(String msg) { - loggerProxy.doLog(Level.CONFIG, msg); + loggerProxy.log(Level.CONFIG, msg, (Object[])null); } public void config(String msg, Throwable t) { - loggerProxy.doLog(Level.CONFIG, msg, t); + loggerProxy.log(Level.CONFIG, msg, t); } public void config(String msg, Object... params) { - loggerProxy.doLog(Level.CONFIG, msg, params); + loggerProxy.log(Level.CONFIG, msg, params); } /** * Logs a FINE message. + * @param msg the message */ public void fine(String msg) { - loggerProxy.doLog(Level.FINE, msg); + loggerProxy.log(Level.FINE, msg, (Object[])null); } public void fine(String msg, Throwable t) { - loggerProxy.doLog(Level.FINE, msg, t); + loggerProxy.log(Level.FINE, msg, t); } public void fine(String msg, Object... params) { - loggerProxy.doLog(Level.FINE, msg, params); + loggerProxy.log(Level.FINE, msg, params); } /** * Logs a FINER message. + * @param msg the message */ public void finer(String msg) { - loggerProxy.doLog(Level.FINER, msg); + loggerProxy.log(Level.FINER, msg, (Object[])null); } public void finer(String msg, Throwable t) { - loggerProxy.doLog(Level.FINER, msg, t); + loggerProxy.log(Level.FINER, msg, t); } public void finer(String msg, Object... params) { - loggerProxy.doLog(Level.FINER, msg, params); + loggerProxy.log(Level.FINER, msg, params); } /** * Logs a FINEST message. + * @param msg the message */ public void finest(String msg) { - loggerProxy.doLog(Level.FINEST, msg); + loggerProxy.log(Level.FINEST, msg, (Object[])null); } public void finest(String msg, Throwable t) { - loggerProxy.doLog(Level.FINEST, msg, t); + loggerProxy.log(Level.FINEST, msg, t); } public void finest(String msg, Object... params) { - loggerProxy.doLog(Level.FINEST, msg, params); + loggerProxy.log(Level.FINEST, msg, params); } - /** - * Abstract base class for logging support, defining the API and common field. - */ - private abstract static class LoggerProxy { - final String name; - - protected LoggerProxy(String name) { - this.name = name; - } - - abstract boolean isEnabled(); - - abstract Level getLevel(); - abstract void setLevel(Level newLevel); - - abstract void doLog(Level level, String msg); - abstract void doLog(Level level, String msg, Throwable thrown); - abstract void doLog(Level level, String msg, Object... params); - - abstract boolean isLoggable(Level level); - } - - - private static final class DefaultLoggerProxy extends LoggerProxy { - /** - * Default platform logging support - output messages to System.err - - * equivalent to ConsoleHandler with SimpleFormatter. - */ - private static PrintStream outputStream() { - return System.err; - } - - volatile Level effectiveLevel; // effective level (never null) - volatile Level level; // current level set for this node (may be null) - - DefaultLoggerProxy(String name) { - super(name); - this.effectiveLevel = deriveEffectiveLevel(null); - this.level = null; - } - - boolean isEnabled() { - return effectiveLevel != Level.OFF; - } - - Level getLevel() { - return level; - } - - void setLevel(Level newLevel) { - Level oldLevel = level; - if (oldLevel != newLevel) { - level = newLevel; - effectiveLevel = deriveEffectiveLevel(newLevel); - } - } - - void doLog(Level level, String msg) { - if (isLoggable(level)) { - outputStream().print(format(level, msg, null)); - } - } - - void doLog(Level level, String msg, Throwable thrown) { - if (isLoggable(level)) { - outputStream().print(format(level, msg, thrown)); - } - } - - void doLog(Level level, String msg, Object... params) { - if (isLoggable(level)) { - String newMsg = formatMessage(msg, params); - outputStream().print(format(level, newMsg, null)); - } - } - - boolean isLoggable(Level level) { - Level effectiveLevel = this.effectiveLevel; - return level.intValue() >= effectiveLevel.intValue() && effectiveLevel != Level.OFF; - } - - // derive effective level (could do inheritance search like j.u.l.Logger) - private Level deriveEffectiveLevel(Level level) { - return level == null ? DEFAULT_LEVEL : level; - } - - // Copied from java.util.logging.Formatter.formatMessage - private String formatMessage(String format, Object... parameters) { - // Do the formatting. - try { - if (parameters == null || parameters.length == 0) { - // No parameters. Just return format string. - return format; - } - // Is it a java.text style format? - // Ideally we could match with - // Pattern.compile("\\{\\d").matcher(format).find()) - // However the cost is 14% higher, so we cheaply check for - // 1 of the first 4 parameters - if (format.indexOf("{0") >= 0 || format.indexOf("{1") >=0 || - format.indexOf("{2") >=0|| format.indexOf("{3") >=0) { - return java.text.MessageFormat.format(format, parameters); - } - return format; - } catch (Exception ex) { - // Formatting failed: use format string. - return format; - } - } - - private static final String formatString = - LoggingSupport.getSimpleFormat(false); // don't check logging.properties - private final ZoneId zoneId = ZoneId.systemDefault(); - private synchronized String format(Level level, String msg, Throwable thrown) { - ZonedDateTime zdt = ZonedDateTime.now(zoneId); - String throwable = ""; - if (thrown != null) { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - pw.println(); - thrown.printStackTrace(pw); - pw.close(); - throwable = sw.toString(); - } - - return String.format(formatString, - zdt, - getCallerInfo(), - name, - level.name(), - msg, - throwable); - } - - // Returns the caller's class and method's name; best effort - // if cannot infer, return the logger's name. - private String getCallerInfo() { - String sourceClassName = null; - String sourceMethodName = null; - - JavaLangAccess access = SharedSecrets.getJavaLangAccess(); - Throwable throwable = new Throwable(); - int depth = access.getStackTraceDepth(throwable); - - String logClassName = "sun.util.logging.PlatformLogger"; - boolean lookingForLogger = true; - for (int ix = 0; ix < depth; ix++) { - // Calling getStackTraceElement directly prevents the VM - // from paying the cost of building the entire stack frame. - StackTraceElement frame = - access.getStackTraceElement(throwable, ix); - String cname = frame.getClassName(); - if (lookingForLogger) { - // Skip all frames until we have found the first logger frame. - if (cname.equals(logClassName)) { - lookingForLogger = false; - } - } else { - if (!cname.equals(logClassName)) { - // We've found the relevant frame. - sourceClassName = cname; - sourceMethodName = frame.getMethodName(); - break; - } - } - } - - if (sourceClassName != null) { - return sourceClassName + " " + sourceMethodName; - } else { - return name; - } - } + // ------------------------------------ + // Maps used for Level conversion + // ------------------------------------ + + // This map is indexed by java.util.spi.Logger.Level.ordinal() and returns + // a PlatformLogger.Level + // + // ALL, TRACE, DEBUG, INFO, WARNING, ERROR, OFF + private static final Level[] spi2platformLevelMapping = { + Level.ALL, // mapped from ALL + Level.FINER, // mapped from TRACE + Level.FINE, // mapped from DEBUG + Level.INFO, // mapped from INFO + Level.WARNING, // mapped from WARNING + Level.SEVERE, // mapped from ERROR + Level.OFF // mapped from OFF + }; + + public static Level toPlatformLevel(java.lang.System.Logger.Level level) { + if (level == null) return null; + assert level.ordinal() < spi2platformLevelMapping.length; + return spi2platformLevelMapping[level.ordinal()]; } - /** - * JavaLoggerProxy forwards all the calls to its corresponding - * java.util.logging.Logger object. - */ - private static final class JavaLoggerProxy extends LoggerProxy { - // initialize javaLevel fields for mapping from Level enum -> j.u.l.Level object - static { - for (Level level : Level.values()) { - level.javaLevel = LoggingSupport.parseLevel(level.name()); - } - } - - private final /* java.util.logging.Logger */ Object javaLogger; - - JavaLoggerProxy(String name) { - this(name, null); - } - - JavaLoggerProxy(String name, Level level) { - super(name); - this.javaLogger = LoggingSupport.getLogger(name); - if (level != null) { - // level has been updated and so set the Logger's level - LoggingSupport.setLevel(javaLogger, level.javaLevel); - } - } - - void doLog(Level level, String msg) { - LoggingSupport.log(javaLogger, level.javaLevel, msg); - } - - void doLog(Level level, String msg, Throwable t) { - LoggingSupport.log(javaLogger, level.javaLevel, msg, t); - } - - void doLog(Level level, String msg, Object... params) { - if (!isLoggable(level)) { - return; - } - // only pass String objects to the j.u.l.Logger which may - // be created by untrusted code - int len = (params != null) ? params.length : 0; - Object[] sparams = new String[len]; - for (int i = 0; i < len; i++) { - sparams [i] = String.valueOf(params[i]); - } - LoggingSupport.log(javaLogger, level.javaLevel, msg, sparams); - } - - boolean isEnabled() { - return LoggingSupport.isLoggable(javaLogger, Level.OFF.javaLevel); - } - - /** - * Returns the PlatformLogger.Level mapped from j.u.l.Level - * set in the logger. If the j.u.l.Logger is set to a custom Level, - * this method will return the nearest Level. - */ - Level getLevel() { - Object javaLevel = LoggingSupport.getLevel(javaLogger); - if (javaLevel == null) return null; - - try { - return Level.valueOf(LoggingSupport.getLevelName(javaLevel)); - } catch (IllegalArgumentException e) { - return Level.valueOf(LoggingSupport.getLevelValue(javaLevel)); - } - } - - void setLevel(Level level) { - LoggingSupport.setLevel(javaLogger, level == null ? null : level.javaLevel); - } - - boolean isLoggable(Level level) { - return LoggingSupport.isLoggable(javaLogger, level.javaLevel); - } - } } --- old/jdk/src/java.desktop/share/classes/sun/font/FontUtilities.java 2015-11-20 17:44:02.000000000 +0100 +++ new/jdk/src/java.desktop/share/classes/sun/font/FontUtilities.java 2015-11-20 17:44:02.000000000 +0100 @@ -72,6 +72,8 @@ static { AccessController.doPrivileged(new PrivilegedAction() { + @SuppressWarnings("deprecation") // PlatformLogger.setLevel is deprecated. + @Override public Object run() { String osName = System.getProperty("os.name", "unknownOS"); isSolaris = osName.startsWith("SunOS"); --- old/jdk/src/java.logging/share/classes/java/util/logging/LogManager.java 2015-11-20 17:44:03.000000000 +0100 +++ new/jdk/src/java.logging/share/classes/java/util/logging/LogManager.java 2015-11-20 17:44:03.000000000 +0100 @@ -43,6 +43,7 @@ import jdk.internal.misc.JavaAWTAccess; import jdk.internal.misc.SharedSecrets; import sun.misc.ManagedLocalsThread; +import sun.util.logging.internal.LoggingProviderImpl; /** * There is a single global LogManager object that is used to @@ -436,7 +437,8 @@ readConfiguration(); // Platform loggers begin to delegate to java.util.logging.Logger - sun.util.logging.PlatformLogger.redirectPlatformLoggers(); + jdk.internal.logger.BootstrapLogger.redirectTemporaryLoggers(); + } catch (Exception ex) { assert false : "Exception raised while reading logging configuration: " + ex; } @@ -1481,7 +1483,7 @@ *

* Any {@linkplain #addConfigurationListener registered configuration * listener} will be invoked after the properties are read. - *

+ * * @apiNote This {@code readConfiguration} method should only be used for * initializing the configuration during LogManager initialization or * used with the "java.util.logging.config.class" property. @@ -2363,7 +2365,8 @@ } } - static final Permission controlPermission = new LoggingPermission("control", null); + static final Permission controlPermission = + new LoggingPermission("control", null); void checkPermission() { SecurityManager sm = System.getSecurityManager(); @@ -2607,4 +2610,69 @@ if (t instanceof RuntimeException) throw (RuntimeException)t; } + /** + * This class allows the {@link LoggingProviderImpl} to demand loggers on + * behalf of system and application classes. + */ + private static final class LoggingProviderAccess + implements LoggingProviderImpl.LogManagerAccess, + PrivilegedAction { + + private LoggingProviderAccess() { + } + + /** + * Demands a logger on behalf of the given {@code caller}. + *

+ * If a named logger suitable for the given caller is found + * returns it. + * Otherwise, creates a new logger suitable for the given caller. + * + * @param name The logger name. + * @param caller The caller on which behalf the logger is created/retrieved. + * @return A logger for the given {@code caller}. + * + * @throws NullPointerException if {@code name} is {@code null} + * or {@code caller} is {@code null}. + * @throws IllegalArgumentException if {@code manager} is not the default + * LogManager. + * @throws SecurityException if a security manager is present and the + * calling code doesn't have the + * {@link LoggingPermission LoggingPermission("demandLogger", null)}. + */ + @Override + public Logger demandLoggerFor(LogManager manager, String name, /* Module */ Class caller) { + if (manager != getLogManager()) { + // having LogManager as parameter just ensures that the + // caller will have initialized the LogManager before reaching + // here. + throw new IllegalArgumentException("manager"); + } + Objects.requireNonNull(name); + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(controlPermission); + } + if (caller.getClassLoader() == null) { + return manager.demandSystemLogger(name, + Logger.SYSTEM_LOGGER_RB_NAME, caller); + } else { + return manager.demandLogger(name, null, caller); + } + } + + @Override + public Void run() { + LoggingProviderImpl.setLogManagerAccess(INSTANCE); + return null; + } + + static final LoggingProviderAccess INSTANCE = new LoggingProviderAccess(); + } + + static { + AccessController.doPrivileged(LoggingProviderAccess.INSTANCE, null, + controlPermission); + } + } --- old/jdk/src/java.logging/share/classes/java/util/logging/LogRecord.java 2015-11-20 17:44:04.000000000 +0100 +++ new/jdk/src/java.logging/share/classes/java/util/logging/LogRecord.java 2015-11-20 17:44:04.000000000 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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 @@ -33,6 +33,7 @@ import jdk.internal.misc.JavaLangAccess; import jdk.internal.misc.SharedSecrets; +import static jdk.internal.logger.SimpleConsoleLogger.skipLoggingFrame; /** * LogRecord objects are used to pass logging requests between @@ -637,6 +638,27 @@ } // Private method to infer the caller's class and method names + // + // Note: + // For testing purposes - it is possible to customize the process + // by which LogRecord will infer the source class name and source method name + // when analyzing the call stack. + //

+ // The system property {@code jdk.logger.packages} can define a comma separated + // list of strings corresponding to additional package name prefixes that + // should be ignored when trying to infer the source caller class name. + // Those stack frames whose {@linkplain StackTraceElement#getClassName() + // declaring class name} start with one such prefix will be ignored. + //

+ // This is primarily useful when providing utility logging classes wrapping + // a logger instance, as it makes it possible to instruct LogRecord to skip + // those utility frames when inferring the caller source class name. + //

+ // The {@code jdk.logger.packages} system property is consulted only once. + //

+ // This property is not standard, implementation specific, and yet + // undocumented (and thus subject to changes without notice). + // private void inferCaller() { needToInferCaller = false; JavaLangAccess access = SharedSecrets.getJavaLangAccess(); @@ -658,8 +680,8 @@ } } else { if (!isLoggerImpl) { - // skip reflection call - if (!cname.startsWith("java.lang.reflect.") && !cname.startsWith("sun.reflect.")) { + // skip logging/logger infrastructure and reflection calls + if (!skipLoggingFrame(cname)) { // We've found the relevant frame. setSourceClassName(cname); setSourceMethodName(frame.getMethodName()); @@ -675,7 +697,6 @@ private boolean isLoggerImplFrame(String cname) { // the log record could be created for a platform logger return (cname.equals("java.util.logging.Logger") || - cname.startsWith("java.util.logging.LoggingProxyImpl") || - cname.startsWith("sun.util.logging.")); + cname.startsWith("sun.util.logging.PlatformLogger")); } } --- old/jdk/src/java.logging/share/classes/java/util/logging/Logger.java 2015-11-20 17:44:05.000000000 +0100 +++ new/jdk/src/java.logging/share/classes/java/util/logging/Logger.java 2015-11-20 17:44:04.000000000 +0100 @@ -447,8 +447,7 @@ private static Logger demandLogger(String name, String resourceBundleName, Class caller) { LogManager manager = LogManager.getLogManager(); - SecurityManager sm = System.getSecurityManager(); - if (sm != null && !SystemLoggerHelper.disableCallerCheck) { + if (!SystemLoggerHelper.disableCallerCheck) { if (caller.getClassLoader() == null) { return manager.demandSystemLogger(name, resourceBundleName, caller); } @@ -1254,14 +1253,14 @@ * with an optional list of message parameters. *

* If the logger is currently enabled for the given message - * level then a corresponding LogRecord is created and forwarded - * to all the registered output Handler objects. + * {@code level} then a corresponding {@code LogRecord} is created and + * forwarded to all the registered output {@code Handler} objects. *

* The {@code msg} string is localized using the given resource bundle. * If the resource bundle is {@code null}, then the {@code msg} string is not * localized. * - * @param level One of the message level identifiers, e.g., SEVERE + * @param level One of the message level identifiers, e.g., {@code SEVERE} * @param sourceClass Name of the class that issued the logging request * @param sourceMethod Name of the method that issued the logging request * @param bundle Resource bundle to localize {@code msg}, @@ -1285,6 +1284,36 @@ } /** + * Log a message, specifying source class, method, and resource bundle, + * with an optional list of message parameters. + *

+ * If the logger is currently enabled for the given message + * {@code level} then a corresponding {@code LogRecord} is created + * and forwarded to all the registered output {@code Handler} objects. + *

+ * The {@code msg} string is localized using the given resource bundle. + * If the resource bundle is {@code null}, then the {@code msg} string is not + * localized. + *

+ * @param level One of the message level identifiers, e.g., {@code SEVERE} + * @param bundle Resource bundle to localize {@code msg}; + * can be {@code null}. + * @param msg The string message (or a key in the message catalog) + * @param params Parameters to the message (optional, may be none). + * @since 1.9 + */ + public void logrb(Level level, ResourceBundle bundle, String msg, Object... params) { + if (!isLoggable(level)) { + return; + } + LogRecord lr = new LogRecord(level, msg); + if (params != null && params.length != 0) { + lr.setParameters(params); + } + doLog(lr, bundle); + } + + /** * Log a message, specifying source class, method, and resource bundle name, * with associated Throwable information. *

@@ -1330,19 +1359,20 @@ * with associated Throwable information. *

* If the logger is currently enabled for the given message - * level then the given arguments are stored in a LogRecord + * {@code level} then the given arguments are stored in a {@code LogRecord} * which is forwarded to all registered output handlers. *

* The {@code msg} string is localized using the given resource bundle. * If the resource bundle is {@code null}, then the {@code msg} string is not * localized. *

- * Note that the thrown argument is stored in the LogRecord thrown - * property, rather than the LogRecord parameters property. Thus it is - * processed specially by output Formatters and is not treated - * as a formatting parameter to the LogRecord message property. + * Note that the {@code thrown} argument is stored in the {@code LogRecord} + * {@code thrown} property, rather than the {@code LogRecord} + * {@code parameters} property. Thus it is + * processed specially by output {@code Formatter} objects and is not treated + * as a formatting parameter to the {@code LogRecord} {@code message} property. * - * @param level One of the message level identifiers, e.g., SEVERE + * @param level One of the message level identifiers, e.g., {@code SEVERE} * @param sourceClass Name of the class that issued the logging request * @param sourceMethod Name of the method that issued the logging request * @param bundle Resource bundle to localize {@code msg}, @@ -1363,6 +1393,42 @@ doLog(lr, bundle); } + /** + * Log a message, specifying source class, method, and resource bundle, + * with associated Throwable information. + *

+ * If the logger is currently enabled for the given message + * {@code level} then the given arguments are stored in a {@code LogRecord} + * which is forwarded to all registered output handlers. + *

+ * The {@code msg} string is localized using the given resource bundle. + * If the resource bundle is {@code null}, then the {@code msg} string is not + * localized. + *

+ * Note that the {@code thrown} argument is stored in the {@code LogRecord} + * {@code thrown} property, rather than the {@code LogRecord} + * {@code parameters} property. Thus it is + * processed specially by output {@code Formatter} objects and is not treated + * as a formatting parameter to the {@code LogRecord} {@code message} + * property. + *

+ * @param level One of the message level identifiers, e.g., {@code SEVERE} + * @param bundle Resource bundle to localize {@code msg}; + * can be {@code null}. + * @param msg The string message (or a key in the message catalog) + * @param thrown Throwable associated with the log message. + * @since 1.9 + */ + public void logrb(Level level, ResourceBundle bundle, String msg, + Throwable thrown) { + if (!isLoggable(level)) { + return; + } + LogRecord lr = new LogRecord(level, msg); + lr.setThrown(thrown); + doLog(lr, bundle); + } + //====================================================================== // Start of convenience methods for logging method entries and returns. //====================================================================== --- old/jdk/src/java.logging/share/classes/java/util/logging/SimpleFormatter.java 2015-11-20 17:44:05.000000000 +0100 +++ new/jdk/src/java.logging/share/classes/java/util/logging/SimpleFormatter.java 2015-11-20 17:44:05.000000000 +0100 @@ -27,10 +27,9 @@ package java.util.logging; import java.io.*; -import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; -import sun.util.logging.LoggingSupport; +import jdk.internal.logger.SimpleConsoleLogger; /** * Print a brief summary of the {@code LogRecord} in a human readable @@ -60,8 +59,12 @@ public class SimpleFormatter extends Formatter { // format string for printing the log record - private final String format = LoggingSupport.getSimpleFormat(); - private final ZoneId zoneId = ZoneId.systemDefault(); + static String getLoggingProperty(String name) { + return LogManager.getLogManager().getProperty(name); + } + + private final String format = + SimpleConsoleLogger.getSimpleFormat(SimpleFormatter::getLoggingProperty); /** * Format the given LogRecord. @@ -152,7 +155,7 @@ @Override public synchronized String format(LogRecord record) { ZonedDateTime zdt = ZonedDateTime.ofInstant( - record.getInstant(), zoneId); + record.getInstant(), ZoneId.systemDefault()); String source; if (record.getSourceClassName() != null) { source = record.getSourceClassName(); --- old/jdk/src/java.management/share/classes/java/lang/management/DefaultPlatformMBeanProvider.java 2015-11-20 17:44:06.000000000 +0100 +++ new/jdk/src/java.management/share/classes/java/lang/management/DefaultPlatformMBeanProvider.java 2015-11-20 17:44:06.000000000 +0100 @@ -355,36 +355,38 @@ } }); - /** - * Logging facility. - */ - initMBeanList.add(new PlatformComponent() { - private final Set platformLoggingMXBeanInterfaceNames + if (ManagementFactoryHelper.isPlatformLoggingMXBeanAvailable()) { + /** + * Logging facility. + */ + initMBeanList.add(new PlatformComponent() { + private final Set platformLoggingMXBeanInterfaceNames = Collections.unmodifiableSet(Collections.singleton( "java.lang.management.PlatformLoggingMXBean")); - @Override - public Set> mbeanInterfaces() { - return Collections.singleton(PlatformLoggingMXBean.class); - } + @Override + public Set> mbeanInterfaces() { + return Collections.singleton(PlatformLoggingMXBean.class); + } - @Override - public Set mbeanInterfaceNames() { - return platformLoggingMXBeanInterfaceNames; - } + @Override + public Set mbeanInterfaceNames() { + return platformLoggingMXBeanInterfaceNames; + } - @Override - public String getObjectNamePattern() { - return "java.util.logging:type=Logging"; - } + @Override + public String getObjectNamePattern() { + return "java.util.logging:type=Logging"; + } - @Override - public Map nameToMBeanMap() { - return Collections.singletonMap( + @Override + public Map nameToMBeanMap() { + return Collections.singletonMap( "java.util.logging:type=Logging", ManagementFactoryHelper.getPlatformLoggingMXBean()); - } - }); + } + }); + } /** * Buffer pools. --- old/jdk/src/java.management/share/classes/sun/management/ManagementFactoryHelper.java 2015-11-20 17:44:07.000000000 +0100 +++ new/jdk/src/java.management/share/classes/sun/management/ManagementFactoryHelper.java 2015-11-20 17:44:07.000000000 +0100 @@ -39,10 +39,14 @@ import jdk.internal.misc.JavaNioAccess; import jdk.internal.misc.SharedSecrets; -import sun.util.logging.LoggingSupport; + import java.util.ArrayList; import java.util.List; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.security.PrivilegedAction; + /** * ManagementFactoryHelper provides static factory methods to create * instances of the management interface. @@ -141,13 +145,17 @@ } public static PlatformLoggingMXBean getPlatformLoggingMXBean() { - if (LoggingSupport.isAvailable()) { + if (LoggingMXBeanSupport.isAvailable()) { return PlatformLoggingImpl.instance; } else { return null; } } + public static boolean isPlatformLoggingMXBeanAvailable() { + return LoggingMXBeanSupport.isAvailable(); + } + /** * The logging MXBean object is an instance of * PlatformLoggingMXBean and java.util.logging.LoggingMXBean @@ -165,8 +173,44 @@ 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 + // whether java.util.logging is present, and load the actual LoggingMXBean + // implementation. + // + static final class LoggingMXBeanSupport { + final static Object loggingImpl = + AccessController.doPrivileged(new PrivilegedAction() { + @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 boolean isAvailable() { + return loggingImpl != null; + } + } + 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"; @@ -188,22 +232,22 @@ @Override public java.util.List getLoggerNames() { - return LoggingSupport.getLoggerNames(); + return impl.getLoggerNames(); } @Override public String getLoggerLevel(String loggerName) { - return LoggingSupport.getLoggerLevel(loggerName); + return impl.getLoggerLevel(loggerName); } @Override public void setLoggerLevel(String loggerName, String levelName) { - LoggingSupport.setLoggerLevel(loggerName, levelName); + impl.setLoggerLevel(loggerName, levelName); } @Override public String getParentLoggerName(String loggerName) { - return LoggingSupport.getParentLoggerName(loggerName); + return impl.getParentLoggerName(loggerName); } } --- old/jdk/test/java/util/logging/LoggerSubclass.java 2015-11-20 17:44:07.000000000 +0100 +++ new/jdk/test/java/util/logging/LoggerSubclass.java 2015-11-20 17:44:07.000000000 +0100 @@ -92,7 +92,7 @@ else fail(x + " not equal to " + y);} public static void main(String[] args) throws Throwable { try {new LoggerSubclass().instanceMain(args);} - catch (Throwable e) {throw e.getCause();}} + catch (Throwable e) {throw e.getCause() == null ? e : e.getCause();}} public void instanceMain(String[] args) throws Throwable { try {test(args);} catch (Throwable t) {unexpected(t);} System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed); --- old/jdk/test/sun/util/logging/PlatformLoggerTest.java 2015-11-20 17:44:08.000000000 +0100 +++ new/jdk/test/sun/util/logging/PlatformLoggerTest.java 2015-11-20 17:44:08.000000000 +0100 @@ -38,7 +38,6 @@ import java.lang.reflect.Modifier; import java.util.logging.*; import sun.util.logging.PlatformLogger; -import sun.util.logging.LoggingSupport; import static sun.util.logging.PlatformLogger.Level.*; public class PlatformLoggerTest { @@ -195,7 +194,9 @@ System.out.println("Testing Java Level with: " + level.getName()); // create a brand new java logger - Logger javaLogger = (Logger) LoggingSupport.getLogger(logger.getName()+"."+level.getName()); + Logger javaLogger = sun.util.logging.internal.LoggingProviderImpl.getLogManagerAccess() + .demandLoggerFor(LogManager.getLogManager(), + logger.getName()+"."+level.getName(), Thread.class); // Set a non standard java.util.logging.Level on the java logger // (except for OFF & ALL - which will remain unchanged) --- /dev/null 2015-11-20 17:44:09.000000000 +0100 +++ new/jdk/src/java.base/share/classes/jdk/internal/logger/AbstractLoggerWrapper.java 2015-11-20 17:44:09.000000000 +0100 @@ -0,0 +1,380 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.logger; + +import java.util.ResourceBundle; +import java.util.function.Supplier; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import sun.util.logging.PlatformLogger; + +/** + * An implementation of {@link System.Logger System.Logger} + * that redirects all calls to a wrapped instance of {@link + * System.Logger System.Logger} + * + * @param Type of the wrapped Logger: {@code Logger} or + * an extension of that interface. + * + */ +abstract class AbstractLoggerWrapper + implements Logger, PlatformLogger.Bridge, PlatformLogger.ConfigurableBridge { + + AbstractLoggerWrapper() { } + + abstract L wrapped(); + + abstract PlatformLogger.Bridge platformProxy(); + + L getWrapped() { + return wrapped(); + } + + @Override + public final String getName() { + return wrapped().getName(); + } + + // ----------------------------------------------------------------- + // Generic methods taking a Level as parameter + // ----------------------------------------------------------------- + + + @Override + public boolean isLoggable(Level level) { + return wrapped().isLoggable(level); + } + + @Override + public void log(Level level, String msg) { + wrapped().log(level, msg); + } + + @Override + public void log(Level level, + Supplier msgSupplier) { + wrapped().log(level, msgSupplier); + } + + @Override + public void log(Level level, Object obj) { + wrapped().log(level, obj); + } + + @Override + public void log(Level level, + String msg, Throwable thrown) { + wrapped().log(level, msg, thrown); + } + + @Override + public void log(Level level, Supplier msgSupplier, Throwable thrown) { + wrapped().log(level, msgSupplier, thrown); + } + + @Override + public void log(Level level, + String format, Object... params) { + wrapped().log(level, format, params); + } + + @Override + public void log(Level level, ResourceBundle bundle, + String key, Throwable thrown) { + wrapped().log(level, bundle, key, thrown); + } + + @Override + public void log(Level level, ResourceBundle bundle, + String format, Object... params) { + wrapped().log(level, bundle, format, params); + } + + // --------------------------------------------------------- + // Methods from PlatformLogger.Bridge + // --------------------------------------------------------- + + @Override + public boolean isLoggable(PlatformLogger.Level level) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) return isLoggable(level.systemLevel()); + else return platformProxy.isLoggable(level); + } + + @Override + public boolean isEnabled() { + final PlatformLogger.Bridge platformProxy = platformProxy(); + return platformProxy == null || platformProxy.isEnabled(); + } + + @Override + public void log(PlatformLogger.Level level, String msg) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { + wrapped().log(level.systemLevel(), msg); + } else { + platformProxy.log(level, msg); + } + } + + @Override + public void log(PlatformLogger.Level level, String msg, Throwable thrown) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { + wrapped().log(level.systemLevel(), msg, thrown); + } else { + platformProxy.log(level, msg, thrown); + } + } + + @Override + public void log(PlatformLogger.Level level, String msg, Object... params) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { + wrapped().log(level.systemLevel(), msg, params); + } else { + platformProxy.log(level, msg, params); + } + } + + @Override + public void log(PlatformLogger.Level level, Supplier msgSupplier) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { + wrapped().log(level.systemLevel(),msgSupplier); + } else { + platformProxy.log(level,msgSupplier); + } + } + + @Override + public void log(PlatformLogger.Level level, Throwable thrown, + Supplier msgSupplier) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { + wrapped().log(level.systemLevel(), msgSupplier, thrown); + } else { + platformProxy.log(level, thrown, msgSupplier); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { + if (sourceClass == null && sourceMethod == null) { // best effort + wrapped().log(level.systemLevel(), msg); + } else { + Level systemLevel = level.systemLevel(); + Logger wrapped = wrapped(); + if (wrapped.isLoggable(systemLevel)) { + sourceClass = sourceClass == null ? "" : sourceClass; + sourceMethod = sourceMethod == null ? "" : sourceMethod; + msg = msg == null ? "" : msg; + wrapped.log(systemLevel, String.format("[%s %s] %s", + sourceClass, sourceMethod, msg)); + } + } + } else { + platformProxy.logp(level, sourceClass, sourceMethod, msg); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Supplier msgSupplier) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { // best effort + if (sourceClass == null && sourceMethod == null) { + wrapped().log(level.systemLevel(), msgSupplier); + } else { + Level systemLevel = level.systemLevel(); + Logger wrapped = wrapped(); + if (wrapped.isLoggable(systemLevel)) { + final String sClass = sourceClass == null ? "" : sourceClass; + final String sMethod = sourceMethod == null ? "" : sourceMethod; + wrapped.log(systemLevel, () -> String.format("[%s %s] %s", + sClass, sMethod, msgSupplier.get())); + } + } + } else { + platformProxy.logp(level, sourceClass, sourceMethod, msgSupplier); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Object... params) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { // best effort + if (sourceClass == null && sourceMethod == null) { + wrapped().log(level.systemLevel(), msg, params); + } else { + Level systemLevel = level.systemLevel(); + Logger wrapped = wrapped(); + if (wrapped.isLoggable(systemLevel)) { + sourceClass = sourceClass == null ? "" : sourceClass; + sourceMethod = sourceMethod == null ? "" : sourceMethod; + msg = msg == null ? "" : msg; + wrapped.log(systemLevel, String.format("[%s %s] %s", + sourceClass, sourceMethod, msg), params); + } + } + } else { + platformProxy.logp(level, sourceClass, sourceMethod, msg, params); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Throwable thrown) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { // best effort + if (sourceClass == null && sourceMethod == null) { + wrapped().log(level.systemLevel(), msg, thrown); + } else { + Level systemLevel = level.systemLevel(); + Logger wrapped = wrapped(); + if (wrapped.isLoggable(systemLevel)) { + sourceClass = sourceClass == null ? "" : sourceClass; + sourceMethod = sourceMethod == null ? "" : sourceMethod; + msg = msg == null ? "" : msg; + wrapped.log(systemLevel, String.format("[%s %s] %s", + sourceClass, sourceMethod, msg), thrown); + } + } + } else { + platformProxy.logp(level, sourceClass, sourceMethod, msg, thrown); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Throwable thrown, + Supplier msgSupplier) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { // best effort + if (sourceClass == null && sourceMethod == null) { + wrapped().log(level.systemLevel(), msgSupplier, thrown); + } else { + Level systemLevel = level.systemLevel(); + Logger wrapped = wrapped(); + if (wrapped.isLoggable(systemLevel)) { + final String sClass = sourceClass == null ? "" : sourceClass; + final String sMethod = sourceMethod == null ? "" : sourceMethod; + wrapped.log(systemLevel, () -> String.format("[%s %s] %s", + sClass, sMethod, msgSupplier.get()), thrown); + } + } + } else { + platformProxy.logp(level, sourceClass, sourceMethod, + thrown, msgSupplier); + } + } + + @Override + public void logrb(PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, + String msg, Object... params) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { // best effort + if (bundle != null || sourceClass == null && sourceMethod == null) { + wrapped().log(level.systemLevel(), bundle, msg, params); + } else { + Level systemLevel = level.systemLevel(); + Logger wrapped = wrapped(); + if (wrapped.isLoggable(systemLevel)) { + sourceClass = sourceClass == null ? "" : sourceClass; + sourceMethod = sourceMethod == null ? "" : sourceMethod; + msg = msg == null ? "" : msg; + wrapped.log(systemLevel, bundle, String.format("[%s %s] %s", + sourceClass, sourceMethod, msg), params); + } + } + } else { + platformProxy.logrb(level, sourceClass, sourceMethod, + bundle, msg, params); + } + } + + @Override + public void logrb(PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String msg, + Throwable thrown) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { // best effort + if (bundle != null || sourceClass == null && sourceMethod == null) { + wrapped().log(level.systemLevel(), bundle, msg, thrown); + } else { + Level systemLevel = level.systemLevel(); + Logger wrapped = wrapped(); + if (wrapped.isLoggable(systemLevel)) { + sourceClass = sourceClass == null ? "" : sourceClass; + sourceMethod = sourceMethod == null ? "" : sourceMethod; + msg = msg == null ? "" : msg; + wrapped.log(systemLevel, bundle, String.format("[%s %s] %s", + sourceClass, sourceMethod, msg), thrown); + } + } + } else { + platformProxy.logrb(level, sourceClass, sourceMethod, bundle, + msg, thrown); + } + } + + @Override + public void logrb(PlatformLogger.Level level, ResourceBundle bundle, + String msg, Throwable thrown) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { + wrapped().log(level.systemLevel(), bundle, msg, thrown); + } else { + platformProxy.logrb(level, bundle, msg, thrown); + } + } + + @Override + public void logrb(PlatformLogger.Level level, ResourceBundle bundle, + String msg, Object... params) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { + wrapped().log(level.systemLevel(), bundle, msg, params); + } else { + platformProxy.logrb(level, bundle, msg, params); + } + } + + + @Override + public LoggerConfiguration getLoggerConfiguration() { + final PlatformLogger.Bridge platformProxy = platformProxy(); + return platformProxy == null ? null + : PlatformLogger.ConfigurableBridge + .getLoggerConfiguration(platformProxy); + } + +} --- /dev/null 2015-11-20 17:44:09.000000000 +0100 +++ new/jdk/src/java.base/share/classes/jdk/internal/logger/BootstrapLogger.java 2015-11-20 17:44:09.000000000 +0100 @@ -0,0 +1,1074 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.logger; + +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.ServiceLoader; +import java.util.function.BooleanSupplier; +import java.util.function.Function; +import java.util.function.Supplier; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.lang.ref.WeakReference; +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import sun.misc.InnocuousThread; +import sun.misc.VM; +import sun.util.logging.PlatformLogger; +import jdk.internal.logger.LazyLoggers.LazyLoggerAccessor; + +/** + * The BootstrapLogger class handles all the logic needed by Lazy Loggers + * to delay the creation of System.Logger instances until the VM is booted. + * By extension - it also contains the logic that will delay the creation + * of JUL Loggers until the LogManager is initialized by the application, in + * the common case where JUL is the default and there is no custom JUL + * configuration. + * + * A BootstrapLogger instance is both a Logger and a + * PlatformLogger.Bridge instance, which will put all Log messages in a queue + * until the VM is booted. + * Once the VM is booted, it obtain the real System.Logger instance from the + * LoggerFinder and flushes the message to the queue. + * + * There are a few caveat: + * - the queue may not be flush until the next message is logged after + * the VM is booted + * - while the BootstrapLogger is active, the default implementation + * for all convenience methods is used + * - PlatformLogger.setLevel calls are ignored + * + * + */ +public final class BootstrapLogger implements Logger, PlatformLogger.Bridge, + PlatformLogger.ConfigurableBridge { + + // We use the BootstrapExecutors class to submit delayed messages + // to an independent InnocuousThread which will ensure that + // delayed log events will be clearly identified as messages that have + // been delayed during the boot sequence. + private static class BootstrapExecutors implements ThreadFactory { + + // Maybe that should be made configurable with system properties. + static final long KEEP_EXECUTOR_ALIVE_SECONDS = 30; + + // The BootstrapMessageLoggerTask is a Runnable which keeps + // a hard ref to the ExecutorService that owns it. + // This ensure that the ExecutorService is not gc'ed until the thread + // has stopped running. + private static class BootstrapMessageLoggerTask implements Runnable { + ExecutorService owner; + Runnable run; + public BootstrapMessageLoggerTask(ExecutorService owner, Runnable r) { + this.owner = owner; + this.run = r; + } + @Override + public void run() { + try { + run.run(); + } finally { + owner = null; // allow the ExecutorService to be gced. + } + } + } + + private static volatile WeakReference executorRef; + private static ExecutorService getExecutor() { + WeakReference ref = executorRef; + ExecutorService executor = ref == null ? null : ref.get(); + if (executor != null) return executor; + synchronized (BootstrapExecutors.class) { + ref = executorRef; + executor = ref == null ? null : ref.get(); + if (executor == null) { + executor = new ThreadPoolExecutor(0, 1, + KEEP_EXECUTOR_ALIVE_SECONDS, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(), new BootstrapExecutors()); + } + // The executor service will be elligible for gc + // KEEP_EXECUTOR_ALIVE_SECONDS seconds (30s) + // after the execution of its last pending task. + executorRef = new WeakReference<>(executor); + return executorRef.get(); + } + } + + @Override + public Thread newThread(Runnable r) { + ExecutorService owner = getExecutor(); + Thread thread = AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Thread run() { + Thread t = new InnocuousThread(new BootstrapMessageLoggerTask(owner, r)); + t.setName("BootstrapMessageLoggerTask-"+t.getName()); + return t; + } + }, null, new RuntimePermission("enableContextClassLoaderOverride")); + thread.setDaemon(true); + return thread; + } + + static void submit(Runnable r) { + getExecutor().execute(r); + } + + // This is used by tests. + static void join(Runnable r) { + try { + getExecutor().submit(r).get(); + } catch (InterruptedException | ExecutionException ex) { + // should not happen + throw new RuntimeException(ex); + } + } + + // This is used by tests. + static void awaitPendingTasks() { + WeakReference ref = executorRef; + ExecutorService executor = ref == null ? null : ref.get(); + if (ref == null) { + synchronized(BootstrapExecutors.class) { + ref = executorRef; + executor = ref == null ? null : ref.get(); + } + } + if (executor != null) { + // since our executor uses a FIFO and has a single thread + // then awaiting the execution of its pending tasks can be done + // simply by registering a new task and waiting until it + // completes. This of course would not work if we were using + // several threads, but we don't. + join(()->{}); + } + } + + // This is used by tests. + static boolean isAlive() { + WeakReference ref = executorRef; + ExecutorService executor = ref == null ? null : ref.get(); + if (executor != null) return true; + synchronized (BootstrapExecutors.class) { + ref = executorRef; + executor = ref == null ? null : ref.get(); + return executor != null; + } + } + + // The pending log event queue. The first event is the head, and + // new events are added at the tail + static LogEvent head, tail; + + static void enqueue(LogEvent event) { + if (event.next != null) return; + synchronized (BootstrapExecutors.class) { + if (event.next != null) return; + event.next = event; + if (tail == null) { + head = tail = event; + } else { + tail.next = event; + tail = event; + } + } + } + + static void flush() { + LogEvent event; + // drain the whole queue + synchronized(BootstrapExecutors.class) { + event = head; + head = tail = null; + } + while(event != null) { + LogEvent.log(event); + synchronized(BootstrapExecutors.class) { + LogEvent prev = event; + event = (event.next == event ? null : event.next); + prev.next = null; + } + } + } + } + + // The accessor in which this logger is temporarily set. + final LazyLoggerAccessor holder; + + BootstrapLogger(LazyLoggerAccessor holder) { + this.holder = holder; + } + + // Temporary data object storing log events + // It would be nice to use a Consumer instead of a LogEvent. + // This way we could simply do things like: + // push((logger) -> logger.log(level, msg)); + // Unfortunately, if we come to here it means we are in the bootsraping + // phase where using lambdas is not safe yet - so we have to use a + // a data object instead... + // + static final class LogEvent { + // only one of these two levels should be non null + final Level level; + final PlatformLogger.Level platformLevel; + final BootstrapLogger bootstrap; + + final ResourceBundle bundle; + final String msg; + final Throwable thrown; + final Object[] params; + final Supplier msgSupplier; + final String sourceClass; + final String sourceMethod; + final long timeMillis; + final long nanoAdjustment; + + // because logging a message may entail calling toString() on + // the parameters etc... we need to store the context of the + // caller who logged the message - so that we can reuse it when + // we finally log the message. + final AccessControlContext acc; + + // The next event in the queue + LogEvent next; + + private LogEvent(BootstrapLogger bootstrap, Level level, + ResourceBundle bundle, String msg, + Throwable thrown, Object[] params) { + this.acc = AccessController.getContext(); + this.timeMillis = System.currentTimeMillis(); + this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis); + this.level = level; + this.platformLevel = null; + this.bundle = bundle; + this.msg = msg; + this.msgSupplier = null; + this.thrown = thrown; + this.params = params; + this.sourceClass = null; + this.sourceMethod = null; + this.bootstrap = bootstrap; + } + + private LogEvent(BootstrapLogger bootstrap, Level level, + Supplier msgSupplier, + Throwable thrown, Object[] params) { + this.acc = AccessController.getContext(); + this.timeMillis = System.currentTimeMillis(); + this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis); + this.level = level; + this.platformLevel = null; + this.bundle = null; + this.msg = null; + this.msgSupplier = msgSupplier; + this.thrown = thrown; + this.params = params; + this.sourceClass = null; + this.sourceMethod = null; + this.bootstrap = bootstrap; + } + + private LogEvent(BootstrapLogger bootstrap, + PlatformLogger.Level platformLevel, + String sourceClass, String sourceMethod, + ResourceBundle bundle, String msg, + Throwable thrown, Object[] params) { + this.acc = AccessController.getContext(); + this.timeMillis = System.currentTimeMillis(); + this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis); + this.level = null; + this.platformLevel = platformLevel; + this.bundle = bundle; + this.msg = msg; + this.msgSupplier = null; + this.thrown = thrown; + this.params = params; + this.sourceClass = sourceClass; + this.sourceMethod = sourceMethod; + this.bootstrap = bootstrap; + } + + private LogEvent(BootstrapLogger bootstrap, + PlatformLogger.Level platformLevel, + String sourceClass, String sourceMethod, + Supplier msgSupplier, + Throwable thrown, Object[] params) { + this.acc = AccessController.getContext(); + this.timeMillis = System.currentTimeMillis(); + this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis); + this.level = null; + this.platformLevel = platformLevel; + this.bundle = null; + this.msg = null; + this.msgSupplier = msgSupplier; + this.thrown = thrown; + this.params = params; + this.sourceClass = sourceClass; + this.sourceMethod = sourceMethod; + this.bootstrap = bootstrap; + } + + // Log this message in the given logger. Do not call directly. + // Use LogEvent.log(LogEvent, logger) instead. + private void log(Logger logger) { + assert platformLevel == null && level != null; + //new Exception("logging delayed message").printStackTrace(); + if (msgSupplier != null) { + if (thrown != null) { + logger.log(level, msgSupplier, thrown); + } else { + logger.log(level, msgSupplier); + } + } else { + // BootstrapLoggers are never localized so we can safely + // use the method that takes a ResourceBundle parameter + // even when that resource bundle is null. + if (thrown != null) { + logger.log(level, bundle, msg, thrown); + } else { + logger.log(level, bundle, msg, params); + } + } + } + + // Log this message in the given logger. Do not call directly. + // Use LogEvent.doLog(LogEvent, logger) instead. + private void log(PlatformLogger.Bridge logger) { + assert platformLevel != null && level == null; + if (sourceClass == null) { + if (msgSupplier != null) { + if (thrown != null) { + logger.log(platformLevel, thrown, msgSupplier); + } else { + logger.log(platformLevel, msgSupplier); + } + } else { + // BootstrapLoggers are never localized so we can safely + // use the method that takes a ResourceBundle parameter + // even when that resource bundle is null. + if (thrown != null) { + logger.logrb(platformLevel, bundle, msg, thrown); + } else { + logger.logrb(platformLevel, bundle, msg, params); + } + } + } else { + if (msgSupplier != null) { + if (thrown != null) { + logger.log(platformLevel, sourceClass, sourceMethod, thrown, msgSupplier); + } else { + logger.log(platformLevel,sourceClass, sourceMethod, msgSupplier); + } + } else { + // BootstrapLoggers are never localized so we can safely + // use the method that takes a ResourceBundle parameter + // even when that resource bundle is null. + if (thrown != null) { + logger.logrb(platformLevel, sourceClass, sourceMethod, bundle, msg, thrown); + } else { + logger.logrb(platformLevel, sourceClass, sourceMethod, bundle, msg, params); + } + } + } + } + + // non default methods from Logger interface + static LogEvent valueOf(BootstrapLogger bootstrap, Level level, + ResourceBundle bundle, String key, Throwable thrown) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), bundle, key, + thrown, null); + } + static LogEvent valueOf(BootstrapLogger bootstrap, Level level, + ResourceBundle bundle, String format, Object[] params) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), bundle, format, + null, params); + } + static LogEvent valueOf(BootstrapLogger bootstrap, Level level, + Supplier msgSupplier, Throwable thrown) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), + Objects.requireNonNull(msgSupplier), thrown, null); + } + static LogEvent valueOf(BootstrapLogger bootstrap, Level level, + Supplier msgSupplier) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), + Objects.requireNonNull(msgSupplier), null, null); + } + static void log(LogEvent log, Logger logger) { + final SecurityManager sm = System.getSecurityManager(); + // not sure we can actually use lambda here. We may need to create + // an anonymous class. Although if we reach here, then it means + // the VM is booted. + if (sm == null || log.acc == null) { + BootstrapExecutors.submit(() -> log.log(logger)); + } else { + BootstrapExecutors.submit(() -> + AccessController.doPrivileged((PrivilegedAction) () -> { + log.log(logger); return null; + }, log.acc)); + } + } + + // non default methods from PlatformLogger.Bridge interface + static LogEvent valueOf(BootstrapLogger bootstrap, + PlatformLogger.Level level, String msg) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), null, null, null, + msg, null, null); + } + static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level, + String msg, Throwable thrown) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), null, null, null, msg, thrown, null); + } + static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level, + String msg, Object[] params) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), null, null, null, msg, null, params); + } + static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level, + Supplier msgSupplier) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), null, null, msgSupplier, null, null); + } + static LogEvent vaueOf(BootstrapLogger bootstrap, PlatformLogger.Level level, + Supplier msgSupplier, + Throwable thrown) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), null, null, + msgSupplier, thrown, null); + } + static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level, + String sourceClass, String sourceMethod, + ResourceBundle bundle, String msg, Object[] params) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), sourceClass, + sourceMethod, bundle, msg, null, params); + } + static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level, + String sourceClass, String sourceMethod, + ResourceBundle bundle, String msg, Throwable thrown) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), sourceClass, + sourceMethod, bundle, msg, thrown, null); + } + static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level, + String sourceClass, String sourceMethod, + Supplier msgSupplier, Throwable thrown) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), sourceClass, + sourceMethod, msgSupplier, thrown, null); + } + static void log(LogEvent log, PlatformLogger.Bridge logger) { + final SecurityManager sm = System.getSecurityManager(); + if (sm == null || log.acc == null) { + log.log(logger); + } else { + // not sure we can actually use lambda here. We may need to create + // an anonymous class. Although if we reach here, then it means + // the VM is booted. + AccessController.doPrivileged((PrivilegedAction) () -> { + log.log(logger); return null; + }, log.acc); + } + } + + static void log(LogEvent event) { + event.bootstrap.flush(event); + } + + } + + // Push a log event at the end of the pending LogEvent queue. + void push(LogEvent log) { + BootstrapExecutors.enqueue(log); + // if the queue has been flushed just before we entered + // the synchronized block we need to flush it again. + checkBootstrapping(); + } + + // Flushes the queue of pending LogEvents to the logger. + void flush(LogEvent event) { + assert event.bootstrap == this; + if (event.platformLevel != null) { + PlatformLogger.Bridge concrete = holder.getConcretePlatformLogger(this); + LogEvent.log(event, concrete); + } else { + Logger concrete = holder.getConcreteLogger(this); + LogEvent.log(event, concrete); + } + } + + /** + * The name of this logger. This is the name of the actual logger for which + * this logger acts as a temporary proxy. + * @return The logger name. + */ + @Override + public String getName() { + return holder.name; + } + + /** + * Check whether the VM is still bootstrapping, and if not, arranges + * for this logger's holder to create the real logger and flush the + * pending event queue. + * @return true if the VM is still bootstrapping. + */ + boolean checkBootstrapping() { + if (isBooted()) { + BootstrapExecutors.flush(); + return false; + } + return true; + } + + // ---------------------------------- + // Methods from Logger + // ---------------------------------- + + @Override + public boolean isLoggable(Level level) { + if (checkBootstrapping()) { + return level.getSeverity() >= Level.INFO.getSeverity(); + } else { + final Logger spi = holder.wrapped(); + return spi.isLoggable(level); + } + } + + @Override + public void log(Level level, ResourceBundle bundle, String key, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, bundle, key, thrown)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, bundle, key, thrown); + } + } + + @Override + public void log(Level level, ResourceBundle bundle, String format, Object... params) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, bundle, format, params)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, bundle, format, params); + } + } + + @Override + public void log(Level level, String msg, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, null, msg, thrown)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, msg, thrown); + } + } + + @Override + public void log(Level level, String format, Object... params) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, null, format, params)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, format, params); + } + } + + @Override + public void log(Level level, Supplier msgSupplier) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, msgSupplier)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, msgSupplier); + } + } + + @Override + public void log(Level level, Object obj) { + if (checkBootstrapping()) { + Logger.super.log(level, obj); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, obj); + } + } + + @Override + public void log(Level level, String msg) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, null, msg, (Object[])null)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, msg); + } + } + + @Override + public void log(Level level, Supplier msgSupplier, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, msgSupplier, thrown)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, msgSupplier, thrown); + } + } + + // ---------------------------------- + // Methods from PlatformLogger.Bridge + // ---------------------------------- + + @Override + public boolean isLoggable(PlatformLogger.Level level) { + if (checkBootstrapping()) { + return level.intValue() >= PlatformLogger.Level.INFO.intValue(); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + return spi.isLoggable(level); + } + } + + @Override + public boolean isEnabled() { + if (checkBootstrapping()) { + return true; + } else { + final PlatformLogger.Bridge spi = holder.platform(); + return spi.isEnabled(); + } + } + + @Override + public void log(PlatformLogger.Level level, String msg) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, msg)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.log(level, msg); + } + } + + @Override + public void log(PlatformLogger.Level level, String msg, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, msg, thrown)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.log(level, msg, thrown); + } + } + + @Override + public void log(PlatformLogger.Level level, String msg, Object... params) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, msg, params)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.log(level, msg, params); + } + } + + @Override + public void log(PlatformLogger.Level level, Supplier msgSupplier) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, msgSupplier)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.log(level, msgSupplier); + } + } + + @Override + public void log(PlatformLogger.Level level, Throwable thrown, + Supplier msgSupplier) { + if (checkBootstrapping()) { + push(LogEvent.vaueOf(this, level, msgSupplier, thrown)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.log(level, thrown, msgSupplier); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, null, + msg, (Object[])null)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logp(level, sourceClass, sourceMethod, msg); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Supplier msgSupplier) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, msgSupplier, null)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logp(level, sourceClass, sourceMethod, msgSupplier); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Object... params) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, null, msg, params)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logp(level, sourceClass, sourceMethod, msg, params); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, null, msg, thrown)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logp(level, sourceClass, sourceMethod, msg, thrown); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Throwable thrown, Supplier msgSupplier) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, msgSupplier, thrown)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logp(level, sourceClass, sourceMethod, thrown, msgSupplier); + } + } + + @Override + public void logrb(PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String msg, Object... params) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, bundle, msg, params)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logrb(level, sourceClass, sourceMethod, bundle, msg, params); + } + } + + @Override + public void logrb(PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String msg, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, bundle, msg, thrown)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logrb(level, sourceClass, sourceMethod, bundle, msg, thrown); + } + } + + @Override + public void logrb(PlatformLogger.Level level, ResourceBundle bundle, + String msg, Object... params) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, null, null, bundle, msg, params)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logrb(level, bundle, msg, params); + } + } + + @Override + public void logrb(PlatformLogger.Level level, ResourceBundle bundle, String msg, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, null, null, bundle, msg, thrown)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logrb(level, bundle, msg, thrown); + } + } + + @Override + public LoggerConfiguration getLoggerConfiguration() { + if (checkBootstrapping()) { + // This practically means that PlatformLogger.setLevel() + // calls will be ignored if the VM is still bootstrapping. We could + // attempt to fix that but is it worth it? + return PlatformLogger.ConfigurableBridge.super.getLoggerConfiguration(); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + return PlatformLogger.ConfigurableBridge.getLoggerConfiguration(spi); + } + } + + // This BooleanSupplier is a hook for tests - so that we can simulate + // what would happen before the VM is booted. + private static volatile BooleanSupplier isBooted; + public static boolean isBooted() { + if (isBooted != null) return isBooted.getAsBoolean(); + else return VM.isBooted(); + } + + // A bit of black magic. We try to find out the nature of the logging + // backend without actually loading it. + private static enum LoggingBackend { + // There is no LoggerFinder and JUL is not present + NONE(true), + + // There is no LoggerFinder, but we have found a + // JdkLoggerFinder installed (which means JUL is present), + // and we haven't found any custom configuration for JUL. + // Until LogManager is initialized we can use a simple console + // logger. + JUL_DEFAULT(false), + + // Same as above, except that we have found a custom configuration + // for JUL. We cannot use the simple console logger in this case. + JUL_WITH_CONFIG(true), + + // We have found a custom LoggerFinder. + CUSTOM(true); + + final boolean useLoggerFinder; + private LoggingBackend(boolean useLoggerFinder) { + this.useLoggerFinder = useLoggerFinder; + } + }; + + // The purpose of this class is to delay the initialization of + // the detectedBackend field until it is actually read. + // We do not want this field to get initialized if VM.isBooted() is false. + private static final class DetectBackend { + static final LoggingBackend detectedBackend; + static { + detectedBackend = AccessController.doPrivileged(new PrivilegedAction() { + @Override + public LoggingBackend run() { + final Iterator iterator = + ServiceLoader.load(LoggerFinder.class, ClassLoader.getSystemClassLoader()) + .iterator(); + if (iterator.hasNext()) { + return LoggingBackend.CUSTOM; // Custom Logger Provider is registered + } + // No custom logger provider: we will be using the default + // backend. + final Iterator iterator2 = + ServiceLoader.loadInstalled(DefaultLoggerFinder.class) + .iterator(); + if (iterator2.hasNext()) { + // LoggingProviderImpl is registered. The default + // implementation is java.util.logging + String cname = System.getProperty("java.util.logging.config.class"); + String fname = System.getProperty("java.util.logging.config.file"); + return (cname != null || fname != null) + ? LoggingBackend.JUL_WITH_CONFIG + : LoggingBackend.JUL_DEFAULT; + } else { + // SimpleLogger is used + return LoggingBackend.NONE; + } + } + }); + + } + } + + // We will use temporary SimpleConsoleLoggers if + // the logging backend is JUL, there is no custom config, + // and the LogManager has not been initialized yet. + private static boolean useTemporaryLoggers() { + // being paranoid: this should already have been checked + if (!isBooted()) return true; + return DetectBackend.detectedBackend == LoggingBackend.JUL_DEFAULT + && !logManagerConfigured; + } + + // We will use lazy loggers if: + // - the VM is not yet booted + // - the logging backend is a custom backend + // - the logging backend is JUL, there is no custom config, + // and the LogManager has not been initialized yet. + public static synchronized boolean useLazyLoggers() { + return !BootstrapLogger.isBooted() + || DetectBackend.detectedBackend == LoggingBackend.CUSTOM + || useTemporaryLoggers(); + } + + // Called by LazyLoggerAccessor. This method will determine whether + // to create a BootstrapLogger (if the VM is not yet booted), + // a SimpleConsoleLogger (if JUL is the default backend and there + // is no custom JUL configuration and LogManager is not yet initialized), + // or a logger returned by the loaded LoggerFinder (all other cases). + static Logger getLogger(LazyLoggerAccessor accessor) { + if (!BootstrapLogger.isBooted()) { + return new BootstrapLogger(accessor); + } else { + boolean temporary = useTemporaryLoggers(); + if (temporary) { + // JUL is the default backend, there is no custom configuration, + // LogManager has not been used. + synchronized(BootstrapLogger.class) { + if (useTemporaryLoggers()) { + return makeTemporaryLogger(accessor); + } + } + } + // Already booted. Return the real logger. + return accessor.createLogger(); + } + } + + + // If the backend is JUL, and there is no custom configuration, and + // nobody has attempted to call LogManager.getLogManager() yet, then + // we can temporarily substitute JUL Logger with SimpleConsoleLoggers, + // which avoids the cost of actually loading up the LogManager... + // The TemporaryLoggers class has the logic to create such temporary + // loggers, and to possibly replace them with real JUL loggers if + // someone calls LogManager.getLogManager(). + static final class TemporaryLoggers implements + Function { + + // all accesses must be synchronized on the outer BootstrapLogger.class + final Map temporaryLoggers = + new HashMap<>(); + + // all accesses must be synchronized on the outer BootstrapLogger.class + // The temporaryLoggers map will be cleared when LogManager is initialized. + boolean cleared; + + @Override + // all accesses must be synchronized on the outer BootstrapLogger.class + public SimpleConsoleLogger apply(LazyLoggerAccessor t) { + if (cleared) throw new IllegalStateException("LoggerFinder already initialized"); + return SimpleConsoleLogger.makeSimpleLogger(t.getLoggerName(), true); + } + + // all accesses must be synchronized on the outer BootstrapLogger.class + SimpleConsoleLogger get(LazyLoggerAccessor a) { + if (cleared) throw new IllegalStateException("LoggerFinder already initialized"); + return temporaryLoggers.computeIfAbsent(a, this); + } + + // all accesses must be synchronized on the outer BootstrapLogger.class + Map drainTemporaryLoggers() { + if (temporaryLoggers.isEmpty()) return null; + if (cleared) throw new IllegalStateException("LoggerFinder already initialized"); + final Map accessors = new HashMap<>(temporaryLoggers); + temporaryLoggers.clear(); + cleared = true; + return accessors; + } + + static void resetTemporaryLoggers(Map accessors) { + // When the backend is JUL we want to force the creation of + // JUL loggers here: some tests are expecting that the + // PlatformLogger will create JUL loggers as soon as the + // LogManager is initialized. + // + // If the backend is not JUL then we can delay the re-creation + // of the wrapped logger until they are next accessed. + // + final LoggingBackend detectedBackend = DetectBackend.detectedBackend; + final boolean lazy = detectedBackend != LoggingBackend.JUL_DEFAULT + && detectedBackend != LoggingBackend.JUL_WITH_CONFIG; + for (Map.Entry a : accessors.entrySet()) { + a.getKey().release(a.getValue(), !lazy); + } + } + + // all accesses must be synchronized on the outer BootstrapLogger.class + static final TemporaryLoggers INSTANCE = new TemporaryLoggers(); + } + + static synchronized Logger makeTemporaryLogger(LazyLoggerAccessor a) { + // accesses to TemporaryLoggers is synchronized on BootstrapLogger.class + return TemporaryLoggers.INSTANCE.get(a); + } + + private static volatile boolean logManagerConfigured; + + private static synchronized Map + releaseTemporaryLoggers() { + // first check whether there's a chance that we have used + // temporary loggers; Will be false if logManagerConfigured is already + // true. + final boolean clearTemporaryLoggers = useTemporaryLoggers(); + + // then sets the flag that tells that the log manager is configured + logManagerConfigured = true; + + // finally replace all temporary loggers by real JUL loggers + if (clearTemporaryLoggers) { + // accesses to TemporaryLoggers is synchronized on BootstrapLogger.class + return TemporaryLoggers.INSTANCE.drainTemporaryLoggers(); + } else { + return null; + } + } + + public static void redirectTemporaryLoggers() { + // This call is synchronized on BootstrapLogger.class. + final Map accessors = + releaseTemporaryLoggers(); + + // We will now reset the logger accessors, triggering the + // (possibly lazy) replacement of any temporary logger by the + // real logger returned from the loaded LoggerFinder. + if (accessors != null) { + TemporaryLoggers.resetTemporaryLoggers(accessors); + } + + BootstrapExecutors.flush(); + } + + // Hook for tests which need to wait until pending messages + // are processed. + static void awaitPendingTasks() { + BootstrapExecutors.awaitPendingTasks(); + } + static boolean isAlive() { + return BootstrapExecutors.isAlive(); + } + +} --- /dev/null 2015-11-20 17:44:10.000000000 +0100 +++ new/jdk/src/java.base/share/classes/jdk/internal/logger/DefaultLoggerFinder.java 2015-11-20 17:44:10.000000000 +0100 @@ -0,0 +1,173 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.logger; + +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.ref.ReferenceQueue; +import java.util.Collection; +import java.util.ResourceBundle; + +/** + * Internal Service Provider Interface (SPI) that makes it possible to use + * {@code java.util.logging} as backend when the {@link + * sun.util.logging.internal.LoggingProviderImpl + * sun.util.logging.internal.LoggingProviderImpl} is present. + *

+ * The JDK default implementation of the {@link LoggerFinder} will + * attempt to locate and load an {@linkplain + * java.util.ServiceLoader#loadInstalled(java.lang.Class) installed} + * implementation of the {@code DefaultLoggerFinder}. If {@code java.util.logging} + * is present, this will usually resolve to an instance of {@link + * sun.util.logging.internal.LoggingProviderImpl sun.util.logging.internal.LoggingProviderImpl}. + * Otherwise, if no concrete service provider is declared for + * {@code DefaultLoggerFinder}, the default implementation provided by this class + * will be used. + *

+ * When the {@link sun.util.logging.internal.LoggingProviderImpl + * sun.util.logging.internal.LoggingProviderImpl} is not present then the + * default implementation provided by this class is to use a simple logger + * that will log messages whose level is INFO and above to the console. + * These simple loggers are not configurable. + *

+ * When configuration is needed, an application should either link with + * {@code java.util.logging} - and use the {@code java.util.logging} for + * configuration, or link with {@link LoggerFinder another implementation} + * of the {@link LoggerFinder} + * that provides the necessary configuration. + * + * @apiNote Programmers are not expected to call this class directly. + * Instead they should rely on the static methods defined by {@link + * java.lang.System java.lang.System} or {@link sun.util.logging.PlatformLogger + * sun.util.logging.PlatformLogger}. + * + * @see java.lang.System.LoggerFinder + * @see jdk.internal.logger + * @see sun.util.logging.internal + * + */ +public class DefaultLoggerFinder extends LoggerFinder { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + + /** + * Creates a new instance of DefaultLoggerFinder. + * @throws SecurityException if the calling code does not have the + * {@code RuntimePermission("loggerFinder")} + */ + protected DefaultLoggerFinder() { + this(checkPermission()); + } + + private DefaultLoggerFinder(Void unused) { + // nothing to do. + } + + private static Void checkPermission() { + final SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGERFINDER_PERMISSION); + } + return null; + } + + // SharedLoggers is a default cache of loggers used when JUL is not + // present - in that case we use instances of SimpleConsoleLogger which + // cannot be directly configure through public APIs. + // + // We can therefore afford to simply maintain two domains - one for the + // system, and one for the application. + // + static final class SharedLoggers { + private final Map> loggers = + new HashMap<>(); + private final ReferenceQueue queue = new ReferenceQueue<>(); + + synchronized Logger get(Function loggerSupplier, final String name) { + Reference ref = loggers.get(name); + Logger w = ref == null ? null : ref.get(); + if (w == null) { + w = loggerSupplier.apply(name); + loggers.put(name, new WeakReference<>(w, queue)); + } + + // Remove stale mapping... + Collection> values = null; + while ((ref = queue.poll()) != null) { + if (values == null) values = loggers.values(); + values.remove(ref); + } + return w; + } + + + final static SharedLoggers system = new SharedLoggers(); + final static SharedLoggers application = new SharedLoggers(); + } + + @Override + public final Logger getLogger(String name, /* Module */ Class caller) { + checkPermission(); + return demandLoggerFor(name, caller); + } + + @Override + public final Logger getLocalizedLogger(String name, ResourceBundle bundle, + /* Module */ Class caller) { + return super.getLocalizedLogger(name, bundle, caller); + } + + + + /** + * Returns a {@link Logger logger} suitable for the caller usage. + * + * @implSpec The default implementation for this method is to return a + * simple logger that will print all messages of INFO level and above + * to the console. That simple logger is not configurable. + * + * @param name The name of the logger. + * @param caller The class on behalf of which the logger is created. + * @return A {@link Logger logger} suitable for the application usage. + * @throws SecurityException if the calling code does not have the + * {@code RuntimePermission("loggerFinder")}. + */ + protected Logger demandLoggerFor(String name, /* Module */ Class caller) { + checkPermission(); + if (caller.getClassLoader() == null) { + return SharedLoggers.system.get(SimpleConsoleLogger::makeSimpleLogger, name); + } else { + return SharedLoggers.application.get(SimpleConsoleLogger::makeSimpleLogger, name); + } + } + +} --- /dev/null 2015-11-20 17:44:11.000000000 +0100 +++ new/jdk/src/java.base/share/classes/jdk/internal/logger/LazyLoggers.java 2015-11-20 17:44:10.000000000 +0100 @@ -0,0 +1,446 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.logger; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.function.BiFunction; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.ref.WeakReference; +import java.util.Objects; +import sun.misc.VM; +import sun.util.logging.PlatformLogger; + +/** + * This class is a factory for Lazy Loggers; only system loggers can be + * Lazy Loggers. + */ +public final class LazyLoggers { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + + private LazyLoggers() { + throw new InternalError(); + } + + /** + * This class is used to hold the factories that a Lazy Logger will use + * to create (or map) its wrapped logger. + * @param {@link Logger} or a subclass of {@link Logger}. + */ + private static final class LazyLoggerFactories { + + /** + * A factory method to create an SPI logger. + * Usually, this will be something like LazyLoggers::getSystemLogger. + */ + final BiFunction, L> loggerSupplier; + + + public LazyLoggerFactories(BiFunction, L> loggerSupplier) { + this(Objects.requireNonNull(loggerSupplier), + (Void)null); + } + + private LazyLoggerFactories(BiFunction, L> loggerSupplier, + Void unused) { + this.loggerSupplier = loggerSupplier; + } + + } + + static interface LoggerAccessor { + /** + * The logger name. + * @return The name of the logger that is / will be lazily created. + */ + public String getLoggerName(); + + /** + * Returns the wrapped logger object. + * @return the wrapped logger object. + */ + public Logger wrapped(); + + /** + * A PlatformLogger.Bridge view of the wrapped logger object. + * @return A PlatformLogger.Bridge view of the wrapped logger object. + */ + public PlatformLogger.Bridge platform(); + } + + /** + * The LazyLoggerAccessor class holds all the logic that delays the creation + * of the SPI logger until such a time that the VM is booted and the logger + * is actually used for logging. + * + * This class uses the services of the BootstrapLogger class to instantiate + * temporary loggers if appropriate. + */ + static final class LazyLoggerAccessor implements LoggerAccessor { + + // The factories that will be used to create the logger lazyly + final LazyLoggerFactories factories; + + // We need to pass the actual caller when creating the logger. + private final WeakReference> callerRef; + + // The name of the logger that will be created lazyly + final String name; + // The plain logger SPI object - null until it is accessed for the + // first time. + private volatile Logger w; + // A PlatformLogger.Bridge view of w. + private volatile PlatformLogger.Bridge p; + + + private LazyLoggerAccessor(String name, + LazyLoggerFactories factories, + Class caller) { + this(Objects.requireNonNull(name), Objects.requireNonNull(factories), + Objects.requireNonNull(caller), null); + } + + private LazyLoggerAccessor(String name, + LazyLoggerFactories factories, + Class caller, Void unused) { + this.name = name; + this.factories = factories; + this.callerRef = new WeakReference>(caller); + } + + /** + * The logger name. + * @return The name of the logger that is / will be lazily created. + */ + @Override + public String getLoggerName() { + return name; + } + + // must be called in synchronized block + // set wrapped logger if not set + private void setWrappedIfNotSet(Logger wrapped) { + if (w == null) { + w = wrapped; + } + } + + /** + * Returns the logger SPI object, creating it if 'w' is still null. + * @return the logger SPI object. + */ + public Logger wrapped() { + Logger wrapped = w; + if (wrapped != null) return wrapped; + // Wrapped logger not created yet: create it. + // BootstrapLogger has the logic to decide whether to invoke the + // SPI or use a temporary (BootstrapLogger or SimpleConsoleLogger) + // logger. + wrapped = BootstrapLogger.getLogger(this); + synchronized(this) { + // if w has already been in between, simply drop 'wrapped'. + setWrappedIfNotSet(wrapped); + return w; + } + } + + /** + * A PlatformLogger.Bridge view of the wrapped logger. + * @return A PlatformLogger.Bridge view of the wrapped logger. + */ + public PlatformLogger.Bridge platform() { + // We can afford to return the platform view of the previous + // logger - if that view is not null. + // Because that view will either be the BootstrapLogger, which + // will redirect to the new wrapper properly, or the temporary + // logger - which in effect is equivalent to logging something + // just before the application initialized LogManager. + PlatformLogger.Bridge platform = p; + if (platform != null) return platform; + synchronized (this) { + if (w != null) { + if (p == null) p = PlatformLogger.Bridge.convert(w); + return p; + } + } + // If we reach here it means that the wrapped logger may not + // have been created yet: attempt to create it. + // BootstrapLogger has the logic to decide whether to invoke the + // SPI or use a temporary (BootstrapLogger or SimpleConsoleLogger) + // logger. + final Logger wrapped = BootstrapLogger.getLogger(this); + synchronized(this) { + // if w has already been set, simply drop 'wrapped'. + setWrappedIfNotSet(wrapped); + if (p == null) p = PlatformLogger.Bridge.convert(w); + return p; + } + } + + /** + * Makes this accessor release a temporary logger. + * This method is called + * by BootstrapLogger when JUL is the default backend and LogManager + * is initialized, in order to replace temporary SimpleConsoleLoggers by + * real JUL loggers. See BootstrapLogger for more details. + * If {@code replace} is {@code true}, then this method will force + * the accessor to eagerly recreate its wrapped logger. + * Note: passing {@code replace=false} is no guarantee that the + * method will not actually replace the released logger. + * @param temporary The temporary logger too be released. + * @param replace Whether the released logger should be eagerly + * replaced. + */ + void release(SimpleConsoleLogger temporary, boolean replace) { + PlatformLogger.ConfigurableBridge.LoggerConfiguration conf = + PlatformLogger.ConfigurableBridge.getLoggerConfiguration(temporary); + PlatformLogger.Level level = conf != null + ? conf.getPlatformLevel() + : null; + synchronized (this) { + if (this.w == temporary) { + this.w = null; this.p = null; + } + } + PlatformLogger.Bridge platform = replace || level != null + ? this.platform() : null; + + if (level != null) { + conf = (platform != null && platform != temporary) + ? PlatformLogger.ConfigurableBridge.getLoggerConfiguration(platform) + : null; + if (conf != null) conf.setPlatformLevel(level); + } + } + + /** + * Replace 'w' by the real SPI logger and flush the log messages pending + * in the temporary 'bootstrap' Logger. Called by BootstrapLogger when + * this accessor's bootstrap logger is accessed and BootstrapLogger + * notices that the VM is no longer booting. + * @param bootstrap This accessor's bootstrap logger (usually this is 'w'). + */ + Logger getConcreteLogger(BootstrapLogger bootstrap) { + assert VM.isBooted(); + synchronized(this) { + // another thread may have already invoked flush() + if (this.w == bootstrap) { + this.w = null; this.p = null; + } + } + return this.wrapped(); + } + + PlatformLogger.Bridge getConcretePlatformLogger(BootstrapLogger bootstrap) { + assert VM.isBooted(); + synchronized(this) { + // another thread may have already invoked flush() + if (this.w == bootstrap) { + this.w = null; this.p = null; + } + } + return this.platform(); + } + + // Creates the wrapped logger by invoking the SPI. + Logger createLogger() { + final Class caller = callerRef.get(); + if (caller == null) { + throw new IllegalStateException("The class for which this logger" + + " was created has been garbage collected"); + } + return this.factories.loggerSupplier.apply(name, caller); + } + + /** + * Creates a new lazy logger accessor for the named logger. The given + * factories will be use when it becomes necessary to actually create + * the logger. + * @param An interface that extends {@link Logger}. + * @param name The logger name. + * @param factories The factories that should be used to create the + * wrapped logger. + * @return A new LazyLoggerAccessor. + */ + public static LazyLoggerAccessor makeAccessor(String name, + LazyLoggerFactories factories, Class caller) { + return new LazyLoggerAccessor(name, factories, caller); + } + + } + + /** + * An implementation of {@link Logger} that redirects all calls to a wrapped + * instance of {@code Logger}. + */ + private static class LazyLoggerWrapper + extends AbstractLoggerWrapper { + + final LoggerAccessor loggerAccessor; + + public LazyLoggerWrapper(LazyLoggerAccessor loggerSinkSupplier) { + this(Objects.requireNonNull(loggerSinkSupplier), (Void)null); + } + + private LazyLoggerWrapper(LazyLoggerAccessor loggerSinkSupplier, + Void unused) { + this.loggerAccessor = loggerSinkSupplier; + } + + @Override + final Logger wrapped() { + return loggerAccessor.wrapped(); + } + + @Override + PlatformLogger.Bridge platformProxy() { + return loggerAccessor.platform(); + } + + } + + // Do not expose this outside of this package. + private static volatile LoggerFinder provider = null; + private static LoggerFinder accessLoggerFinder() { + if (provider == null) { + // no need to lock: it doesn't matter if we call + // getLoggerFinder() twice - since LoggerFinder already caches + // the result. + // This is just an optimization to avoid the cost of calling + // doPrivileged every time. + final SecurityManager sm = System.getSecurityManager(); + provider = sm == null ? LoggerFinder.getLoggerFinder() : + AccessController.doPrivileged( + (PrivilegedAction)LoggerFinder::getLoggerFinder); + } + return provider; + } + + // Avoid using lambda here as lazy loggers could be created early + // in the bootstrap sequence... + private static final BiFunction, Logger> loggerSupplier = + new BiFunction<>() { + @Override + public Logger apply(String name, Class caller) { + return LazyLoggers.getLoggerFromFinder(name, caller); + } + }; + + private static final LazyLoggerFactories factories = + new LazyLoggerFactories<>(loggerSupplier); + + + + // A concrete implementation of Logger that delegates to a System.Logger, + // but only creates the System.Logger instance lazily when it's used for + // the first time. + // The JdkLazyLogger uses a LazyLoggerAccessor objects, which relies + // on the logic embedded in BootstrapLogger to avoid loading the concrete + // logger provider until the VM has finished booting. + // + private static final class JdkLazyLogger extends LazyLoggerWrapper { + JdkLazyLogger(String name, Class caller) { + this(LazyLoggerAccessor.makeAccessor(name, factories, caller), + (Void)null); + } + private JdkLazyLogger(LazyLoggerAccessor holder, Void unused) { + super(holder); + } + } + + /** + * Gets a logger from the LoggerFinder. Creates the actual concrete + * logger. + * @param name name of the logger + * @param caller class on behalf of which the logger is created + * @return The logger returned by the LoggerFinder. + */ + static Logger getLoggerFromFinder(String name, Class caller) { + final SecurityManager sm = System.getSecurityManager(); + if (sm == null) { + return accessLoggerFinder().getLogger(name, caller); + } else { + return AccessController.doPrivileged((PrivilegedAction) + () -> {return accessLoggerFinder().getLogger(name, caller);}, + null, LOGGERFINDER_PERMISSION); + } + } + + /** + * Returns a (possibly lazy) Logger for the caller. + * + * @param name the logger name + * @param caller The class on behalf of which the logger is created. + * If the caller is not loaded from the Boot ClassLoader, + * the LoggerFinder is accessed and the logger returned + * by {@link LoggerFinder#getLogger(java.lang.String, java.lang.Class)} + * is returned to the caller directly. + * Otherwise, the logger returned by + * {@link #getLazyLogger(java.lang.String, java.lang.Class)} + * is returned to the caller. + * + * @return a (possibly lazy) Logger instance. + */ + public static final Logger getLogger(String name, Class caller) { + if (caller.getClassLoader() == null) { + return getLazyLogger(name, caller); + } else { + return getLoggerFromFinder(name, caller); + } + } + + /** + * Returns a (possibly lazy) Logger suitable for system classes. + * Whether the returned logger is lazy or not depend on the result + * returned by {@link BootstrapLogger#useLazyLoggers()}. + * + * @param name the logger name + * @param caller the class on behalf of which the logger is created. + * @return a (possibly lazy) Logger instance. + */ + public static final Logger getLazyLogger(String name, Class caller) { + + // BootstrapLogger has the logic to determine whether a LazyLogger + // should be used. Usually, it is worth it only if: + // - the VM is not yet booted + // - or, the backend is JUL and there is no configuration + // - or, the backend is a custom backend, as we don't know what + // that is going to load... + // So if for instance the VM is booted and we use JUL with a custom + // configuration, we're not going to delay the creation of loggers... + final boolean useLazyLogger = BootstrapLogger.useLazyLoggers(); + if (useLazyLogger) { + return new JdkLazyLogger(name, caller); + } else { + // Directly invoke the LoggerFinder. + return getLoggerFromFinder(name, caller); + } + } + +} --- /dev/null 2015-11-20 17:44:11.000000000 +0100 +++ new/jdk/src/java.base/share/classes/jdk/internal/logger/LocalizedLoggerWrapper.java 2015-11-20 17:44:11.000000000 +0100 @@ -0,0 +1,155 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + + +package jdk.internal.logger; + +import java.util.ResourceBundle; +import java.util.function.Supplier; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; + +/** + * This implementation of {@link Logger} redirects all logging method + * calls to calls to {@code log(Level, String, ResourceBundle, ...)} + * methods, passing the Logger's ResourceBundle as parameter. + * So for instance a call to {@link Logger#log(Level, String) + * log(Level.INFO, msg)} will be redirected + * to a call to {@link #log(java.lang.System.Logger.Level, + * java.util.ResourceBundle, java.lang.String, java.lang.Object...) + * this.log(Level.INFO, this.bundle, msg, (Object[]) null)}. + *

+ * Note that methods that take a {@link Supplier Supplier<String>} + * or an Object are not redirected. It is assumed that a string returned + * by a {@code Supplier} is already localized, or cannot be localized. + * + * @param Type of the wrapped Logger: {@code Logger} or an + * extension of the {@code Logger} interface. + */ +public class LocalizedLoggerWrapper extends LoggerWrapper { + + private final ResourceBundle bundle; + + public LocalizedLoggerWrapper(L wrapped, ResourceBundle bundle) { + super(wrapped); + this.bundle = bundle; + } + + public final ResourceBundle getBundle() { + return bundle; + } + + // We assume that messages returned by Supplier and Object are + // either already localized or not localizable. To be evaluated. + + // ----------------------------------------------------------------- + // Generic methods taking a Level as parameter + // ----------------------------------------------------------------- + + @Override + public final void log(Level level, String msg) { + log(level, bundle, msg, (Object[]) null); + } + + @Override + public final void log(Level level, + String msg, Throwable thrown) { + log(level, bundle, msg, thrown); + } + + @Override + public final void log(Level level, + String format, Object... params) { + log(level, bundle, format, params); + } + + @Override + public final void log(Level level, Object obj) { + wrapped.log(level, obj); + } + + @Override + public final void log(Level level, Supplier msgSupplier) { + wrapped.log(level, msgSupplier); + } + + @Override + public final void log(Level level, Supplier msgSupplier, Throwable thrown) { + wrapped.log(level, msgSupplier, thrown); + } + + @Override + public final void log(Level level, ResourceBundle bundle, String format, Object... params) { + wrapped.log(level, bundle, format, params); + } + + @Override + public final void log(Level level, ResourceBundle bundle, String key, Throwable thrown) { + wrapped.log(level, bundle, key, thrown); + } + + @Override + public final boolean isLoggable(Level level) { + return wrapped.isLoggable(level); + } + + // Override methods from PlatformLogger.Bridge that don't take a + // resource bundle... + + @Override + public final void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod, + String key) { + logrb(level, sourceClass, sourceMethod, bundle, key, (Object[]) null); + } + + @Override + public final void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod, + String key, Throwable thrown) { + logrb(level, sourceClass, sourceMethod, bundle, key, thrown); + } + + @Override + public final void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod, + String key, Object... params) { + logrb(level, sourceClass, sourceMethod, bundle, key, params); + } + + @Override + public final void log(sun.util.logging.PlatformLogger.Level level, String msg, Throwable thrown) { + logrb(level, bundle, msg, thrown); + } + + @Override + public final void log(sun.util.logging.PlatformLogger.Level level, String msg) { + logrb(level, bundle, msg, (Object[]) null); + } + + @Override + public final void log(sun.util.logging.PlatformLogger.Level level, String format, Object... params) { + logrb(level, bundle, format, params); + } + + +} --- /dev/null 2015-11-20 17:44:12.000000000 +0100 +++ new/jdk/src/java.base/share/classes/jdk/internal/logger/LoggerFinderLoader.java 2015-11-20 17:44:12.000000000 +0100 @@ -0,0 +1,210 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.logger; + +import java.io.FilePermission; +import java.security.AccessController; +import java.security.Permission; +import java.security.PrivilegedAction; +import java.util.Iterator; +import java.util.Locale; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; +import sun.security.util.SecurityConstants; + +/** + * Helper class used to load the {@link java.lang.System.LoggerFinder}. + */ +public final class LoggerFinderLoader { + private static volatile System.LoggerFinder service; + private static final Object lock = new int[0]; + static final Permission CLASSLOADER_PERMISSION = + SecurityConstants.GET_CLASSLOADER_PERMISSION; + static final Permission READ_PERMISSION = + new FilePermission("<>", + SecurityConstants.FILE_READ_ACTION); + public static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + + // This is used to control how the LoggerFinderLoader handles + // errors when instantiating the LoggerFinder provider. + // ERROR => throws ServiceConfigurationError + // WARNING => Do not fail, use plain default (simple logger) implementation, + // prints warning on console. (this is the default) + // DEBUG => Do not fail, use plain default (simple logger) implementation, + // prints warning and exception stack trace on console. + // QUIET => Do not fail and stay silent. + private static enum ErrorPolicy { ERROR, WARNING, DEBUG, QUIET }; + + // This class is static and cannot be instantiated. + private LoggerFinderLoader() { + throw new InternalError("LoggerFinderLoader cannot be instantiated"); + } + + + // Return the loaded LoggerFinder, or load it if not already loaded. + private static System.LoggerFinder service() { + if (service != null) return service; + synchronized(lock) { + if (service != null) return service; + service = loadLoggerFinder(); + } + // Since the LoggerFinder is already loaded - we can stop using + // temporary loggers. + BootstrapLogger.redirectTemporaryLoggers(); + return service; + } + + // Get configuration error policy + private static ErrorPolicy configurationErrorPolicy() { + final PrivilegedAction getConfigurationErrorPolicy = + () -> System.getProperty("jdk.logger.finder.error"); + String errorPolicy = AccessController.doPrivileged(getConfigurationErrorPolicy); + if (errorPolicy == null || errorPolicy.isEmpty()) { + return ErrorPolicy.WARNING; + } + try { + return ErrorPolicy.valueOf(errorPolicy.toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException x) { + return ErrorPolicy.WARNING; + } + } + + // Whether multiple provider should be considered as an error. + // This is further submitted to the configuration error policy. + private static boolean ensureSingletonProvider() { + final PrivilegedAction ensureSingletonProvider = + () -> Boolean.getBoolean("jdk.logger.finder.singleton"); + return AccessController.doPrivileged(ensureSingletonProvider); + } + + private static Iterator findLoggerFinderProviders() { + final Iterator iterator; + if (System.getSecurityManager() == null) { + iterator = ServiceLoader.load(System.LoggerFinder.class, + ClassLoader.getSystemClassLoader()).iterator(); + } else { + final PrivilegedAction> pa = + () -> ServiceLoader.load(System.LoggerFinder.class, + ClassLoader.getSystemClassLoader()).iterator(); + iterator = AccessController.doPrivileged(pa, null, + LOGGERFINDER_PERMISSION, CLASSLOADER_PERMISSION, + READ_PERMISSION); + } + return iterator; + } + + // Loads the LoggerFinder using ServiceLoader. If no LoggerFinder + // is found returns the default (possibly JUL based) implementation + private static System.LoggerFinder loadLoggerFinder() { + System.LoggerFinder result; + try { + // Iterator iterates with the access control context stored + // at ServiceLoader creation time. + final Iterator iterator = + findLoggerFinderProviders(); + if (iterator.hasNext()) { + result = iterator.next(); + if (iterator.hasNext() && ensureSingletonProvider()) { + throw new ServiceConfigurationError( + "More than on LoggerFinder implementation"); + } + } else { + result = loadDefaultImplementation(); + } + } catch (Error | RuntimeException x) { + // next caller will get the plain default impl (not linked + // to java.util.logging) + service = result = new DefaultLoggerFinder(); + ErrorPolicy errorPolicy = configurationErrorPolicy(); + if (errorPolicy == ErrorPolicy.ERROR) { + // rethrow any exception as a ServiceConfigurationError. + if (x instanceof Error) { + throw x; + } else { + throw new ServiceConfigurationError( + "Failed to instantiate LoggerFinder provider; Using default.", x); + } + } else if (errorPolicy != ErrorPolicy.QUIET) { + // if QUIET just silently use the plain default impl + // otherwise, log a warning, possibly adding the exception + // stack trace (if DEBUG is specified). + SimpleConsoleLogger logger = + new SimpleConsoleLogger("jdk.internal.logger", false); + logger.log(System.Logger.Level.WARNING, + "Failed to instantiate LoggerFinder provider; Using default."); + if (errorPolicy == ErrorPolicy.DEBUG) { + logger.log(System.Logger.Level.WARNING, + "Exception raised trying to instantiate LoggerFinder", x); + } + } + } + return result; + } + + private static System.LoggerFinder loadDefaultImplementation() { + final SecurityManager sm = System.getSecurityManager(); + final Iterator iterator; + if (sm == null) { + iterator = ServiceLoader.loadInstalled(DefaultLoggerFinder.class).iterator(); + } else { + // We use limited do privileged here - the minimum set of + // permissions required to 'see' the META-INF/services resources + // seems to be CLASSLOADER_PERMISSION and READ_PERMISSION. + // Note that do privileged is required because + // otherwise the SecurityManager will prevent the ServiceLoader + // from seeing the installed provider. + PrivilegedAction> pa = () -> + ServiceLoader.loadInstalled(DefaultLoggerFinder.class).iterator(); + iterator = AccessController.doPrivileged(pa, null, + LOGGERFINDER_PERMISSION, CLASSLOADER_PERMISSION, + READ_PERMISSION); + } + DefaultLoggerFinder result = null; + try { + // Iterator iterates with the access control context stored + // at ServiceLoader creation time. + if (iterator.hasNext()) { + result = iterator.next(); + } + } catch (RuntimeException x) { + throw new ServiceConfigurationError( + "Failed to instantiate default LoggerFinder", x); + } + if (result == null) { + result = new DefaultLoggerFinder(); + } + return result; + } + + public static System.LoggerFinder getLoggerFinder() { + final SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGERFINDER_PERMISSION); + } + return service(); + } + +} --- /dev/null 2015-11-20 17:44:12.000000000 +0100 +++ new/jdk/src/java.base/share/classes/jdk/internal/logger/LoggerWrapper.java 2015-11-20 17:44:12.000000000 +0100 @@ -0,0 +1,65 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + + +package jdk.internal.logger; + +import java.util.Objects; +import java.lang.System.Logger; +import sun.util.logging.PlatformLogger; + +/** + * An implementation of {@link Logger} that redirects all calls to a wrapped + instance of Logger. + * + * @param Type of the wrapped Logger: {@code Logger} or an + * extension of that interface. + */ +public class LoggerWrapper extends AbstractLoggerWrapper { + + final L wrapped; + final PlatformLogger.Bridge platformProxy; + + public LoggerWrapper(L wrapped) { + this(Objects.requireNonNull(wrapped), (Void)null); + } + + LoggerWrapper(L wrapped, Void unused) { + this.wrapped = wrapped; + this.platformProxy = (wrapped instanceof PlatformLogger.Bridge) ? + (PlatformLogger.Bridge) wrapped : null; + } + + @Override + public final L wrapped() { + return wrapped; + } + + @Override + public final PlatformLogger.Bridge platformProxy() { + return platformProxy; + } + +} --- /dev/null 2015-11-20 17:44:13.000000000 +0100 +++ new/jdk/src/java.base/share/classes/jdk/internal/logger/SimpleConsoleLogger.java 2015-11-20 17:44:13.000000000 +0100 @@ -0,0 +1,486 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.logger; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.time.ZonedDateTime; +import java.util.ResourceBundle; +import java.util.function.Function; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.util.function.Supplier; +import jdk.internal.misc.JavaLangAccess; +import jdk.internal.misc.SharedSecrets; +import sun.util.logging.PlatformLogger; +import sun.util.logging.PlatformLogger.ConfigurableBridge.LoggerConfiguration; + +/** + * A simple console logger to emulate the behavior of JUL loggers when + * in the default configuration. SimpleConsoleLoggers are also used when + * JUL is not present and no DefaultLoggerFinder is installed. + */ +public class SimpleConsoleLogger extends LoggerConfiguration + implements Logger, PlatformLogger.Bridge, PlatformLogger.ConfigurableBridge { + + static final PlatformLogger.Level DEFAULT_LEVEL = PlatformLogger.Level.INFO; + + final String name; + volatile PlatformLogger.Level level; + final boolean usePlatformLevel; + SimpleConsoleLogger(String name, boolean usePlatformLevel) { + this.name = name; + this.usePlatformLevel = usePlatformLevel; + } + + @Override + public String getName() { + return name; + } + + private Enum logLevel(PlatformLogger.Level level) { + return usePlatformLevel ? level : level.systemLevel(); + } + + private Enum logLevel(Level level) { + return usePlatformLevel ? PlatformLogger.toPlatformLevel(level) : level; + } + + // --------------------------------------------------- + // From Logger + // --------------------------------------------------- + + @Override + public boolean isLoggable(Level level) { + return isLoggable(PlatformLogger.toPlatformLevel(level)); + } + + @Override + public void log(Level level, ResourceBundle bundle, String key, Throwable thrown) { + if (isLoggable(level)) { + if (bundle != null) { + key = bundle.getString(key); + } + publish(getCallerInfo(), logLevel(level), key, thrown); + } + } + + @Override + public void log(Level level, ResourceBundle bundle, String format, Object... params) { + if (isLoggable(level)) { + if (bundle != null) { + format = bundle.getString(format); + } + publish(getCallerInfo(), logLevel(level), format, params); + } + } + + // --------------------------------------------------- + // From PlatformLogger.Bridge + // --------------------------------------------------- + + @Override + public boolean isLoggable(PlatformLogger.Level level) { + final PlatformLogger.Level effectiveLevel = effectiveLevel(); + return level != PlatformLogger.Level.OFF + && level.ordinal() >= effectiveLevel.ordinal(); + } + + @Override + public boolean isEnabled() { + return level != PlatformLogger.Level.OFF; + } + + @Override + public void log(PlatformLogger.Level level, String msg) { + if (isLoggable(level)) { + publish(getCallerInfo(), logLevel(level), msg); + } + } + + @Override + public void log(PlatformLogger.Level level, String msg, Throwable thrown) { + if (isLoggable(level)) { + publish(getCallerInfo(), logLevel(level), msg, thrown); + } + } + + @Override + public void log(PlatformLogger.Level level, String msg, Object... params) { + if (isLoggable(level)) { + publish(getCallerInfo(), logLevel(level), msg, params); + } + } + + private PlatformLogger.Level effectiveLevel() { + if (level == null) return DEFAULT_LEVEL; + return level; + } + + @Override + public PlatformLogger.Level getPlatformLevel() { + return level; + } + + @Override + public void setPlatformLevel(PlatformLogger.Level newLevel) { + level = newLevel; + } + + @Override + public LoggerConfiguration getLoggerConfiguration() { + return this; + } + + /** + * Default platform logging support - output messages to System.err - + * equivalent to ConsoleHandler with SimpleFormatter. + */ + static PrintStream outputStream() { + return System.err; + } + + // Returns the caller's class and method's name; best effort + // if cannot infer, return the logger's name. + private String getCallerInfo() { + String sourceClassName = null; + String sourceMethodName = null; + + JavaLangAccess access = SharedSecrets.getJavaLangAccess(); + Throwable throwable = new Throwable(); + int depth = access.getStackTraceDepth(throwable); + + String logClassName = "sun.util.logging.PlatformLogger"; + String simpleLoggerClassName = "jdk.internal.logger.SimpleConsoleLogger"; + boolean lookingForLogger = true; + for (int ix = 0; ix < depth; ix++) { + // Calling getStackTraceElement directly prevents the VM + // from paying the cost of building the entire stack frame. + final StackTraceElement frame = + access.getStackTraceElement(throwable, ix); + final String cname = frame.getClassName(); + if (lookingForLogger) { + // Skip all frames until we have found the first logger frame. + if (cname.equals(logClassName) || cname.equals(simpleLoggerClassName)) { + lookingForLogger = false; + } + } else { + if (skipLoggingFrame(cname)) continue; + if (!cname.equals(logClassName) && !cname.equals(simpleLoggerClassName)) { + // We've found the relevant frame. + sourceClassName = cname; + sourceMethodName = frame.getMethodName(); + break; + } + } + } + + if (sourceClassName != null) { + return sourceClassName + " " + sourceMethodName; + } else { + return name; + } + } + + private String getCallerInfo(String sourceClassName, String sourceMethodName) { + if (sourceClassName == null) return name; + if (sourceMethodName == null) return sourceClassName; + return sourceClassName + " " + sourceMethodName; + } + + private String toString(Throwable thrown) { + String throwable = ""; + if (thrown != null) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + pw.println(); + thrown.printStackTrace(pw); + pw.close(); + throwable = sw.toString(); + } + return throwable; + } + + private synchronized String format(Enum level, + String msg, Throwable thrown, String callerInfo) { + + ZonedDateTime zdt = ZonedDateTime.now(); + String throwable = toString(thrown); + + return String.format(Formatting.formatString, + zdt, + callerInfo, + name, + level.name(), + msg, + throwable); + } + + // publish accepts both PlatformLogger Levels and LoggerFinder Levels. + private void publish(String callerInfo, Enum level, String msg) { + outputStream().print(format(level, msg, null, callerInfo)); + } + // publish accepts both PlatformLogger Levels and LoggerFinder Levels. + private void publish(String callerInfo, Enum level, String msg, Throwable thrown) { + outputStream().print(format(level, msg, thrown, callerInfo)); + } + // publish accepts both PlatformLogger Levels and LoggerFinder Levels. + private void publish(String callerInfo, Enum level, String msg, Object... params) { + msg = params == null || params.length == 0 ? msg + : Formatting.formatMessage(msg, params); + outputStream().print(format(level, msg, null, callerInfo)); + } + + public static SimpleConsoleLogger makeSimpleLogger(String name, boolean usePlatformLevel) { + return new SimpleConsoleLogger(name, usePlatformLevel); + } + + public static SimpleConsoleLogger makeSimpleLogger(String name) { + return new SimpleConsoleLogger(name, false); + } + + public static String getSimpleFormat(Function defaultPropertyGetter) { + return Formatting.getSimpleFormat(defaultPropertyGetter); + } + + public static boolean skipLoggingFrame(String cname) { + return Formatting.skipLoggingFrame(cname); + } + + @Override + public void log(PlatformLogger.Level level, Supplier msgSupplier) { + if (isLoggable(level)) { + publish(getCallerInfo(), logLevel(level), msgSupplier.get()); + } + } + + @Override + public void log(PlatformLogger.Level level, Throwable thrown, + Supplier msgSupplier) { + if (isLoggable(level)) { + publish(getCallerInfo(), logLevel(level), msgSupplier.get(), thrown); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg) { + if (isLoggable(level)) { + publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Supplier msgSupplier) { + if (isLoggable(level)) { + publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msgSupplier.get()); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, String sourceMethod, + String msg, Object... params) { + if (isLoggable(level)) { + publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, params); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Throwable thrown) { + if (isLoggable(level)) { + publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, thrown); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Throwable thrown, Supplier msgSupplier) { + if (isLoggable(level)) { + publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msgSupplier.get(), thrown); + } + } + + @Override + public void logrb(PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String key, Object... params) { + if (isLoggable(level)) { + String msg = bundle == null ? key : bundle.getString(key); + publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, params); + } + } + + @Override + public void logrb(PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String key, Throwable thrown) { + if (isLoggable(level)) { + String msg = bundle == null ? key : bundle.getString(key); + publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, thrown); + } + } + + @Override + public void logrb(PlatformLogger.Level level, ResourceBundle bundle, + String key, Object... params) { + if (isLoggable(level)) { + String msg = bundle == null ? key : bundle.getString(key); + publish(getCallerInfo(), logLevel(level), msg, params); + } + } + + @Override + public void logrb(PlatformLogger.Level level, ResourceBundle bundle, + String key, Throwable thrown) { + if (isLoggable(level)) { + String msg = bundle == null ? key : bundle.getString(key); + publish(getCallerInfo(), logLevel(level), msg, thrown); + } + } + + private static final class Formatting { + static final String DEFAULT_FORMAT = + "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n"; + static final String FORMAT_PROP_KEY = + "java.util.logging.SimpleFormatter.format"; + static final String formatString = getSimpleFormat(null); + + // Make it easier to wrap Logger... + static private final String[] skips; + static { + String additionalPkgs = AccessController.doPrivileged( + (PrivilegedAction) + () -> System.getProperty("jdk.logger.packages")); + skips = additionalPkgs == null ? new String[0] : additionalPkgs.split(","); + + } + + static boolean skipLoggingFrame(String cname) { + // skip logging/logger infrastructure + + // fast escape path: all the prefixes below start with 's' or 'j' and + // have more than 12 characters. + char c = cname.length() < 12 ? 0 : cname.charAt(0); + if (c == 's') { + // skip internal machinery classes + if (cname.startsWith("sun.util.logging.")) return true; + if (cname.startsWith("sun.reflect.")) return true; + if (cname.startsWith("sun.rmi.runtime.Log")) return true; + } else if (c == 'j') { + // Message delayed at Bootstrap: no need to go further up. + if (cname.startsWith("jdk.internal.logger.BootstrapLogger$LogEvent")) return false; + // skip public machinery classes + if (cname.startsWith("jdk.internal.logger.")) return true; + if (cname.startsWith("java.util.logging.")) return true; + if (cname.startsWith("java.lang.System$Logger")) return true; + if (cname.startsWith("java.lang.reflect.")) return true; + if (cname.startsWith("java.lang.invoke.MethodHandle")) return true; + if (cname.startsWith("java.lang.invoke.LambdaForm")) return true; + if (cname.startsWith("java.security.AccessController")) return true; + } + + // check additional prefixes if any are specified. + if (skips.length > 0) { + for (int i=0; i defaultPropertyGetter) { + // Using a lambda here causes + // jdk/test/java/lang/invoke/lambda/LogGeneratedClassesTest.java + // to fail - because that test has a testcase which somehow references + // PlatformLogger and counts the number of generated lambda classes + // So we explicitely use new PrivilegedAction here. + String format = + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public String run() { + return System.getProperty(FORMAT_PROP_KEY); + } + }); + if (format == null && defaultPropertyGetter != null) { + format = defaultPropertyGetter.apply(FORMAT_PROP_KEY); + } + if (format != null) { + try { + // validate the user-defined format string + String.format(format, ZonedDateTime.now(), "", "", "", "", ""); + } catch (IllegalArgumentException e) { + // illegal syntax; fall back to the default format + format = DEFAULT_FORMAT; + } + } else { + format = DEFAULT_FORMAT; + } + return format; + } + + + // Copied from java.util.logging.Formatter.formatMessage + static String formatMessage(String format, Object... parameters) { + // Do the formatting. + try { + if (parameters == null || parameters.length == 0) { + // No parameters. Just return format string. + return format; + } + // Is it a java.text style format? + // Ideally we could match with + // Pattern.compile("\\{\\d").matcher(format).find()) + // However the cost is 14% higher, so we cheaply check for + // + boolean isJavaTestFormat = false; + final int len = format.length(); + for (int i=0; i= '0' && d <= '9') { + isJavaTestFormat = true; + break; + } + } + } + if (isJavaTestFormat) { + return java.text.MessageFormat.format(format, parameters); + } + return format; + } catch (Exception ex) { + // Formatting failed: use format string. + return format; + } + } + } +} --- /dev/null 2015-11-20 17:44:14.000000000 +0100 +++ new/jdk/src/java.base/share/classes/jdk/internal/logger/package-info.java 2015-11-20 17:44:13.000000000 +0100 @@ -0,0 +1,68 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/** + * [JDK INTERNAL] + * The {@code jdk.internal.logger} package defines an internal provider + * whose default naive implementation is replaced by the {@code java.logging} + * module when the {@code java.logging} module is present. + *

+ * Default Implementation + *

+ * The JDK default implementation of the System.LoggerFinder will attempt to + * load an installed instance of the {@link jdk.internal.logger.DefaultLoggerFinder} + * defined in this package. + * When the {@code java.util.logging} package is present, this will usually + * resolve to an instance of {@link sun.util.logging.internal.LoggingProviderImpl} - + * which provides an implementation of the Logger whose backend is a + * {@link java.util.logging.Logger java.util.logging.Logger}. + * Configuration can thus be performed by direct access to the regular + * {@code java.util.logging} APIs, + * using {@link java.util.logging.Logger java.util.logging.Logger} and + * {@link java.util.logging.LogManager} to access and configure the backend + * Loggers. + *
+ * If however {@code java.util.logging} is not linked with the application, then + * the default implementation will return a simple logger that will print out + * all log messages of INFO level and above to the console ({@code System.err}), + * as implemented by the base {@link jdk.internal.logger.DefaultLoggerFinder} class. + *

+ * Message Levels and Mapping to java.util.logging + *

+ * The {@link java.lang.System.LoggerFinder} class documentation describe how + * {@linkplain java.lang.System.Logger.Level System.Logger levels} are mapped + * to {@linkplain java.util.logging.Level JUL levels} when {@code + * java.util.logging} is the backend. + * + * @see jdk.internal.logger.DefaultLoggerFinder + * @see sun.util.logging.internal.LoggingProviderImpl + * @see java.lang.System.LoggerFinder + * @see java.lang.System.Logger + * @see sun.util.logging.PlatformLogger.Bridge + * @see sun.util.logging.internal + * + * @since 1.9 + */ +package jdk.internal.logger; --- /dev/null 2015-11-20 17:44:14.000000000 +0100 +++ new/jdk/src/java.logging/share/classes/META-INF/services/jdk.internal.logger.DefaultLoggerFinder 2015-11-20 17:44:14.000000000 +0100 @@ -0,0 +1 @@ +sun.util.logging.internal.LoggingProviderImpl --- /dev/null 2015-11-20 17:44:15.000000000 +0100 +++ new/jdk/src/java.logging/share/classes/sun/util/logging/internal/LoggingProviderImpl.java 2015-11-20 17:44:15.000000000 +0100 @@ -0,0 +1,466 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + + +package sun.util.logging.internal; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ResourceBundle; +import java.util.function.Supplier; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.util.Objects; +import java.util.logging.LogManager; +import jdk.internal.logger.DefaultLoggerFinder; +import java.util.logging.LoggingPermission; +import sun.util.logging.PlatformLogger; +import sun.util.logging.PlatformLogger.ConfigurableBridge.LoggerConfiguration; + +/** + * This {@code LoggingProviderImpl} is the JDK internal implementation of the + * {@link jdk.internal.logger.DefaultLoggerFinder} which is used by + * the default implementation of the {@link Logger} + * when no {@link LoggerFinder} is found + * and {@code java.util.logging} is present. + * When {@code java.util.logging} is present, the {@code LoggingProviderImpl} + * is {@linkplain java.util.ServiceLoader#loadInstalled(Class) installed} as + * an internal service provider, making it possible to use {@code java.util.logging} + * as the backend for loggers returned by the default LoggerFinder implementation. + *

+ * This implementation of {@link DefaultLoggerFinder} returns instances of + * {@link java.lang.System.Logger} which + * delegate to a wrapped instance of {@link java.util.logging.Logger + * java.util.logging.Logger}. + *
+ * Loggers returned by this class can therefore be configured by accessing + * their wrapped implementation through the regular {@code java.util.logging} + * APIs - such as {@link java.util.logging.LogManager java.util.logging.LogManager} + * and {@link java.util.logging.Logger java.util.logging.Logger}. + * + * @apiNote Programmers are not expected to call this class directly. + * Instead they should rely on the static methods defined by + * {@link java.lang.System java.lang.System}. + *

+ * To replace this default + * {@code java.util.logging} backend, an application is expected to install + * its own {@link java.lang.System.LoggerFinder}. + * + * @see java.lang.System.Logger + * @see java.lang.System.LoggerFinder + * @see sun.util.logging.PlatformLogger.Bridge + * @see java.lang.System + * @see jdk.internal.logger + * @see jdk.internal.logger + * + */ +public final class LoggingProviderImpl extends DefaultLoggerFinder { + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + private static final LoggingPermission LOGGING_CONTROL_PERMISSION = + new LoggingPermission("control", null); + + /** + * Creates a new instance of LoggingProviderImpl. + * @throws SecurityException if the calling code does not have the + * {@code RuntimePermission("loggerFinder")}. + */ + public LoggingProviderImpl() { + } + + /** + * A logger that delegates to a java.util.logging.Logger delegate. + */ + static final class JULWrapper extends LoggerConfiguration + implements System.Logger, PlatformLogger.Bridge, + PlatformLogger.ConfigurableBridge { + + + private static final java.util.logging.Level[] spi2JulLevelMapping = { + java.util.logging.Level.ALL, // mapped from ALL + java.util.logging.Level.FINER, // mapped from TRACE + java.util.logging.Level.FINE, // mapped from DEBUG + java.util.logging.Level.INFO, // mapped from INFO + java.util.logging.Level.WARNING, // mapped from WARNING + java.util.logging.Level.SEVERE, // mapped from ERROR + java.util.logging.Level.OFF // mapped from OFF + }; + + private static final java.util.logging.Level[] platform2JulLevelMapping = { + java.util.logging.Level.ALL, // mapped from ALL + java.util.logging.Level.FINEST, // mapped from FINEST + java.util.logging.Level.FINER, // mapped from FINER + java.util.logging.Level.FINE, // mapped from FINE + java.util.logging.Level.CONFIG, // mapped from CONFIG + java.util.logging.Level.INFO, // mapped from INFO + java.util.logging.Level.WARNING, // mapped from WARNING + java.util.logging.Level.SEVERE, // mapped from SEVERE + java.util.logging.Level.OFF // mapped from OFF + }; + + private final java.util.logging.Logger julLogger; + + + private JULWrapper(java.util.logging.Logger logger) { + this.julLogger = logger; + } + + @Override + public String getName() { + return julLogger.getName(); + } + @Override + public void log(sun.util.logging.PlatformLogger.Level level, String msg, Throwable throwable) { + julLogger.log(toJUL(level), msg, throwable); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, String format, Object... params) { + julLogger.log(toJUL(level), format, params); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, String msg) { + julLogger.log(toJUL(level), msg); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, Supplier msgSuppier) { + julLogger.log(toJUL(level), msgSuppier); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, Throwable thrown, Supplier msgSuppier) { + julLogger.log(toJUL(level), thrown, msgSuppier); + } + + @Override + public void logrb(sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, String key, Throwable throwable) { + julLogger.logrb(toJUL(level), bundle, key, throwable); + } + + @Override + public void logrb(sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, String key, Object... params) { + julLogger.logrb(toJUL(level), bundle, key, params); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod, String msg) { + julLogger.logp(toJUL(level), sourceClass, sourceMethod, msg); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod, + Supplier msgSupplier) { + julLogger.logp(toJUL(level), sourceClass, sourceMethod, msgSupplier); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod, + String msg, Object... params) { + julLogger.logp(toJUL(level), sourceClass, sourceMethod, msg, params); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod, + String msg, Throwable thrown) { + julLogger.logp(toJUL(level), sourceClass, sourceMethod, msg, thrown); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod, + Throwable thrown, Supplier msgSupplier) { + julLogger.logp(toJUL(level), sourceClass, sourceMethod, + thrown, msgSupplier); + } + + @Override + public void logrb(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod, + ResourceBundle bundle, String key, Object... params) { + julLogger.logrb(toJUL(level), sourceClass, sourceMethod, + bundle, key, params); + } + + @Override + public void logrb(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod, + ResourceBundle bundle, String key, Throwable thrown) { + julLogger.logrb(toJUL(level), sourceClass, sourceMethod, + bundle, key, thrown); + } + + @Override + public boolean isLoggable(sun.util.logging.PlatformLogger.Level level) { + return julLogger.isLoggable(toJUL(level)); + } + + // ----------------------------------------------------------------- + // Generic methods taking a Level as parameter + // ----------------------------------------------------------------- + + + @Override + public boolean isLoggable(Level level) { + return julLogger.isLoggable(toJUL(level)); + } + + @Override + public void log(Level level, String msg) { + julLogger.log(toJUL(level), msg); + } + + @Override + public void log(Level level, + Supplier msgSupplier) { + // We need to check for null here to satisfy the contract + // of System.Logger - because the underlying implementation + // of julLogger will check for it only if the level is + // loggable + Objects.requireNonNull(msgSupplier); + julLogger.log(toJUL(level), msgSupplier); + } + + @Override + public void log(Level level, Object obj) { + // We need to check for null here to satisfy the contract + // of System.Logger - because the underlying implementation + // of julLogger will check for it only if the level is + // loggable + Objects.requireNonNull(obj); + julLogger.log(toJUL(level), () -> obj.toString()); + } + + @Override + public void log(Level level, + String msg, Throwable thrown) { + julLogger.log(toJUL(level), msg, thrown); + } + + @Override + public void log(Level level, Supplier msgSupplier, + Throwable thrown) { + // We need to check for null here to satisfy the contract + // of System.Logger - because the underlying implementation + // of julLogger will check for it only if the level is + // loggable + Objects.requireNonNull(msgSupplier); + julLogger.log(toJUL(level), thrown, msgSupplier); + } + + @Override + public void log(Level level, + String format, Object... params) { + julLogger.log(toJUL(level), format, params); + } + + @Override + public void log(Level level, ResourceBundle bundle, + String key, Throwable thrown) { + julLogger.logrb(toJUL(level), bundle, key, thrown); + } + + @Override + public void log(Level level, ResourceBundle bundle, + String format, Object... params) { + julLogger.logrb(toJUL(level), bundle, format, params); + } + + static java.util.logging.Level toJUL(Level level) { + if (level == null) return null; + assert level.ordinal() < spi2JulLevelMapping.length; + return spi2JulLevelMapping[level.ordinal()]; + } + + // --------------------------------------------------------- + // Methods from PlatformLogger.Bridge + // --------------------------------------------------------- + + @Override + public boolean isEnabled() { + return julLogger.getLevel() != java.util.logging.Level.OFF; + } + + @Override + public PlatformLogger.Level getPlatformLevel() { + final java.util.logging.Level javaLevel = julLogger.getLevel(); + if (javaLevel == null) return null; + try { + return PlatformLogger.Level.valueOf(javaLevel.getName()); + } catch (IllegalArgumentException e) { + return PlatformLogger.Level.valueOf(javaLevel.intValue()); + } + } + + @Override + public void setPlatformLevel(PlatformLogger.Level level) { + // null is allowed here + julLogger.setLevel(toJUL(level)); + } + + @Override + public LoggerConfiguration getLoggerConfiguration() { + return this; + } + + static java.util.logging.Level toJUL(PlatformLogger.Level level) { + // The caller will throw if null is invalid in its context. + // There's at least one case where a null level is valid. + if (level == null) return null; + assert level.ordinal() < platform2JulLevelMapping.length; + return platform2JulLevelMapping[level.ordinal()]; + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof JULWrapper) + && obj.getClass() == this.getClass() + && ((JULWrapper)obj).julLogger == this.julLogger; + } + + @Override + public int hashCode() { + return julLogger.hashCode(); + } + + // A JULWrapper is just a stateless thin shell over a JUL logger - so + // for a given JUL logger, we could always return the same wrapper. + // + // This is an optimization which may - or may not - be worth the + // trouble: if many classes use the same logger, and if each class + // keeps a reference to that logger, then caching the wrapper will + // be worthwhile. Otherwise, if each logger is only referred once, + // then the cache will eat up more memory than would be necessary... + // + // Here is an example of how we could implement JULWrapper.of(...) + // if we wanted to create at most one wrapper instance for each logger + // instance: + // + // static final WeakHashMap> + // wrappers = new WeakHashMap<>(); + // + // static JULWrapper of(java.util.logging.Logger logger) { + // + // // First access without synchronizing + // final JULWrapper candidate = new JULWrapper(logger); + // WeakReference ref = wrappers.get(candidate); + // JULWrapper found = ref.get(); + // + // // OK - we found it - lets return it. + // if (found != null) return found; + // + // // Not found. Need to synchronize. + // synchronized (wrappers) { + // ref = wrappers.get(candidate); + // found = ref.get(); + // if (found == null) { + // wrappers.put(candidate, new WeakReference<>(candidate)); + // found = candidate; + // } + // } + // assert found != null; + // return found; + // } + // + // But given that it may end up eating more memory in the nominal case + // (where each class that does logging has its own logger with the + // class name as logger name and stashes that logger away in a static + // field, thus making the cache redundant - as only one wrapper will + // ever be created anyway) - then we will simply return a new wrapper + // for each invocation of JULWrapper.of(...) - which may + // still prove more efficient in terms of memory consumption... + // + static JULWrapper of(java.util.logging.Logger logger) { + return new JULWrapper(logger); + } + + + } + + /** + * Creates a java.util.logging.Logger for the given caller. + * @param name the logger name. + * @param caller the caller for which the logger should be created. + * @return a Logger suitable for use in the given caller. + */ + private static java.util.logging.Logger demandJULLoggerFor(final String name, + /* Module */ + final Class caller) { + final LogManager manager = LogManager.getLogManager(); + final SecurityManager sm = System.getSecurityManager(); + if (sm == null) { + return logManagerAccess.demandLoggerFor(manager, name, caller); + } else { + final PrivilegedAction pa = + () -> logManagerAccess.demandLoggerFor(manager, name, caller); + return AccessController.doPrivileged(pa, null, LOGGING_CONTROL_PERMISSION); + } + } + + /** + * {@inheritDoc} + * + * @apiNote The logger returned by this method can be configured through + * its {@linkplain java.util.logging.LogManager#getLogger(String) + * corresponding java.util.logging.Logger backend}. + * + * @return {@inheritDoc} + * @throws SecurityException if the calling code doesn't have the + * {@code RuntimePermission("loggerFinder")}. + */ + @Override + protected Logger demandLoggerFor(String name, /* Module */ Class caller) { + final SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGERFINDER_PERMISSION); + } + return JULWrapper.of(demandJULLoggerFor(name,caller)); + } + + public static interface LogManagerAccess { + java.util.logging.Logger demandLoggerFor(LogManager manager, + String name, /* Module */ Class caller); + } + + // Hook for tests + public static LogManagerAccess getLogManagerAccess() { + final SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGING_CONTROL_PERMISSION); + } + // Triggers initialization of accessJulLogger if not set. + if (logManagerAccess == null) LogManager.getLogManager(); + return logManagerAccess; + } + + + private static volatile LogManagerAccess logManagerAccess; + public static void setLogManagerAccess(LogManagerAccess accesLoggers) { + final SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGING_CONTROL_PERMISSION); + } + logManagerAccess = accesLoggers; + } + +} --- /dev/null 2015-11-20 17:44:15.000000000 +0100 +++ new/jdk/src/java.logging/share/classes/sun/util/logging/internal/package-info.java 2015-11-20 17:44:15.000000000 +0100 @@ -0,0 +1,55 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/** + *

+ * [JDK INTERNAL] + * The {@code sun.util.logging.internal} package defines an internal + * implementation of the {@link jdk.internal.logger.DefaultLoggerFinder} which + * provides an extension of the {@link java.lang.System.Logger System.Logger} + * interface making it easy to bridge from {@link java.util.logging}; + * the JDK default implementation of the LoggerFinder will return loggers + * implementing this extension when {@code java.util.logging} is present. + *

+ *

+ * When {@code java.util.logging} is present, Logger instances returned by + * the JDK default implementation of the LoggerFinder + * wrap an instance of {@link java.util.logging.Logger java.util.logging.Logger} + * and implement the {@link + * sun.util.logging.PlatformLogger.Bridge PlatformLogger.Bridge} + * extension, overriding all the methods defined in + * that extension in order to call the corresponding methods on their wrapped + * {@linkplain java.util.logging.Logger backend Logger} instance. + *

+ *
+ * @see java.lang.System.LoggerFinder + * @see java.lang.System.Logger + * @see sun.util.logging.PlatformLogger + * @see sun.util.logging.PlatformLogger.Bridge + * @see jdk.internal.logger + * + * @since 1.9 + */ +package sun.util.logging.internal; --- /dev/null 2015-11-20 17:44:16.000000000 +0100 +++ new/jdk/test/java/lang/System/Logger/Level/LoggerLevelTest.java 2015-11-20 17:44:16.000000000 +0100 @@ -0,0 +1,86 @@ +/* + * 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.lang.System.Logger.Level; +import java.util.EnumSet; +import java.util.Objects; +import java.util.Set; +/** + * @test + * @bug 8140364 + * @summary Tests System.Logger.Level names and severity. + * @author danielfuchs + */ +public class LoggerLevelTest { + public static void main(String[] args) { + Set untested = EnumSet.allOf(Level.class); + testLevel(untested, Level.ALL, java.util.logging.Level.ALL); + testLevel(untested, Level.TRACE, java.util.logging.Level.FINER); + testLevel(untested, Level.DEBUG, java.util.logging.Level.FINE); + testLevel(untested, Level.INFO, java.util.logging.Level.INFO); + testLevel(untested, Level.WARNING, java.util.logging.Level.WARNING); + testLevel(untested, Level.ERROR, java.util.logging.Level.SEVERE); + testLevel(untested, Level.OFF, java.util.logging.Level.OFF); + if (!untested.isEmpty()) { + throw new RuntimeException("Some level values were not tested: " + untested); + } + } + + private static void testLevel(Set untested, Level systemLevel, java.util.logging.Level julLevel) { + untested.remove(systemLevel); + assertEquals(systemLevel.getName(), systemLevel.name(), + "System.Logger.Level." + systemLevel.name() + ".getName()"); + assertEquals(systemLevel.getSeverity(), julLevel.intValue(), + "System.Logger.Level." + systemLevel.name() + ".getSeverity"); + } + + private static void assertEquals(Object actual, Object expected, String what) { + if (!Objects.equals(actual, expected)) { + throw new RuntimeException("Bad value for " + what + + "\n\t expected: " + expected + + "\n\t actual: " + actual); + } else { + System.out.println("Got expected value for " + what + ": " + actual); + } + } + + private static void assertEquals(int actual, int expected, String what) { + if (!Objects.equals(actual, expected)) { + throw new RuntimeException("Bad value for " + what + + "\n\t expected: " + toString(expected) + + "\n\t actual: " + toString(actual)); + } else { + System.out.println("Got expected value for " + what + ": " + toString(actual)); + } + } + + private static String toString(int value) { + switch (value) { + case Integer.MAX_VALUE: return "Integer.MAX_VALUE"; + case Integer.MIN_VALUE: return "Integer.MIN_VALUE"; + default: + return Integer.toString(value); + } + } + +} --- /dev/null 2015-11-20 17:44:17.000000000 +0100 +++ new/jdk/test/java/lang/System/Logger/custom/AccessSystemLogger.java 2015-11-20 17:44:16.000000000 +0100 @@ -0,0 +1,78 @@ +/* + * 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.IOException; +import java.lang.System.Logger; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ResourceBundle; + +/** + * + * @author danielfuchs + */ +public final class AccessSystemLogger { + + public AccessSystemLogger() { + this(check()); + } + + private AccessSystemLogger(Void unused) { + } + + private static Void check() { + if (AccessSystemLogger.class.getClassLoader() != null) { + throw new RuntimeException("AccessSystemLogger should be loaded by the null classloader"); + } + return null; + } + + public Logger getLogger(String name) { + Logger logger = System.getLogger(name); + System.out.println("System.getLogger(\"" + name + "\"): " + logger); + return logger; + } + + public Logger getLogger(String name, ResourceBundle bundle) { + Logger logger = System.getLogger(name, bundle); + System.out.println("System.getLogger(\"" + name + "\", bundle): " + logger); + return logger; + } + + // copy AccessSystemLogger.class to ./boot + public static void main(String[] args) throws IOException { + Path testDir = Paths.get(System.getProperty("user.dir", ".")); + Path bootDir = Paths.get(testDir.toString(), "boot"); + Path classes = Paths.get(System.getProperty("test.classes", "build/classes")); + Path thisClass = Paths.get(classes.toString(), + AccessSystemLogger.class.getSimpleName()+".class"); + if (Files.notExists(bootDir)) { + Files.createDirectory(bootDir); + } + Path dest = Paths.get(bootDir.toString(), + AccessSystemLogger.class.getSimpleName()+".class"); + Files.copy(thisClass, dest, StandardCopyOption.REPLACE_EXISTING); + } + +} --- /dev/null 2015-11-20 17:44:17.000000000 +0100 +++ new/jdk/test/java/lang/System/Logger/custom/CustomLoggerTest.java 2015-11-20 17:44:17.000000000 +0100 @@ -0,0 +1,728 @@ +/* + * 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.security.AccessControlException; +import java.security.AccessController; +import java.security.CodeSource; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.Permissions; +import java.security.Policy; +import java.security.PrivilegedAction; +import java.security.ProtectionDomain; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.ResourceBundle; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.util.stream.Stream; + +/** + * @test + * @bug 8140364 + * @summary Tests loggers returned by System.getLogger with a naive implementation + * of LoggerFinder, and in particular the default body of + * System.Logger methods. + * @build CustomLoggerTest AccessSystemLogger + * @run driver AccessSystemLogger + * @run main/othervm -Xbootclasspath/a:boot CustomLoggerTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot CustomLoggerTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot CustomLoggerTest WITHPERMISSIONS + * @author danielfuchs + */ +public class CustomLoggerTest { + + final static AtomicLong sequencer = new AtomicLong(); + final static boolean VERBOSE = false; + static final ThreadLocal allowControl = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + + public static class MyBundle extends ResourceBundle { + + final ConcurrentHashMap map = new ConcurrentHashMap<>(); + + @Override + protected Object handleGetObject(String key) { + if (key.contains(" (translated)")) { + throw new RuntimeException("Unexpected key: " + key); + } + return map.computeIfAbsent(key, k -> k + " (translated)"); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(map.keySet()); + } + + } + public static class MyLoggerBundle extends MyBundle { + + } + + + public static class BaseLoggerFinder extends LoggerFinder { + final ConcurrentHashMap system = new ConcurrentHashMap<>(); + final ConcurrentHashMap user = new ConcurrentHashMap<>(); + public Queue eventQueue = new ArrayBlockingQueue<>(128); + + // changing this to true requires changing the logic in the + // test in order to load this class with a protection domain + // that has the CONTROL_PERMISSION (e.g. by using a custom + // system class loader. + final boolean doChecks = false; + + public static final class LogEvent { + + public LogEvent() { + this(sequencer.getAndIncrement()); + } + + LogEvent(long sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + long sequenceNumber; + boolean isLoggable; + String loggerName; + Level level; + ResourceBundle bundle; + Throwable thrown; + Object[] args; + Supplier supplier; + String msg; + + Object[] toArray() { + return new Object[] { + sequenceNumber, + isLoggable, + loggerName, + level, + bundle, + thrown, + args, + supplier, + msg, + }; + } + + @Override + public String toString() { + return Arrays.deepToString(toArray()); + } + + + + @Override + public boolean equals(Object obj) { + return obj instanceof LogEvent + && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray()); + } + + @Override + public int hashCode() { + return Objects.hash(toArray()); + } + + + public static LogEvent of(boolean isLoggable, String name, + Level level, ResourceBundle bundle, + String key, Throwable thrown) { + LogEvent evt = new LogEvent(); + evt.isLoggable = isLoggable; + evt.loggerName = name; + evt.level = level; + evt.args = null; + evt.bundle = bundle; + evt.thrown = thrown; + evt.supplier = null; + evt.msg = key; + return evt; + } + + public static LogEvent of(boolean isLoggable, String name, + Level level, ResourceBundle bundle, + String key, Object... params) { + LogEvent evt = new LogEvent(); + evt.isLoggable = isLoggable; + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = null; + evt.supplier = null; + evt.msg = key; + return evt; + } + + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + Level level, ResourceBundle bundle, + String key, Supplier supplier, + Throwable thrown, Object... params) { + LogEvent evt = new LogEvent(sequenceNumber); + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = thrown; + evt.supplier = supplier; + evt.msg = key; + evt.isLoggable = isLoggable; + return evt; + } + + } + + public class LoggerImpl implements Logger { + private final String name; + private Level level = Level.INFO; + + public LoggerImpl(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isLoggable(Level level) { + return this.level != Level.OFF && this.level.getSeverity() <= level.getSeverity(); + } + + @Override + public void log(Level level, ResourceBundle bundle, String key, Throwable thrown) { + log(LogEvent.of(isLoggable(level), this.name, level, bundle, key, thrown)); + } + + @Override + public void log(Level level, ResourceBundle bundle, String format, Object... params) { + log(LogEvent.of(isLoggable(level), name, level, bundle, format, params)); + } + + void log(LogEvent event) { + eventQueue.add(event); + } + } + + @Override + public Logger getLogger(String name, Class caller) { + // We should check the permission to obey the API contract, but + // what happens if we don't? + // This is the main difference compared with what we test in + // java/lang/System/LoggerFinder/BaseLoggerFinderTest + SecurityManager sm = System.getSecurityManager(); + if (sm != null && doChecks) { + sm.checkPermission(SimplePolicy.LOGGERFINDER_PERMISSION); + } + + PrivilegedAction pa = () -> caller.getClassLoader(); + ClassLoader callerLoader = AccessController.doPrivileged(pa); + if (callerLoader == null) { + return system.computeIfAbsent(name, (n) -> new LoggerImpl(n)); + } else { + return user.computeIfAbsent(name, (n) -> new LoggerImpl(n)); + } + } + } + + static final AccessSystemLogger accessSystemLogger = new AccessSystemLogger(); + + static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS}; + + static void setSecurityManager() { + if (System.getSecurityManager() == null) { + Policy.setPolicy(new SimplePolicy(allowControl)); + System.setSecurityManager(new SecurityManager()); + } + } + public static void main(String[] args) { + if (args.length == 0) + args = new String[] { + "NOSECURITY", + "NOPERMISSIONS", + "WITHPERMISSIONS" + }; + + // 1. Obtain destination loggers directly from the LoggerFinder + // - LoggerFinder.getLogger("foo", type) + BaseLoggerFinder provider = + BaseLoggerFinder.class.cast(LoggerFinder.getLoggerFinder()); + BaseLoggerFinder.LoggerImpl appSink = + BaseLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", CustomLoggerTest.class)); + BaseLoggerFinder.LoggerImpl sysSink = + BaseLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", Thread.class)); + + + Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> { + switch (testCase) { + case NOSECURITY: + System.out.println("\n*** Without Security Manager\n"); + test(provider, true, appSink, sysSink); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case NOPERMISSIONS: + System.out.println("\n*** With Security Manager, without permissions\n"); + setSecurityManager(); + test(provider, false, appSink, sysSink); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case WITHPERMISSIONS: + System.out.println("\n*** With Security Manager, with control permission\n"); + setSecurityManager(); + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + test(provider, true, appSink, sysSink); + } finally { + allowControl.get().set(control); + } + break; + default: + throw new RuntimeException("Unknown test case: " + testCase); + } + }); + System.out.println("\nPASSED: Tested " + sequencer.get() + " cases."); + } + + public static void test(BaseLoggerFinder provider, boolean hasRequiredPermissions, + BaseLoggerFinder.LoggerImpl appSink, BaseLoggerFinder.LoggerImpl sysSink) { + + ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName()); + final Map loggerDescMap = new HashMap<>(); + + + // 1. Test loggers returned by: + // - System.getLogger("foo") + // - and AccessSystemLogger.getLogger("foo") + Logger appLogger1 = System.getLogger("foo"); + loggerDescMap.put(appLogger1, "System.getLogger(\"foo\");"); + + Logger sysLogger1 = null; + try { + sysLogger1 = accessSystemLogger.getLogger("foo"); + loggerDescMap.put(sysLogger1, "AccessSystemLogger.getLogger(\"foo\")"); + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(SimplePolicy.LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + throw new RuntimeException("unexpected exception: " + acx, acx); + } + + if (appLogger1 == sysLogger1) { + throw new RuntimeException("identical loggers"); + } + + if (provider.system.contains(appLogger1)) { + throw new RuntimeException("app logger in system map"); + } + if (provider.user.contains(sysLogger1)) { + throw new RuntimeException("sys logger in appplication map"); + } + if (provider.system.contains(sysLogger1)) { + // sysLogger should be a a LazyLoggerWrapper + throw new RuntimeException("sys logger is in system map (should be wrapped)"); + } + + + // 2. Test loggers returned by: + // - System.getLogger(\"foo\", loggerBundle) + // - and AccessSystemLogger.getLogger(\"foo\", loggerBundle) + Logger appLogger2 = + System.getLogger("foo", loggerBundle); + loggerDescMap.put(appLogger2, "System.getLogger(\"foo\", loggerBundle)"); + + Logger sysLogger2 = null; + try { + sysLogger2 = accessSystemLogger.getLogger("foo", loggerBundle); + loggerDescMap.put(sysLogger2, "AccessSystemLogger.getLogger(\"foo\", loggerBundle)"); + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(SimplePolicy.LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + throw new RuntimeException("unexpected exception: " + acx, acx); + } + if (appLogger2 == sysLogger2) { + throw new RuntimeException("identical loggers"); + } + if (appLogger2 == appSink) { + throw new RuntimeException("identical loggers"); + } + if (sysLogger2 == sysSink) { + throw new RuntimeException("identical loggers"); + } + + if (provider.system.contains(appLogger2)) { + throw new RuntimeException("localized app logger in system map"); + } + if (provider.user.contains(appLogger2)) { + throw new RuntimeException("localized app logger in appplication map"); + } + if (provider.user.contains(sysLogger2)) { + throw new RuntimeException("localized sys logger in appplication map"); + } + if (provider.system.contains(sysLogger2)) { + throw new RuntimeException("localized sys logger not in system map"); + } + + testLogger(provider, loggerDescMap, "foo", null, appLogger1, appSink); + testLogger(provider, loggerDescMap, "foo", null, sysLogger1, sysSink); + testLogger(provider, loggerDescMap, "foo", loggerBundle, appLogger2, appSink); + testLogger(provider, loggerDescMap, "foo", loggerBundle, sysLogger2, sysSink); + } + + public static class Foo { + + } + + static void verbose(String msg) { + if (VERBOSE) { + System.out.println(msg); + } + } + + // Calls the 8 methods defined on Logger and verify the + // parameters received by the underlying BaseLoggerFinder.LoggerImpl + // logger. + private static void testLogger(BaseLoggerFinder provider, + Map loggerDescMap, + String name, + ResourceBundle loggerBundle, + Logger logger, + BaseLoggerFinder.LoggerImpl sink) { + + System.out.println("Testing " + loggerDescMap.get(logger)); + + Foo foo = new Foo(); + String fooMsg = foo.toString(); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, foo): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + BaseLoggerFinder.LogEvent expected = + BaseLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0, + name, messageLevel, (ResourceBundle)null, + fooMsg, null, (Throwable)null, (Object[])null); + logger.log(messageLevel, foo); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (provider.eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + BaseLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + String msg = "blah"; + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, \"blah\"): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + BaseLoggerFinder.LogEvent expected = + BaseLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, messageLevel, loggerBundle, + msg, null, (Throwable)null, (Object[])null); + logger.log(messageLevel, msg); + BaseLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + + Supplier fooSupplier = new Supplier() { + @Override + public String get() { + return this.toString(); + } + }; + + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, fooSupplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + BaseLoggerFinder.LogEvent expected = + BaseLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0, + name, messageLevel, (ResourceBundle)null, + fooSupplier.get(), null, + (Throwable)null, (Object[])null); + logger.log(messageLevel, fooSupplier); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (provider.eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + BaseLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + String format = "two params [{1} {2}]"; + Object arg1 = foo; + Object arg2 = msg; + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, format, params...): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + BaseLoggerFinder.LogEvent expected = + BaseLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, messageLevel, loggerBundle, + format, null, (Throwable)null, new Object[] {arg1, arg2}); + logger.log(messageLevel, format, arg1, arg2); + BaseLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + + Throwable thrown = new Exception("OK: log me!"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, \"blah\", thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + BaseLoggerFinder.LogEvent expected = + BaseLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, messageLevel, loggerBundle, + msg, null, thrown, (Object[]) null); + logger.log(messageLevel, msg, thrown); + BaseLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + + + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, thrown, fooSupplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + BaseLoggerFinder.LogEvent expected = + BaseLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0, + name, messageLevel, (ResourceBundle)null, + fooSupplier.get(), null, + (Throwable)thrown, (Object[])null); + logger.log(messageLevel, fooSupplier, thrown); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (provider.eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + BaseLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName()); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, bundle, format, params...): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + BaseLoggerFinder.LogEvent expected = + BaseLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, messageLevel, bundle, + format, null, (Throwable)null, new Object[] {foo, msg}); + logger.log(messageLevel, bundle, format, foo, msg); + BaseLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, bundle, \"blah\", thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + BaseLoggerFinder.LogEvent expected = + BaseLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, messageLevel, bundle, + msg, null, thrown, (Object[]) null); + logger.log(messageLevel, bundle, msg, thrown); + BaseLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + 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 { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + final Permissions permissions; + final Permissions allPermissions; + final ThreadLocal allowControl; + public SimplePolicy(ThreadLocal allowControl) { + this.allowControl = allowControl; + permissions = new Permissions(); + + // these are used for configuring the test itself... + allPermissions = new Permissions(); + allPermissions.add(LOGGERFINDER_PERMISSION); + + } + + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + if (allowControl.get().get()) return allPermissions.implies(permission); + return permissions.implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll(allowControl.get().get() + ? allPermissions : permissions).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll(allowControl.get().get() + ? allPermissions : permissions).toPermissions(); + } + } +} --- /dev/null 2015-11-20 17:44:18.000000000 +0100 +++ new/jdk/test/java/lang/System/Logger/custom/META-INF/services/java.lang.System$LoggerFinder 2015-11-20 17:44:18.000000000 +0100 @@ -0,0 +1 @@ +CustomLoggerTest$BaseLoggerFinder --- /dev/null 2015-11-20 17:44:18.000000000 +0100 +++ new/jdk/test/java/lang/System/Logger/default/AccessSystemLogger.java 2015-11-20 17:44:18.000000000 +0100 @@ -0,0 +1,83 @@ +/* + * 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.IOException; +import java.lang.System.Logger; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ResourceBundle; +import java.util.logging.LogManager; + +/** + * + * @author danielfuchs + */ +public final class AccessSystemLogger { + + public AccessSystemLogger() { + this(check()); + } + + private AccessSystemLogger(Void unused) { + } + + private static Void check() { + if (AccessSystemLogger.class.getClassLoader() != null) { + throw new RuntimeException("AccessSystemLogger should be loaded by the null classloader"); + } + return null; + } + + public Logger getLogger(String name) { + Logger logger = System.getLogger(name); + System.out.println("System.getLogger(\"" + name + "\"): " + logger); + return logger; + } + + public Logger getLogger(String name, ResourceBundle bundle) { + Logger logger = System.getLogger(name, bundle); + System.out.println("System.getLogger(\"" + name + "\", bundle): " + logger); + return logger; + } + + public java.util.logging.Logger demandSystemLogger(String name) { + return java.util.logging.Logger.getLogger(name); + } + + // copy AccessSystemLogger.class to ./boot + public static void main(String[] args) throws IOException { + Path testDir = Paths.get(System.getProperty("user.dir", ".")); + Path bootDir = Paths.get(testDir.toString(), "boot"); + Path classes = Paths.get(System.getProperty("test.classes", "build/classes")); + Path thisClass = Paths.get(classes.toString(), + AccessSystemLogger.class.getSimpleName()+".class"); + if (Files.notExists(bootDir)) { + Files.createDirectory(bootDir); + } + Path dest = Paths.get(bootDir.toString(), + AccessSystemLogger.class.getSimpleName()+".class"); + Files.copy(thisClass, dest, StandardCopyOption.REPLACE_EXISTING); + } + +} --- /dev/null 2015-11-20 17:44:19.000000000 +0100 +++ new/jdk/test/java/lang/System/Logger/default/DefaultLoggerTest.java 2015-11-20 17:44:19.000000000 +0100 @@ -0,0 +1,726 @@ +/* + * 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.security.AccessControlException; +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.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.ResourceBundle; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.util.logging.Handler; +import java.util.logging.LogRecord; +import java.util.stream.Stream; + +/** + * @test + * @bug 8140364 + * @summary Tests default loggers returned by System.getLogger, and in + * particular the implementation of the the System.Logger method + * performed by the default binding. + * + * @build DefaultLoggerTest AccessSystemLogger + * @run driver AccessSystemLogger + * @run main/othervm -Xbootclasspath/a:boot DefaultLoggerTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot DefaultLoggerTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot DefaultLoggerTest WITHPERMISSIONS + * @author danielfuchs + */ +public class DefaultLoggerTest { + + final static AtomicLong sequencer = new AtomicLong(); + final static boolean VERBOSE = false; + static final ThreadLocal allowControl = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAll = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + + public static final Queue eventQueue = new ArrayBlockingQueue<>(128); + + public static final class LogEvent { + + public LogEvent() { + this(sequencer.getAndIncrement()); + } + + LogEvent(long sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + long sequenceNumber; + boolean isLoggable; + String loggerName; + java.util.logging.Level level; + ResourceBundle bundle; + Throwable thrown; + Object[] args; + String msg; + String className; + String methodName; + + Object[] toArray() { + return new Object[] { + sequenceNumber, + isLoggable, + loggerName, + level, + bundle, + thrown, + args, + msg, + className, + methodName, + }; + } + + @Override + public String toString() { + return Arrays.deepToString(toArray()); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof LogEvent + && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray()); + } + + @Override + public int hashCode() { + return Objects.hash(toArray()); + } + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + java.util.logging.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + return LogEvent.of(sequenceNumber, isLoggable, name, + DefaultLoggerTest.class.getName(), + "testLogger", level, bundle, key, + thrown, params); + } + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + String className, String methodName, + java.util.logging.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + LogEvent evt = new LogEvent(sequenceNumber); + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = thrown; + evt.msg = key; + evt.isLoggable = isLoggable; + evt.className = className; + evt.methodName = methodName; + return evt; + } + + } + + static java.util.logging.Level mapToJul(Level level) { + switch (level) { + case ALL: return java.util.logging.Level.ALL; + case TRACE: return java.util.logging.Level.FINER; + case DEBUG: return java.util.logging.Level.FINE; + case INFO: return java.util.logging.Level.INFO; + case WARNING: return java.util.logging.Level.WARNING; + case ERROR: return java.util.logging.Level.SEVERE; + case OFF: return java.util.logging.Level.OFF; + } + throw new InternalError("No such level: " + level); + } + + static void setLevel(java.util.logging.Logger sink, java.util.logging.Level loggerLevel) { + boolean before = allowAll.get().get(); + try { + allowAll.get().set(true); + sink.setLevel(loggerLevel); + } finally { + allowAll.get().set(before); + } + } + + public static class MyHandler extends Handler { + + @Override + public java.util.logging.Level getLevel() { + return java.util.logging.Level.ALL; + } + + @Override + public void publish(LogRecord record) { + eventQueue.add(LogEvent.of(sequencer.getAndIncrement(), + true, record.getLoggerName(), + record.getSourceClassName(), + record.getSourceMethodName(), + record.getLevel(), + record.getResourceBundle(), record.getMessage(), + record.getThrown(), record.getParameters())); + } + @Override + public void flush() { + } + @Override + public void close() throws SecurityException { + } + + } + public static class MyBundle extends ResourceBundle { + + final ConcurrentHashMap map = new ConcurrentHashMap<>(); + + @Override + protected Object handleGetObject(String key) { + if (key.contains(" (translated)")) { + throw new RuntimeException("Unexpected key: " + key); + } + return map.computeIfAbsent(key, k -> k + " (translated)"); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(map.keySet()); + } + + } + public static class MyLoggerBundle extends MyBundle { + + } + + static final AccessSystemLogger accessSystemLogger = new AccessSystemLogger(); + + static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS}; + + static void setSecurityManager() { + if (System.getSecurityManager() == null) { + Policy.setPolicy(new SimplePolicy(allowControl, allowAll)); + System.setSecurityManager(new SecurityManager()); + } + } + public static void main(String[] args) { + if (args.length == 0) + args = new String[] { + "NOSECURITY", + "NOPERMISSIONS", + "WITHPERMISSIONS" + }; + + // 1. Obtain destination loggers directly from the LoggerFinder + // - LoggerFinder.getLogger("foo", type) + + + Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> { + switch (testCase) { + case NOSECURITY: + System.out.println("\n*** Without Security Manager\n"); + test(true); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case NOPERMISSIONS: + System.out.println("\n*** With Security Manager, without permissions\n"); + setSecurityManager(); + test(false); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case WITHPERMISSIONS: + System.out.println("\n*** With Security Manager, with control permission\n"); + setSecurityManager(); + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + test(true); + } finally { + allowControl.get().set(control); + } + break; + default: + throw new RuntimeException("Unknown test case: " + testCase); + } + }); + System.out.println("\nPASSED: Tested " + sequencer.get() + " cases."); + } + + public static void test(boolean hasRequiredPermissions) { + + ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName()); + final Map loggerDescMap = new HashMap<>(); + + + // 1. Test loggers returned by: + // - System.getLogger("foo") + // - and AccessSystemLogger.getLogger("foo") + Logger sysLogger1 = null; + try { + sysLogger1 = accessSystemLogger.getLogger("foo"); + loggerDescMap.put(sysLogger1, "AccessSystemLogger.getLogger(\"foo\")"); + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(SimplePolicy.LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + throw new RuntimeException("unexpected exception: " + acx, acx); + } + + Logger appLogger1 = System.getLogger("foo"); + loggerDescMap.put(appLogger1, "System.getLogger(\"foo\");"); + + if (appLogger1 == sysLogger1) { + throw new RuntimeException("identical loggers"); + } + + // 2. Test loggers returned by: + // - System.getLogger(\"foo\", loggerBundle) + // - and AccessSystemLogger.getLogger(\"foo\", loggerBundle) + Logger appLogger2 = + System.getLogger("foo", loggerBundle); + loggerDescMap.put(appLogger2, "System.getLogger(\"foo\", loggerBundle)"); + + Logger sysLogger2 = null; + try { + sysLogger2 = accessSystemLogger.getLogger("foo", loggerBundle); + loggerDescMap.put(sysLogger2, "AccessSystemLogger.getLogger(\"foo\", loggerBundle)"); + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(SimplePolicy.LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + throw new RuntimeException("unexpected exception: " + acx, acx); + } + if (appLogger2 == sysLogger2) { + throw new RuntimeException("identical loggers"); + } + + final java.util.logging.Logger appSink; + final java.util.logging.Logger sysSink; + final java.util.logging.Handler appHandler; + final java.util.logging.Handler sysHandler; + final LoggerFinder provider; + allowAll.get().set(true); + try { + appSink = java.util.logging.Logger.getLogger("foo"); + sysSink = accessSystemLogger.demandSystemLogger("foo"); + appSink.addHandler(appHandler = new MyHandler()); + sysSink.addHandler(sysHandler = new MyHandler()); + appSink.setUseParentHandlers(false); + sysSink.setUseParentHandlers(false); + provider = LoggerFinder.getLoggerFinder(); + } finally { + allowAll.get().set(false); + } + try { + testLogger(provider, loggerDescMap, "foo", null, sysLogger1, sysSink); + testLogger(provider, loggerDescMap, "foo", null, appLogger1, appSink); + testLogger(provider, loggerDescMap, "foo", loggerBundle, sysLogger2, sysSink); + testLogger(provider, loggerDescMap, "foo", loggerBundle, appLogger2, appSink); + } finally { + allowAll.get().set(true); + try { + appSink.removeHandler(appHandler); + sysSink.removeHandler(sysHandler); + sysSink.setLevel(null); + appSink.setLevel(null); + } finally { + allowAll.get().set(false); + } + } + } + + public static class Foo { + + } + + static void verbose(String msg) { + if (VERBOSE) { + System.out.println(msg); + } + } + + // Calls the 8 methods defined on Logger and verify the + // parameters received by the underlying BaseLoggerFinder.LoggerImpl + // logger. + private static void testLogger(LoggerFinder provider, + Map loggerDescMap, + String name, + ResourceBundle loggerBundle, + Logger logger, + java.util.logging.Logger sink) { + + System.out.println("Testing " + loggerDescMap.get(logger)); + + Foo foo = new Foo(); + String fooMsg = foo.toString(); + for (Level loggerLevel : Level.values()) { + setLevel(sink, mapToJul(loggerLevel)); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, foo): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + + LogEvent expected = + LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0, + name, mapToJul(messageLevel), (ResourceBundle)null, + fooMsg, (Throwable)null, (Object[])null); + logger.log(messageLevel, foo); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + String msg = "blah"; + for (Level loggerLevel : Level.values()) { + setLevel(sink, mapToJul(loggerLevel)); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, \"blah\"): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, mapToJul(messageLevel), loggerBundle, + msg, (Throwable)null, (Object[])null); + logger.log(messageLevel, msg); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + Supplier fooSupplier = new Supplier() { + @Override + public String get() { + return this.toString(); + } + }; + + for (Level loggerLevel : Level.values()) { + setLevel(sink, mapToJul(loggerLevel)); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, fooSupplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0, + name, mapToJul(messageLevel), (ResourceBundle)null, + fooSupplier.get(), + (Throwable)null, (Object[])null); + logger.log(messageLevel, fooSupplier); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + String format = "two params [{1} {2}]"; + Object arg1 = foo; + Object arg2 = msg; + for (Level loggerLevel : Level.values()) { + setLevel(sink, mapToJul(loggerLevel)); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, format, params...): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, mapToJul(messageLevel), loggerBundle, + format, (Throwable)null, new Object[] {arg1, arg2}); + logger.log(messageLevel, format, arg1, arg2); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + Throwable thrown = new Exception("OK: log me!"); + for (Level loggerLevel : Level.values()) { + setLevel(sink, mapToJul(loggerLevel)); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, \"blah\", thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, mapToJul(messageLevel), loggerBundle, + msg, thrown, (Object[]) null); + logger.log(messageLevel, msg, thrown); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + + for (Level loggerLevel : Level.values()) { + setLevel(sink, mapToJul(loggerLevel)); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, thrown, fooSupplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0, + name, mapToJul(messageLevel), (ResourceBundle)null, + fooSupplier.get(), + (Throwable)thrown, (Object[])null); + logger.log(messageLevel, fooSupplier, thrown); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName()); + for (Level loggerLevel : Level.values()) { + setLevel(sink, mapToJul(loggerLevel)); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, bundle, format, params...): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, mapToJul(messageLevel), bundle, + format, (Throwable)null, new Object[] {foo, msg}); + logger.log(messageLevel, bundle, format, foo, msg); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + for (Level loggerLevel : Level.values()) { + setLevel(sink, mapToJul(loggerLevel)); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, bundle, \"blah\", thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, mapToJul(messageLevel), bundle, + msg, thrown, (Object[]) null); + logger.log(messageLevel, bundle, msg, thrown); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + } + + 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 { + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + final Permissions permissions; + final Permissions allPermissions; + final Permissions controlPermissions; + final ThreadLocal allowControl; + final ThreadLocal allowAll; + public SimplePolicy(ThreadLocal allowControl, ThreadLocal allowAll) { + this.allowControl = allowControl; + this.allowAll = allowAll; + permissions = new Permissions(); + + // these are used for configuring the test itself... + controlPermissions = new Permissions(); + controlPermissions.add(LOGGERFINDER_PERMISSION); + 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); + if (allowControl.get().get()) return controlPermissions.implies(permission); + return permissions.implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll(allowAll.get().get() + ? allPermissions : allowControl.get().get() + ? controlPermissions : permissions).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll(allowAll.get().get() + ? allPermissions : allowControl.get().get() + ? controlPermissions : permissions).toPermissions(); + } + } +} --- /dev/null 2015-11-20 17:44:20.000000000 +0100 +++ new/jdk/test/java/lang/System/Logger/interface/LoggerInterfaceTest.java 2015-11-20 17:44:19.000000000 +0100 @@ -0,0 +1,592 @@ +/* + * 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.util.ResourceBundle; +import java.util.function.Consumer; +import java.lang.System.Logger.Level; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.Objects; +import java.util.Queue; +import java.util.function.Supplier; + +/** + * @test + * @bug 8140364 + * @summary Tests the default body of the System.Logger interface. + * @author danielfuchs + */ +public class LoggerInterfaceTest { + + public static class LoggerImpl implements System.Logger { + + public static class LogEvent implements Cloneable { + Level level; + ResourceBundle bundle; + String msg; + Throwable thrown; + Object[] params; + StackTraceElement[] callStack; + + @Override + protected LogEvent clone() { + try { + return (LogEvent)super.clone(); + } catch (CloneNotSupportedException x) { + throw new RuntimeException(x); + } + } + + + } + + public static class LogEventBuilder { + private LogEvent event = new LogEvent(); + public LogEventBuilder level(Level level) { + event.level = level; + return this; + } + public LogEventBuilder stack(StackTraceElement... stack) { + event.callStack = stack; + return this; + } + public LogEventBuilder bundle(ResourceBundle bundle) { + event.bundle = bundle; + return this; + } + public LogEventBuilder msg(String msg) { + event.msg = msg; + return this; + } + public LogEventBuilder thrown(Throwable thrown) { + event.thrown = thrown; + return this; + } + public LogEventBuilder params(Object... params) { + event.params = params; + return this; + } + public LogEvent build() { + return event.clone(); + } + + public LogEventBuilder clear() { + event = new LogEvent(); + return this; + } + + } + + Level level = Level.WARNING; + Consumer consumer; + final LogEventBuilder builder = new LogEventBuilder(); + + @Override + public String getName() { + return "noname"; + } + + @Override + public boolean isLoggable(Level level) { + return level.getSeverity() >= this.level.getSeverity(); + } + + @Override + public void log(Level level, ResourceBundle bundle, String msg, Throwable thrown) { + builder.clear().level(level).bundle(bundle).msg(msg).thrown(thrown) + .stack(new Exception().getStackTrace()); + consumer.accept(builder.build()); + } + + @Override + public void log(Level level, ResourceBundle bundle, String format, Object... params) { + builder.clear().level(level).bundle(bundle).msg(format).params(params) + .stack(new Exception().getStackTrace()); + consumer.accept(builder.build()); + } + + } + + static class Throwing { + @Override + public String toString() { + throw new RuntimeException("should not have been called"); + } + } + static class NotTrowing { + private final String toString; + private int count = 0; + public NotTrowing(String toString) { + this.toString = toString; + } + + @Override + public String toString() { + return toString + "[" + (++count) + "]"; + } + } + + public static void main(String[] args) { + final LoggerImpl loggerImpl = new LoggerImpl(); + final System.Logger logger = loggerImpl; + final Queue events = new LinkedList<>(); + loggerImpl.consumer = (x) -> events.add(x); + + System.out.println("\nlogger.isLoggable(Level)"); + assertTrue(logger.isLoggable(Level.WARNING), "logger.isLoggable(Level.WARNING)"," "); + assertFalse(logger.isLoggable(Level.INFO), "logger.isLoggable(Level.INFO)", " "); + + + System.out.println("\nlogger.log(Level, Object)"); + for (Level l : Level.values()) { + boolean logged = l.compareTo(Level.WARNING) >= 0; + Object[][] cases = new Object[][] { + {null}, {"baz"} + }; + for (Object[] p : cases) { + String msg = (String)p[0]; + final Object obj = msg == null ? null : logged ? new NotTrowing(msg) : new Throwing(); + String par1 = msg == null ? "(Object)null" + : logged ? "new NotTrowing(\""+ msg+"\")" : "new Throwing()"; + System.out.println(" logger.log(" + l + ", " + par1 + ")"); + try { + logger.log(l, obj); + if (obj == null) { + throw new RuntimeException("Expected NullPointerException not thrown for" + + " logger.log(" + l + ", " + par1 + ")"); + } + } catch (NullPointerException x) { + if (obj == null) { + System.out.println(" Got expected exception: " + x); + continue; + } else { + throw x; + } + } + LoggerImpl.LogEvent e = events.poll(); + if (logged) { + assertNonNull(e, "e", " "); + assertEquals(l, e.level, "e.level", " "); + assertToString(e.msg, msg, 1, "e.msg", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.params, null, "e.params", " "); + assertEquals(e.thrown, null, "e.thrown", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.callStack[0].getMethodName(), "log", + "e.callStack[0].getMethodName()", " "); + assertEquals(e.callStack[0].getClassName(), + logger.getClass().getName(), + "e.callStack[0].getClassName() ", " "); + assertEquals(e.callStack[1].getMethodName(), "log", + "e.callStack[1].getMethodName()", " "); + assertEquals(e.callStack[1].getClassName(), + System.Logger.class.getName(), + "e.callStack[1].getClassName() ", " "); + assertEquals(e.callStack[2].getMethodName(), "main", + "e.callStack[2].getMethodName()", " "); + } else { + assertEquals(e, null, "e", " "); + } + } + } + System.out.println(" logger.log(" + null + ", " + + "new NotThrowing(\"foobar\")" + ")"); + try { + logger.log(null, new NotTrowing("foobar")); + throw new RuntimeException("Expected NullPointerException not thrown for" + + " logger.log(" + null + ", " + + "new NotThrowing(\"foobar\")" + ")"); + } catch (NullPointerException x) { + System.out.println(" Got expected exception: " + x); + } + + + System.out.println("\nlogger.log(Level, String)"); + for (Level l : Level.values()) { + boolean logged = l.compareTo(Level.WARNING) >= 0; + String par = "bar"; + System.out.println(" logger.log(" + l + ", \"" + par +"\");"); + logger.log(l, par); + LoggerImpl.LogEvent e = events.poll(); + assertNonNull(e, "e", " "); + assertEquals(e.level, l, "e.level", " "); + assertEquals(e.msg, "bar", "e.msg", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.params, null, "e.params", " "); + assertEquals(e.thrown, null, "e.thrown", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.callStack[0].getMethodName(), "log", + "e.callStack[0].getMethodName()", " "); + assertEquals(e.callStack[0].getClassName(), + logger.getClass().getName(), + "e.callStack[0].getClassName() ", " "); + assertEquals(e.callStack[1].getMethodName(), "log", + "e.callStack[1].getMethodName()", " "); + assertEquals(e.callStack[1].getClassName(), + System.Logger.class.getName(), + "e.callStack[1].getClassName() ", " "); + assertEquals(e.callStack[2].getMethodName(), "main", + "e.callStack[2].getMethodName()", " "); + + System.out.println(" logger.log(" + l + ", (String)null);"); + logger.log(l, (String)null); + e = events.poll(); + assertNonNull(e, "e", " "); + assertEquals(e.level, l, "e.level", " "); + assertEquals(e.msg, null, "e.msg", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.params, null, "e.params", " "); + assertEquals(e.thrown, null, "e.thrown", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.callStack[0].getMethodName(), "log", + "e.callStack[0].getMethodName()", " "); + assertEquals(e.callStack[0].getClassName(), + logger.getClass().getName(), + "e.callStack[0].getClassName() ", " "); + assertEquals(e.callStack[1].getMethodName(), "log", + "e.callStack[1].getMethodName()", " "); + assertEquals(e.callStack[1].getClassName(), + System.Logger.class.getName(), + "e.callStack[1].getClassName() ", " "); + assertEquals(e.callStack[2].getMethodName(), "main", + "e.callStack[2].getMethodName()", " "); + } + + System.out.println("\nlogger.log(Level, Supplier)"); + for (Level l : Level.values()) { + boolean logged = l.compareTo(Level.WARNING) >= 0; + Object[][] cases = new Object[][] { + {null}, {"baz"} + }; + for (Object[] p : cases) { + String msg = (String)p[0]; + final Object obj = msg == null ? null : logged ? new NotTrowing(msg) : new Throwing(); + final Supplier s = msg == null ? null : () -> obj.toString(); + String par1 = msg == null ? "(Supplier)null" + : logged ? "() -> new NotTrowing(\""+ msg+"\").toString()" : "new Throwing()"; + System.out.println(" logger.log(" + l + ", " + par1 + ")"); + try { + logger.log(l, s); + if (s == null) { + throw new RuntimeException("Expected NullPointerException not thrown for" + + " logger.log(" + l + ", " + par1 + ")"); + } + } catch (NullPointerException x) { + if (s == null) { + System.out.println(" Got expected exception: " + x); + continue; + } else { + throw x; + } + } + LoggerImpl.LogEvent e = events.poll(); + if (logged) { + assertNonNull(e, "e", " "); + assertEquals(l, e.level, "e.level", " "); + assertToString(e.msg, msg, 1, "e.msg", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.params, null, "e.params", " "); + assertEquals(e.thrown, null, "e.thrown", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.callStack[0].getMethodName(), "log", + "e.callStack[0].getMethodName()", " "); + assertEquals(e.callStack[0].getClassName(), + logger.getClass().getName(), + "e.callStack[0].getClassName() ", " "); + assertEquals(e.callStack[1].getMethodName(), "log", + "e.callStack[1].getMethodName()", " "); + assertEquals(e.callStack[1].getClassName(), + System.Logger.class.getName(), + "e.callStack[1].getClassName() ", " "); + assertEquals(e.callStack[2].getMethodName(), "main", + "e.callStack[2].getMethodName()", " "); + } else { + assertEquals(e, null, "e", " "); + } + } + } + System.out.println(" logger.log(" + null + ", " + "() -> \"biz\"" + ")"); + try { + logger.log(null, () -> "biz"); + throw new RuntimeException("Expected NullPointerException not thrown for" + + " logger.log(" + null + ", " + + "() -> \"biz\"" + ")"); + } catch (NullPointerException x) { + System.out.println(" Got expected exception: " + x); + } + + System.out.println("\nlogger.log(Level, String, Object...)"); + for (Level l : Level.values()) { + boolean logged = l.compareTo(Level.WARNING) >= 0; + String par = "bam"; + Object[] params = null; + System.out.println(" logger.log(" + l + ", \"" + par +"\", null);"); + logger.log(l, par, params); + LoggerImpl.LogEvent e = events.poll(); + assertNonNull(e, "e", " "); + assertEquals(l, e.level, "e.level", " "); + assertEquals(e.msg, "bam", "e.msg", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.params, null, "e.params", " "); + assertEquals(e.thrown, null, "e.thrown", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.callStack[0].getMethodName(), "log", + "e.callStack[0].getMethodName()", " "); + assertEquals(e.callStack[0].getClassName(), + logger.getClass().getName(), + "e.callStack[0].getClassName() ", " "); + assertEquals(e.callStack[1].getMethodName(), "log", + "e.callStack[1].getMethodName()", " "); + assertEquals(e.callStack[1].getClassName(), + System.Logger.class.getName(), + "e.callStack[1].getClassName() ", " "); + assertEquals(e.callStack[2].getMethodName(), "main", + "e.callStack[2].getMethodName()", " "); + + params = new Object[] {new NotTrowing("one")}; + par = "bam {0}"; + System.out.println(" logger.log(" + l + ", \"" + par + + "\", new NotTrowing(\"one\"));"); + logger.log(l, par, params[0]); + e = events.poll(); + assertNonNull(e, "e", " "); + assertEquals(l, e.level, "e.level", " "); + assertEquals(e.msg, par, "e.msg", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertArrayEquals(e.params, params, "e.params", " "); + assertEquals(e.thrown, null, "e.thrown", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.callStack[0].getMethodName(), "log", + "e.callStack[0].getMethodName()", " "); + assertEquals(e.callStack[0].getClassName(), + logger.getClass().getName(), + "e.callStack[0].getClassName() ", " "); + assertEquals(e.callStack[1].getMethodName(), "log", + "e.callStack[1].getMethodName()", " "); + assertEquals(e.callStack[1].getClassName(), + System.Logger.class.getName(), + "e.callStack[1].getClassName() ", " "); + assertEquals(e.callStack[2].getMethodName(), "main", + "e.callStack[2].getMethodName()", " "); + + params = new Object[] {new NotTrowing("fisrt"), new NotTrowing("second")}; + par = "bam {0} {1}"; + System.out.println(" logger.log(" + l + ", \"" + par + + "\", new NotTrowing(\"fisrt\")," + + " new NotTrowing(\"second\"));"); + logger.log(l, par, params[0], params[1]); + e = events.poll(); + assertNonNull(e, "e", " "); + assertEquals(l, e.level, "e.level", " "); + assertEquals(e.msg, par, "e.msg", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertArrayEquals(e.params, params, "e.params", " "); + assertEquals(e.thrown, null, "e.thrown", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.callStack[0].getMethodName(), "log", + "e.callStack[0].getMethodName()", " "); + assertEquals(e.callStack[0].getClassName(), + logger.getClass().getName(), + "e.callStack[0].getClassName() ", " "); + assertEquals(e.callStack[1].getMethodName(), "log", + "e.callStack[1].getMethodName()", " "); + assertEquals(e.callStack[1].getClassName(), + System.Logger.class.getName(), + "e.callStack[1].getClassName() ", " "); + assertEquals(e.callStack[2].getMethodName(), "main", + "e.callStack[2].getMethodName()", " "); + + params = new Object[] {new NotTrowing("third"), new NotTrowing("fourth")}; + par = "bam {2}"; + System.out.println(" logger.log(" + l + ", \"" + par + + "\", new Object[] {new NotTrowing(\"third\")," + + " new NotTrowing(\"fourth\")});"); + logger.log(l, par, params); + e = events.poll(); + assertNonNull(e, "e", " "); + assertEquals(l, e.level, "e.level", " "); + assertEquals(e.msg, par, "e.msg", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertArrayEquals(e.params, params, "e.params", " "); + assertEquals(e.thrown, null, "e.thrown", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.callStack[0].getMethodName(), "log", + "e.callStack[0].getMethodName()", " "); + assertEquals(e.callStack[0].getClassName(), logger.getClass().getName(), + "e.callStack[0].getClassName() ", " "); + assertEquals(e.callStack[1].getMethodName(), "log", + "e.callStack[1].getMethodName()", " "); + assertEquals(e.callStack[1].getClassName(), + System.Logger.class.getName(), + "e.callStack[1].getClassName() ", " "); + assertEquals(e.callStack[2].getMethodName(), "main", + "e.callStack[2].getMethodName()", " "); + } + + System.out.println("\nlogger.log(Level, String, Throwable)"); + for (Level l : Level.values()) { + boolean logged = l.compareTo(Level.WARNING) >= 0; + Object[][] cases = new Object[][] { + {null, null}, {null, new Throwable()}, {"biz", null}, {"boz", new Throwable()} + }; + for (Object[] p : cases) { + String msg = (String)p[0]; + Throwable thrown = (Throwable)p[1]; + String par1 = msg == null ? "(String)null" : "\"" + msg + "\""; + String par2 = thrown == null ? "(Throwable)null" : "new Throwable()"; + System.out.println(" logger.log(" + l + ", " + par1 +", " + par2 + ")"); + logger.log(l, msg, thrown); + LoggerImpl.LogEvent e = events.poll(); + assertNonNull(e, "e", " "); + assertEquals(e.level, l, "e.level", " "); + assertEquals(e.msg, msg, "e.msg", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.params, null, "e.params", " "); + assertEquals(e.thrown, thrown, "e.thrown", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.callStack[0].getMethodName(), + "log", "e.callStack[0].getMethodName()", " "); + assertEquals(e.callStack[0].getClassName(), + logger.getClass().getName(), + "e.callStack[0].getClassName() ", " "); + assertEquals(e.callStack[1].getMethodName(), "log", + "e.callStack[1].getMethodName()", " "); + assertEquals(e.callStack[1].getClassName(), + System.Logger.class.getName(), + "e.callStack[1].getClassName() ", " "); + assertEquals(e.callStack[2].getMethodName(), "main", + "e.callStack[2].getMethodName()", " "); + } + } + + System.out.println("\nlogger.log(Level, Supplier, Throwable)"); + for (Level l : Level.values()) { + boolean logged = l.compareTo(Level.WARNING) >= 0; + Object[][] cases = new Object[][] { + {null, null}, {null, new Throwable()}, {"biz", null}, {"boz", new Throwable()} + }; + for (Object[] p : cases) { + String msg = (String)p[0]; + Throwable thrown = (Throwable)p[1]; + final Object obj = msg == null ? null : logged ? new NotTrowing(msg) : new Throwing(); + final Supplier s = msg == null ? null : () -> obj.toString(); + String par1 = msg == null ? "(Supplier)null" + : logged ? "() -> new NotTrowing(\""+ msg+"\").toString()" : "new Throwing()"; + String par2 = thrown == null ? "(Throwable)null" : "new Throwable()"; + System.out.println(" logger.log(" + l + ", " + par1 +", " + par2 + ")"); + try { + logger.log(l, s, thrown); + if (s== null) { + throw new RuntimeException("Expected NullPointerException not thrown for" + + " logger.log(" + l + ", " + par1 +", " + par2 + ")"); + } + } catch (NullPointerException x) { + if (s == null) { + System.out.println(" Got expected exception: " + x); + continue; + } else { + throw x; + } + } + LoggerImpl.LogEvent e = events.poll(); + if (logged) { + assertNonNull(e, "e", " "); + assertEquals(l, e.level, "e.level", " "); + assertToString(e.msg, msg, 1, "e.msg", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.params, null, "e.params", " "); + assertEquals(e.thrown, thrown, "e.thrown", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.callStack[0].getMethodName(), "log", + "e.callStack[0].getMethodName()", " "); + assertEquals(e.callStack[0].getClassName(), + logger.getClass().getName(), + "e.callStack[0].getClassName() ", " "); + assertEquals(e.callStack[1].getMethodName(), "log", + "e.callStack[1].getMethodName()", " "); + assertEquals(e.callStack[1].getClassName(), + System.Logger.class.getName(), + "e.callStack[1].getClassName() ", " "); + assertEquals(e.callStack[2].getMethodName(), "main", + "e.callStack[2].getMethodName()", " "); + } else { + assertEquals(e, null, "e", " "); + } + } + } + System.out.println(" logger.log(" + null + ", " + "() -> \"biz\"" + + ", " + "new Throwable()" + ")"); + try { + logger.log(null, () -> "biz", new Throwable()); + throw new RuntimeException("Expected NullPointerException not thrown for" + + " logger.log(" + null + ", " + + "() -> \"biz\"" + ", " + + "new Throwable()" + ")"); + } catch (NullPointerException x) { + System.out.println(" Got expected exception: " + x); + } + + System.out.println("Checking that we have no spurious events in the queue"); + assertEquals(events.poll(), null, "events.poll()", " "); + } + + static void assertTrue(boolean test, String what, String prefix) { + if (!test) { + throw new RuntimeException("Expected true for " + what); + } + System.out.println(prefix + "Got expected " + what + ": " + test); + } + static void assertFalse(boolean test, String what, String prefix) { + if (test) { + throw new RuntimeException("Expected false for " + what); + } + System.out.println(prefix + "Got expected " + what + ": " + test); + } + static void assertToString(String actual, String expected, int count, String what, String prefix) { + assertEquals(actual, expected + "["+count+"]", what, prefix); + } + static void assertEquals(Object actual, Object expected, String what, String prefix) { + if (!Objects.equals(actual, expected)) { + throw new RuntimeException("Bad " + what + ":" + + "\n\t expected: " + expected + + "\n\t actual: " + actual); + } + System.out.println(prefix + "Got expected " + what + ": " + actual); + } + static void assertArrayEquals(Object[] actual, Object[] expected, String what, String prefix) { + if (!Objects.deepEquals(actual, expected)) { + throw new RuntimeException("Bad " + what + ":" + + "\n\t expected: " + expected == null ? "null" : Arrays.deepToString(expected) + + "\n\t actual: " + actual == null ? "null" : Arrays.deepToString(actual)); + } + System.out.println(prefix + "Got expected " + what + ": " + Arrays.deepToString(actual)); + } + static void assertNonNull(Object actual, String what, String prefix) { + if (Objects.equals(actual, null)) { + throw new RuntimeException("Bad " + what + ":" + + "\n\t expected: non null" + + "\n\t actual: " + actual); + } + System.out.println(prefix + "Got expected " + what + ": " + "non null"); + } +} --- /dev/null 2015-11-20 17:44:20.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/AccessSystemLogger.java 2015-11-20 17:44:20.000000000 +0100 @@ -0,0 +1,78 @@ +/* + * 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.IOException; +import java.lang.System.Logger; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ResourceBundle; + +/** + * + * @author danielfuchs + */ +public final class AccessSystemLogger { + + public AccessSystemLogger() { + this(check()); + } + + private AccessSystemLogger(Void unused) { + } + + private static Void check() { + if (AccessSystemLogger.class.getClassLoader() != null) { + throw new RuntimeException("AccessSystemLogger should be loaded by the null classloader"); + } + return null; + } + + public Logger getLogger(String name) { + Logger logger = System.getLogger(name); + System.out.println("System.getLogger(\"" + name + "\"): " + logger); + return logger; + } + + public Logger getLogger(String name, ResourceBundle bundle) { + Logger logger = System.getLogger(name, bundle); + System.out.println("System.getLogger(\"" + name + "\", bundle): " + logger); + return logger; + } + + // copy AccessSystemLogger.class to ./boot + public static void main(String[] args) throws IOException { + Path testDir = Paths.get(System.getProperty("user.dir", ".")); + Path bootDir = Paths.get(testDir.toString(), "boot"); + Path classes = Paths.get(System.getProperty("test.classes", "build/classes")); + Path thisClass = Paths.get(classes.toString(), + AccessSystemLogger.class.getSimpleName()+".class"); + if (Files.notExists(bootDir)) { + Files.createDirectory(bootDir); + } + Path dest = Paths.get(bootDir.toString(), + AccessSystemLogger.class.getSimpleName()+".class"); + Files.copy(thisClass, dest, StandardCopyOption.REPLACE_EXISTING); + } + +} --- /dev/null 2015-11-20 17:44:21.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/BaseLoggerFinder.java 2015-11-20 17:44:21.000000000 +0100 @@ -0,0 +1,47 @@ +/* + * 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.security.AccessController; +import java.security.PrivilegedAction; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; + +public class BaseLoggerFinder extends LoggerFinder implements TestLoggerFinder { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + @Override + public Logger getLogger(String name, Class caller) { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGERFINDER_PERMISSION); + } + PrivilegedAction pa = () -> caller.getClassLoader(); + ClassLoader callerLoader = AccessController.doPrivileged(pa); + if (callerLoader == null) { + return system.computeIfAbsent(name, (n) -> new LoggerImpl(n)); + } else { + return user.computeIfAbsent(name, (n) -> new LoggerImpl(n)); + } + } +} --- /dev/null 2015-11-20 17:44:22.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/BaseLoggerFinderTest.java 2015-11-20 17:44:21.000000000 +0100 @@ -0,0 +1,694 @@ +/* + * 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.security.AccessControlException; +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.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.stream.Stream; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; + +/** + * @test + * @bug 8140364 + * @summary Tests a naive implementation of LoggerFinder, and in particular + * the default body of System.Logger methods. + * @build AccessSystemLogger BaseLoggerFinderTest CustomSystemClassLoader BaseLoggerFinder TestLoggerFinder + * @run driver AccessSystemLogger + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader BaseLoggerFinderTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader BaseLoggerFinderTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader BaseLoggerFinderTest WITHPERMISSIONS + * @author danielfuchs + */ +public class BaseLoggerFinderTest { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + final static boolean VERBOSE = false; + static final ThreadLocal allowControl = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAccess = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + + final static AccessSystemLogger accessSystemLogger = new AccessSystemLogger(); + static final Class providerClass; + static { + try { + providerClass = ClassLoader.getSystemClassLoader().loadClass("BaseLoggerFinder"); + } catch (ClassNotFoundException ex) { + throw new ExceptionInInitializerError(ex); + } + } + + public static class MyBundle extends ResourceBundle { + + final ConcurrentHashMap map = new ConcurrentHashMap<>(); + + @Override + protected Object handleGetObject(String key) { + if (key.contains(" (translated)")) { + throw new RuntimeException("Unexpected key: " + key); + } + return map.computeIfAbsent(key, k -> k + " (translated)"); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(map.keySet()); + } + + } + public static class MyLoggerBundle extends MyBundle { + + } + + static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS}; + + static void setSecurityManager() { + if (System.getSecurityManager() == null) { + Policy.setPolicy(new SimplePolicy(allowControl, allowAccess)); + System.setSecurityManager(new SecurityManager()); + } + } + + public static void main(String[] args) { + if (args.length == 0) + args = new String[] { + //"NOSECURITY", + "NOPERMISSIONS", + "WITHPERMISSIONS" + }; + + System.out.println("Using provider class: " + providerClass + "[" + providerClass.getClassLoader() + "]"); + + Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> { + TestLoggerFinder provider; + switch (testCase) { + case NOSECURITY: + System.out.println("\n*** Without Security Manager\n"); + provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder()); + test(provider, true); + System.out.println("Tetscase count: " + TestLoggerFinder.sequencer.get()); + break; + case NOPERMISSIONS: + System.out.println("\n*** With Security Manager, without permissions\n"); + setSecurityManager(); + try { + provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder()); + throw new RuntimeException("Expected exception not raised"); + } catch (AccessControlException x) { + if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) { + throw new RuntimeException("Unexpected permission check", x); + } + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder()); + } finally { + allowControl.get().set(control); + } + } + test(provider, false); + System.out.println("Tetscase count: " + TestLoggerFinder.sequencer.get()); + break; + case WITHPERMISSIONS: + System.out.println("\n*** With Security Manager, with control permission\n"); + setSecurityManager(); + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder()); + test(provider, true); + } finally { + allowControl.get().set(control); + } + break; + default: + throw new RuntimeException("Unknown test case: " + testCase); + } + }); + System.out.println("\nPASSED: Tested " + TestLoggerFinder.sequencer.get() + " cases."); + } + + public static void test(TestLoggerFinder provider, boolean hasRequiredPermissions) { + + ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName()); + final Map loggerDescMap = new HashMap<>(); + + + // 1. Test loggers returned by LoggerFinder, both for system callers + // and not system callers. + TestLoggerFinder.LoggerImpl appLogger1 = null; + try { + appLogger1 = + TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", BaseLoggerFinderTest.class)); + loggerDescMap.put(appLogger1, "provider.getLogger(\"foo\", BaseLoggerFinderTest.class)"); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for logger: " + acx); + final boolean old = allowControl.get().get(); + allowControl.get().set(true); + try { + appLogger1 = + TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", BaseLoggerFinderTest.class)); + loggerDescMap.put(appLogger1, "provider.getLogger(\"foo\", BaseLoggerFinderTest.class)"); + } finally { + allowControl.get().set(old); + } + } + + TestLoggerFinder.LoggerImpl sysLogger1 = null; + try { + sysLogger1 = TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", Thread.class)); + loggerDescMap.put(sysLogger1, "provider.getLogger(\"foo\", Thread.class)"); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a system logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for system logger: " + acx); + final boolean old = allowControl.get().get(); + allowControl.get().set(true); + try { + sysLogger1 = TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", Thread.class)); + loggerDescMap.put(sysLogger1, "provider.getLogger(\"foo\", Thread.class)"); + } finally { + allowControl.get().set(old); + } + } + if (appLogger1 == sysLogger1) { + throw new RuntimeException("identical loggers"); + } + + if (provider.system.contains(appLogger1)) { + throw new RuntimeException("app logger in system map"); + } + if (!provider.user.contains(appLogger1)) { + throw new RuntimeException("app logger not in appplication map"); + } + if (provider.user.contains(sysLogger1)) { + throw new RuntimeException("sys logger in appplication map"); + } + if (!provider.system.contains(sysLogger1)) { + throw new RuntimeException("sys logger not in system map"); + } + + testLogger(provider, loggerDescMap, "foo", null, appLogger1, appLogger1); + testLogger(provider, loggerDescMap, "foo", null, sysLogger1, sysLogger1); + + // 2. Test localized loggers returned LoggerFinder, both for system + // callers and non system callers + Logger appLogger2 = null; + try { + appLogger2 = provider.getLocalizedLogger("foo", loggerBundle, BaseLoggerFinderTest.class); + loggerDescMap.put(appLogger2, "provider.getLocalizedLogger(\"foo\", loggerBundle, BaseLoggerFinderTest.class)"); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for logger: " + acx); + final boolean old = allowControl.get().get(); + allowControl.get().set(true); + try { + appLogger2 = provider.getLocalizedLogger("foo", loggerBundle, BaseLoggerFinderTest.class); + loggerDescMap.put(appLogger2, "provider.getLocalizedLogger(\"foo\", loggerBundle, BaseLoggerFinderTest.class)"); + } finally { + allowControl.get().set(old); + } + } + + Logger sysLogger2 = null; + try { + sysLogger2 = provider.getLocalizedLogger("foo", loggerBundle, Thread.class); + loggerDescMap.put(sysLogger2, "provider.getLocalizedLogger(\"foo\", loggerBundle, Thread.class)"); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a system logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for localized system logger: " + acx); + final boolean old = allowControl.get().get(); + allowControl.get().set(true); + try { + sysLogger2 = provider.getLocalizedLogger("foo", loggerBundle, Thread.class); + loggerDescMap.put(sysLogger2, "provider.getLocalizedLogger(\"foo\", loggerBundle, Thread.class))"); + } finally { + allowControl.get().set(old); + } + } + if (appLogger2 == sysLogger2) { + throw new RuntimeException("identical loggers"); + } + if (appLogger2 == appLogger1) { + throw new RuntimeException("identical loggers"); + } + if (sysLogger2 == sysLogger1) { + throw new RuntimeException("identical loggers"); + } + + if (provider.system.contains(appLogger2)) { + throw new RuntimeException("localized app logger in system map"); + } + if (provider.user.contains(appLogger2)) { + throw new RuntimeException("localized app logger in appplication map"); + } + if (provider.user.contains(sysLogger2)) { + throw new RuntimeException("localized sys logger in appplication map"); + } + if (provider.system.contains(sysLogger2)) { + throw new RuntimeException("localized sys logger not in system map"); + } + + testLogger(provider, loggerDescMap, "foo", loggerBundle, appLogger2, appLogger1); + testLogger(provider, loggerDescMap, "foo", loggerBundle, sysLogger2, sysLogger1); + + // 3 Test loggers returned by: + // 3.1: System.getLogger("foo") + Logger appLogger3 = System.getLogger("foo"); + loggerDescMap.put(appLogger3, "System.getLogger(\"foo\")"); + testLogger(provider, loggerDescMap, "foo", null, appLogger3, appLogger1); + + // 3.2: System.getLogger("foo") + // Emulate what System.getLogger() does when the caller is a + // platform classes + Logger sysLogger3 = accessSystemLogger.getLogger("foo"); + loggerDescMap.put(sysLogger3, "AccessSystemLogger.getLogger(\"foo\")"); + + if (appLogger3 == sysLogger3) { + throw new RuntimeException("identical loggers"); + } + + testLogger(provider, loggerDescMap, "foo", null, sysLogger3, sysLogger1); + + // 4. Test loggers returned by: + // 4.1 System.getLogger("foo", loggerBundle) + Logger appLogger4 = + System.getLogger("foo", loggerBundle); + loggerDescMap.put(appLogger4, "System.getLogger(\"foo\", loggerBundle)"); + if (appLogger4 == appLogger1) { + throw new RuntimeException("identical loggers"); + } + + testLogger(provider, loggerDescMap, "foo", loggerBundle, appLogger4, appLogger1); + + // 4.2: System.getLogger("foo", loggerBundle) + // Emulate what System.getLogger() does when the caller is a + // platform classes + Logger sysLogger4 = accessSystemLogger.getLogger("foo", loggerBundle); + loggerDescMap.put(sysLogger4, "AccessSystemLogger.getLogger(\"foo\", loggerBundle)"); + if (appLogger4 == sysLogger4) { + throw new RuntimeException("identical loggers"); + } + + testLogger(provider, loggerDescMap, "foo", loggerBundle, sysLogger4, sysLogger1); + + } + + public static class Foo { + + } + + static void verbose(String msg) { + if (VERBOSE) { + System.out.println(msg); + } + } + + // Calls the 8 methods defined on Logger and verify the + // parameters received by the underlying TestProvider.LoggerImpl + // logger. + private static void testLogger(TestLoggerFinder provider, + Map loggerDescMap, + String name, + ResourceBundle loggerBundle, + Logger logger, + TestLoggerFinder.LoggerImpl sink) { + + System.out.println("Testing " + loggerDescMap.get(logger) + " [" + logger +"]"); + AtomicLong sequencer = TestLoggerFinder.sequencer; + + Foo foo = new Foo(); + String fooMsg = foo.toString(); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, foo): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0, + name, messageLevel, (ResourceBundle)null, + fooMsg, null, (Throwable)null, (Object[])null); + logger.log(messageLevel, foo); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (provider.eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + TestLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + String msg = "blah"; + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, \"blah\"): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, messageLevel, loggerBundle, + msg, null, (Throwable)null, (Object[])null); + logger.log(messageLevel, msg); + TestLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + + Supplier fooSupplier = new Supplier() { + @Override + public String get() { + return this.toString(); + } + }; + + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, fooSupplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0, + name, messageLevel, (ResourceBundle)null, + fooSupplier.get(), null, + (Throwable)null, (Object[])null); + logger.log(messageLevel, fooSupplier); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (provider.eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + TestLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + String format = "two params [{1} {2}]"; + Object arg1 = foo; + Object arg2 = msg; + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, format, params...): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, messageLevel, loggerBundle, + format, null, (Throwable)null, new Object[] {foo, msg}); + logger.log(messageLevel, format, foo, msg); + TestLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + + Throwable thrown = new Exception("OK: log me!"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, \"blah\", thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, messageLevel, loggerBundle, + msg, null, thrown, (Object[]) null); + logger.log(messageLevel, msg, thrown); + TestLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + + + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, thrown, fooSupplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0, + name, messageLevel, (ResourceBundle)null, + fooSupplier.get(), null, + (Throwable)thrown, (Object[])null); + logger.log(messageLevel, fooSupplier, thrown); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (provider.eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + TestLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName()); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, bundle, format, params...): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, messageLevel, bundle, + format, null, (Throwable)null, new Object[] {foo, msg}); + logger.log(messageLevel, bundle, format, foo, msg); + TestLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, bundle, \"blah\", thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, messageLevel, bundle, + msg, null, thrown, (Object[]) null); + logger.log(messageLevel, bundle, msg, thrown); + TestLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + 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 static RuntimePermission CONTROL = LOGGERFINDER_PERMISSION; + final static RuntimePermission ACCESS = new RuntimePermission("accessClassInPackage.jdk.internal.logger"); + + final Permissions permissions; + final ThreadLocal allowControl; + final ThreadLocal allowAccess; + public SimplePolicy(ThreadLocal allowControl, ThreadLocal allowAccess) { + this.allowControl = allowControl; + this.allowAccess = allowAccess; + permissions = new Permissions(); + } + + Permissions getPermissions() { + if (allowControl.get().get() || allowAccess.get().get()) { + PermissionsBuilder builder = new PermissionsBuilder() + .addAll(permissions); + if (allowControl.get().get()) { + builder.add(CONTROL); + } + if (allowAccess.get().get()) { + builder.add(ACCESS); + } + return builder.toPermissions(); + } + return permissions; + } + + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + return getPermissions().implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + } +} --- /dev/null 2015-11-20 17:44:22.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/CustomSystemClassLoader.java 2015-11-20 17:44:22.000000000 +0100 @@ -0,0 +1,101 @@ +/* + * 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.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.security.AllPermission; +import java.security.Permissions; +import java.security.ProtectionDomain; + + +/** + * A custom ClassLoader to load the concrete LoggerFinder class + * with all permissions. + * + * @author danielfuchs + */ +public class CustomSystemClassLoader extends ClassLoader { + + + Class finderClass = null; + + public CustomSystemClassLoader() { + super(); + } + public CustomSystemClassLoader(ClassLoader parent) { + super(parent); + } + + private Class defineFinderClass(String name) + throws ClassNotFoundException { + final Object obj = getClassLoadingLock(name); + synchronized(obj) { + if (finderClass != null) return finderClass; + + URL url = this.getClass().getProtectionDomain().getCodeSource().getLocation(); + File file = new File(url.getPath(), name+".class"); + if (file.canRead()) { + try { + byte[] b = Files.readAllBytes(file.toPath()); + Permissions perms = new Permissions(); + perms.add(new AllPermission()); + finderClass = defineClass( + name, b, 0, b.length, new ProtectionDomain( + this.getClass().getProtectionDomain().getCodeSource(), + perms)); + System.out.println("Loaded " + name); + return finderClass; + } catch (Throwable ex) { + ex.printStackTrace(); + throw new ClassNotFoundException(name, ex); + } + } else { + throw new ClassNotFoundException(name, + new IOException(file.toPath() + ": can't read")); + } + } + } + + @Override + public synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (name.equals("BaseLoggerFinder")) { + Class c = defineFinderClass(name); + if (resolve) { + resolveClass(c); + } + return c; + } + return super.loadClass(name, resolve); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if (name.equals("BaseLoggerFinder")) { + return defineFinderClass(name); + } + return super.findClass(name); + } + +} --- /dev/null 2015-11-20 17:44:23.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/META-INF/services/java.lang.System$LoggerFinder 2015-11-20 17:44:23.000000000 +0100 @@ -0,0 +1 @@ +BaseLoggerFinder --- /dev/null 2015-11-20 17:44:23.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/TestLoggerFinder.java 2015-11-20 17:44:23.000000000 +0100 @@ -0,0 +1,181 @@ +/* + * 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.util.Arrays; +import java.util.Objects; +import java.util.Queue; +import java.util.ResourceBundle; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import java.lang.System.Logger; + +/** + * What our test provider needs to implement. + * @author danielfuchs + */ +public interface TestLoggerFinder { + public final static AtomicLong sequencer = new AtomicLong(); + public final ConcurrentHashMap system = new ConcurrentHashMap<>(); + public final ConcurrentHashMap user = new ConcurrentHashMap<>(); + public final Queue eventQueue = new ArrayBlockingQueue<>(128); + + public static final class LogEvent { + + public LogEvent() { + this(sequencer.getAndIncrement()); + } + + LogEvent(long sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + long sequenceNumber; + boolean isLoggable; + String loggerName; + Logger.Level level; + ResourceBundle bundle; + Throwable thrown; + Object[] args; + Supplier supplier; + String msg; + + Object[] toArray() { + return new Object[] { + sequenceNumber, + isLoggable, + loggerName, + level, + bundle, + thrown, + args, + supplier, + msg, + }; + } + + @Override + public String toString() { + return Arrays.deepToString(toArray()); + } + + + + @Override + public boolean equals(Object obj) { + return obj instanceof LogEvent + && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray()); + } + + @Override + public int hashCode() { + return Objects.hash(toArray()); + } + + + public static LogEvent of(boolean isLoggable, String name, + Logger.Level level, ResourceBundle bundle, + String key, Throwable thrown) { + LogEvent evt = new LogEvent(); + evt.isLoggable = isLoggable; + evt.loggerName = name; + evt.level = level; + evt.args = null; + evt.bundle = bundle; + evt.thrown = thrown; + evt.supplier = null; + evt.msg = key; + return evt; + } + + public static LogEvent of(boolean isLoggable, String name, + Logger.Level level, ResourceBundle bundle, + String key, Object... params) { + LogEvent evt = new LogEvent(); + evt.isLoggable = isLoggable; + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = null; + evt.supplier = null; + evt.msg = key; + return evt; + } + + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + Logger.Level level, ResourceBundle bundle, + String key, Supplier supplier, + Throwable thrown, Object... params) { + LogEvent evt = new LogEvent(sequenceNumber); + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = thrown; + evt.supplier = supplier; + evt.msg = key; + evt.isLoggable = isLoggable; + return evt; + } + + } + + public class LoggerImpl implements Logger { + final String name; + Logger.Level level = Logger.Level.INFO; + + public LoggerImpl(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isLoggable(Logger.Level level) { + return this.level != Logger.Level.OFF && this.level.getSeverity() <= level.getSeverity(); + } + + @Override + public void log(Logger.Level level, ResourceBundle bundle, String key, Throwable thrown) { + log(LogEvent.of(isLoggable(level), this.name, level, bundle, key, thrown)); + } + + @Override + public void log(Logger.Level level, ResourceBundle bundle, String format, Object... params) { + log(LogEvent.of(isLoggable(level), name, level, bundle, format, params)); + } + + void log(LogEvent event) { + eventQueue.add(event); + } + } + + public Logger getLogger(String name, Class caller); + public Logger getLocalizedLogger(String name, ResourceBundle bundle, Class caller); +} --- /dev/null 2015-11-20 17:44:24.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/DefaultLoggerFinderTest/AccessSystemLogger.java 2015-11-20 17:44:24.000000000 +0100 @@ -0,0 +1,82 @@ +/* + * 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.IOException; +import java.lang.System.Logger; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ResourceBundle; + +/** + * + * @author danielfuchs + */ +public final class AccessSystemLogger { + + public AccessSystemLogger() { + this(check()); + } + + private AccessSystemLogger(Void unused) { + } + + private static Void check() { + if (AccessSystemLogger.class.getClassLoader() != null) { + throw new RuntimeException("AccessSystemLogger should be loaded by the null classloader"); + } + return null; + } + + public Logger getLogger(String name) { + Logger logger = System.getLogger(name); + System.out.println("System.getLogger(\"" + name + "\"): " + logger); + return logger; + } + + public Logger getLogger(String name, ResourceBundle bundle) { + Logger logger = System.getLogger(name, bundle); + System.out.println("System.getLogger(\"" + name + "\", bundle): " + logger); + return logger; + } + + public java.util.logging.Logger demandSystemLogger(String name) { + return java.util.logging.Logger.getLogger(name); + } + + // copy AccessSystemLogger.class to ./boot + public static void main(String[] args) throws IOException { + Path testDir = Paths.get(System.getProperty("user.dir", ".")); + Path bootDir = Paths.get(testDir.toString(), "boot"); + Path classes = Paths.get(System.getProperty("test.classes", "build/classes")); + Path thisClass = Paths.get(classes.toString(), + AccessSystemLogger.class.getSimpleName()+".class"); + if (Files.notExists(bootDir)) { + Files.createDirectory(bootDir); + } + Path dest = Paths.get(bootDir.toString(), + AccessSystemLogger.class.getSimpleName()+".class"); + Files.copy(thisClass, dest, StandardCopyOption.REPLACE_EXISTING); + } + +} --- /dev/null 2015-11-20 17:44:25.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/DefaultLoggerFinderTest/DefaultLoggerFinderTest.java 2015-11-20 17:44:24.000000000 +0100 @@ -0,0 +1,887 @@ +/* + * 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.security.AccessControlException; +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.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.ResourceBundle; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import java.util.logging.Handler; +import java.util.logging.LogRecord; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.util.stream.Stream; + +/** + * @test + * @bug 8140364 + * @summary Tests the default implementation of System.Logger, when + * JUL is the default backend. + * @build AccessSystemLogger DefaultLoggerFinderTest + * @run driver AccessSystemLogger + * @run main/othervm -Xbootclasspath/a:boot DefaultLoggerFinderTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot DefaultLoggerFinderTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot DefaultLoggerFinderTest WITHPERMISSIONS + * @author danielfuchs + */ +public class DefaultLoggerFinderTest { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + final static AtomicLong sequencer = new AtomicLong(); + final static boolean VERBOSE = false; + static final ThreadLocal allowControl = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAll = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + + static final AccessSystemLogger accessSystemLogger = new AccessSystemLogger(); + + public static final Queue eventQueue = new ArrayBlockingQueue<>(128); + + public static final class LogEvent { + + public LogEvent() { + this(sequencer.getAndIncrement()); + } + + LogEvent(long sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + long sequenceNumber; + boolean isLoggable; + String loggerName; + java.util.logging.Level level; + ResourceBundle bundle; + Throwable thrown; + Object[] args; + String msg; + String className; + String methodName; + + Object[] toArray() { + return new Object[] { + sequenceNumber, + isLoggable, + loggerName, + level, + bundle, + thrown, + args, + msg, + className, + methodName, + }; + } + + @Override + public String toString() { + return Arrays.deepToString(toArray()); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof LogEvent + && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray()); + } + + @Override + public int hashCode() { + return Objects.hash(toArray()); + } + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + java.util.logging.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + return LogEvent.of(sequenceNumber, isLoggable, name, + DefaultLoggerFinderTest.class.getName(), + "testLogger", level, bundle, key, + thrown, params); + } + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + String className, String methodName, + java.util.logging.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + LogEvent evt = new LogEvent(sequenceNumber); + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = thrown; + evt.msg = key; + evt.isLoggable = isLoggable; + evt.className = className; + evt.methodName = methodName; + return evt; + } + + } + + static java.util.logging.Level mapToJul(Level level) { + switch (level) { + case ALL: return java.util.logging.Level.ALL; + case TRACE: return java.util.logging.Level.FINER; + case DEBUG: return java.util.logging.Level.FINE; + case INFO: return java.util.logging.Level.INFO; + case WARNING: return java.util.logging.Level.WARNING; + case ERROR: return java.util.logging.Level.SEVERE; + case OFF: return java.util.logging.Level.OFF; + } + throw new InternalError("No such level: " + level); + } + + static final java.util.logging.Level[] julLevels = { + java.util.logging.Level.ALL, + new java.util.logging.Level("FINER_THAN_FINEST", java.util.logging.Level.FINEST.intValue() - 10) {}, + java.util.logging.Level.FINEST, + new java.util.logging.Level("FINER_THAN_FINER", java.util.logging.Level.FINER.intValue() - 10) {}, + java.util.logging.Level.FINER, + new java.util.logging.Level("FINER_THAN_FINE", java.util.logging.Level.FINE.intValue() - 10) {}, + java.util.logging.Level.FINE, + new java.util.logging.Level("FINER_THAN_CONFIG", java.util.logging.Level.FINE.intValue() + 10) {}, + java.util.logging.Level.CONFIG, + new java.util.logging.Level("FINER_THAN_INFO", java.util.logging.Level.INFO.intValue() - 10) {}, + java.util.logging.Level.INFO, + new java.util.logging.Level("FINER_THAN_WARNING", java.util.logging.Level.INFO.intValue() + 10) {}, + java.util.logging.Level.WARNING, + new java.util.logging.Level("FINER_THAN_SEVERE", java.util.logging.Level.SEVERE.intValue() - 10) {}, + java.util.logging.Level.SEVERE, + new java.util.logging.Level("FATAL", java.util.logging.Level.SEVERE.intValue() + 10) {}, + java.util.logging.Level.OFF, + }; + + static final Level[] mappedLevels = { + Level.ALL, // ALL + Level.DEBUG, // FINER_THAN_FINEST + Level.DEBUG, // FINEST + Level.DEBUG, // FINER_THAN_FINER + Level.TRACE, // FINER + Level.TRACE, // FINER_THAN_FINE + Level.DEBUG, // FINE + Level.DEBUG, // FINER_THAN_CONFIG + Level.DEBUG, // CONFIG + Level.DEBUG, // FINER_THAN_INFO + Level.INFO, // INFO + Level.INFO, // FINER_THAN_WARNING + Level.WARNING, // WARNING + Level.WARNING, // FINER_THAN_SEVERE + Level.ERROR, // SEVERE + Level.ERROR, // FATAL + Level.OFF, // OFF + }; + + final static Map julToSpiMap; + static { + Map map = new HashMap<>(); + if (mappedLevels.length != julLevels.length) { + throw new ExceptionInInitializerError("Array lengths differ" + + "\n\tjulLevels=" + Arrays.deepToString(julLevels) + + "\n\tmappedLevels=" + Arrays.deepToString(mappedLevels)); + } + for (int i=0; i map = new ConcurrentHashMap<>(); + + @Override + protected Object handleGetObject(String key) { + if (key.contains(" (translated)")) { + throw new RuntimeException("Unexpected key: " + key); + } + return map.computeIfAbsent(key, k -> k + " (translated)"); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(map.keySet()); + } + + } + + public static class MyHandler extends Handler { + + @Override + public java.util.logging.Level getLevel() { + return java.util.logging.Level.ALL; + } + + @Override + public void publish(LogRecord record) { + eventQueue.add(LogEvent.of(sequencer.getAndIncrement(), + true, record.getLoggerName(), + record.getSourceClassName(), + record.getSourceMethodName(), + record.getLevel(), + record.getResourceBundle(), record.getMessage(), + record.getThrown(), record.getParameters())); + } + @Override + public void flush() { + } + @Override + public void close() throws SecurityException { + } + + } + + public static class MyLoggerBundle extends MyBundle { + + } + + + static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS}; + + static void setSecurityManager() { + if (System.getSecurityManager() == null) { + Policy.setPolicy(new SimplePolicy(allowAll, allowControl)); + System.setSecurityManager(new SecurityManager()); + } + } + + public static void main(String[] args) { + if (args.length == 0) + args = new String[] { + "NOSECURITY", + "NOPERMISSIONS", + "WITHPERMISSIONS" + }; + + final java.util.logging.Logger appSink = java.util.logging.Logger.getLogger("foo"); + final java.util.logging.Logger sysSink = accessSystemLogger.demandSystemLogger("foo"); + appSink.addHandler(new MyHandler()); + sysSink.addHandler(new MyHandler()); + appSink.setUseParentHandlers(VERBOSE); + sysSink.setUseParentHandlers(VERBOSE); + + Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> { + LoggerFinder provider; + switch (testCase) { + case NOSECURITY: + System.out.println("\n*** Without Security Manager\n"); + provider = LoggerFinder.getLoggerFinder(); + test(provider, true, appSink, sysSink); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case NOPERMISSIONS: + System.out.println("\n*** With Security Manager, without permissions\n"); + setSecurityManager(); + try { + provider = LoggerFinder.getLoggerFinder(); + throw new RuntimeException("Expected exception not raised"); + } catch (AccessControlException x) { + if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) { + throw new RuntimeException("Unexpected permission check", x); + } + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = LoggerFinder.getLoggerFinder(); + } finally { + allowControl.get().set(control); + } + } + test(provider, false, appSink, sysSink); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case WITHPERMISSIONS: + System.out.println("\n*** With Security Manager, with control permission\n"); + setSecurityManager(); + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = LoggerFinder.getLoggerFinder(); + test(provider, true, appSink, sysSink); + } finally { + allowControl.get().set(control); + } + break; + default: + throw new RuntimeException("Unknown test case: " + testCase); + } + }); + System.out.println("\nPASSED: Tested " + sequencer.get() + " cases."); + } + + public static void test(LoggerFinder provider, + boolean hasRequiredPermissions, + java.util.logging.Logger appSink, + java.util.logging.Logger sysSink) { + + ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName()); + final Map loggerDescMap = new HashMap<>(); + + + Logger appLogger1 = null; + try { + appLogger1 = provider.getLogger("foo", DefaultLoggerFinderTest.class); + loggerDescMap.put(appLogger1, "provider.getApplicationLogger(\"foo\")"); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for logger: " + acx); + boolean old = allowControl.get().get(); + allowControl.get().set(true); + try { + appLogger1 =provider.getLogger("foo", DefaultLoggerFinderTest.class); + loggerDescMap.put(appLogger1, "provider.getApplicationLogger(\"foo\")"); + } finally { + allowControl.get().set(old); + } + } + + Logger sysLogger1 = null; + try { + sysLogger1 = provider.getLogger("foo", Thread.class); + loggerDescMap.put(sysLogger1, "provider.getSystemLogger(\"foo\")"); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a system logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for system logger: " + acx); + boolean old = allowControl.get().get(); + allowControl.get().set(true); + try { + sysLogger1 = provider.getLogger("foo", Thread.class); + loggerDescMap.put(sysLogger1, "provider.getSystemLogger(\"foo\")"); + } finally { + allowControl.get().set(old); + } + } + if (appLogger1 == sysLogger1) { + throw new RuntimeException("identical loggers"); + } + + Logger appLogger2 = null; + try { + appLogger2 = provider.getLocalizedLogger("foo", loggerBundle, DefaultLoggerFinderTest.class); + loggerDescMap.put(appLogger2, "provider.getLocalizedApplicationLogger(\"foo\", loggerBundle)"); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for logger: " + acx); + boolean old = allowControl.get().get(); + allowControl.get().set(true); + try { + appLogger2 = provider.getLocalizedLogger("foo", loggerBundle, DefaultLoggerFinderTest.class); + loggerDescMap.put(appLogger2, "provider.getLocalizedApplicationLogger(\"foo\", loggerBundle)"); + } finally { + allowControl.get().set(old); + } + } + + Logger sysLogger2 = null; + try { + sysLogger2 = provider.getLocalizedLogger("foo", loggerBundle, Thread.class); + loggerDescMap.put(sysLogger2, "provider.getLocalizedSystemLogger(\"foo\", loggerBundle)"); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a system logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for localized system logger: " + acx); + boolean old = allowControl.get().get(); + allowControl.get().set(true); + try { + sysLogger2 = provider.getLocalizedLogger("foo", loggerBundle, Thread.class); + loggerDescMap.put(sysLogger2, "provider.getLocalizedSystemLogger(\"foo\", loggerBundle)"); + } finally { + allowControl.get().set(old); + } + } + if (appLogger2 == sysLogger2) { + throw new RuntimeException("identical loggers"); + } + if (appLogger2 == appLogger1) { + throw new RuntimeException("identical loggers"); + } + if (sysLogger2 == sysLogger1) { + throw new RuntimeException("identical loggers"); + } + + + testLogger(provider, loggerDescMap, "foo", null, appLogger1, appSink); + testLogger(provider, loggerDescMap, "foo", null, sysLogger1, sysSink); + testLogger(provider, loggerDescMap, "foo", loggerBundle, appLogger2, appSink); + testLogger(provider, loggerDescMap, "foo", loggerBundle, sysLogger2, sysSink); + + + Logger appLogger3 = System.getLogger("foo"); + loggerDescMap.put(appLogger3, "System.getLogger(\"foo\")"); + + testLogger(provider, loggerDescMap, "foo", null, appLogger3, appSink); + + Logger appLogger4 = + System.getLogger("foo", loggerBundle); + loggerDescMap.put(appLogger4, "System.getLogger(\"foo\", loggerBundle)"); + + if (appLogger4 == appLogger1) { + throw new RuntimeException("identical loggers"); + } + + testLogger(provider, loggerDescMap, "foo", loggerBundle, appLogger4, appSink); + + Logger sysLogger3 = accessSystemLogger.getLogger("foo"); + loggerDescMap.put(sysLogger3, "AccessSystemLogger.getLogger(\"foo\")"); + + testLogger(provider, loggerDescMap, "foo", null, sysLogger3, sysSink); + + Logger sysLogger4 = + accessSystemLogger.getLogger("foo", loggerBundle); + loggerDescMap.put(appLogger4, "AccessSystemLogger.getLogger(\"foo\", loggerBundle)"); + + if (sysLogger4 == sysLogger1) { + throw new RuntimeException("identical loggers"); + } + + testLogger(provider, loggerDescMap, "foo", loggerBundle, sysLogger4, sysSink); + + } + + public static class Foo { + + } + + static void verbose(String msg) { + if (VERBOSE) { + System.out.println(msg); + } + } + + static void setLevel(java.util.logging.Logger sink, java.util.logging.Level loggerLevel) { + boolean before = allowAll.get().get(); + try { + allowAll.get().set(true); + sink.setLevel(loggerLevel); + } finally { + allowAll.get().set(before); + } + } + + + // Calls the 8 methods defined on Logger and verify the + // parameters received by the underlying Logger Impl + // logger. + private static void testLogger(LoggerFinder provider, + Map loggerDescMap, + String name, + ResourceBundle loggerBundle, + Logger logger, + java.util.logging.Logger sink) { + + System.out.println("Testing " + loggerDescMap.get(logger) + " [" + logger + "]"); + final java.util.logging.Level OFF = java.util.logging.Level.OFF; + + Foo foo = new Foo(); + String fooMsg = foo.toString(); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (Level messageLevel : Level.values()) { + java.util.logging.Level julLevel = mapToJul(messageLevel); + String desc = "logger.log(messageLevel, foo): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + julLevel.intValue() >= loggerLevel.intValue(), + name, julLevel, (ResourceBundle)null, + fooMsg, (Throwable)null, (Object[])null); + logger.log(messageLevel, foo); + if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + String msg = "blah"; + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (Level messageLevel : Level.values()) { + java.util.logging.Level julLevel = mapToJul(messageLevel); + String desc = "logger.log(messageLevel, \"blah\"): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + julLevel.intValue() >= loggerLevel.intValue(), + name, julLevel, loggerBundle, + msg, (Throwable)null, (Object[])null); + logger.log(messageLevel, msg); + if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + Supplier fooSupplier = new Supplier() { + @Override + public String get() { + return this.toString(); + } + }; + + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (Level messageLevel : Level.values()) { + java.util.logging.Level julLevel = mapToJul(messageLevel); + String desc = "logger.log(messageLevel, fooSupplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + julLevel.intValue() >= loggerLevel.intValue(), + name, julLevel, (ResourceBundle)null, + fooSupplier.get(), + (Throwable)null, (Object[])null); + logger.log(messageLevel, fooSupplier); + if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + String format = "two params [{1} {2}]"; + Object arg1 = foo; + Object arg2 = msg; + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (Level messageLevel : Level.values()) { + java.util.logging.Level julLevel = mapToJul(messageLevel); + String desc = "logger.log(messageLevel, format, params...): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + julLevel.intValue() >= loggerLevel.intValue(), + name, julLevel, loggerBundle, + format, (Throwable)null, new Object[] {arg1, arg2}); + logger.log(messageLevel, format, arg1, arg2); + if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + Throwable thrown = new Exception("OK: log me!"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (Level messageLevel : Level.values()) { + java.util.logging.Level julLevel = mapToJul(messageLevel); + String desc = "logger.log(messageLevel, \"blah\", thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + julLevel.intValue() >= loggerLevel.intValue(), + name, julLevel, loggerBundle, + msg, thrown, (Object[]) null); + logger.log(messageLevel, msg, thrown); + if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (Level messageLevel : Level.values()) { + java.util.logging.Level julLevel = mapToJul(messageLevel); + String desc = "logger.log(messageLevel, thrown, fooSupplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + julLevel.intValue() >= loggerLevel.intValue(), + name, julLevel, (ResourceBundle)null, + fooSupplier.get(), + (Throwable)thrown, (Object[])null); + logger.log(messageLevel, fooSupplier, thrown); + if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName()); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (Level messageLevel : Level.values()) { + java.util.logging.Level julLevel = mapToJul(messageLevel); + String desc = "logger.log(messageLevel, bundle, format, params...): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + julLevel.intValue() >= loggerLevel.intValue(), + name, julLevel, bundle, + format, (Throwable)null, new Object[] {foo, msg}); + logger.log(messageLevel, bundle, format, foo, msg); + if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (Level messageLevel : Level.values()) { + java.util.logging.Level julLevel = mapToJul(messageLevel); + String desc = "logger.log(messageLevel, bundle, \"blah\", thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + julLevel.intValue() >= loggerLevel.intValue(), + name, julLevel, bundle, + msg, thrown, (Object[]) null); + logger.log(messageLevel, bundle, msg, thrown); + if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + } + + 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 withControlPermissions; + final Permissions allPermissions; + final ThreadLocal allowAll; + final ThreadLocal allowControl; + public SimplePolicy(ThreadLocal allowAll, + ThreadLocal allowControl) { + this.allowAll = allowAll; + this.allowControl = allowControl; + permissions = new Permissions(); + + withControlPermissions = new Permissions(); + withControlPermissions.add(LOGGERFINDER_PERMISSION); + + // 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); + if (allowControl.get().get()) return withControlPermissions.implies(permission); + return permissions.implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll( + allowAll.get().get() ? allPermissions : + allowControl.get().get() + ? withControlPermissions : permissions).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll( + allowAll.get().get() ? allPermissions : + allowControl.get().get() + ? withControlPermissions : permissions).toPermissions(); + } + } +} --- /dev/null 2015-11-20 17:44:25.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/AccessSystemLogger.java 2015-11-20 17:44:25.000000000 +0100 @@ -0,0 +1,82 @@ +/* + * 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.IOException; +import java.lang.System.Logger; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ResourceBundle; + +/** + * + * @author danielfuchs + */ +public final class AccessSystemLogger { + + public AccessSystemLogger() { + this(check()); + } + + private AccessSystemLogger(Void unused) { + } + + private static Void check() { + if (AccessSystemLogger.class.getClassLoader() != null) { + throw new RuntimeException("AccessSystemLogger should be loaded by the null classloader"); + } + return null; + } + + public Logger getLogger(String name) { + Logger logger = System.getLogger(name); + System.out.println("System.getLogger(\"" + name + "\"): " + logger); + return logger; + } + + public Logger getLogger(String name, ResourceBundle bundle) { + Logger logger = System.getLogger(name, bundle); + System.out.println("System.getLogger(\"" + name + "\", bundle): " + logger); + return logger; + } + + static final Class[] toCopy = { AccessSystemLogger.class, CustomSystemClassLoader.class }; + + // copy AccessSystemLogger.class to ./boot + public static void main(String[] args) throws IOException { + Path testDir = Paths.get(System.getProperty("user.dir", ".")); + Path bootDir = Paths.get(testDir.toString(), "boot"); + Path classes = Paths.get(System.getProperty("test.classes", "build/classes")); + if (Files.notExists(bootDir)) { + Files.createDirectory(bootDir); + } + for (Class c : toCopy) { + Path thisClass = Paths.get(classes.toString(), + c.getSimpleName()+".class"); + Path dest = Paths.get(bootDir.toString(), + c.getSimpleName()+".class"); + Files.copy(thisClass, dest, StandardCopyOption.REPLACE_EXISTING); + } + } + +} --- /dev/null 2015-11-20 17:44:26.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/BaseDefaultLoggerFinderTest.java 2015-11-20 17:44:26.000000000 +0100 @@ -0,0 +1,768 @@ +/* + * 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.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.io.UncheckedIOException; +import java.security.AccessControlException; +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.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.stream.Stream; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Locale; +import java.util.concurrent.atomic.AtomicReference; +import jdk.internal.logger.DefaultLoggerFinder; +import jdk.internal.logger.SimpleConsoleLogger; +import sun.util.logging.PlatformLogger; + +/** + * @test + * @bug 8140364 + * @summary JDK implementation specific unit test for the base DefaultLoggerFinder. + * Tests the behavior of DefaultLoggerFinder and SimpleConsoleLogger + * implementation. + * @modules java.base/sun.util.logging + * java.base/jdk.internal.logger + * @build AccessSystemLogger BaseDefaultLoggerFinderTest CustomSystemClassLoader + * @run driver AccessSystemLogger + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader BaseDefaultLoggerFinderTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader BaseDefaultLoggerFinderTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader BaseDefaultLoggerFinderTest WITHPERMISSIONS + * @author danielfuchs + */ +public class BaseDefaultLoggerFinderTest { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + final static boolean VERBOSE = false; + static final ThreadLocal allowControl = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAccess = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + + final static AccessSystemLogger accessSystemLogger = new AccessSystemLogger(); + static final Class[] providerClass; + static { + try { + providerClass = new Class[] { + ClassLoader.getSystemClassLoader().loadClass("BaseDefaultLoggerFinderTest$BaseLoggerFinder"), + }; + } catch (ClassNotFoundException ex) { + throw new ExceptionInInitializerError(ex); + } + } + + /** + * What our test provider needs to implement. + */ + public static interface TestLoggerFinder { + public final static AtomicBoolean fails = new AtomicBoolean(); + public final static AtomicReference conf = new AtomicReference<>(""); + public final static AtomicLong sequencer = new AtomicLong(); + + + public Logger getLogger(String name, Class caller); + public Logger getLocalizedLogger(String name, ResourceBundle bundle, Class caller); + void setLevel(Logger logger, Level level, Class caller); + void setLevel(Logger logger, PlatformLogger.Level level, Class caller); + PlatformLogger.Bridge asPlatformLoggerBridge(Logger logger); + } + + public static class BaseLoggerFinder extends DefaultLoggerFinder implements TestLoggerFinder { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + public BaseLoggerFinder() { + if (fails.get()) { + throw new RuntimeException("Simulate exception while loading provider"); + } + } + + @Override + public void setLevel(Logger logger, Level level, Class caller) { + PrivilegedAction pa = () -> { + setLevel(logger, PlatformLogger.toPlatformLevel(level), caller); + return null; + }; + AccessController.doPrivileged(pa); + } + + @Override + public void setLevel(Logger logger, PlatformLogger.Level level, Class caller) { + PrivilegedAction pa = () -> demandLoggerFor(logger.getName(), caller); + Logger impl = AccessController.doPrivileged(pa); + SimpleConsoleLogger.class.cast(impl) + .getLoggerConfiguration() + .setPlatformLevel(level); + } + + @Override + public PlatformLogger.Bridge asPlatformLoggerBridge(Logger logger) { + PrivilegedAction pa = () -> + PlatformLogger.Bridge.convert(logger); + return AccessController.doPrivileged(pa); + } + + } + + public static class MyBundle extends ResourceBundle { + + final ConcurrentHashMap map = new ConcurrentHashMap<>(); + + @Override + protected Object handleGetObject(String key) { + if (key.contains(" (translated)")) { + throw new RuntimeException("Unexpected key: " + key); + } + return map.computeIfAbsent(key, k -> k.toUpperCase(Locale.ROOT) + " (translated)"); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(map.keySet()); + } + + } + public static class MyLoggerBundle extends MyBundle { + + } + + static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS}; + + static void setSecurityManager() { + if (System.getSecurityManager() == null) { + Policy.setPolicy(new SimplePolicy(allowControl, allowAccess)); + System.setSecurityManager(new SecurityManager()); + } + } + + static TestLoggerFinder getLoggerFinder(Class expectedClass) { + LoggerFinder provider = null; + try { + TestLoggerFinder.sequencer.incrementAndGet(); + provider = LoggerFinder.getLoggerFinder(); + } catch(AccessControlException a) { + throw a; + } + ErrorStream.errorStream.store(); + System.out.println("*** Actual LoggerFinder class is: " + provider.getClass().getName()); + expectedClass.cast(provider); + return TestLoggerFinder.class.cast(provider); + } + + + static class ErrorStream extends PrintStream { + + static AtomicBoolean forward = new AtomicBoolean(); + ByteArrayOutputStream out; + String saved = ""; + public ErrorStream(ByteArrayOutputStream out) { + super(out); + this.out = out; + } + + @Override + public void write(int b) { + super.write(b); + if (forward.get()) err.write(b); + } + + @Override + public void write(byte[] b) throws IOException { + super.write(b); + if (forward.get()) err.write(b); + } + + @Override + public void write(byte[] buf, int off, int len) { + super.write(buf, off, len); + if (forward.get()) err.write(buf, off, len); + } + + public String peek() { + flush(); + return out.toString(); + } + + public String drain() { + flush(); + String res = out.toString(); + out.reset(); + return res; + } + + public void store() { + flush(); + saved = out.toString(); + out.reset(); + } + + public void restore() { + out.reset(); + try { + out.write(saved.getBytes()); + } catch(IOException io) { + throw new UncheckedIOException(io); + } + } + + static final PrintStream err = System.err; + static final ErrorStream errorStream = new ErrorStream(new ByteArrayOutputStream()); + } + + private static StringBuilder appendProperty(StringBuilder b, String name) { + String value = System.getProperty(name); + if (value == null) return b; + return b.append(name).append("=").append(value).append('\n'); + } + + public static void main(String[] args) { + if (args.length == 0) { + args = new String[] { + //"NOSECURITY", + "NOPERMISSIONS", + "WITHPERMISSIONS" + }; + } + Locale.setDefault(Locale.ENGLISH); + System.setErr(ErrorStream.errorStream); + //System.setProperty("jdk.logger.finder.error", "ERROR"); + //System.setProperty("jdk.logger.finder.singleton", "true"); + //System.setProperty("test.fails", "true"); + TestLoggerFinder.fails.set(Boolean.getBoolean("test.fails")); + StringBuilder c = new StringBuilder(); + appendProperty(c, "jdk.logger.packages"); + appendProperty(c, "jdk.logger.finder.error"); + appendProperty(c, "jdk.logger.finder.singleton"); + appendProperty(c, "test.fails"); + TestLoggerFinder.conf.set(c.toString()); + try { + test(args); + } finally { + try { + System.setErr(ErrorStream.err); + } catch (Error | RuntimeException x) { + x.printStackTrace(ErrorStream.err); + } + } + } + + + public static void test(String[] args) { + + final Class expectedClass = jdk.internal.logger.DefaultLoggerFinder.class; + + System.out.println("Declared provider class: " + providerClass[0] + + "[" + providerClass[0].getClassLoader() + "]"); + + Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> { + TestLoggerFinder provider; + ErrorStream.errorStream.restore(); + switch (testCase) { + case NOSECURITY: + System.out.println("\n*** Without Security Manager\n"); + System.out.println(TestLoggerFinder.conf.get()); + provider = getLoggerFinder(expectedClass); + if (!provider.getClass().getName().equals("BaseDefaultLoggerFinderTest$BaseLoggerFinder")) { + throw new RuntimeException("Unexpected provider: " + provider.getClass().getName()); + } + test(provider, true); + System.out.println("Tetscase count: " + TestLoggerFinder.sequencer.get()); + break; + case NOPERMISSIONS: + System.out.println("\n*** With Security Manager, without permissions\n"); + System.out.println(TestLoggerFinder.conf.get()); + setSecurityManager(); + try { + provider = getLoggerFinder(expectedClass); + throw new RuntimeException("Expected exception not raised"); + } catch (AccessControlException x) { + if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) { + throw new RuntimeException("Unexpected permission check", x); + } + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = getLoggerFinder(expectedClass); + if (!provider.getClass().getName().equals("BaseDefaultLoggerFinderTest$BaseLoggerFinder")) { + throw new RuntimeException("Unexpected provider: " + provider.getClass().getName()); + } + } finally { + allowControl.get().set(control); + } + } + test(provider, false); + System.out.println("Tetscase count: " + TestLoggerFinder.sequencer.get()); + break; + case WITHPERMISSIONS: + System.out.println("\n*** With Security Manager, with control permission\n"); + System.out.println(TestLoggerFinder.conf.get()); + setSecurityManager(); + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = getLoggerFinder(expectedClass); + if (!provider.getClass().getName().equals("BaseDefaultLoggerFinderTest$BaseLoggerFinder")) { + throw new RuntimeException("Unexpected provider: " + provider.getClass().getName()); + } + test(provider, true); + } finally { + allowControl.get().set(control); + } + break; + default: + throw new RuntimeException("Unknown test case: " + testCase); + } + }); + System.out.println("\nPASSED: Tested " + TestLoggerFinder.sequencer.get() + " cases."); + } + + public static void test(TestLoggerFinder provider, boolean hasRequiredPermissions) { + + ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName()); + final Map loggerDescMap = new HashMap<>(); + + System.Logger sysLogger = accessSystemLogger.getLogger("foo"); + loggerDescMap.put(sysLogger, "accessSystemLogger.getLogger(\"foo\")"); + System.Logger localizedSysLogger = accessSystemLogger.getLogger("fox", loggerBundle); + loggerDescMap.put(localizedSysLogger, "accessSystemLogger.getLogger(\"fox\", loggerBundle)"); + System.Logger appLogger = System.getLogger("bar"); + loggerDescMap.put(appLogger,"System.getLogger(\"bar\")"); + System.Logger localizedAppLogger = System.getLogger("baz", loggerBundle); + loggerDescMap.put(localizedAppLogger,"System.getLogger(\"baz\", loggerBundle)"); + + testLogger(provider, loggerDescMap, "foo", null, sysLogger, accessSystemLogger.getClass()); + testLogger(provider, loggerDescMap, "foo", loggerBundle, localizedSysLogger, accessSystemLogger.getClass()); + testLogger(provider, loggerDescMap, "foo", null, appLogger, BaseDefaultLoggerFinderTest.class); + testLogger(provider, loggerDescMap, "foo", loggerBundle, localizedAppLogger, BaseDefaultLoggerFinderTest.class); + } + + public static class Foo { + + } + + static void verbose(String msg) { + if (VERBOSE) { + System.out.println(msg); + } + } + + // Calls the 8 methods defined on Logger and verify the + // parameters received by the underlying TestProvider.LoggerImpl + // logger. + private static void testLogger(TestLoggerFinder provider, + Map loggerDescMap, + String name, + ResourceBundle loggerBundle, + Logger logger, + Class caller) { + + System.out.println("Testing " + loggerDescMap.get(logger) + " [" + logger +"]"); + AtomicLong sequencer = TestLoggerFinder.sequencer; + + Foo foo = new Foo(); + String fooMsg = foo.toString(); + for (Level loggerLevel : Level.values()) { + provider.setLevel(logger, loggerLevel, caller); + for (Level messageLevel : Level.values()) { + ErrorStream.errorStream.drain(); + String desc = "logger.log(messageLevel, foo): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, foo); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + if (!logged.contains("BaseDefaultLoggerFinderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + fooMsg)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] BaseDefaultLoggerFinderTest testLogger\n" + + messageLevel.getName() + " " + fooMsg + + "\n>>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + String msg = "blah"; + for (Level loggerLevel : Level.values()) { + provider.setLevel(logger, loggerLevel, caller); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, \"blah\"): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, msg); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + String msgText = loggerBundle == null ? msg : loggerBundle.getString(msg); + if (!logged.contains("BaseDefaultLoggerFinderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + msgText)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] BaseDefaultLoggerFinderTest testLogger\n" + + messageLevel.getName() + " " + msgText + + "\n>>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + Supplier fooSupplier = new Supplier() { + @Override + public String get() { + return this.toString(); + } + }; + + for (Level loggerLevel : Level.values()) { + provider.setLevel(logger, loggerLevel, caller); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, fooSupplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, fooSupplier); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + if (!logged.contains("BaseDefaultLoggerFinderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + fooSupplier.get())) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] BaseDefaultLoggerFinderTest testLogger\n" + + messageLevel.getName() + " " + fooSupplier.get() + + "\n>>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + + String format = "two params [{1} {2}]"; + Object arg1 = foo; + Object arg2 = msg; + for (Level loggerLevel : Level.values()) { + provider.setLevel(logger, loggerLevel, caller); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, format, params...): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, format, foo, msg); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + String msgFormat = loggerBundle == null ? format : loggerBundle.getString(format); + String text = java.text.MessageFormat.format(msgFormat, foo, msg); + if (!logged.contains("BaseDefaultLoggerFinderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + text)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] BaseDefaultLoggerFinderTest testLogger\n" + + messageLevel.getName() + " " + text + + "\n>>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + Throwable thrown = new Exception("OK: log me!"); + for (Level loggerLevel : Level.values()) { + provider.setLevel(logger, loggerLevel, caller); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, \"blah\", thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, msg, thrown); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + thrown.printStackTrace(new PrintStream(baos)); + String text = baos.toString(); + String msgText = loggerBundle == null ? msg : loggerBundle.getString(msg); + if (!logged.contains("BaseDefaultLoggerFinderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + msgText) + || !logged.contains(text)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] BaseDefaultLoggerFinderTest testLogger\n" + + messageLevel.getName() + " " + msgText +"\n" + + text + + ">>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + + for (Level loggerLevel : Level.values()) { + provider.setLevel(logger, loggerLevel, caller); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, thrown, fooSupplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, fooSupplier, thrown); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + thrown.printStackTrace(new PrintStream(baos)); + String text = baos.toString(); + if (!logged.contains("BaseDefaultLoggerFinderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + fooSupplier.get()) + || !logged.contains(text)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] BaseDefaultLoggerFinderTest testLogger\n" + + messageLevel.getName() + " " + fooSupplier.get() +"\n" + + text + + ">>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName()); + for (Level loggerLevel : Level.values()) { + provider.setLevel(logger, loggerLevel, caller); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, bundle, format, params...): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, bundle, format, foo, msg); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + String text = java.text.MessageFormat.format(bundle.getString(format), foo, msg); + if (!logged.contains("BaseDefaultLoggerFinderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + text)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] BaseDefaultLoggerFinderTest testLogger\n" + + messageLevel.getName() + " " + text + + "\n>>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + for (Level loggerLevel : Level.values()) { + provider.setLevel(logger, loggerLevel, caller); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, bundle, \"blah\", thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, bundle, msg, thrown); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + String textMsg = bundle.getString(msg); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + thrown.printStackTrace(new PrintStream(baos)); + String text = baos.toString(); + if (!logged.contains("BaseDefaultLoggerFinderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + textMsg) + || !logged.contains(text)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] BaseDefaultLoggerFinderTest testLogger\n" + + messageLevel.getName() + " " + textMsg +"\n" + + text + + ">>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + } + + 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 static RuntimePermission CONTROL = LOGGERFINDER_PERMISSION; + final static RuntimePermission ACCESS = new RuntimePermission("accessClassInPackage.jdk.internal.logger"); + + final Permissions permissions; + final ThreadLocal allowControl; + final ThreadLocal allowAccess; + public SimplePolicy(ThreadLocal allowControl, ThreadLocal allowAccess) { + this.allowControl = allowControl; + this.allowAccess = allowAccess; + permissions = new Permissions(); + permissions.add(new RuntimePermission("setIO")); + } + + Permissions getPermissions() { + if (allowControl.get().get() || allowAccess.get().get()) { + PermissionsBuilder builder = new PermissionsBuilder() + .addAll(permissions); + if (allowControl.get().get()) { + builder.add(CONTROL); + } + if (allowAccess.get().get()) { + builder.add(ACCESS); + } + return builder.toPermissions(); + } + return permissions; + } + + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + return getPermissions().implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + } +} --- /dev/null 2015-11-20 17:44:26.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/CustomSystemClassLoader.java 2015-11-20 17:44:26.000000000 +0100 @@ -0,0 +1,117 @@ +/* + * 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.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.security.AllPermission; +import java.security.Permissions; +import java.security.ProtectionDomain; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * A custom ClassLoader to load the concrete LoggerFinder class + * with all permissions. The CustomSystemClassLoader class must be + * in the BCL, otherwise when system classes - such as + * ZoneDateTime try to load their resource bundle a MissingResourceBundle + * caused by a SecurityException may be thrown, as the CustomSystemClassLoader + * code base will be found in the stack called by doPrivileged. + * + * @author danielfuchs + */ +public class CustomSystemClassLoader extends ClassLoader { + + + final List finderClassNames = + Arrays.asList("BaseDefaultLoggerFinderTest$BaseLoggerFinder"); + final Map> finderClasses = new HashMap<>(); + Class testLoggerFinderClass; + + public CustomSystemClassLoader() { + super(); + } + public CustomSystemClassLoader(ClassLoader parent) { + super(parent); + } + + private Class defineFinderClass(String name) + throws ClassNotFoundException { + final Object obj = getClassLoadingLock(name); + synchronized(obj) { + if (finderClasses.get(name) != null) return finderClasses.get(name); + if (testLoggerFinderClass == null) { + // Hack: we load testLoggerFinderClass to get its code source. + // we can't use this.getClass() since we are in the boot. + testLoggerFinderClass = super.loadClass("BaseDefaultLoggerFinderTest$TestLoggerFinder"); + } + URL url = testLoggerFinderClass.getProtectionDomain().getCodeSource().getLocation(); + File file = new File(url.getPath(), name+".class"); + if (file.canRead()) { + try { + byte[] b = Files.readAllBytes(file.toPath()); + Permissions perms = new Permissions(); + perms.add(new AllPermission()); + Class finderClass = defineClass( + name, b, 0, b.length, new ProtectionDomain( + this.getClass().getProtectionDomain().getCodeSource(), + perms)); + System.out.println("Loaded " + name); + finderClasses.put(name, finderClass); + return finderClass; + } catch (Throwable ex) { + ex.printStackTrace(); + throw new ClassNotFoundException(name, ex); + } + } else { + throw new ClassNotFoundException(name, + new IOException(file.toPath() + ": can't read")); + } + } + } + + @Override + public synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (finderClassNames.contains(name)) { + Class c = defineFinderClass(name); + if (resolve) { + resolveClass(c); + } + return c; + } + return super.loadClass(name, resolve); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if (finderClassNames.contains(name)) { + return defineFinderClass(name); + } + return super.findClass(name); + } + +} --- /dev/null 2015-11-20 17:44:27.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/META-INF/services/java.lang.System$LoggerFinder 2015-11-20 17:44:27.000000000 +0100 @@ -0,0 +1 @@ +BaseDefaultLoggerFinderTest$BaseLoggerFinder --- /dev/null 2015-11-20 17:44:28.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/internal/BaseLoggerBridgeTest/BaseLoggerBridgeTest.java 2015-11-20 17:44:27.000000000 +0100 @@ -0,0 +1,1058 @@ +/* + * 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.security.AccessControlException; +import java.security.AccessController; +import java.security.CodeSource; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.Permissions; +import java.security.Policy; +import java.security.PrivilegedAction; +import java.security.ProtectionDomain; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.ResourceBundle; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import sun.util.logging.PlatformLogger; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.util.stream.Stream; + +/** + * @test + * @bug 8140364 + * @summary JDK implementation specific unit test for JDK internal artifacts. + * Tests a naive implementation of System.Logger, and in particular + * the default mapping provided by PlatformLogger.Bridge. + * @modules java.base/sun.util.logging java.base/jdk.internal.logger + * @build CustomSystemClassLoader BaseLoggerBridgeTest + * @run main/othervm -Djava.system.class.loader=CustomSystemClassLoader BaseLoggerBridgeTest NOSECURITY + * @run main/othervm -Djava.system.class.loader=CustomSystemClassLoader BaseLoggerBridgeTest NOPERMISSIONS + * @run main/othervm -Djava.system.class.loader=CustomSystemClassLoader BaseLoggerBridgeTest WITHPERMISSIONS + * @author danielfuchs + */ +public class BaseLoggerBridgeTest { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + final static AtomicLong sequencer = new AtomicLong(); + final static boolean VERBOSE = false; + // whether the implementation of Logger try to do a best + // effort for logp... Our base logger finder stub doesn't + // support logp, and thus the logp() implementation comes from + // LoggerWrapper - which does a best effort. + static final boolean BEST_EFFORT_FOR_LOGP = true; + static final ThreadLocal allowControl = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAccess = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAll = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + + static final Class providerClass; + static { + try { + providerClass = ClassLoader.getSystemClassLoader().loadClass("BaseLoggerBridgeTest$BaseLoggerFinder"); + } catch (ClassNotFoundException ex) { + throw new ExceptionInInitializerError(ex); + } + } + + static final sun.util.logging.PlatformLogger.Level[] julLevels = { + sun.util.logging.PlatformLogger.Level.ALL, + sun.util.logging.PlatformLogger.Level.FINEST, + sun.util.logging.PlatformLogger.Level.FINER, + sun.util.logging.PlatformLogger.Level.FINE, + sun.util.logging.PlatformLogger.Level.CONFIG, + sun.util.logging.PlatformLogger.Level.INFO, + sun.util.logging.PlatformLogger.Level.WARNING, + sun.util.logging.PlatformLogger.Level.SEVERE, + sun.util.logging.PlatformLogger.Level.OFF, + }; + + static final Level[] mappedLevels = { + Level.ALL, // ALL + Level.TRACE, // FINEST + Level.TRACE, // FINER + Level.DEBUG, // FINE + Level.DEBUG, // CONFIG + Level.INFO, // INFO + Level.WARNING, // WARNING + Level.ERROR, // SEVERE + Level.OFF, // OFF + }; + + final static Map julToSpiMap; + static { + Map map = new HashMap<>(); + if (mappedLevels.length != julLevels.length) { + throw new ExceptionInInitializerError("Array lengths differ" + + "\n\tjulLevels=" + Arrays.deepToString(julLevels) + + "\n\tmappedLevels=" + Arrays.deepToString(mappedLevels)); + } + for (int i=0; i map = new ConcurrentHashMap<>(); + + @Override + protected Object handleGetObject(String key) { + if (key.contains(" (translated)")) { + throw new RuntimeException("Unexpected key: " + key); + } + return map.computeIfAbsent(key, k -> k + " (translated)"); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(map.keySet()); + } + + } + public static class MyLoggerBundle extends MyBundle { + + } + + public static interface TestLoggerFinder { + final ConcurrentHashMap system = new ConcurrentHashMap<>(); + final ConcurrentHashMap user = new ConcurrentHashMap<>(); + public Queue eventQueue = new ArrayBlockingQueue<>(128); + + public static final class LogEvent implements Cloneable { + + public LogEvent() { + this(sequencer.getAndIncrement()); + } + + LogEvent(long sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + boolean callSupplier = false; + long sequenceNumber; + boolean isLoggable; + String loggerName; + Level level; + ResourceBundle bundle; + Throwable thrown; + Object[] args; + Supplier supplier; + String msg; + + Object[] toArray(boolean callSupplier) { + return new Object[] { + sequenceNumber, + isLoggable, + loggerName, + level, + bundle, + thrown, + args, + callSupplier && supplier != null ? supplier.get() : supplier, + msg, + }; + } + + boolean callSupplier(Object obj) { + return callSupplier || ((LogEvent)obj).callSupplier; + } + + @Override + public String toString() { + return Arrays.deepToString(toArray(false)); + } + + + + @Override + public boolean equals(Object obj) { + return obj instanceof LogEvent + && Objects.deepEquals(toArray(callSupplier(obj)), ((LogEvent)obj).toArray(callSupplier(obj))); + } + + @Override + public int hashCode() { + return Objects.hash(toArray(true)); + } + + public LogEvent cloneWith(long sequenceNumber) + throws CloneNotSupportedException { + LogEvent cloned = (LogEvent)super.clone(); + cloned.sequenceNumber = sequenceNumber; + return cloned; + } + + public static LogEvent of(boolean isLoggable, String name, + Level level, ResourceBundle bundle, + String key, Throwable thrown) { + LogEvent evt = new LogEvent(); + evt.isLoggable = isLoggable; + evt.loggerName = name; + evt.level = level; + evt.args = null; + evt.bundle = bundle; + evt.thrown = thrown; + evt.supplier = null; + evt.msg = key; + return evt; + } + + public static LogEvent of(boolean isLoggable, String name, + Level level, Throwable thrown, Supplier supplier) { + LogEvent evt = new LogEvent(); + evt.isLoggable = isLoggable; + evt.loggerName = name; + evt.level = level; + evt.args = null; + evt.bundle = null; + evt.thrown = thrown; + evt.supplier = supplier; + evt.msg = null; + return evt; + } + + public static LogEvent of(boolean isLoggable, String name, + Level level, ResourceBundle bundle, + String key, Object... params) { + LogEvent evt = new LogEvent(); + evt.isLoggable = isLoggable; + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = null; + evt.supplier = null; + evt.msg = key; + return evt; + } + + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + Level level, ResourceBundle bundle, + String key, Supplier supplier, + Throwable thrown, Object... params) { + LogEvent evt = new LogEvent(sequenceNumber); + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = thrown; + evt.supplier = supplier; + evt.msg = key; + evt.isLoggable = isLoggable; + return evt; + } + + public static LogEvent ofp(boolean callSupplier, LogEvent evt) { + evt.callSupplier = callSupplier; + return evt; + } + } + + public class LoggerImpl implements Logger { + private final String name; + private Level level = Level.INFO; + + public LoggerImpl(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isLoggable(Level level) { + return this.level != Level.OFF && this.level.getSeverity() <= level.getSeverity(); + } + + @Override + public void log(Level level, ResourceBundle bundle, String key, Throwable thrown) { + log(LogEvent.of(isLoggable(level), this.name, level, bundle, key, thrown)); + } + + @Override + public void log(Level level, ResourceBundle bundle, String format, Object... params) { + log(LogEvent.of(isLoggable(level), name, level, bundle, format, params)); + } + + void log(LogEvent event) { + eventQueue.add(event); + } + + @Override + public void log(Level level, Supplier msgSupplier) { + log(LogEvent.of(isLoggable(level), name, level, null, msgSupplier)); + } + + @Override + public void log(Level level, Supplier msgSupplier, Throwable thrown) { + log(LogEvent.of(isLoggable(level), name, level, thrown, msgSupplier)); + } + + + + } + + public Logger getLogger(String name, Class caller); + public Logger getLocalizedLogger(String name, ResourceBundle bundle, Class caller); + } + + public static class BaseLoggerFinder extends LoggerFinder implements TestLoggerFinder { + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + @Override + public Logger getLogger(String name, Class caller) { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGERFINDER_PERMISSION); + } + PrivilegedAction pa = () -> caller.getClassLoader(); + ClassLoader callerLoader = AccessController.doPrivileged(pa); + if (callerLoader == null) { + return system.computeIfAbsent(name, (n) -> new LoggerImpl(n)); + } else { + return user.computeIfAbsent(name, (n) -> new LoggerImpl(n)); + } + } + } + + static PlatformLogger.Bridge convert(Logger logger) { + boolean old = allowAll.get().get(); + allowAccess.get().set(true); + try { + return PlatformLogger.Bridge.convert(logger); + } finally { + allowAccess.get().set(old); + } + } + + static Logger getLogger(String name, Class caller) { + boolean old = allowAll.get().get(); + allowAccess.get().set(true); + try { + return jdk.internal.logger.LazyLoggers.getLogger(name, caller); + } finally { + allowAccess.get().set(old); + } + } + + static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS}; + + static void setSecurityManager() { + if (System.getSecurityManager() == null) { + // Ugly test hack: preload the resources needed by String.format + // We need to do that before setting the security manager + // because our implementation of CustomSystemClassLoader + // doesn't have the required permission. + System.out.println(String.format("debug: %s", "Setting security manager")); + Policy.setPolicy(new SimplePolicy(allowControl, allowAccess, allowAll)); + System.setSecurityManager(new SecurityManager()); + } + } + + public static void main(String[] args) { + if (args.length == 0) + args = new String[] { + "NOSECURITY", + "NOPERMISSIONS", + "WITHPERMISSIONS" + }; + + + Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> { + TestLoggerFinder provider; + switch (testCase) { + case NOSECURITY: + System.out.println("\n*** Without Security Manager\n"); + provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder()); + test(provider, true); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case NOPERMISSIONS: + System.out.println("\n*** With Security Manager, without permissions\n"); + setSecurityManager(); + try { + provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder()); + throw new RuntimeException("Expected exception not raised"); + } catch (AccessControlException x) { + if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) { + throw new RuntimeException("Unexpected permission check", x); + } + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder()); + } finally { + allowControl.get().set(control); + } + } + test(provider, false); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case WITHPERMISSIONS: + System.out.println("\n*** With Security Manager, with control permission\n"); + setSecurityManager(); + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder()); + test(provider, true); + } finally { + allowControl.get().set(control); + } + break; + default: + throw new RuntimeException("Unknown test case: " + testCase); + } + }); + System.out.println("\nPASSED: Tested " + sequencer.get() + " cases."); + } + + public static void test(TestLoggerFinder provider, boolean hasRequiredPermissions) { + + ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName()); + final Map loggerDescMap = new HashMap<>(); + + + TestLoggerFinder.LoggerImpl appSink = null; + try { + appSink = TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", BaseLoggerBridgeTest.class)); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a system logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for logger: " + acx); + boolean old = allowControl.get().get(); + allowControl.get().set(true); + try { + appSink = TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", BaseLoggerBridgeTest.class)); + } finally { + allowControl.get().set(old); + } + } + + + TestLoggerFinder.LoggerImpl sysSink = null; + try { + sysSink = TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", Thread.class)); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a system logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for system logger: " + acx); + } + if (hasRequiredPermissions && appSink == sysSink) { + throw new RuntimeException("identical loggers"); + } + + if (provider.system.contains(appSink)) { + throw new RuntimeException("app logger in system map"); + } + if (!provider.user.contains(appSink)) { + throw new RuntimeException("app logger not in appplication map"); + } + if (hasRequiredPermissions && provider.user.contains(sysSink)) { + throw new RuntimeException("sys logger in appplication map"); + } + if (hasRequiredPermissions && !provider.system.contains(sysSink)) { + throw new RuntimeException("sys logger not in system map"); + } + + Logger appLogger1 = System.getLogger("foo"); + loggerDescMap.put(appLogger1, "System.getLogger(\"foo\")"); + PlatformLogger.Bridge bridge = convert(appLogger1); + loggerDescMap.putIfAbsent(bridge, "PlatformLogger.Bridge.convert(System.getLogger(\"foo\"))"); + testLogger(provider, loggerDescMap, "foo", null, bridge, appSink); + + Logger sysLogger1 = null; + try { + sysLogger1 = getLogger("foo", Thread.class); + loggerDescMap.put(sysLogger1, + "jdk.internal.logger.LazyLoggers.getLogger(\"foo\", Thread.class)"); + + if (!hasRequiredPermissions) { + // check that the provider would have thrown an exception + provider.getLogger("foo", Thread.class); + throw new RuntimeException("Managed to obtain a system logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for system logger: " + acx); + } + + if (hasRequiredPermissions) { + // if we don't have permissions sysSink will be null. + testLogger(provider, loggerDescMap, "foo", null, + PlatformLogger.Bridge.convert(sysLogger1), sysSink); + } + + Logger appLogger2 = + System.getLogger("foo", loggerBundle); + loggerDescMap.put(appLogger2, "System.getLogger(\"foo\", loggerBundle)"); + + if (appLogger2 == appLogger1) { + throw new RuntimeException("identical loggers"); + } + + if (provider.system.contains(appLogger2)) { + throw new RuntimeException("localized app logger in system map"); + } + if (provider.user.contains(appLogger2)) { + throw new RuntimeException("localized app logger in appplication map"); + } + + testLogger(provider, loggerDescMap, "foo", loggerBundle, + PlatformLogger.Bridge.convert(appLogger2), appSink); + + Logger sysLogger2 = null; + try { + sysLogger2 = provider.getLocalizedLogger("foo", loggerBundle, Thread.class); + loggerDescMap.put(sysLogger2, "provider.getLocalizedLogger(\"foo\", loggerBundle, Thread.class)"); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a system logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for localized system logger: " + acx); + } + if (hasRequiredPermissions && appLogger2 == sysLogger2) { + throw new RuntimeException("identical loggers"); + } + if (hasRequiredPermissions && sysLogger2 == sysLogger1) { + throw new RuntimeException("identical loggers"); + } + if (hasRequiredPermissions && provider.user.contains(sysLogger2)) { + throw new RuntimeException("localized sys logger in appplication map"); + } + if (hasRequiredPermissions && provider.system.contains(sysLogger2)) { + throw new RuntimeException("localized sys logger not in system map"); + } + + if (hasRequiredPermissions) { + // if we don't have permissions sysSink will be null. + testLogger(provider, loggerDescMap, "foo", loggerBundle, + PlatformLogger.Bridge.convert(sysLogger2), sysSink); + } + + } + + public static class Foo { + + } + + static void verbose(String msg) { + if (VERBOSE) { + System.out.println(msg); + } + } + + static void checkLogEvent(TestLoggerFinder provider, String desc, + TestLoggerFinder.LogEvent expected) { + TestLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!Objects.equals(expected, actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + + static void checkLogEvent(TestLoggerFinder provider, String desc, + TestLoggerFinder.LogEvent expected, boolean expectNotNull) { + TestLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (actual == null && !expectNotNull) return; + if (actual != null && !expectNotNull) { + throw new RuntimeException("Unexpected log event found for " + desc + + "\n\tgot: " + actual); + } + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + + static Supplier logpMessage(ResourceBundle bundle, + String className, String methodName, Supplier msg) { + if (BEST_EFFORT_FOR_LOGP && bundle == null + && (className != null || methodName != null)) { + final String cName = className == null ? "" : className; + final String mName = methodName == null ? "" : methodName; + return () -> String.format("[%s %s] %s", cName, mName, msg.get()); + } else { + return msg; + } + } + + static String logpMessage(ResourceBundle bundle, + String className, String methodName, String msg) { + if (BEST_EFFORT_FOR_LOGP && bundle == null + && (className != null || methodName != null)) { + final String cName = className == null ? "" : className; + final String mName = methodName == null ? "" : methodName; + return String.format("[%s %s] %s", cName, mName, msg == null ? "" : msg); + } else { + return msg; + } + } + + // Calls the methods defined on LogProducer and verify the + // parameters received by the underlying TestLoggerFinder.LoggerImpl + // logger. + private static void testLogger(TestLoggerFinder provider, + Map loggerDescMap, + String name, + ResourceBundle loggerBundle, + PlatformLogger.Bridge logger, + TestLoggerFinder.LoggerImpl sink) { + + if (loggerDescMap.get(logger) == null) { + throw new RuntimeException("Test bug: Missing description"); + } + System.out.println("Testing " + loggerDescMap.get(logger) +" [" + logger + "]"); + + Foo foo = new Foo(); + String fooMsg = foo.toString(); + System.out.println("\tlogger.log(messageLevel, fooMsg)"); + System.out.println("\tlogger.(fooMsg)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, loggerBundle, + fooMsg, null, (Throwable)null, (Object[])null); + logger.log(messageLevel, fooMsg); + checkLogEvent(provider, desc, expected); + } + } + + Supplier supplier = new Supplier() { + @Override + public String get() { + return this.toString(); + } + }; + System.out.println("\tlogger.log(messageLevel, supplier)"); + System.out.println("\tlogger.(supplier)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, supplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, (ResourceBundle) null, + null, supplier, (Throwable)null, (Object[])null); + logger.log(messageLevel, supplier); + checkLogEvent(provider, desc, expected); + } + } + + String format = "two params [{1} {2}]"; + Object arg1 = foo; + Object arg2 = fooMsg; + System.out.println("\tlogger.log(messageLevel, format, arg1, arg2)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, format, foo, fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, loggerBundle, + format, null, (Throwable)null, arg1, arg2); + logger.log(messageLevel, format, arg1, arg2); + checkLogEvent(provider, desc, expected); + } + } + + Throwable thrown = new Exception("OK: log me!"); + System.out.println("\tlogger.log(messageLevel, fooMsg, thrown)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, fooMsg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, loggerBundle, + fooMsg, null, thrown, (Object[])null); + logger.log(messageLevel, fooMsg, thrown); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.log(messageLevel, thrown, supplier)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, thrown, supplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, (ResourceBundle)null, + null, supplier, thrown, (Object[])null); + logger.log(messageLevel, thrown, supplier); + checkLogEvent(provider, desc, expected); + } + } + + String sourceClass = "blah.Blah"; + String sourceMethod = "blih"; + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, fooMsg)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + boolean isLoggable = loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0; + TestLoggerFinder.LogEvent expected = + isLoggable || loggerBundle != null && BEST_EFFORT_FOR_LOGP? + TestLoggerFinder.LogEvent.of( + sequencer.get(), + isLoggable, + name, expectedMessageLevel, loggerBundle, + logpMessage(loggerBundle, sourceClass, sourceMethod, fooMsg), + null, (Throwable)null, (Object[]) null) : null; + logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, supplier)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, supplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + boolean isLoggable = loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0; + TestLoggerFinder.LogEvent expected = isLoggable ? + TestLoggerFinder.LogEvent.ofp(BEST_EFFORT_FOR_LOGP, + TestLoggerFinder.LogEvent.of( + sequencer.get(), + isLoggable, + name, expectedMessageLevel, null, null, + logpMessage(null, sourceClass, sourceMethod, supplier), + (Throwable)null, (Object[]) null)) : null; + logger.logp(messageLevel, sourceClass, sourceMethod, supplier); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, format, arg1, arg2)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, format, arg1, arg2): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + boolean isLoggable = loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0; + TestLoggerFinder.LogEvent expected = + isLoggable || loggerBundle != null && BEST_EFFORT_FOR_LOGP? + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, loggerBundle, + logpMessage(loggerBundle, sourceClass, sourceMethod, format), + null, (Throwable)null, arg1, arg2) : null; + logger.logp(messageLevel, sourceClass, sourceMethod, format, arg1, arg2); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, fooMsg, thrown)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + boolean isLoggable = loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0; + TestLoggerFinder.LogEvent expected = + isLoggable || loggerBundle != null && BEST_EFFORT_FOR_LOGP ? + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, loggerBundle, + logpMessage(loggerBundle, sourceClass, sourceMethod, fooMsg), + null, thrown, (Object[])null) : null; + logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg, thrown); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, thrown, supplier)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, thrown, supplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + boolean isLoggable = loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0; + TestLoggerFinder.LogEvent expected = isLoggable ? + TestLoggerFinder.LogEvent.ofp(BEST_EFFORT_FOR_LOGP, + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, null, null, + logpMessage(null, sourceClass, sourceMethod, supplier), + thrown, (Object[])null)) : null; + logger.logp(messageLevel, sourceClass, sourceMethod, thrown, supplier); + checkLogEvent(provider, desc, expected); + } + } + + ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName()); + System.out.println("\tlogger.logrb(messageLevel, bundle, format, arg1, arg2)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logrb(messageLevel, bundle, format, arg1, arg2): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, bundle, + format, null, (Throwable)null, arg1, arg2); + logger.logrb(messageLevel, bundle, format, arg1, arg2); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logrb(messageLevel, bundle, msg, thrown)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logrb(messageLevel, bundle, msg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, bundle, + fooMsg, null, thrown, (Object[])null); + logger.logrb(messageLevel, bundle, fooMsg, thrown); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logrb(messageLevel, sourceClass, sourceMethod, bundle, format, arg1, arg2)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, format, arg1, arg2): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, bundle, + format, null, (Throwable)null, arg1, arg2); + logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, format, arg1, arg2); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logrb(messageLevel, sourceClass, sourceMethod, bundle, msg, thrown)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, msg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, bundle, + fooMsg, null, thrown, (Object[])null); + logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, fooMsg, thrown); + checkLogEvent(provider, desc, expected); + } + } + } + + 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 static RuntimePermission CONTROL = LOGGERFINDER_PERMISSION; + final static RuntimePermission ACCESS_LOGGER = new RuntimePermission("accessClassInPackage.jdk.internal.logger"); + final static RuntimePermission ACCESS_LOGGING = new RuntimePermission("accessClassInPackage.sun.util.logging"); + + final Permissions permissions; + final Permissions allPermissions; + final ThreadLocal allowControl; + final ThreadLocal allowAccess; + final ThreadLocal allowAll; + public SimplePolicy(ThreadLocal allowControl, + ThreadLocal allowAccess, + ThreadLocal allowAll) { + this.allowControl = allowControl; + this.allowAccess = allowAccess; + this.allowAll = allowAll; + permissions = new Permissions(); + allPermissions = new PermissionsBuilder() + .add(new java.security.AllPermission()) + .toPermissions(); + } + + Permissions getPermissions() { + if (allowControl.get().get() || allowAccess.get().get() || allowAll.get().get()) { + PermissionsBuilder builder = new PermissionsBuilder() + .addAll(permissions); + if (allowControl.get().get()) { + builder.add(CONTROL); + } + if (allowAccess.get().get()) { + builder.add(ACCESS_LOGGER); + builder.add(ACCESS_LOGGING); + } + if (allowAll.get().get()) { + builder.addAll(allPermissions); + } + return builder.toPermissions(); + } + return permissions; + } + + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + return getPermissions().implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + } +} --- /dev/null 2015-11-20 17:44:28.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/internal/BaseLoggerBridgeTest/CustomSystemClassLoader.java 2015-11-20 17:44:28.000000000 +0100 @@ -0,0 +1,101 @@ +/* + * 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.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.security.AllPermission; +import java.security.Permissions; +import java.security.ProtectionDomain; + + +/** + * A custom ClassLoader to load the concrete LoggerFinder class + * with all permissions. + * + * @author danielfuchs + */ +public class CustomSystemClassLoader extends ClassLoader { + + + Class finderClass = null; + + public CustomSystemClassLoader() { + super(); + } + public CustomSystemClassLoader(ClassLoader parent) { + super(parent); + } + + private Class defineFinderClass(String name) + throws ClassNotFoundException { + final Object obj = getClassLoadingLock(name); + synchronized(obj) { + if (finderClass != null) return finderClass; + + URL url = this.getClass().getProtectionDomain().getCodeSource().getLocation(); + File file = new File(url.getPath(), name+".class"); + if (file.canRead()) { + try { + byte[] b = Files.readAllBytes(file.toPath()); + Permissions perms = new Permissions(); + perms.add(new AllPermission()); + finderClass = defineClass( + name, b, 0, b.length, new ProtectionDomain( + this.getClass().getProtectionDomain().getCodeSource(), + perms)); + System.out.println("Loaded " + name); + return finderClass; + } catch (Throwable ex) { + ex.printStackTrace(); + throw new ClassNotFoundException(name, ex); + } + } else { + throw new ClassNotFoundException(name, + new IOException(file.toPath() + ": can't read")); + } + } + } + + @Override + public synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (name.endsWith("$BaseLoggerFinder")) { + Class c = defineFinderClass(name); + if (resolve) { + resolveClass(c); + } + return c; + } + return super.loadClass(name, resolve); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if (name.endsWith("$BaseLoggerFinder")) { + return defineFinderClass(name); + } + return super.findClass(name); + } + +} --- /dev/null 2015-11-20 17:44:29.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/internal/BaseLoggerBridgeTest/META-INF/services/java.lang.System$LoggerFinder 2015-11-20 17:44:29.000000000 +0100 @@ -0,0 +1 @@ +BaseLoggerBridgeTest$BaseLoggerFinder --- /dev/null 2015-11-20 17:44:29.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/internal/BasePlatformLoggerTest/BasePlatformLoggerTest.java 2015-11-20 17:44:29.000000000 +0100 @@ -0,0 +1,732 @@ +/* + * 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.security.AccessController; +import java.security.CodeSource; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.Permissions; +import java.security.Policy; +import java.security.PrivilegedAction; +import java.security.ProtectionDomain; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.ResourceBundle; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.security.AccessControlException; +import java.util.stream.Stream; +import sun.util.logging.PlatformLogger; + +/** + * @test + * @bug 8140364 + * @summary JDK implementation specific unit test for JDK internal API. + * Tests a naive implementation of System.Logger, and in particular + * the default mapping provided by PlatformLogger. + * @modules java.base/sun.util.logging + * @build CustomSystemClassLoader BasePlatformLoggerTest + * @run main/othervm -Djava.system.class.loader=CustomSystemClassLoader BasePlatformLoggerTest NOSECURITY + * @run main/othervm -Djava.system.class.loader=CustomSystemClassLoader BasePlatformLoggerTest NOPERMISSIONS + * @run main/othervm -Djava.system.class.loader=CustomSystemClassLoader BasePlatformLoggerTest WITHPERMISSIONS + * @author danielfuchs + */ +public class BasePlatformLoggerTest { + + public static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + + final static AtomicLong sequencer = new AtomicLong(); + final static boolean VERBOSE = false; + static final ThreadLocal allowControl = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAccess = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAll = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + + static final Class providerClass; + static { + try { + providerClass = ClassLoader.getSystemClassLoader().loadClass("BasePlatformLoggerTest$BaseLoggerFinder"); + } catch (ClassNotFoundException ex) { + throw new ExceptionInInitializerError(ex); + } + } + + static final PlatformLogger.Level[] julLevels = { + PlatformLogger.Level.ALL, + PlatformLogger.Level.FINEST, + PlatformLogger.Level.FINER, + PlatformLogger.Level.FINE, + PlatformLogger.Level.CONFIG, + PlatformLogger.Level.INFO, + PlatformLogger.Level.WARNING, + PlatformLogger.Level.SEVERE, + PlatformLogger.Level.OFF, + }; + + static final Level[] mappedLevels = { + Level.ALL, // ALL + Level.TRACE, // FINEST + Level.TRACE, // FINER + Level.DEBUG, // FINE + Level.DEBUG, // CONFIG + Level.INFO, // INFO + Level.WARNING, // WARNING + Level.ERROR, // SEVERE + Level.OFF, // OFF + }; + + final static Map julToSpiMap; + static { + Map map = new HashMap<>(); + if (mappedLevels.length != julLevels.length) { + throw new ExceptionInInitializerError("Array lengths differ" + + "\n\tjulLevels=" + Arrays.deepToString(julLevels) + + "\n\tmappedLevels=" + Arrays.deepToString(mappedLevels)); + } + for (int i=0; i map = new ConcurrentHashMap<>(); + + @Override + protected Object handleGetObject(String key) { + if (key.contains(" (translated)")) { + throw new RuntimeException("Unexpected key: " + key); + } + return map.computeIfAbsent(key, k -> k + " (translated)"); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(map.keySet()); + } + + } + public static class MyLoggerBundle extends MyBundle { + + } + + + public static interface TestLoggerFinder { + final ConcurrentHashMap system = new ConcurrentHashMap<>(); + final ConcurrentHashMap user = new ConcurrentHashMap<>(); + public Queue eventQueue = new ArrayBlockingQueue<>(128); + + public static final class LogEvent implements Cloneable { + + public LogEvent() { + this(sequencer.getAndIncrement()); + } + + LogEvent(long sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + long sequenceNumber; + boolean isLoggable; + String loggerName; + Level level; + ResourceBundle bundle; + Throwable thrown; + Object[] args; + Supplier supplier; + String msg; + + Object[] toArray() { + return new Object[] { + sequenceNumber, + isLoggable, + loggerName, + level, + bundle, + thrown, + args, + supplier, + msg, + }; + } + + @Override + public String toString() { + return Arrays.deepToString(toArray()); + } + + + + @Override + public boolean equals(Object obj) { + return obj instanceof LogEvent + && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray()); + } + + @Override + public int hashCode() { + return Objects.hash(toArray()); + } + + public LogEvent cloneWith(long sequenceNumber) + throws CloneNotSupportedException { + LogEvent cloned = (LogEvent)super.clone(); + cloned.sequenceNumber = sequenceNumber; + return cloned; + } + + public static LogEvent of(boolean isLoggable, String name, + Level level, ResourceBundle bundle, + String key, Throwable thrown) { + LogEvent evt = new LogEvent(); + evt.isLoggable = isLoggable; + evt.loggerName = name; + evt.level = level; + evt.args = null; + evt.bundle = bundle; + evt.thrown = thrown; + evt.supplier = null; + evt.msg = key; + return evt; + } + + public static LogEvent of(boolean isLoggable, String name, + Level level, Throwable thrown, Supplier supplier) { + LogEvent evt = new LogEvent(); + evt.isLoggable = isLoggable; + evt.loggerName = name; + evt.level = level; + evt.args = null; + evt.bundle = null; + evt.thrown = thrown; + evt.supplier = supplier; + evt.msg = null; + return evt; + } + + public static LogEvent of(boolean isLoggable, String name, + Level level, ResourceBundle bundle, + String key, Object... params) { + LogEvent evt = new LogEvent(); + evt.isLoggable = isLoggable; + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = null; + evt.supplier = null; + evt.msg = key; + return evt; + } + + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + Level level, ResourceBundle bundle, + String key, Supplier supplier, + Throwable thrown, Object... params) { + LogEvent evt = new LogEvent(sequenceNumber); + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = thrown; + evt.supplier = supplier; + evt.msg = key; + evt.isLoggable = isLoggable; + return evt; + } + + } + + public class LoggerImpl implements Logger { + private final String name; + private Level level = Level.INFO; + + public LoggerImpl(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isLoggable(Level level) { + return this.level != Level.OFF && this.level.getSeverity() <= level.getSeverity(); + } + + @Override + public void log(Level level, ResourceBundle bundle, String key, Throwable thrown) { + log(LogEvent.of(isLoggable(level), this.name, level, bundle, key, thrown)); + } + + @Override + public void log(Level level, ResourceBundle bundle, String format, Object... params) { + log(LogEvent.of(isLoggable(level), name, level, bundle, format, params)); + } + + void log(LogEvent event) { + eventQueue.add(event); + } + + @Override + public void log(Level level, Supplier msgSupplier) { + log(LogEvent.of(isLoggable(level), name, level, null, msgSupplier)); + } + + @Override + public void log(Level level, Supplier msgSupplier, Throwable thrown) { + log(LogEvent.of(isLoggable(level), name, level, thrown, msgSupplier)); + } + } + + public Logger getLogger(String name, Class caller); + } + + public static class BaseLoggerFinder extends LoggerFinder implements TestLoggerFinder { + @Override + public Logger getLogger(String name, Class caller) { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGERFINDER_PERMISSION); + } + PrivilegedAction pa = () -> caller.getClassLoader(); + ClassLoader callerLoader = AccessController.doPrivileged(pa); + if (callerLoader == null) { + return system.computeIfAbsent(name, (n) -> new LoggerImpl(n)); + } else { + return user.computeIfAbsent(name, (n) -> new LoggerImpl(n)); + } + } + } + + static PlatformLogger getPlatformLogger(String name) { + boolean old = allowAccess.get().get(); + allowAccess.get().set(true); + try { + return PlatformLogger.getLogger(name); + } finally { + allowAccess.get().set(old); + } + } + + static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS}; + + static void setSecurityManager() { + if (System.getSecurityManager() == null) { + Policy.setPolicy(new SimplePolicy(allowControl, allowAccess, allowAll)); + System.setSecurityManager(new SecurityManager()); + } + } + + public static void main(String[] args) { + if (args.length == 0) + args = new String[] { + "NOSECURITY", + "NOPERMISSIONS", + "WITHPERMISSIONS" + }; + + + Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> { + TestLoggerFinder provider; + switch (testCase) { + case NOSECURITY: + System.out.println("\n*** Without Security Manager\n"); + provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder()); + test(provider, true); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case NOPERMISSIONS: + System.out.println("\n*** With Security Manager, without permissions\n"); + setSecurityManager(); + try { + provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder()); + throw new RuntimeException("Expected exception not raised"); + } catch (AccessControlException x) { + if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) { + throw new RuntimeException("Unexpected permission check", x); + } + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder()); + } finally { + allowControl.get().set(control); + } + } + test(provider, false); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case WITHPERMISSIONS: + System.out.println("\n*** With Security Manager, with control permission\n"); + setSecurityManager(); + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder()); + test(provider, true); + } finally { + allowControl.get().set(control); + } + break; + default: + throw new RuntimeException("Unknown test case: " + testCase); + } + }); + System.out.println("\nPASSED: Tested " + sequencer.get() + " cases."); + } + + public static void test(TestLoggerFinder provider, boolean hasRequiredPermissions) { + + final Map loggerDescMap = new HashMap<>(); + + TestLoggerFinder.LoggerImpl appSink; + boolean before = allowControl.get().get(); + try { + allowControl.get().set(true); + appSink = TestLoggerFinder.LoggerImpl.class.cast( + provider.getLogger("foo", BasePlatformLoggerTest.class)); + } finally { + allowControl.get().set(before); + } + + TestLoggerFinder.LoggerImpl sysSink = null; + before = allowControl.get().get(); + try { + allowControl.get().set(true); + sysSink = TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", Thread.class)); + } finally { + allowControl.get().set(before); + } + + if (hasRequiredPermissions && appSink == sysSink) { + throw new RuntimeException("identical loggers"); + } + + if (provider.system.contains(appSink)) { + throw new RuntimeException("app logger in system map"); + } + if (!provider.user.contains(appSink)) { + throw new RuntimeException("app logger not in appplication map"); + } + if (hasRequiredPermissions && provider.user.contains(sysSink)) { + throw new RuntimeException("sys logger in appplication map"); + } + if (hasRequiredPermissions && !provider.system.contains(sysSink)) { + throw new RuntimeException("sys logger not in system map"); + } + + PlatformLogger platform = getPlatformLogger("foo"); + loggerDescMap.put(platform, "PlatformLogger.getLogger(\"foo\")"); + + testLogger(provider, loggerDescMap, "foo", null, platform, sysSink); + } + + public static class Foo { + + } + + static void verbose(String msg) { + if (VERBOSE) { + System.out.println(msg); + } + } + + static void checkLogEvent(TestLoggerFinder provider, String desc, + TestLoggerFinder.LogEvent expected) { + TestLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + + static void checkLogEvent(TestLoggerFinder provider, String desc, + TestLoggerFinder.LogEvent expected, boolean expectNotNull) { + TestLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (actual == null && !expectNotNull) return; + if (actual != null && !expectNotNull) { + throw new RuntimeException("Unexpected log event found for " + desc + + "\n\tgot: " + actual); + } + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + + // Calls the methods defined on LogProducer and verify the + // parameters received by the underlying TestLoggerFinder.LoggerImpl + // logger. + private static void testLogger(TestLoggerFinder provider, + Map loggerDescMap, + String name, + ResourceBundle loggerBundle, + PlatformLogger logger, + TestLoggerFinder.LoggerImpl sink) { + + System.out.println("Testing " + loggerDescMap.get(logger)); + + Foo foo = new Foo(); + String fooMsg = foo.toString(); + System.out.println("\tlogger.(fooMsg)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (PlatformLogger.Level messageLevel :julLevels) { + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, loggerBundle, + fooMsg, null, (Throwable)null, (Object[])null); + String desc2 = "logger." + messageLevel.toString().toLowerCase() + + "(fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + if (messageLevel == PlatformLogger.Level.FINEST) { + logger.finest(fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.FINER) { + logger.finer(fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.FINE) { + logger.fine(fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.CONFIG) { + logger.config(fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.INFO) { + logger.info(fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.WARNING) { + logger.warning(fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.SEVERE) { + logger.severe(fooMsg); + checkLogEvent(provider, desc2, expected); + } + } + } + + Throwable thrown = new Exception("OK: log me!"); + System.out.println("\tlogger.(msg, thrown)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (PlatformLogger.Level messageLevel :julLevels) { + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, (ResourceBundle) null, + fooMsg, null, (Throwable)thrown, (Object[])null); + String desc2 = "logger." + messageLevel.toString().toLowerCase() + + "(msg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + if (messageLevel == PlatformLogger.Level.FINEST) { + logger.finest(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.FINER) { + logger.finer(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.FINE) { + logger.fine(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.CONFIG) { + logger.config(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.INFO) { + logger.info(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.WARNING) { + logger.warning(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.SEVERE) { + logger.severe(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } + } + } + + String format = "two params [{1} {2}]"; + Object arg1 = foo; + Object arg2 = fooMsg; + System.out.println("\tlogger.(format, arg1, arg2)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (PlatformLogger.Level messageLevel :julLevels) { + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, (ResourceBundle) null, + format, null, (Throwable)null, foo, fooMsg); + String desc2 = "logger." + messageLevel.toString().toLowerCase() + + "(format, foo, fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + if (messageLevel == PlatformLogger.Level.FINEST) { + logger.finest(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.FINER) { + logger.finer(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.FINE) { + logger.fine(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.CONFIG) { + logger.config(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.INFO) { + logger.info(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.WARNING) { + logger.warning(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.SEVERE) { + logger.severe(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected); + } + } + } + + } + + 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 static RuntimePermission CONTROL = LOGGERFINDER_PERMISSION; + final static RuntimePermission ACCESS_LOGGING = new RuntimePermission("accessClassInPackage.sun.util.logging"); + + final Permissions permissions; + final Permissions allPermissions; + final ThreadLocal allowControl; + final ThreadLocal allowAccess; + final ThreadLocal allowAll; + public SimplePolicy(ThreadLocal allowControl, + ThreadLocal allowAccess, + ThreadLocal allowAll) { + this.allowControl = allowControl; + this.allowAccess = allowAccess; + this.allowAll = allowAll; + permissions = new Permissions(); + allPermissions = new PermissionsBuilder() + .add(new java.security.AllPermission()) + .toPermissions(); + } + + Permissions getPermissions() { + if (allowControl.get().get() || allowAccess.get().get() || allowAll.get().get()) { + PermissionsBuilder builder = new PermissionsBuilder() + .addAll(permissions); + if (allowControl.get().get()) { + builder.add(CONTROL); + } + if (allowAccess.get().get()) { + builder.add(ACCESS_LOGGING); + } + if (allowAll.get().get()) { + builder.addAll(allPermissions); + } + return builder.toPermissions(); + } + return permissions; + } + + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + return getPermissions().implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + } +} --- /dev/null 2015-11-20 17:44:30.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/internal/BasePlatformLoggerTest/CustomSystemClassLoader.java 2015-11-20 17:44:30.000000000 +0100 @@ -0,0 +1,101 @@ +/* + * 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.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.security.AllPermission; +import java.security.Permissions; +import java.security.ProtectionDomain; + + +/** + * A custom ClassLoader to load the concrete LoggerFinder class + * with all permissions. + * + * @author danielfuchs + */ +public class CustomSystemClassLoader extends ClassLoader { + + + Class finderClass = null; + + public CustomSystemClassLoader() { + super(); + } + public CustomSystemClassLoader(ClassLoader parent) { + super(parent); + } + + private Class defineFinderClass(String name) + throws ClassNotFoundException { + final Object obj = getClassLoadingLock(name); + synchronized(obj) { + if (finderClass != null) return finderClass; + + URL url = this.getClass().getProtectionDomain().getCodeSource().getLocation(); + File file = new File(url.getPath(), name+".class"); + if (file.canRead()) { + try { + byte[] b = Files.readAllBytes(file.toPath()); + Permissions perms = new Permissions(); + perms.add(new AllPermission()); + finderClass = defineClass( + name, b, 0, b.length, new ProtectionDomain( + this.getClass().getProtectionDomain().getCodeSource(), + perms)); + System.out.println("Loaded " + name); + return finderClass; + } catch (Throwable ex) { + ex.printStackTrace(); + throw new ClassNotFoundException(name, ex); + } + } else { + throw new ClassNotFoundException(name, + new IOException(file.toPath() + ": can't read")); + } + } + } + + @Override + public synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (name.endsWith("$BaseLoggerFinder")) { + Class c = defineFinderClass(name); + if (resolve) { + resolveClass(c); + } + return c; + } + return super.loadClass(name, resolve); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if (name.endsWith("$BaseLoggerFinder")) { + return defineFinderClass(name); + } + return super.findClass(name); + } + +} --- /dev/null 2015-11-20 17:44:31.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/internal/BasePlatformLoggerTest/META-INF/services/java.lang.System$LoggerFinder 2015-11-20 17:44:30.000000000 +0100 @@ -0,0 +1 @@ +BasePlatformLoggerTest$BaseLoggerFinder --- /dev/null 2015-11-20 17:44:31.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/internal/BootstrapLogger/BootstrapLoggerTest.java 2015-11-20 17:44:31.000000000 +0100 @@ -0,0 +1,430 @@ +/* + * Copyright (c) 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 + * 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.ByteArrayOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BooleanSupplier; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.AllPermission; +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.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import jdk.internal.logger.BootstrapLogger; +import jdk.internal.logger.LazyLoggers; + +/* + * @test + * @bug 8140364 + * @author danielfuchs + * @summary JDK implementation specific unit test for JDK internal artifacts. + Tests the behavior of bootstrap loggers (and SimpleConsoleLoggers + * too). + * @modules java.base/jdk.internal.logger + * @run main/othervm BootstrapLoggerTest NO_SECURITY + * @run main/othervm BootstrapLoggerTest SECURE + * @run main/othervm/timeout=120 BootstrapLoggerTest SECURE_AND_WAIT + */ +public class BootstrapLoggerTest { + + static final Method awaitPending; + static final Method isAlive; + static final Field isBooted; + static final Field logManagerInitialized; + static { + try { + isBooted = BootstrapLogger.class.getDeclaredField("isBooted"); + isBooted.setAccessible(true); + // private reflection hook that allows us to test wait until all + // the tasks pending in the BootstrapExecutor are finished. + awaitPending = BootstrapLogger.class + .getDeclaredMethod("awaitPendingTasks"); + awaitPending.setAccessible(true); + // private reflection hook that allows us to test whether + // the BootstrapExecutor is alive. + isAlive = BootstrapLogger.class + .getDeclaredMethod("isAlive"); + isAlive.setAccessible(true); + // private reflection hook that allows us to test whether the LogManager + // has initialized and registered with the BootstrapLogger class + logManagerInitialized = BootstrapLogger.class + .getDeclaredField("logManagerConfigured"); + logManagerInitialized.setAccessible(true); + } catch (Exception ex) { + throw new ExceptionInInitializerError(ex); + } + } + + static void awaitPending() { + try { + awaitPending.invoke(null); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + ex.printStackTrace(LogStream.err); + } + } + + /** + * We use an instance of this class to check what the logging system has + * printed on System.err. + */ + public static class LogStream extends OutputStream { + + final static PrintStream err = System.err; + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + public LogStream() { + super(); + } + + @Override + public synchronized void write(int b) { + baos.write(b); + err.write(b); + } + + public String drain() { + awaitPending(); + synchronized(this) { + String txt = baos.toString(); + baos.reset(); + return txt; + } + } + } + + static enum TestCase { + NO_SECURITY, SECURE, SECURE_AND_WAIT + } + + public static void main(String[] args) throws Exception { + if (args == null || args.length == 0) { + args = new String[] { TestCase.SECURE_AND_WAIT.name() }; + } + if (args.length > 1) throw new RuntimeException("Only one argument allowed"); + TestCase test = TestCase.valueOf(args[0]); + System.err.println("Testing: " + test); + + + // private reflection hook that allows us to simulate a non booted VM + final AtomicBoolean vmBooted = new AtomicBoolean(false); + isBooted.set(null,(BooleanSupplier) () -> vmBooted.get()); + + // We replace System.err to check the messages that have been logged + // by the JUL ConsoleHandler and default SimpleConsoleLogger + // implementaion + final LogStream err = new LogStream(); + System.setErr(new PrintStream(err)); + + if (BootstrapLogger.isBooted()) { + throw new RuntimeException("VM should not be booted!"); + } + Logger logger = LazyLoggers.getLogger("foo.bar", Thread.class); + + if (test != TestCase.NO_SECURITY) { + LogStream.err.println("Setting security manager"); + Policy.setPolicy(new SimplePolicy()); + System.setSecurityManager(new SecurityManager()); + } + + Level[] levels = {Level.INFO, Level.WARNING, Level.INFO}; + int index = 0; + logger.log(levels[index], "Early message #" + (index+1)); index++; + logger.log(levels[index], "Early message #" + (index+1)); index++; + LogStream.err.println("VM Booted: " + vmBooted.get()); + LogStream.err.println("LogManager initialized: " + logManagerInitialized.get(null)); + logger.log(levels[index], "Early message #" + (index+1)); index++; + if (err.drain().contains("Early message")) { + // We're expecting that logger will be a LazyLogger wrapping a + // BootstrapLogger. The Bootstrap logger will stack the log messages + // it receives until the VM is booted. + // Since our private hook pretend that the VM is not booted yet, + // the logged messages shouldn't have reached System.err yet. + throw new RuntimeException("Early message logged while VM is not booted!"); + } + + // Now pretend that the VM is booted. Nothing should happen yet, until + // we try to log a new message. + vmBooted.getAndSet(true); + LogStream.err.println("VM Booted: " + vmBooted.get()); + LogStream.err.println("LogManager initialized: " + logManagerInitialized.get(null)); + if (!BootstrapLogger.isBooted()) { + throw new RuntimeException("VM should now be booted!"); + } + if (((Boolean)logManagerInitialized.get(null)).booleanValue()) { + throw new RuntimeException("LogManager shouldn't be initialized yet!"); + } + + // Logging a message should cause the BootstrapLogger to replace itself + // by a 'real' logger in the LazyLogger. But since the LogManager isn't + // initialized yet, this should be a SimpleConsoleLogger... + logger.log(Level.INFO, "LOG#4: VM now booted: {0}", vmBooted.get()); + logger.log(Level.DEBUG, "LOG#5: hi!"); + SimplePolicy.allowAll.set(Boolean.TRUE); + WeakReference threadRef = null; + ReferenceQueue queue = new ReferenceQueue<>(); + try { + Set set = Thread.getAllStackTraces().keySet().stream() + .filter((t) -> t.getName().startsWith("BootstrapMessageLoggerTask-")) + .collect(Collectors.toSet()); + set.stream().forEach(t -> LogStream.err.println("Found: " + t)); + if (set.size() > 1) { + throw new RuntimeException("Too many bootsrap threads found"); + } + Optional t = set.stream().findFirst(); + if (t.isPresent()) { + threadRef = new WeakReference<>(t.get(), queue); + } + } finally{ + SimplePolicy.allowAll.set(Boolean.FALSE); + } + if (!BootstrapLogger.isBooted()) { + throw new RuntimeException("VM should still be booted!"); + } + if (((Boolean)logManagerInitialized.get(null)).booleanValue()) { + throw new RuntimeException("LogManager shouldn't be initialized yet!"); + } + + // Now check that the early messages we had printed before the VM was + // booted have appeared on System.err... + String afterBoot = err.drain(); + for (int i=0; i loggerClass = Class.forName("java.util.logging.Logger"); + Class levelClass = Class.forName("java.util.logging.Level"); + Class handlerClass = Class.forName("java.util.logging.Handler"); + + // java.util.logging.Logger.getLogger("foo") + // .setLevel(java.util.logging.Level.FINEST); + Object fooLogger = loggerClass.getMethod("getLogger", String.class) + .invoke(null, "foo"); + loggerClass.getMethod("setLevel", levelClass) + .invoke(fooLogger, levelClass.getField("FINEST").get(null)); + + // java.util.logging.Logger.getLogger("").getHandlers()[0] + // .setLevel(java.util.logging.Level.ALL); + Object rootLogger = loggerClass.getMethod("getLogger", String.class) + .invoke(null, ""); + Object handlers = loggerClass.getMethod("getHandlers"). + invoke(rootLogger); + handlerClass.getMethod("setLevel", levelClass) + .invoke(Array.get(handlers, 0), levelClass.getField("ALL") + .get(null)); + + hasJUL = true; + } catch (ClassNotFoundException x) { + LogStream.err.println("JUL is not present: class " + x.getMessage() + + " not found"); + hasJUL = false; + } finally { + SimplePolicy.allowAll.set(Boolean.FALSE); + } + + logger.log(Level.DEBUG, "hi now!"); + String debug = err.drain(); + if (hasJUL) { + if (!((Boolean)logManagerInitialized.get(null)).booleanValue()) { + throw new RuntimeException("LogManager should be initialized now!"); + } + if (!debug.contains("FINE: hi now!")) { + throw new RuntimeException("System.err does not contain: " + + "FINE: hi now!"); + } + } else { + if (debug.contains("hi now!")) { + throw new RuntimeException("System.err contains: " + "hi now!"); + } + if (((Boolean)logManagerInitialized.get(null)).booleanValue()) { + throw new RuntimeException("LogManager shouldn't be initialized yet!"); + } + Logger baz = System.getLogger("foo.bar.baz"); + if (((Boolean)logManagerInitialized.get(null)).booleanValue()) { + throw new RuntimeException("LogManager shouldn't be initialized yet!"); + } + } + Logger bazbaz = null; + SimplePolicy.allowAll.set(Boolean.TRUE); + try { + bazbaz = java.lang.System.LoggerFinder + .getLoggerFinder().getLogger("foo.bar.baz.baz", BootstrapLoggerTest.class); + } finally { + SimplePolicy.allowAll.set(Boolean.FALSE); + } + if (!((Boolean)logManagerInitialized.get(null)).booleanValue()) { + throw new RuntimeException("LogManager should be initialized now!"); + } + Logger bazbaz2 = System.getLogger("foo.bar.baz.baz"); + if (bazbaz2.getClass() != bazbaz.getClass()) { + throw new RuntimeException("bazbaz2.class != bazbaz.class [" + + bazbaz2.getClass() + " != " + + bazbaz.getClass() + "]"); + } + if (hasJUL != bazbaz2.getClass().getName() + .equals("sun.util.logging.internal.LoggingProviderImpl$JULWrapper")) { + throw new RuntimeException("Unexpected class for bazbaz: " + + bazbaz.getClass().getName() + + "\n\t expected: " + + "sun.util.logging.internal.LoggingProviderImpl$JULWrapper"); + } + + // Now we're going to check that the thread of the BootstrapLogger + // executor terminates, and that the Executor is GC'ed after that. + // This will involve a bit of waiting, hence the timeout=120 in + // the @run line. + // If this test fails in timeout - we could envisage skipping this part, + // or adding some System property to configure the keep alive delay + // of the executor. + SimplePolicy.allowAll.set(Boolean.TRUE); + try { + Stream stream = Thread.getAllStackTraces().keySet().stream(); + stream.filter((t) -> t.getName().startsWith("BootstrapMessageLoggerTask-")) + .forEach(t -> LogStream.err.println(t)); + stream = null; + if (threadRef != null && test == TestCase.SECURE_AND_WAIT) { + Thread t = threadRef.get(); + if (t != null) { + if (!(Boolean)isAlive.invoke(null)) { + throw new RuntimeException("Executor already terminated"); + } else { + LogStream.err.println("Executor still alive as expected."); + } + LogStream.err.println("Waiting for " + t.getName() + " to terminate (join)"); + t.join(60_000); + t = null; + } + LogStream.err.println("Calling System.gc()"); + System.gc(); + LogStream.err.println("Waiting for BootstrapMessageLoggerTask to be gc'ed"); + while (queue.remove(1000) == null) { + LogStream.err.println("Calling System.gc()"); + System.gc(); + } + + // Call the reference here to make sure threadRef will not be + // eagerly garbage collected before the thread it references. + // otherwise, it might not be enqueued, resulting in the + // queue.remove() call above to always return null.... + if (threadRef.get() != null) { + throw new RuntimeException("Reference should have been cleared"); + } + + LogStream.err.println("BootstrapMessageLoggerTask has been gc'ed"); + // Wait for the executor to be gc'ed... + for (int i=0; i<10; i++) { + LogStream.err.println("Calling System.gc()"); + System.gc(); + if (!(Boolean)isAlive.invoke(null)) break; + // It would be unexpected that we reach here... + Thread.sleep(1000); + } + + if ((Boolean)isAlive.invoke(null)) { + throw new RuntimeException("Executor still alive"); + } else { + LogStream.err.println("Executor terminated as expected."); + } + } else { + LogStream.err.println("Not checking executor termination for " + test); + } + } finally { + SimplePolicy.allowAll.set(Boolean.FALSE); + } + LogStream.err.println(test.name() + ": PASSED"); + } + + final static class SimplePolicy extends Policy { + static final ThreadLocal allowAll = new ThreadLocal() { + @Override + protected Boolean initialValue() { + return Boolean.FALSE; + } + }; + + Permissions getPermissions() { + Permissions perms = new Permissions(); + if (allowAll.get()) { + perms.add(new AllPermission()); + } + return perms; + } + + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + return getPermissions(domain).implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return getPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return getPermissions(); + } + + } +} --- /dev/null 2015-11-20 17:44:32.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/internal/LoggerBridgeTest/CustomSystemClassLoader.java 2015-11-20 17:44:32.000000000 +0100 @@ -0,0 +1,141 @@ +/* + * 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.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.security.AllPermission; +import java.security.Permissions; +import java.security.ProtectionDomain; + + +/** + * A custom ClassLoader to load the concrete LoggerFinder class + * with all permissions. + * + * @author danielfuchs + */ +public class CustomSystemClassLoader extends ClassLoader { + + + Class loggerFinderClass = null; +// Class loggerImplClass = null; + + public CustomSystemClassLoader() { + super(); + } + public CustomSystemClassLoader(ClassLoader parent) { + super(parent); + } + + private Class defineFinderClass(String name) + throws ClassNotFoundException { + final Object obj = getClassLoadingLock(name); + synchronized(obj) { + if (loggerFinderClass != null) return loggerFinderClass; + + URL url = this.getClass().getProtectionDomain().getCodeSource().getLocation(); + File file = new File(url.getPath(), name+".class"); + if (file.canRead()) { + try { + byte[] b = Files.readAllBytes(file.toPath()); + Permissions perms = new Permissions(); + perms.add(new AllPermission()); + loggerFinderClass = defineClass( + name, b, 0, b.length, new ProtectionDomain( + this.getClass().getProtectionDomain().getCodeSource(), + perms)); + System.out.println("Loaded " + name); + return loggerFinderClass; + } catch (Throwable ex) { + ex.printStackTrace(); + throw new ClassNotFoundException(name, ex); + } + } else { + throw new ClassNotFoundException(name, + new IOException(file.toPath() + ": can't read")); + } + } + } +// private Class defineLoggerImplClass(String name) +// throws ClassNotFoundException { +// final Object obj = getClassLoadingLock(name); +// synchronized(obj) { +// if (loggerImplClass != null) return loggerImplClass; +// +// URL url = this.getClass().getProtectionDomain().getCodeSource().getLocation(); +// File file = new File(url.getPath(), name+".class"); +// if (file.canRead()) { +// try { +// byte[] b = Files.readAllBytes(file.toPath()); +// Permissions perms = new Permissions(); +// perms.add(new AllPermission()); +// loggerImplClass = defineClass( +// name, b, 0, b.length, new ProtectionDomain( +// this.getClass().getProtectionDomain().getCodeSource(), +// perms)); +// System.out.println("Loaded " + name); +// return loggerImplClass; +// } catch (Throwable ex) { +// ex.printStackTrace(); +// throw new ClassNotFoundException(name, ex); +// } +// } else { +// throw new ClassNotFoundException(name, +// new IOException(file.toPath() + ": can't read")); +// } +// } +// } +// + @Override + public synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (name.endsWith("$LogProducerFinder")) { + Class c = defineFinderClass(name); + if (resolve) { + resolveClass(c); + } + return c; + } +// if (name.endsWith("$LogProducerFinder$LoggerImpl")) { +// Class c = defineLoggerImplClass(name); +// if (resolve) { +// resolveClass(c); +// } +// return c; +// } + return super.loadClass(name, resolve); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { +// if (name.endsWith("$LogProducerFinder$LoggerImpl")) { +// return defineLoggerImplClass(name); +// } + if (name.endsWith("$$LogProducerFinder")) { + return defineFinderClass(name); + } + return super.findClass(name); + } + +} --- /dev/null 2015-11-20 17:44:32.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/internal/LoggerBridgeTest/LoggerBridgeTest.java 2015-11-20 17:44:32.000000000 +0100 @@ -0,0 +1,1087 @@ +/* + * 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.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.AccessControlException; +import java.security.AccessController; +import java.security.CodeSource; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.Permissions; +import java.security.Policy; +import java.security.PrivilegedAction; +import java.security.ProtectionDomain; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.ResourceBundle; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import java.util.logging.Handler; +import java.util.logging.LogRecord; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.util.stream.Stream; +import sun.util.logging.PlatformLogger; + +/** + * @test + * @bug 8140364 + * @summary JDK implementation specific unit test for JDK internal artifacts. + * Tests all bridge methods with the a custom backend whose + * loggers implement PlatformLogger.Bridge. + * @modules java.base/sun.util.logging java.base/jdk.internal.logger + * @build CustomSystemClassLoader LoggerBridgeTest + * @run main/othervm -Djava.system.class.loader=CustomSystemClassLoader LoggerBridgeTest NOSECURITY + * @run main/othervm -Djava.system.class.loader=CustomSystemClassLoader LoggerBridgeTest NOPERMISSIONS + * @run main/othervm -Djava.system.class.loader=CustomSystemClassLoader LoggerBridgeTest WITHPERMISSIONS + * @author danielfuchs + */ +public class LoggerBridgeTest { + + public static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + + final static AtomicLong sequencer = new AtomicLong(); + final static boolean VERBOSE = false; + static final ThreadLocal allowControl = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAccess = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAll = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + + public static final Queue eventQueue = new ArrayBlockingQueue<>(128); + + public static final class LogEvent implements Cloneable { + + public LogEvent() { + this(sequencer.getAndIncrement()); + } + + LogEvent(long sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + long sequenceNumber; + boolean isLoggable; + String loggerName; + sun.util.logging.PlatformLogger.Level level; + ResourceBundle bundle; + Throwable thrown; + Object[] args; + String msg; + Supplier supplier; + String className; + String methodName; + + Object[] toArray() { + return new Object[] { + sequenceNumber, + loggerName, + level, + isLoggable, + bundle, + msg, + supplier, + thrown, + args, + className, + methodName, + }; + } + + @Override + public String toString() { + return Arrays.deepToString(toArray()); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof LogEvent + && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray()); + } + + @Override + public int hashCode() { + return Objects.hash(toArray()); + } + + public LogEvent cloneWith(long sequenceNumber) + throws CloneNotSupportedException { + LogEvent cloned = (LogEvent)super.clone(); + cloned.sequenceNumber = sequenceNumber; + return cloned; + } + + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + return LogEvent.of(sequenceNumber, isLoggable, name, + null, null, level, bundle, key, + thrown, params); + } + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + Supplier supplier, Throwable thrown, Object... params) { + return LogEvent.of(sequenceNumber, isLoggable, name, + null, null, level, bundle, supplier, + thrown, params); + } + + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + String className, String methodName, + sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + LogEvent evt = new LogEvent(sequenceNumber); + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = thrown; + evt.msg = key; + evt.isLoggable = isLoggable; + evt.className = className; + evt.methodName = methodName; + return evt; + } + + public static LogEvent of(boolean isLoggable, String name, + String className, String methodName, + sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + return LogEvent.of(sequencer.getAndIncrement(), isLoggable, name, + className, methodName, level, bundle, key, thrown, params); + } + + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + String className, String methodName, + sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + Supplier supplier, Throwable thrown, Object... params) { + LogEvent evt = new LogEvent(sequenceNumber); + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = thrown; + evt.supplier = supplier; + evt.isLoggable = isLoggable; + evt.className = className; + evt.methodName = methodName; + return evt; + } + + public static LogEvent of(boolean isLoggable, String name, + String className, String methodName, + sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + Supplier supplier, Throwable thrown, Object... params) { + return LogEvent.of(sequencer.getAndIncrement(), isLoggable, name, + className, methodName, level, bundle, supplier, thrown, params); + } + + } + static final Class providerClass; + static { + try { + // Preload classes before the security manager is on. + providerClass = ClassLoader.getSystemClassLoader().loadClass("LoggerBridgeTest$LogProducerFinder"); + ((LoggerFinder)providerClass.newInstance()).getLogger("foo", providerClass); + } catch (Exception ex) { + throw new ExceptionInInitializerError(ex); + } + } + + public static class LogProducerFinder extends LoggerFinder { + final ConcurrentHashMap system = new ConcurrentHashMap<>(); + final ConcurrentHashMap user = new ConcurrentHashMap<>(); + + public class LoggerImpl implements Logger, PlatformLogger.Bridge { + private final String name; + private sun.util.logging.PlatformLogger.Level level = sun.util.logging.PlatformLogger.Level.INFO; + private sun.util.logging.PlatformLogger.Level OFF = sun.util.logging.PlatformLogger.Level.OFF; + private sun.util.logging.PlatformLogger.Level FINE = sun.util.logging.PlatformLogger.Level.FINE; + private sun.util.logging.PlatformLogger.Level FINER = sun.util.logging.PlatformLogger.Level.FINER; + private sun.util.logging.PlatformLogger.Level FINEST = sun.util.logging.PlatformLogger.Level.FINEST; + private sun.util.logging.PlatformLogger.Level CONFIG = sun.util.logging.PlatformLogger.Level.CONFIG; + private sun.util.logging.PlatformLogger.Level INFO = sun.util.logging.PlatformLogger.Level.INFO; + private sun.util.logging.PlatformLogger.Level WARNING = sun.util.logging.PlatformLogger.Level.WARNING; + private sun.util.logging.PlatformLogger.Level SEVERE = sun.util.logging.PlatformLogger.Level.SEVERE; + + public LoggerImpl(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isLoggable(Level level) { + return this.level != OFF && this.level.intValue() <= level.getSeverity(); + } + + @Override + public void log(Level level, ResourceBundle bundle, + String key, Throwable thrown) { + throw new UnsupportedOperationException(); + } + + @Override + public void log(Level level, ResourceBundle bundle, + String format, Object... params) { + throw new UnsupportedOperationException(); + } + + void log(LogEvent event) { + eventQueue.add(event); + } + + @Override + public void log(Level level, Supplier msgSupplier) { + throw new UnsupportedOperationException(); + } + + @Override + public void log(Level level, Supplier msgSupplier, + Throwable thrown) { + throw new UnsupportedOperationException(); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, String msg) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, null, msg, null, (Object[])null)); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, + Supplier msgSupplier) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, null, msgSupplier, null, (Object[])null)); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, String msg, + Object... params) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, null, msg, null, params)); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, String msg, + Throwable thrown) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, null, msg, thrown, (Object[])null)); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, Throwable thrown, + Supplier msgSupplier) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, null, msgSupplier, thrown, (Object[])null)); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, null, msg, null, (Object[])null)); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, Supplier msgSupplier) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, null, msgSupplier, null, (Object[])null)); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Object... params) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, null, msg, null, params)); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Throwable thrown) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, null, msg, thrown, (Object[])null)); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, Throwable thrown, + Supplier msgSupplier) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, null, msgSupplier, thrown, (Object[])null)); + } + + @Override + public void logrb(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String msg, + Object... params) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, bundle, msg, null, params)); + } + + @Override + public void logrb(sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + String msg, Object... params) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, bundle, msg, null, params)); + } + + @Override + public void logrb(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String msg, + Throwable thrown) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, bundle, msg, thrown, (Object[])null)); + } + + @Override + public void logrb(sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + String msg, Throwable thrown) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, bundle, msg, thrown, (Object[])null)); + } + + @Override + public boolean isLoggable(sun.util.logging.PlatformLogger.Level level) { + return this.level != OFF && level.intValue() + >= this.level.intValue(); + } + + @Override + public boolean isEnabled() { + return this.level != OFF; + } + + } + + @Override + public Logger getLogger(String name, Class caller) { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGERFINDER_PERMISSION); + } + PrivilegedAction pa = () -> caller.getClassLoader(); + ClassLoader callerLoader = AccessController.doPrivileged(pa); + if (callerLoader == null) { + return system.computeIfAbsent(name, (n) -> new LoggerImpl(n)); + } else { + return user.computeIfAbsent(name, (n) -> new LoggerImpl(n)); + } + } + } + + static final sun.util.logging.PlatformLogger.Level[] julLevels = { + sun.util.logging.PlatformLogger.Level.ALL, + sun.util.logging.PlatformLogger.Level.FINEST, + sun.util.logging.PlatformLogger.Level.FINER, + sun.util.logging.PlatformLogger.Level.FINE, + sun.util.logging.PlatformLogger.Level.CONFIG, + sun.util.logging.PlatformLogger.Level.INFO, + sun.util.logging.PlatformLogger.Level.WARNING, + sun.util.logging.PlatformLogger.Level.SEVERE, + sun.util.logging.PlatformLogger.Level.OFF, + }; + + public static class MyBundle extends ResourceBundle { + + final ConcurrentHashMap map = new ConcurrentHashMap<>(); + + @Override + protected Object handleGetObject(String key) { + if (key.contains(" (translated)")) { + throw new RuntimeException("Unexpected key: " + key); + } + return map.computeIfAbsent(key, k -> k + " (translated)"); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(map.keySet()); + } + + } + + public static class MyHandler extends Handler { + + @Override + public java.util.logging.Level getLevel() { + return java.util.logging.Level.ALL; + } + + @Override + public void publish(LogRecord record) { + eventQueue.add(LogEvent.of(sequencer.getAndIncrement(), + true, record.getLoggerName(), + record.getSourceClassName(), + record.getSourceMethodName(), + PlatformLogger.Level.valueOf(record.getLevel().getName()), + record.getResourceBundle(), record.getMessage(), + record.getThrown(), record.getParameters())); + } + @Override + public void flush() { + } + @Override + public void close() throws SecurityException { + } + + } + + public static class MyLoggerBundle extends MyBundle { + + } + + final static Method lazyGetLogger; + static { + // jdk.internal.logging.LoggerBridge.getLogger(name, caller) + try { + Class bridgeClass = Class.forName("jdk.internal.logger.LazyLoggers"); + lazyGetLogger = bridgeClass.getDeclaredMethod("getLogger", + String.class, Class.class); + lazyGetLogger.setAccessible(true); + } catch (Throwable ex) { + throw new ExceptionInInitializerError(ex); + } + } + + static Logger getLogger(LoggerFinder provider, String name, Class caller) { + Logger logger; + try { + logger = Logger.class.cast(lazyGetLogger.invoke(null, name, caller)); + } catch (Throwable x) { + Throwable t = (x instanceof InvocationTargetException) ? + ((InvocationTargetException)x).getTargetException() : x; + if (t instanceof RuntimeException) { + throw (RuntimeException)t; + } else if (t instanceof Exception) { + throw new RuntimeException(t); + } else { + throw (Error)t; + } + } + // The method above does not throw exception... + // call the provider here to verify that an exception would have + // been thrown by the provider. + if (logger != null && caller == Thread.class) { + Logger log = provider.getLogger(name, caller); + } + return logger; + } + + static Logger getLogger(LoggerFinder provider, String name, ResourceBundle bundle, Class caller) { + if (caller.getClassLoader() != null) { + return System.getLogger(name,bundle); + } else { + return provider.getLocalizedLogger(name, bundle, caller); + } + } + + static PlatformLogger.Bridge convert(Logger logger) { + return PlatformLogger.Bridge.convert(logger); + } + + static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS}; + + static void setSecurityManager() { + if (System.getSecurityManager() == null) { + Policy.setPolicy(new SimplePolicy(allowControl, allowAccess, allowAll)); + System.setSecurityManager(new SecurityManager()); + } + } + + public static void main(String[] args) { + if (args.length == 0) + args = new String[] { + //"NOSECURITY", + "NOPERMISSIONS", + "WITHPERMISSIONS" + }; + + + Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> { + LoggerFinder provider; + switch (testCase) { + case NOSECURITY: + System.out.println("\n*** Without Security Manager\n"); + provider = LoggerFinder.getLoggerFinder(); + test(provider, true); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case NOPERMISSIONS: + System.out.println("\n*** With Security Manager, without permissions\n"); + setSecurityManager(); + try { + provider = LoggerFinder.getLoggerFinder(); + throw new RuntimeException("Expected exception not raised"); + } catch (AccessControlException x) { + if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) { + throw new RuntimeException("Unexpected permission check", x); + } + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = LoggerFinder.getLoggerFinder(); + } finally { + allowControl.get().set(control); + } + } + test(provider, false); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case WITHPERMISSIONS: + System.out.println("\n*** With Security Manager, with control permission\n"); + setSecurityManager(); + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = LoggerFinder.getLoggerFinder(); + test(provider, true); + } finally { + allowControl.get().set(control); + } + break; + default: + throw new RuntimeException("Unknown test case: " + testCase); + } + }); + System.out.println("\nPASSED: Tested " + sequencer.get() + " cases."); + } + + public static void test(LoggerFinder provider, boolean hasRequiredPermissions) { + + ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName()); + final Map loggerDescMap = new HashMap<>(); + + + Logger appLogger1 = System.getLogger("foo"); + loggerDescMap.put(appLogger1, "LogProducer.getApplicationLogger(\"foo\")"); + + Logger sysLogger1 = null; + try { + sysLogger1 = getLogger(provider, "foo", Thread.class); + loggerDescMap.put(sysLogger1, "LogProducer.getSystemLogger(\"foo\")"); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a system logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for system logger: " + acx); + } + + + Logger appLogger2 = + System.getLogger("foo", loggerBundle); + loggerDescMap.put(appLogger2, "LogProducer.getApplicationLogger(\"foo\", loggerBundle)"); + + Logger sysLogger2 = null; + try { + sysLogger2 = getLogger(provider, "foo", loggerBundle, Thread.class); + loggerDescMap.put(sysLogger2, "provider.getSystemLogger(\"foo\", loggerBundle)"); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a system logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for localized system logger: " + acx); + } + if (hasRequiredPermissions && appLogger2 == sysLogger2) { + throw new RuntimeException("identical loggers"); + } + if (appLogger2 == appLogger1) { + throw new RuntimeException("identical loggers"); + } + if (hasRequiredPermissions && sysLogger2 == sysLogger1) { + throw new RuntimeException("identical loggers"); + } + + + final LogProducerFinder.LoggerImpl appSink; + final LogProducerFinder.LoggerImpl sysSink; + boolean old = allowControl.get().get(); + allowControl.get().set(true); + try { + appSink = LogProducerFinder.LoggerImpl.class.cast( + provider.getLogger("foo", LoggerBridgeTest.class)); + sysSink = LogProducerFinder.LoggerImpl.class.cast( + provider.getLogger("foo", Thread.class)); + } finally { + allowControl.get().set(old); + } + + testLogger(provider, loggerDescMap, "foo", null, convert(appLogger1), appSink); + if (hasRequiredPermissions) { + testLogger(provider, loggerDescMap, "foo", null, convert(sysLogger1), sysSink); + } + testLogger(provider, loggerDescMap, "foo", loggerBundle, convert(appLogger2), appSink); + if (hasRequiredPermissions) { + testLogger(provider, loggerDescMap, "foo", loggerBundle, convert(sysLogger2), sysSink); + } + } + + public static class Foo { + + } + + static void verbose(String msg) { + if (VERBOSE) { + System.out.println(msg); + } + } + + static void checkLogEvent(LoggerFinder provider, String desc, + LogEvent expected) { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + + static void checkLogEvent(LoggerFinder provider, String desc, + LogEvent expected, boolean expectNotNull) { + LogEvent actual = eventQueue.poll(); + if (actual == null && !expectNotNull) return; + if (actual != null && !expectNotNull) { + throw new RuntimeException("Unexpected log event found for " + desc + + "\n\tgot: " + actual); + } + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + + static void setLevel( LogProducerFinder.LoggerImpl sink, + sun.util.logging.PlatformLogger.Level loggerLevel) { + sink.level = loggerLevel; + } + + // Calls the methods defined on LogProducer and verify the + // parameters received by the underlying LogProducerFinder.LoggerImpl + // logger. + private static void testLogger(LoggerFinder provider, + Map loggerDescMap, + String name, + ResourceBundle loggerBundle, + PlatformLogger.Bridge logger, + LogProducerFinder.LoggerImpl sink) { + + System.out.println("Testing " + loggerDescMap.get(logger) + "[" + logger + "]"); + final sun.util.logging.PlatformLogger.Level OFF = sun.util.logging.PlatformLogger.Level.OFF; + + Foo foo = new Foo(); + String fooMsg = foo.toString(); + System.out.println("\tlogger.log(messageLevel, fooMsg)"); + System.out.println("\tlogger.(fooMsg)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, loggerBundle, + fooMsg, (Throwable)null, (Object[])null); + logger.log(messageLevel, fooMsg); + checkLogEvent(provider, desc, expected); + } + } + + Supplier supplier = new Supplier() { + @Override + public String get() { + return this.toString(); + } + }; + System.out.println("\tlogger.log(messageLevel, supplier)"); + System.out.println("\tlogger.(supplier)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, supplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, null, + supplier, (Throwable)null, (Object[])null); + logger.log(messageLevel, supplier); + checkLogEvent(provider, desc, expected); + } + } + + String format = "two params [{1} {2}]"; + Object arg1 = foo; + Object arg2 = fooMsg; + System.out.println("\tlogger.log(messageLevel, format, arg1, arg2)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, format, foo, fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, loggerBundle, + format, (Throwable)null, arg1, arg2); + logger.log(messageLevel, format, arg1, arg2); + checkLogEvent(provider, desc, expected); + } + } + + Throwable thrown = new Exception("OK: log me!"); + System.out.println("\tlogger.log(messageLevel, fooMsg, thrown)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, fooMsg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, loggerBundle, + fooMsg, thrown, (Object[])null); + logger.log(messageLevel, fooMsg, thrown); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.log(messageLevel, thrown, supplier)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, thrown, supplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, null, + supplier, thrown, (Object[])null); + logger.log(messageLevel, thrown, supplier); + checkLogEvent(provider, desc, expected); + } + } + + String sourceClass = "blah.Blah"; + String sourceMethod = "blih"; + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, fooMsg)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, loggerBundle, + fooMsg, (Throwable)null, (Object[])null); + logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, supplier)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, supplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, null, + supplier, (Throwable)null, (Object[])null); + logger.logp(messageLevel, sourceClass, sourceMethod, supplier); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, format, arg1, arg2)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, format, arg1, arg2): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, loggerBundle, + format, (Throwable)null, arg1, arg2); + logger.logp(messageLevel, sourceClass, sourceMethod, format, arg1, arg2); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, fooMsg, thrown)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, loggerBundle, + fooMsg, thrown, (Object[])null); + logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg, thrown); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, thrown, supplier)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, thrown, supplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, null, + supplier, thrown, (Object[])null); + logger.logp(messageLevel, sourceClass, sourceMethod, thrown, supplier); + checkLogEvent(provider, desc, expected); + } + } + + ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName()); + System.out.println("\tlogger.logrb(messageLevel, bundle, format, arg1, arg2)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logrb(messageLevel, bundle, format, arg1, arg2): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, bundle, + format, (Throwable)null, arg1, arg2); + logger.logrb(messageLevel, bundle, format, arg1, arg2); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logrb(messageLevel, bundle, msg, thrown)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logrb(messageLevel, bundle, msg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, bundle, + fooMsg, thrown, (Object[])null); + logger.logrb(messageLevel, bundle, fooMsg, thrown); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logrb(messageLevel, sourceClass, sourceMethod, bundle, format, arg1, arg2)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, format, arg1, arg2): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, bundle, + format, (Throwable)null, arg1, arg2); + logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, format, arg1, arg2); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logrb(messageLevel, sourceClass, sourceMethod, bundle, msg, thrown)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, msg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, bundle, + fooMsg, thrown, (Object[])null); + logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, fooMsg, thrown); + checkLogEvent(provider, desc, expected); + } + } + } + + 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 static RuntimePermission CONTROL = LOGGERFINDER_PERMISSION; + final static RuntimePermission ACCESS_LOGGER = new RuntimePermission("accessClassInPackage.jdk.internal.logger"); + final static RuntimePermission ACCESS_LOGGING = new RuntimePermission("accessClassInPackage.sun.util.logging"); + + final Permissions permissions; + final Permissions allPermissions; + final ThreadLocal allowControl; + final ThreadLocal allowAccess; + final ThreadLocal allowAll; + public SimplePolicy(ThreadLocal allowControl, + ThreadLocal allowAccess, + ThreadLocal allowAll) { + this.allowControl = allowControl; + this.allowAccess = allowAccess; + this.allowAll = allowAll; + permissions = new Permissions(); + allPermissions = new PermissionsBuilder() + .add(new java.security.AllPermission()) + .toPermissions(); + } + + Permissions getPermissions() { + if (allowControl.get().get() || allowAccess.get().get() || allowAll.get().get()) { + PermissionsBuilder builder = new PermissionsBuilder() + .addAll(permissions); + if (allowControl.get().get()) { + builder.add(CONTROL); + } + if (allowAccess.get().get()) { + builder.add(ACCESS_LOGGER); + builder.add(ACCESS_LOGGING); + } + if (allowAll.get().get()) { + builder.addAll(allPermissions); + } + return builder.toPermissions(); + } + return permissions; + } + + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + return getPermissions().implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + } +} --- /dev/null 2015-11-20 17:44:33.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/internal/LoggerBridgeTest/META-INF/services/java.lang.System$LoggerFinder 2015-11-20 17:44:33.000000000 +0100 @@ -0,0 +1 @@ +LoggerBridgeTest$LogProducerFinder --- /dev/null 2015-11-20 17:44:34.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/AccessSystemLogger.java 2015-11-20 17:44:33.000000000 +0100 @@ -0,0 +1,82 @@ +/* + * 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.IOException; +import java.lang.System.Logger; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ResourceBundle; + +/** + * + * @author danielfuchs + */ +public final class AccessSystemLogger { + + public AccessSystemLogger() { + this(check()); + } + + private AccessSystemLogger(Void unused) { + } + + private static Void check() { + if (AccessSystemLogger.class.getClassLoader() != null) { + throw new RuntimeException("AccessSystemLogger should be loaded by the null classloader"); + } + return null; + } + + public Logger getLogger(String name) { + Logger logger = System.getLogger(name); + System.out.println("System.getLogger(\"" + name + "\"): " + logger); + return logger; + } + + public Logger getLogger(String name, ResourceBundle bundle) { + Logger logger = System.getLogger(name, bundle); + System.out.println("System.getLogger(\"" + name + "\", bundle): " + logger); + return logger; + } + + static final Class[] toCopy = { AccessSystemLogger.class, CustomSystemClassLoader.class }; + + // copy AccessSystemLogger.class to ./boot + public static void main(String[] args) throws IOException { + Path testDir = Paths.get(System.getProperty("user.dir", ".")); + Path bootDir = Paths.get(testDir.toString(), "boot"); + Path classes = Paths.get(System.getProperty("test.classes", "build/classes")); + if (Files.notExists(bootDir)) { + Files.createDirectory(bootDir); + } + for (Class c : toCopy) { + Path thisClass = Paths.get(classes.toString(), + c.getSimpleName()+".class"); + Path dest = Paths.get(bootDir.toString(), + c.getSimpleName()+".class"); + Files.copy(thisClass, dest, StandardCopyOption.REPLACE_EXISTING); + } + } + +} --- /dev/null 2015-11-20 17:44:34.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/CustomSystemClassLoader.java 2015-11-20 17:44:34.000000000 +0100 @@ -0,0 +1,118 @@ +/* + * 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.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.security.AllPermission; +import java.security.Permissions; +import java.security.ProtectionDomain; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * A custom ClassLoader to load the concrete LoggerFinder class + * with all permissions. The CustomSystemClassLoader class must be + * in the BCL, otherwise when system classes - such as + * ZoneDateTime try to load their resource bundle a MissingResourceBundle + * caused by a SecurityException may be thrown, as the CustomSystemClassLoader + * code base will be found in the stack called by doPrivileged. + * + * @author danielfuchs + */ +public class CustomSystemClassLoader extends ClassLoader { + + + final List finderClassNames = + Arrays.asList("LoggerFinderLoaderTest$BaseLoggerFinder", + "LoggerFinderLoaderTest$BaseLoggerFinder2"); + final Map> finderClasses = new HashMap<>(); + Class testLoggerFinderClass; + + public CustomSystemClassLoader() { + super(); + } + public CustomSystemClassLoader(ClassLoader parent) { + super(parent); + } + + private Class defineFinderClass(String name) + throws ClassNotFoundException { + final Object obj = getClassLoadingLock(name); + synchronized(obj) { + if (finderClasses.get(name) != null) return finderClasses.get(name); + if (testLoggerFinderClass == null) { + // Hack: we load testLoggerFinderClass to get its code source. + // we can't use this.getClass() since we are in the boot. + testLoggerFinderClass = super.loadClass("LoggerFinderLoaderTest$TestLoggerFinder"); + } + URL url = testLoggerFinderClass.getProtectionDomain().getCodeSource().getLocation(); + File file = new File(url.getPath(), name+".class"); + if (file.canRead()) { + try { + byte[] b = Files.readAllBytes(file.toPath()); + Permissions perms = new Permissions(); + perms.add(new AllPermission()); + Class finderClass = defineClass( + name, b, 0, b.length, new ProtectionDomain( + this.getClass().getProtectionDomain().getCodeSource(), + perms)); + System.out.println("Loaded " + name); + finderClasses.put(name, finderClass); + return finderClass; + } catch (Throwable ex) { + ex.printStackTrace(); + throw new ClassNotFoundException(name, ex); + } + } else { + throw new ClassNotFoundException(name, + new IOException(file.toPath() + ": can't read")); + } + } + } + + @Override + public synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (finderClassNames.contains(name)) { + Class c = defineFinderClass(name); + if (resolve) { + resolveClass(c); + } + return c; + } + return super.loadClass(name, resolve); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if (finderClassNames.contains(name)) { + return defineFinderClass(name); + } + return super.findClass(name); + } + +} --- /dev/null 2015-11-20 17:44:35.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/LoggerFinderLoaderTest.java 2015-11-20 17:44:35.000000000 +0100 @@ -0,0 +1,882 @@ +/* + * 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.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.io.UncheckedIOException; +import java.security.AccessControlException; +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.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.stream.Stream; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.EnumSet; +import java.util.Iterator; +import java.util.Locale; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; +import java.util.concurrent.atomic.AtomicReference; +import jdk.internal.logger.SimpleConsoleLogger; + +/** + * @test + * @bug 8140364 + * @summary JDK implementation specific unit test for LoggerFinderLoader. + * Tests the behavior of LoggerFinderLoader with respect to the + * value of the internal diagnosability switches. Also test the + * DefaultLoggerFinder and SimpleConsoleLogger implementation. + * @modules java.base/sun.util.logging + * java.base/jdk.internal.logger + * @build AccessSystemLogger LoggerFinderLoaderTest CustomSystemClassLoader + * @run driver AccessSystemLogger + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader LoggerFinderLoaderTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader LoggerFinderLoaderTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader LoggerFinderLoaderTest WITHPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true LoggerFinderLoaderTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true LoggerFinderLoaderTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true LoggerFinderLoaderTest WITHPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=ERROR LoggerFinderLoaderTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=ERROR LoggerFinderLoaderTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=ERROR LoggerFinderLoaderTest WITHPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=DEBUG LoggerFinderLoaderTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=DEBUG LoggerFinderLoaderTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=DEBUG LoggerFinderLoaderTest WITHPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=QUIET LoggerFinderLoaderTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=QUIET LoggerFinderLoaderTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=QUIET LoggerFinderLoaderTest WITHPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true LoggerFinderLoaderTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true LoggerFinderLoaderTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true LoggerFinderLoaderTest WITHPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=ERROR LoggerFinderLoaderTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=ERROR LoggerFinderLoaderTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=ERROR LoggerFinderLoaderTest WITHPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=DEBUG LoggerFinderLoaderTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=DEBUG LoggerFinderLoaderTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=DEBUG LoggerFinderLoaderTest WITHPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=QUIET LoggerFinderLoaderTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=QUIET LoggerFinderLoaderTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=QUIET LoggerFinderLoaderTest WITHPERMISSIONS + * @author danielfuchs + */ +public class LoggerFinderLoaderTest { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + final static boolean VERBOSE = false; + static final ThreadLocal allowControl = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAccess = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + + final static AccessSystemLogger accessSystemLogger = new AccessSystemLogger(); + static final Class[] providerClass; + static { + try { + providerClass = new Class[] { + ClassLoader.getSystemClassLoader().loadClass("LoggerFinderLoaderTest$BaseLoggerFinder"), + ClassLoader.getSystemClassLoader().loadClass("LoggerFinderLoaderTest$BaseLoggerFinder2") + }; + } catch (ClassNotFoundException ex) { + throw new ExceptionInInitializerError(ex); + } + } + + /** + * What our test provider needs to implement. + */ + public static interface TestLoggerFinder { + public final static AtomicBoolean fails = new AtomicBoolean(); + public final static AtomicReference conf = new AtomicReference<>(""); + public final static AtomicLong sequencer = new AtomicLong(); + public final ConcurrentHashMap system = new ConcurrentHashMap<>(); + public final ConcurrentHashMap user = new ConcurrentHashMap<>(); + + public class LoggerImpl implements System.Logger { + final String name; + final Logger logger; + + public LoggerImpl(String name, Logger logger) { + this.name = name; + this.logger = logger; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isLoggable(Logger.Level level) { + return logger.isLoggable(level); + } + + @Override + public void log(Logger.Level level, ResourceBundle bundle, String key, Throwable thrown) { + logger.log(level, bundle, key, thrown); + } + + @Override + public void log(Logger.Level level, ResourceBundle bundle, String format, Object... params) { + logger.log(level, bundle, format, params); + } + + } + + public Logger getLogger(String name, Class caller); + public Logger getLocalizedLogger(String name, ResourceBundle bundle, Class caller); + } + + public static class BaseLoggerFinder extends LoggerFinder implements TestLoggerFinder { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + public BaseLoggerFinder() { + if (fails.get()) { + throw new RuntimeException("Simulate exception while loading provider"); + } + } + + System.Logger createSimpleLogger(String name) { + PrivilegedAction pa = () -> SimpleConsoleLogger.makeSimpleLogger(name, false); + return AccessController.doPrivileged(pa); + } + + + @Override + public Logger getLogger(String name, Class caller) { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGERFINDER_PERMISSION); + } + PrivilegedAction pa = () -> caller.getClassLoader(); + ClassLoader callerLoader = AccessController.doPrivileged(pa); + if (callerLoader == null) { + return system.computeIfAbsent(name, (n) -> new LoggerImpl(n, createSimpleLogger(name))); + } else { + return user.computeIfAbsent(name, (n) -> new LoggerImpl(n, createSimpleLogger(name))); + } + } + } + + public static class BaseLoggerFinder2 extends LoggerFinder implements TestLoggerFinder { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + public BaseLoggerFinder2() { + throw new ServiceConfigurationError("Should not come here"); + } + @Override + public Logger getLogger(String name, Class caller) { + throw new ServiceConfigurationError("Should not come here"); + } + } + + public static class MyBundle extends ResourceBundle { + + final ConcurrentHashMap map = new ConcurrentHashMap<>(); + + @Override + protected Object handleGetObject(String key) { + if (key.contains(" (translated)")) { + throw new RuntimeException("Unexpected key: " + key); + } + return map.computeIfAbsent(key, k -> k.toUpperCase(Locale.ROOT) + " (translated)"); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(map.keySet()); + } + + } + public static class MyLoggerBundle extends MyBundle { + + } + + static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS}; + + static void setSecurityManager() { + if (System.getSecurityManager() == null) { + Policy.setPolicy(new SimplePolicy(allowControl, allowAccess)); + System.setSecurityManager(new SecurityManager()); + } + } + + static LoggerFinder getLoggerFinder(Class expectedClass, + String errorPolicy, boolean singleton) { + LoggerFinder provider = null; + try { + TestLoggerFinder.sequencer.incrementAndGet(); + provider = LoggerFinder.getLoggerFinder(); + if (TestLoggerFinder.fails.get() || singleton) { + if ("ERROR".equals(errorPolicy.toUpperCase(Locale.ROOT))) { + throw new RuntimeException("Expected exception not thrown"); + } else if ("WARNING".equals(errorPolicy.toUpperCase(Locale.ROOT))) { + String warning = ErrorStream.errorStream.peek(); + if (!warning.contains("WARNING: Failed to instantiate LoggerFinder provider; Using default.")) { + throw new RuntimeException("Expected message not found. Error stream contained: " + warning); + } + } else if ("DEBUG".equals(errorPolicy.toUpperCase(Locale.ROOT))) { + String warning = ErrorStream.errorStream.peek(); + if (!warning.contains("WARNING: Failed to instantiate LoggerFinder provider; Using default.")) { + throw new RuntimeException("Expected message not found. Error stream contained: " + warning); + } + if (!warning.contains("WARNING: Exception raised trying to instantiate LoggerFinder")) { + throw new RuntimeException("Expected message not found. Error stream contained: " + warning); + } + if (TestLoggerFinder.fails.get()) { + if (!warning.contains("java.util.ServiceConfigurationError: java.lang.System$LoggerFinder: Provider LoggerFinderLoaderTest$BaseLoggerFinder could not be instantiated")) { + throw new RuntimeException("Expected message not found. Error stream contained: " + warning); + } + } else if (singleton) { + if (!warning.contains("java.util.ServiceConfigurationError: More than on LoggerFinder implementation")) { + throw new RuntimeException("Expected message not found. Error stream contained: " + warning); + } + } + } else if ("QUIET".equals(errorPolicy.toUpperCase(Locale.ROOT))) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("Unexpected error message found: " + + ErrorStream.errorStream.peek()); + } + } + } + } catch(AccessControlException a) { + throw a; + } catch(Throwable t) { + if (TestLoggerFinder.fails.get() || singleton) { + // must check System.err + if ("ERROR".equals(errorPolicy.toUpperCase(Locale.ROOT))) { + provider = LoggerFinder.getLoggerFinder(); + } else { + Throwable orig = t.getCause(); + while (orig != null && orig.getCause() != null) orig = orig.getCause(); + if (orig != null) orig.printStackTrace(ErrorStream.err); + throw new RuntimeException("Unexpected exception: " + t, t); + } + } else { + throw new RuntimeException("Unexpected exception: " + t, t); + } + } + expectedClass.cast(provider); + ErrorStream.errorStream.store(); + System.out.println("*** Actual LoggerFinder class is: " + provider.getClass().getName()); + return provider; + } + + + static class ErrorStream extends PrintStream { + + static AtomicBoolean forward = new AtomicBoolean(); + ByteArrayOutputStream out; + String saved = ""; + public ErrorStream(ByteArrayOutputStream out) { + super(out); + this.out = out; + } + + @Override + public void write(int b) { + super.write(b); + if (forward.get()) err.write(b); + } + + @Override + public void write(byte[] b) throws IOException { + super.write(b); + if (forward.get()) err.write(b); + } + + @Override + public void write(byte[] buf, int off, int len) { + super.write(buf, off, len); + if (forward.get()) err.write(buf, off, len); + } + + public String peek() { + flush(); + return out.toString(); + } + + public String drain() { + flush(); + String res = out.toString(); + out.reset(); + return res; + } + + public void store() { + flush(); + saved = out.toString(); + out.reset(); + } + + public void restore() { + out.reset(); + try { + out.write(saved.getBytes()); + } catch(IOException io) { + throw new UncheckedIOException(io); + } + } + + static final PrintStream err = System.err; + static final ErrorStream errorStream = new ErrorStream(new ByteArrayOutputStream()); + } + + private static StringBuilder appendProperty(StringBuilder b, String name) { + String value = System.getProperty(name); + if (value == null) return b; + return b.append(name).append("=").append(value).append('\n'); + } + + public static void main(String[] args) { + if (args.length == 0) { + args = new String[] { + "NOSECURITY", + "NOPERMISSIONS", + "WITHPERMISSIONS" + }; + } + Locale.setDefault(Locale.ENGLISH); + System.setErr(ErrorStream.errorStream); + System.setProperty("jdk.logger.packages", TestLoggerFinder.LoggerImpl.class.getName()); + //System.setProperty("jdk.logger.finder.error", "ERROR"); + //System.setProperty("jdk.logger.finder.singleton", "true"); + //System.setProperty("test.fails", "true"); + TestLoggerFinder.fails.set(Boolean.getBoolean("test.fails")); + StringBuilder c = new StringBuilder(); + appendProperty(c, "jdk.logger.packages"); + appendProperty(c, "jdk.logger.finder.error"); + appendProperty(c, "jdk.logger.finder.singleton"); + appendProperty(c, "test.fails"); + TestLoggerFinder.conf.set(c.toString()); + try { + test(args); + } finally { + try { + System.setErr(ErrorStream.err); + } catch (Error | RuntimeException x) { + x.printStackTrace(ErrorStream.err); + } + } + } + + + public static void test(String[] args) { + + final String errorPolicy = System.getProperty("jdk.logger.finder.error", "WARNING"); + final Boolean ensureSingleton = Boolean.getBoolean("jdk.logger.finder.singleton"); + + final Class expectedClass = + TestLoggerFinder.fails.get() || ensureSingleton + ? jdk.internal.logger.DefaultLoggerFinder.class + : TestLoggerFinder.class; + + System.out.println("Declared provider class: " + providerClass[0] + + "[" + providerClass[0].getClassLoader() + "]"); + + if (!TestLoggerFinder.fails.get()) { + ServiceLoader serviceLoader = + ServiceLoader.load(LoggerFinder.class, ClassLoader.getSystemClassLoader()); + Iterator iterator = serviceLoader.iterator(); + Object firstProvider = iterator.next(); + if (!firstProvider.getClass().getName().equals("LoggerFinderLoaderTest$BaseLoggerFinder")) { + throw new RuntimeException("Unexpected provider: " + firstProvider.getClass().getName()); + } + if (!iterator.hasNext()) { + throw new RuntimeException("Expected two providers"); + } + } + + Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> { + LoggerFinder provider; + ErrorStream.errorStream.restore(); + switch (testCase) { + case NOSECURITY: + System.out.println("\n*** Without Security Manager\n"); + System.out.println(TestLoggerFinder.conf.get()); + provider = getLoggerFinder(expectedClass, errorPolicy, ensureSingleton); + test(provider, true); + System.out.println("Tetscase count: " + TestLoggerFinder.sequencer.get()); + break; + case NOPERMISSIONS: + System.out.println("\n*** With Security Manager, without permissions\n"); + System.out.println(TestLoggerFinder.conf.get()); + setSecurityManager(); + try { + provider = getLoggerFinder(expectedClass, errorPolicy, ensureSingleton); + throw new RuntimeException("Expected exception not raised"); + } catch (AccessControlException x) { + if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) { + throw new RuntimeException("Unexpected permission check", x); + } + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = getLoggerFinder(expectedClass, errorPolicy, ensureSingleton); + } finally { + allowControl.get().set(control); + } + } + test(provider, false); + System.out.println("Tetscase count: " + TestLoggerFinder.sequencer.get()); + break; + case WITHPERMISSIONS: + System.out.println("\n*** With Security Manager, with control permission\n"); + System.out.println(TestLoggerFinder.conf.get()); + setSecurityManager(); + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = getLoggerFinder(expectedClass, errorPolicy, ensureSingleton); + test(provider, true); + } finally { + allowControl.get().set(control); + } + break; + default: + throw new RuntimeException("Unknown test case: " + testCase); + } + }); + System.out.println("\nPASSED: Tested " + TestLoggerFinder.sequencer.get() + " cases."); + } + + public static void test(LoggerFinder provider, boolean hasRequiredPermissions) { + + ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName()); + final Map loggerDescMap = new HashMap<>(); + + System.Logger sysLogger = accessSystemLogger.getLogger("foo"); + loggerDescMap.put(sysLogger, "accessSystemLogger.getLogger(\"foo\")"); + System.Logger localizedSysLogger = accessSystemLogger.getLogger("fox", loggerBundle); + loggerDescMap.put(localizedSysLogger, "accessSystemLogger.getLogger(\"fox\", loggerBundle)"); + System.Logger appLogger = System.getLogger("bar"); + loggerDescMap.put(appLogger,"System.getLogger(\"bar\")"); + System.Logger localizedAppLogger = System.getLogger("baz", loggerBundle); + loggerDescMap.put(localizedAppLogger,"System.getLogger(\"baz\", loggerBundle)"); + + testLogger(provider, loggerDescMap, "foo", null, sysLogger); + testLogger(provider, loggerDescMap, "foo", loggerBundle, localizedSysLogger); + testLogger(provider, loggerDescMap, "foo", null, appLogger); + testLogger(provider, loggerDescMap, "foo", loggerBundle, localizedAppLogger); + } + + public static class Foo { + + } + + static void verbose(String msg) { + if (VERBOSE) { + System.out.println(msg); + } + } + + // Calls the 8 methods defined on Logger and verify the + // parameters received by the underlying TestProvider.LoggerImpl + // logger. + private static void testLogger(LoggerFinder provider, + Map loggerDescMap, + String name, + ResourceBundle loggerBundle, + Logger logger) { + + System.out.println("Testing " + loggerDescMap.get(logger) + " [" + logger +"]"); + AtomicLong sequencer = TestLoggerFinder.sequencer; + + Foo foo = new Foo(); + String fooMsg = foo.toString(); + for (Level loggerLevel : EnumSet.of(Level.INFO)) { + for (Level messageLevel : Level.values()) { + ErrorStream.errorStream.drain(); + String desc = "logger.log(messageLevel, foo): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, foo); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + if (!logged.contains("LoggerFinderLoaderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + fooMsg)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] LoggerFinderLoaderTest testLogger\n" + + messageLevel.getName() + " " + fooMsg + + "\n>>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + String msg = "blah"; + for (Level loggerLevel : EnumSet.of(Level.INFO)) { + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, \"blah\"): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, msg); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + String msgText = loggerBundle == null ? msg : loggerBundle.getString(msg); + if (!logged.contains("LoggerFinderLoaderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + msgText)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] LoggerFinderLoaderTest testLogger\n" + + messageLevel.getName() + " " + msgText + + "\n>>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + Supplier fooSupplier = new Supplier() { + @Override + public String get() { + return this.toString(); + } + }; + + for (Level loggerLevel : EnumSet.of(Level.INFO)) { + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, fooSupplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, fooSupplier); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + if (!logged.contains("LoggerFinderLoaderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + fooSupplier.get())) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] LoggerFinderLoaderTest testLogger\n" + + messageLevel.getName() + " " + fooSupplier.get() + + "\n>>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + + String format = "two params [{1} {2}]"; + Object arg1 = foo; + Object arg2 = msg; + for (Level loggerLevel : EnumSet.of(Level.INFO)) { + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, format, params...): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, format, foo, msg); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + String msgFormat = loggerBundle == null ? format : loggerBundle.getString(format); + String text = java.text.MessageFormat.format(msgFormat, foo, msg); + if (!logged.contains("LoggerFinderLoaderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + text)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] LoggerFinderLoaderTest testLogger\n" + + messageLevel.getName() + " " + text + + "\n>>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + Throwable thrown = new Exception("OK: log me!"); + for (Level loggerLevel : EnumSet.of(Level.INFO)) { + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, \"blah\", thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, msg, thrown); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + thrown.printStackTrace(new PrintStream(baos)); + String text = baos.toString(); + String msgText = loggerBundle == null ? msg : loggerBundle.getString(msg); + if (!logged.contains("LoggerFinderLoaderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + msgText) + || !logged.contains(text)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] LoggerFinderLoaderTest testLogger\n" + + messageLevel.getName() + " " + msgText +"\n" + + text + + ">>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + + for (Level loggerLevel : EnumSet.of(Level.INFO)) { + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, thrown, fooSupplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, fooSupplier, thrown); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + thrown.printStackTrace(new PrintStream(baos)); + String text = baos.toString(); + if (!logged.contains("LoggerFinderLoaderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + fooSupplier.get()) + || !logged.contains(text)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] LoggerFinderLoaderTest testLogger\n" + + messageLevel.getName() + " " + fooSupplier.get() +"\n" + + text + + ">>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName()); + for (Level loggerLevel : EnumSet.of(Level.INFO)) { + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, bundle, format, params...): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, bundle, format, foo, msg); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + String text = java.text.MessageFormat.format(bundle.getString(format), foo, msg); + if (!logged.contains("LoggerFinderLoaderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + text)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] LoggerFinderLoaderTest testLogger\n" + + messageLevel.getName() + " " + text + + "\n>>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + for (Level loggerLevel : EnumSet.of(Level.INFO)) { + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, bundle, \"blah\", thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, bundle, msg, thrown); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + String textMsg = bundle.getString(msg); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + thrown.printStackTrace(new PrintStream(baos)); + String text = baos.toString(); + if (!logged.contains("LoggerFinderLoaderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + textMsg) + || !logged.contains(text)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] LoggerFinderLoaderTest testLogger\n" + + messageLevel.getName() + " " + textMsg +"\n" + + text + + ">>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + } + + 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 static RuntimePermission CONTROL = LOGGERFINDER_PERMISSION; + final static RuntimePermission ACCESS = new RuntimePermission("accessClassInPackage.jdk.internal.logger"); + + final Permissions permissions; + final ThreadLocal allowControl; + final ThreadLocal allowAccess; + public SimplePolicy(ThreadLocal allowControl, ThreadLocal allowAccess) { + this.allowControl = allowControl; + this.allowAccess = allowAccess; + permissions = new Permissions(); + permissions.add(new RuntimePermission("setIO")); + } + + Permissions getPermissions() { + if (allowControl.get().get() || allowAccess.get().get()) { + PermissionsBuilder builder = new PermissionsBuilder() + .addAll(permissions); + if (allowControl.get().get()) { + builder.add(CONTROL); + } + if (allowAccess.get().get()) { + builder.add(ACCESS); + } + return builder.toPermissions(); + } + return permissions; + } + + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + return getPermissions().implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + } +} --- /dev/null 2015-11-20 17:44:35.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/META-INF/services/java.lang.System$LoggerFinder 2015-11-20 17:44:35.000000000 +0100 @@ -0,0 +1,3 @@ +LoggerFinderLoaderTest$BaseLoggerFinder +LoggerFinderLoaderTest$BaseLoggerFinder2 + --- /dev/null 2015-11-20 17:44:36.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/internal/PlatformLoggerBridgeTest/CustomSystemClassLoader.java 2015-11-20 17:44:36.000000000 +0100 @@ -0,0 +1,101 @@ +/* + * 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.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.security.AllPermission; +import java.security.Permissions; +import java.security.ProtectionDomain; + + +/** + * A custom ClassLoader to load the concrete LoggerFinder class + * with all permissions. + * + * @author danielfuchs + */ +public class CustomSystemClassLoader extends ClassLoader { + + + Class loggerFinderClass = null; + + public CustomSystemClassLoader() { + super(); + } + public CustomSystemClassLoader(ClassLoader parent) { + super(parent); + } + + private Class defineFinderClass(String name) + throws ClassNotFoundException { + final Object obj = getClassLoadingLock(name); + synchronized(obj) { + if (loggerFinderClass != null) return loggerFinderClass; + + URL url = this.getClass().getProtectionDomain().getCodeSource().getLocation(); + File file = new File(url.getPath(), name+".class"); + if (file.canRead()) { + try { + byte[] b = Files.readAllBytes(file.toPath()); + Permissions perms = new Permissions(); + perms.add(new AllPermission()); + loggerFinderClass = defineClass( + name, b, 0, b.length, new ProtectionDomain( + this.getClass().getProtectionDomain().getCodeSource(), + perms)); + System.out.println("Loaded " + name); + return loggerFinderClass; + } catch (Throwable ex) { + ex.printStackTrace(); + throw new ClassNotFoundException(name, ex); + } + } else { + throw new ClassNotFoundException(name, + new IOException(file.toPath() + ": can't read")); + } + } + } + + @Override + public synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (name.endsWith("$LogProducerFinder")) { + Class c = defineFinderClass(name); + if (resolve) { + resolveClass(c); + } + return c; + } + return super.loadClass(name, resolve); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if (name.endsWith("$$LogProducerFinder")) { + return defineFinderClass(name); + } + return super.findClass(name); + } + +} --- /dev/null 2015-11-20 17:44:37.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/internal/PlatformLoggerBridgeTest/META-INF/services/java.lang.System$LoggerFinder 2015-11-20 17:44:36.000000000 +0100 @@ -0,0 +1 @@ +PlatformLoggerBridgeTest$LogProducerFinder --- /dev/null 2015-11-20 17:44:37.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/internal/PlatformLoggerBridgeTest/PlatformLoggerBridgeTest.java 2015-11-20 17:44:37.000000000 +0100 @@ -0,0 +1,876 @@ +/* + * 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.security.AccessControlException; +import java.security.AccessController; +import java.security.CodeSource; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.Permissions; +import java.security.Policy; +import java.security.PrivilegedAction; +import java.security.ProtectionDomain; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.ResourceBundle; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import java.util.logging.Handler; +import java.util.logging.LogRecord; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.util.stream.Stream; +import sun.util.logging.PlatformLogger; + +/** + * @test + * @bug 8140364 + * @summary JDK implementation specific unit test for JDK internal artifacts. + * Tests all bridge methods from PlatformLogger with the a custom + * backend whose loggers implement PlatformLogger.Bridge. + * @modules java.base/sun.util.logging + * @build CustomSystemClassLoader PlatformLoggerBridgeTest + * @run main/othervm -Djava.system.class.loader=CustomSystemClassLoader PlatformLoggerBridgeTest NOSECURITY + * @run main/othervm -Djava.system.class.loader=CustomSystemClassLoader PlatformLoggerBridgeTest NOPERMISSIONS + * @run main/othervm -Djava.system.class.loader=CustomSystemClassLoader PlatformLoggerBridgeTest WITHPERMISSIONS + * @author danielfuchs + */ +public class PlatformLoggerBridgeTest { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + final static AtomicLong sequencer = new AtomicLong(); + final static boolean VERBOSE = false; + static final ThreadLocal allowControl = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAccess = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAll = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + + static final Class providerClass; + static { + try { + // Preload classes before the security manager is on. + providerClass = ClassLoader.getSystemClassLoader().loadClass("PlatformLoggerBridgeTest$LogProducerFinder"); + ((LoggerFinder)providerClass.newInstance()).getLogger("foo", providerClass); + } catch (Exception ex) { + throw new ExceptionInInitializerError(ex); + } + } + + public static final Queue eventQueue = new ArrayBlockingQueue<>(128); + + public static final class LogEvent implements Cloneable { + + public LogEvent() { + this(sequencer.getAndIncrement()); + } + + LogEvent(long sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + long sequenceNumber; + boolean isLoggable; + String loggerName; + sun.util.logging.PlatformLogger.Level level; + ResourceBundle bundle; + Throwable thrown; + Object[] args; + String msg; + Supplier supplier; + String className; + String methodName; + + Object[] toArray() { + return new Object[] { + sequenceNumber, + loggerName, + level, + isLoggable, + bundle, + msg, + supplier, + thrown, + args, + className, + methodName, + }; + } + + @Override + public String toString() { + return Arrays.deepToString(toArray()); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof LogEvent + && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray()); + } + + @Override + public int hashCode() { + return Objects.hash(toArray()); + } + + public LogEvent cloneWith(long sequenceNumber) + throws CloneNotSupportedException { + LogEvent cloned = (LogEvent)super.clone(); + cloned.sequenceNumber = sequenceNumber; + return cloned; + } + + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + return LogEvent.of(sequenceNumber, isLoggable, name, + null, null, level, bundle, key, + thrown, params); + } + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + Supplier supplier, Throwable thrown, Object... params) { + return LogEvent.of(sequenceNumber, isLoggable, name, + null, null, level, bundle, supplier, + thrown, params); + } + + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + String className, String methodName, + sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + LogEvent evt = new LogEvent(sequenceNumber); + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = thrown; + evt.msg = key; + evt.isLoggable = isLoggable; + evt.className = className; + evt.methodName = methodName; + return evt; + } + + public static LogEvent of(boolean isLoggable, String name, + String className, String methodName, + sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + return LogEvent.of(sequencer.getAndIncrement(), isLoggable, name, + className, methodName, level, bundle, key, thrown, params); + } + + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + String className, String methodName, + sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + Supplier supplier, Throwable thrown, Object... params) { + LogEvent evt = new LogEvent(sequenceNumber); + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = thrown; + evt.supplier = supplier; + evt.isLoggable = isLoggable; + evt.className = className; + evt.methodName = methodName; + return evt; + } + + public static LogEvent of(boolean isLoggable, String name, + String className, String methodName, + sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + Supplier supplier, Throwable thrown, Object... params) { + return LogEvent.of(sequencer.getAndIncrement(), isLoggable, name, + className, methodName, level, bundle, supplier, thrown, params); + } + + } + + public static class LogProducerFinder extends LoggerFinder { + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + final ConcurrentHashMap system = new ConcurrentHashMap<>(); + final ConcurrentHashMap user = new ConcurrentHashMap<>(); + + public class LoggerImpl implements Logger, PlatformLogger.Bridge { + private final String name; + private sun.util.logging.PlatformLogger.Level level = sun.util.logging.PlatformLogger.Level.INFO; + private sun.util.logging.PlatformLogger.Level OFF = sun.util.logging.PlatformLogger.Level.OFF; + private sun.util.logging.PlatformLogger.Level FINE = sun.util.logging.PlatformLogger.Level.FINE; + private sun.util.logging.PlatformLogger.Level FINER = sun.util.logging.PlatformLogger.Level.FINER; + private sun.util.logging.PlatformLogger.Level FINEST = sun.util.logging.PlatformLogger.Level.FINEST; + private sun.util.logging.PlatformLogger.Level CONFIG = sun.util.logging.PlatformLogger.Level.CONFIG; + private sun.util.logging.PlatformLogger.Level INFO = sun.util.logging.PlatformLogger.Level.INFO; + private sun.util.logging.PlatformLogger.Level WARNING = sun.util.logging.PlatformLogger.Level.WARNING; + private sun.util.logging.PlatformLogger.Level SEVERE = sun.util.logging.PlatformLogger.Level.SEVERE; + + public LoggerImpl(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isLoggable(Level level) { + return this.level != OFF && this.level.intValue() <= level.getSeverity(); + } + + @Override + public void log(Level level, ResourceBundle bundle, + String key, Throwable thrown) { + throw new UnsupportedOperationException(); + } + + @Override + public void log(Level level, ResourceBundle bundle, + String format, Object... params) { + throw new UnsupportedOperationException(); + } + + void log(LogEvent event) { + eventQueue.add(event); + } + + @Override + public void log(Level level, Supplier msgSupplier) { + throw new UnsupportedOperationException(); + } + + @Override + public void log(Level level, Supplier msgSupplier, + Throwable thrown) { + throw new UnsupportedOperationException(); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, String msg) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, null, msg, null, (Object[])null)); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, + Supplier msgSupplier) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, null, msgSupplier, null, (Object[])null)); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, String msg, + Object... params) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, null, msg, null, params)); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, String msg, + Throwable thrown) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, null, msg, thrown, (Object[])null)); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, Throwable thrown, + Supplier msgSupplier) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, null, msgSupplier, thrown, (Object[])null)); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, null, msg, null, (Object[])null)); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, Supplier msgSupplier) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, null, msgSupplier, null, (Object[])null)); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Object... params) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, null, msg, null, params)); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Throwable thrown) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, null, msg, thrown, (Object[])null)); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, Throwable thrown, + Supplier msgSupplier) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, null, msgSupplier, thrown, (Object[])null)); + } + + @Override + public void logrb(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String msg, + Object... params) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, bundle, msg, null, params)); + } + + @Override + public void logrb(sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + String msg, Object... params) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, bundle, msg, null, params)); + } + + @Override + public void logrb(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String msg, + Throwable thrown) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, bundle, msg, thrown, (Object[])null)); + } + + @Override + public void logrb(sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + String msg, Throwable thrown) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, bundle, msg, thrown, (Object[])null)); + } + + @Override + public boolean isLoggable(sun.util.logging.PlatformLogger.Level level) { + return this.level != OFF && level.intValue() + >= this.level.intValue(); + } + + @Override + public boolean isEnabled() { + return this.level != OFF; + } + + + } + + @Override + public Logger getLogger(String name, Class caller) { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGERFINDER_PERMISSION); + } + PrivilegedAction pa = () -> caller.getClassLoader(); + ClassLoader callerLoader = AccessController.doPrivileged(pa); + if (callerLoader == null) { + return system.computeIfAbsent(name, (n) -> new LoggerImpl(n)); + } else { + return user.computeIfAbsent(name, (n) -> new LoggerImpl(n)); + } + } + } + + static final sun.util.logging.PlatformLogger.Level[] julLevels = { + sun.util.logging.PlatformLogger.Level.ALL, + sun.util.logging.PlatformLogger.Level.FINEST, + sun.util.logging.PlatformLogger.Level.FINER, + sun.util.logging.PlatformLogger.Level.FINE, + sun.util.logging.PlatformLogger.Level.CONFIG, + sun.util.logging.PlatformLogger.Level.INFO, + sun.util.logging.PlatformLogger.Level.WARNING, + sun.util.logging.PlatformLogger.Level.SEVERE, + sun.util.logging.PlatformLogger.Level.OFF, + }; + + public static class MyBundle extends ResourceBundle { + + final ConcurrentHashMap map = new ConcurrentHashMap(); + + @Override + protected Object handleGetObject(String key) { + if (key.contains(" (translated)")) { + throw new RuntimeException("Unexpected key: " + key); + } + return map.computeIfAbsent(key, k -> k + " (translated)"); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(map.keySet()); + } + + } + + public static class MyHandler extends Handler { + + @Override + public java.util.logging.Level getLevel() { + return java.util.logging.Level.ALL; + } + + @Override + public void publish(LogRecord record) { + eventQueue.add(LogEvent.of(sequencer.getAndIncrement(), + true, record.getLoggerName(), + record.getSourceClassName(), + record.getSourceMethodName(), + PlatformLogger.Level.valueOf(record.getLevel().getName()), + record.getResourceBundle(), record.getMessage(), + record.getThrown(), record.getParameters())); + } + @Override + public void flush() { + } + @Override + public void close() throws SecurityException { + } + + } + + public static class MyLoggerBundle extends MyBundle { + + } + + static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS}; + + static void setSecurityManager() { + if (System.getSecurityManager() == null) { + Policy.setPolicy(new SimplePolicy(allowControl, allowAccess, allowAll)); + System.setSecurityManager(new SecurityManager()); + } + } + + public static void main(String[] args) { + if (args.length == 0) + args = new String[] { + //"NOSECURITY", + "NOPERMISSIONS", + "WITHPERMISSIONS" + }; + + + Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> { + LoggerFinder provider; + switch (testCase) { + case NOSECURITY: + System.out.println("\n*** Without Security Manager\n"); + provider = LoggerFinder.getLoggerFinder(); + test(provider, true); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case NOPERMISSIONS: + System.out.println("\n*** With Security Manager, without permissions\n"); + setSecurityManager(); + try { + provider = LoggerFinder.getLoggerFinder(); + throw new RuntimeException("Expected exception not raised"); + } catch (AccessControlException x) { + if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) { + throw new RuntimeException("Unexpected permission check", x); + } + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = LoggerFinder.getLoggerFinder(); + } finally { + allowControl.get().set(control); + } + } + test(provider, false); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case WITHPERMISSIONS: + System.out.println("\n*** With Security Manager, with access permission\n"); + setSecurityManager(); + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = LoggerFinder.getLoggerFinder(); + } finally { + allowControl.get().set(control); + } + final boolean access = allowAccess.get().get(); + try { + allowAccess.get().set(true); + test(provider, true); + } finally { + allowAccess.get().set(access); + } + break; + default: + throw new RuntimeException("Unknown test case: " + testCase); + } + }); + System.out.println("\nPASSED: Tested " + sequencer.get() + " cases."); + } + + public static void test(LoggerFinder provider, boolean hasRequiredPermissions) { + + final Map loggerDescMap = new HashMap<>(); + + PlatformLogger sysLogger1 = null; + try { + sysLogger1 = PlatformLogger.getLogger("foo"); + loggerDescMap.put(sysLogger1, "PlatformLogger.getLogger(\"foo\")"); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a system logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(SimplePolicy.ACCESS_LOGGING)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + final boolean old = allowAccess.get().get(); + allowAccess.get().set(true); + try { + sysLogger1 = PlatformLogger.getLogger("foo"); + loggerDescMap.put(sysLogger1, "PlatformLogger.getLogger(\"foo\")"); + } finally { + allowAccess.get().set(old); + } + System.out.println("Got expected exception for system logger: " + acx); + } + + final LogProducerFinder.LoggerImpl sysSink; + boolean old = allowControl.get().get(); + allowControl.get().set(true); + try { + sysSink = LogProducerFinder.LoggerImpl.class.cast( + provider.getLogger("foo", Thread.class)); + } finally { + allowControl.get().set(old); + } + + testLogger(provider, loggerDescMap, "foo", null, sysLogger1, sysSink); + } + + public static class Foo { + + } + + static void verbose(String msg) { + if (VERBOSE) { + System.out.println(msg); + } + } + + static void checkLogEvent(LoggerFinder provider, String desc, + LogEvent expected) { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + + static void checkLogEvent(LoggerFinder provider, String desc, + LogEvent expected, boolean expectNotNull) { + LogEvent actual = eventQueue.poll(); + if (actual == null && !expectNotNull) return; + if (actual != null && !expectNotNull) { + throw new RuntimeException("Unexpected log event found for " + desc + + "\n\tgot: " + actual); + } + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + + static void setLevel( LogProducerFinder.LoggerImpl sink, + sun.util.logging.PlatformLogger.Level loggerLevel) { + sink.level = loggerLevel; + } + + // Calls the methods defined on LogProducer and verify the + // parameters received by the underlying LogProducerFinder.LoggerImpl + // logger. + private static void testLogger(LoggerFinder provider, + Map loggerDescMap, + String name, + ResourceBundle loggerBundle, + PlatformLogger logger, + LogProducerFinder.LoggerImpl sink) { + + System.out.println("Testing " + loggerDescMap.get(logger) + " [" + logger +"]"); + final sun.util.logging.PlatformLogger.Level OFF = sun.util.logging.PlatformLogger.Level.OFF; + final sun.util.logging.PlatformLogger.Level ALL = sun.util.logging.PlatformLogger.Level.OFF; + + Foo foo = new Foo(); + String fooMsg = foo.toString(); + System.out.println("\tlogger.log(messageLevel, fooMsg)"); + System.out.println("\tlogger.(fooMsg)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + if (messageLevel == ALL || messageLevel == OFF) continue; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, loggerBundle, + fooMsg, (Throwable)null, (Object[])null); + String desc2 = "logger." + messageLevel.toString().toLowerCase() + + "(fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + if (messageLevel == sun.util.logging.PlatformLogger.Level.FINEST) { + logger.finest(fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.FINER) { + logger.finer(fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.FINE) { + logger.fine(fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.CONFIG) { + logger.config(fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.INFO) { + logger.info(fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.WARNING) { + logger.warning(fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.SEVERE) { + logger.severe(fooMsg); + checkLogEvent(provider, desc2, expected); + } + } + } + + String format = "two params [{1} {2}]"; + Object arg1 = foo; + Object arg2 = fooMsg; + System.out.println("\tlogger.log(messageLevel, format, arg1, arg2)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + if (messageLevel == ALL || messageLevel == OFF) continue; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, loggerBundle, + format, (Throwable)null, arg1, arg2); + String desc2 = "logger." + messageLevel.toString().toLowerCase() + + "(format, foo, fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + if (messageLevel == sun.util.logging.PlatformLogger.Level.FINEST) { + logger.finest(format, arg1, arg2); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.FINER) { + logger.finer(format, arg1, arg2); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.FINE) { + logger.fine(format, arg1, arg2); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.CONFIG) { + logger.config(format, arg1, arg2); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.INFO) { + logger.info(format, arg1, arg2); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.WARNING) { + logger.warning(format, arg1, arg2); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.SEVERE) { + logger.severe(format, arg1, arg2); + checkLogEvent(provider, desc2, expected); + } + } + } + + Throwable thrown = new Exception("OK: log me!"); + System.out.println("\tlogger.log(messageLevel, fooMsg, thrown)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + if (messageLevel == ALL || messageLevel == OFF) continue; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, loggerBundle, + fooMsg, thrown, (Object[])null); + String desc2 = "logger." + messageLevel.toString().toLowerCase() + + "(fooMsg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + if (messageLevel == sun.util.logging.PlatformLogger.Level.FINEST) { + logger.finest(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.FINER) { + logger.finer(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.FINE) { + logger.fine(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.CONFIG) { + logger.config(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.INFO) { + logger.info(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.WARNING) { + logger.warning(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.SEVERE) { + logger.severe(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } + } + } + } + + 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 static RuntimePermission CONTROL = LOGGERFINDER_PERMISSION; + final static RuntimePermission ACCESS_LOGGER = new RuntimePermission("accessClassInPackage.jdk.internal.logger"); + final static RuntimePermission ACCESS_LOGGING = new RuntimePermission("accessClassInPackage.sun.util.logging"); + + final Permissions permissions; + final Permissions allPermissions; + final ThreadLocal allowControl; + final ThreadLocal allowAccess; + final ThreadLocal allowAll; + public SimplePolicy(ThreadLocal allowControl, + ThreadLocal allowAccess, + ThreadLocal allowAll) { + this.allowControl = allowControl; + this.allowAccess = allowAccess; + this.allowAll = allowAll; + permissions = new Permissions(); + allPermissions = new PermissionsBuilder() + .add(new java.security.AllPermission()) + .toPermissions(); + } + + Permissions getPermissions() { + if (allowControl.get().get() || allowAccess.get().get() || allowAll.get().get()) { + PermissionsBuilder builder = new PermissionsBuilder() + .addAll(permissions); + if (allowControl.get().get()) { + builder.add(CONTROL); + } + if (allowAccess.get().get()) { + builder.add(ACCESS_LOGGER); + builder.add(ACCESS_LOGGING); + } + if (allowAll.get().get()) { + builder.addAll(allPermissions); + } + return builder.toPermissions(); + } + return permissions; + } + + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + return getPermissions().implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + } +} --- /dev/null 2015-11-20 17:44:38.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/internal/api/LoggerFinderAPITest.java 2015-11-20 17:44:38.000000000 +0100 @@ -0,0 +1,497 @@ +/* + * 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. + */ + +/* + * @test + * @bug 8140364 + * @author danielfuchs + * @summary JDK implementation specific unit test for JDK internal artifacts. + * Tests the consistency of the LoggerFinder and JDK extensions. + * @modules java.base/sun.util.logging + * java.base/jdk.internal.logger + * @run main LoggerFinderAPITest + */ + + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.function.Supplier; +import java.util.logging.ConsoleHandler; +import java.util.logging.Handler; +import java.util.logging.LogRecord; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import sun.util.logging.PlatformLogger; + +public class LoggerFinderAPITest { + + static final Class spiLoggerClass + = java.lang.System.Logger.class; + static final Class jdkLoggerClass + = java.lang.System.Logger.class; + static final Class bridgeLoggerClass + = sun.util.logging.PlatformLogger.Bridge.class; + static final Class julLoggerClass + = java.util.logging.Logger.class; + static final Class julLogProducerClass + = PlatformLogger.Bridge.class; + static final Pattern julLogNames = Pattern.compile( + "^((log(p|rb)?)|severe|warning|info|config|fine|finer|finest|isLoggable)$"); + static final Collection julLoggerIgnores; + static { + List ignores = new ArrayList<>(); + try { + ignores.add(julLoggerClass.getDeclaredMethod("log", LogRecord.class)); + } catch (NoSuchMethodException | SecurityException ex) { + throw new ExceptionInInitializerError(ex); + } + julLoggerIgnores = Collections.unmodifiableList(ignores); + } + + + + // Don't require LoggerBridge to have a body for those methods + interface LoggerBridgeMethodsWithNoBody extends + PlatformLogger.Bridge, java.lang.System.Logger { + + @Override + public default String getName() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public default boolean isLoggable(PlatformLogger.Level level) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public default void log(sun.util.logging.PlatformLogger.Level level, + String msg, Throwable thrown) { + } + @Override + public default void log(sun.util.logging.PlatformLogger.Level level, + Throwable thrown, Supplier msgSupplier) { + } + @Override + public default void log(sun.util.logging.PlatformLogger.Level level, + Supplier msgSupplier) { + } + @Override + public default void log(sun.util.logging.PlatformLogger.Level level, String msg) { + } + @Override + public default void log(sun.util.logging.PlatformLogger.Level level, + String format, Object... params) { + } + @Override + public default void logrb(sun.util.logging.PlatformLogger.Level level, + ResourceBundle bundle, String key, Throwable thrown) { + } + @Override + public default void logrb(sun.util.logging.PlatformLogger.Level level, + ResourceBundle bundle, String format, Object... params) { + } + + @Override + public default void logrb(PlatformLogger.Level level, + String sourceClass, String sourceMethod, + ResourceBundle bundle, String msg, Throwable thrown) { + } + + @Override + public default void logrb(PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String msg, + Object... params) { + } + + @Override + public default void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Supplier msgSupplier) { + } + + @Override + public default void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Object... params) { + } + + @Override + public default void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Throwable thrown) { + } + + @Override + public default void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg) { + } + + @Override + public default void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Throwable thrown, + Supplier msgSupplier) { + } + + static boolean requiresDefaultBodyFor(Method m) { + try { + Method m2 = LoggerBridgeMethodsWithNoBody.class + .getDeclaredMethod(m.getName(), + m.getParameterTypes()); + return !m2.isDefault(); + } catch (NoSuchMethodException x) { + return true; + } + } + } + + final boolean warnDuplicateMappings; + public LoggerFinderAPITest(boolean verbose) { + this.warnDuplicateMappings = verbose; + for (Handler h : Logger.getLogger("").getHandlers()) { + if (h instanceof ConsoleHandler) { + Logger.getLogger("").removeHandler(h); + } + } + Logger.getLogger("").addHandler( new Handler() { + @Override + public void publish(LogRecord record) { + StringBuilder builder = new StringBuilder(); + builder.append("GOT LogRecord: ") + .append(record.getLevel().getLocalizedName()) + .append(": [").append(record.getLoggerName()) + .append("] ").append(record.getSourceClassName()) + .append('.') + .append(record.getSourceMethodName()).append(" -> ") + .append(record.getMessage()) + .append(' ') + .append(record.getParameters() == null ? "" + : Arrays.toString(record.getParameters())) + ; + System.out.println(builder); + if (record.getThrown() != null) { + record.getThrown().printStackTrace(System.out); + } + } + @Override public void flush() {} + @Override public void close() {} + }); + } + + public Stream getJulLogMethodStream(Class loggerClass) { + + return Stream.of(loggerClass.getMethods()).filter((x) -> { + final Matcher m = julLogNames.matcher(x.getName()); + return m.matches() ? x.getAnnotation(Deprecated.class) == null : false; + }); + } + + /** + * Tells whether a method invocation of 'origin' can be transformed in a + * method invocation of 'target'. + * This method only look at the parameter signatures, it doesn't look at + * the name, nor does it look at the return types. + *

+ * Example: + *

    + *
  • java.util.logging.Logger.log(Level, String, Object) can be invoked as
    + java.util.logging.spi.Logger.log(Level, String, Object...) because the + last parameter in 'target' is a varargs.
  • + *
  • java.util.logging.Logger.log(Level, String) can also be invoked as
    + java.util.logging.spi.Logger.log(Level, String, Object...) for the + same reason.
  • + *
+ *

+ * The algorithm is tailored for our needs: when the last parameter in the + * target is a vararg, and when origin & target have the same number of + * parameters, then we consider that the types of the last parameter *must* + * match. + *

+ * Similarly - we do not consider that o(X x, Y y, Y y) matches t(X x, Y... y) + * although strictly speaking, it should... + * + * @param origin The method in the original class + * @param target The correspondent candidate in the target class + * @return true if a method invocation of 'origin' can be transformed in a + * method invocation of 'target'. + */ + public boolean canBeInvokedAs(Method origin, Method target, + Map,Class> substitutes) { + final Class[] xParams = target.getParameterTypes(); + final Class[] mParams = Stream.of(origin.getParameterTypes()) + .map((x) -> substitutes.getOrDefault(x, x)) + .collect(Collectors.toList()).toArray(new Class[0]); + if (Arrays.deepEquals(xParams, mParams)) return true; + if (target.isVarArgs()) { + if (xParams.length == mParams.length) { + if (xParams[xParams.length-1].isArray()) { + return mParams[mParams.length -1].equals( + xParams[xParams.length -1].getComponentType()); + } + } else if (xParams.length == mParams.length + 1) { + return Arrays.deepEquals( + Arrays.copyOfRange(xParams, 0, xParams.length-1), mParams); + } + } + return false; + } + + /** + * Look whether {@code otherClass} has a public method similar to m + * @param m + * @param otherClass + * @return + */ + public Stream findInvokable(Method m, Class otherClass) { + final Map,Class> substitues = + Collections.singletonMap(java.util.logging.Level.class, + sun.util.logging.PlatformLogger.Level.class); + return Stream.of(otherClass.getMethods()) + .filter((x) -> m.getName().equals(x.getName())) + .filter((x) -> canBeInvokedAs(m, x, substitues)); + } + + /** + * Test that the concrete Logger implementation passed as parameter + * overrides all the methods defined by its interface. + * @param julLogger A concrete implementation of System.Logger + * whose backend is a JUL Logger. + */ + StringBuilder testDefaultJULLogger(java.lang.System.Logger julLogger) { + final StringBuilder errors = new StringBuilder(); + if (!bridgeLoggerClass.isInstance(julLogger)) { + final String errorMsg = + "Logger returned by LoggerFactory.getLogger(\"foo\") is not a " + + bridgeLoggerClass + "\n\t" + julLogger; + System.err.println(errorMsg); + errors.append(errorMsg).append('\n'); + } + final Class xClass = julLogger.getClass(); + List notOverridden = + Stream.of(bridgeLoggerClass.getDeclaredMethods()).filter((m) -> { + try { + Method x = xClass.getDeclaredMethod(m.getName(), m.getParameterTypes()); + return x == null; + } catch (NoSuchMethodException ex) { + return !Modifier.isStatic(m.getModifiers()); + } + }).collect(Collectors.toList()); + notOverridden.stream().filter((x) -> { + boolean shouldOverride = true; + try { + final Method m = xClass.getMethod(x.getName(), x.getParameterTypes()); + Method m2 = null; + try { + m2 = jdkLoggerClass.getDeclaredMethod(x.getName(), x.getParameterTypes()); + } catch (Exception e) { + + } + shouldOverride = m.isDefault() || m2 == null; + } catch (Exception e) { + // should override. + } + return shouldOverride; + }).forEach(x -> { + final String errorMsg = xClass.getName() + " should override\n\t" + x.toString(); + System.err.println(errorMsg); + errors.append(errorMsg).append('\n'); + }); + if (notOverridden.isEmpty()) { + System.out.println(xClass + " overrides all methods from " + bridgeLoggerClass); + } + return errors; + } + + public static class ResourceBundeParam extends ResourceBundle { + Map map = Collections.synchronizedMap(new LinkedHashMap<>()); + @Override + protected Object handleGetObject(String key) { + map.putIfAbsent(key, "${"+key+"}"); + return map.get(key); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(new LinkedHashSet<>(map.keySet())); + } + + } + + final ResourceBundle bundleParam = + ResourceBundle.getBundle(ResourceBundeParam.class.getName()); + + public static class ResourceBundeLocalized extends ResourceBundle { + Map map = Collections.synchronizedMap(new LinkedHashMap<>()); + @Override + protected Object handleGetObject(String key) { + map.putIfAbsent(key, "Localized:${"+key+"}"); + return map.get(key); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(new LinkedHashSet<>(map.keySet())); + } + + } + + final static ResourceBundle bundleLocalized = + ResourceBundle.getBundle(ResourceBundeLocalized.class.getName()); + + final Map, Object> params = new HashMap<>(); + { + params.put(String.class, "TestString"); + params.put(sun.util.logging.PlatformLogger.Level.class, sun.util.logging.PlatformLogger.Level.WARNING); + params.put(java.lang.System.Logger.Level.class, java.lang.System.Logger.Level.WARNING); + params.put(ResourceBundle.class, bundleParam); + params.put(Throwable.class, new Throwable("TestThrowable (Please ignore it!)")); + params.put(Object[].class, new Object[] {"One", "Two"}); + params.put(Object.class, new Object() { + @Override public String toString() { return "I am an object!"; } + }); + } + + public Object[] getParamsFor(Method m) { + final Object[] res = new Object[m.getParameterCount()]; + final Class[] sig = m.getParameterTypes(); + if (res.length == 0) { + return res; + } + for (int i=0; i) () -> msg; + } + if (p instanceof String) { + res[i] = String.valueOf(p)+"["+i+"]"; + } else { + res[i] = p; + } + } + return res; + } + + public void invokeOn(java.lang.System.Logger logger, Method m) { + Object[] p = getParamsFor(m); + try { + m.invoke(logger, p); + } catch (Exception e) { + throw new RuntimeException("Failed to invoke "+m.toString(), e); + } + } + + public void testAllJdkExtensionMethods(java.lang.System.Logger logger) { + Stream.of(jdkLoggerClass.getDeclaredMethods()) + .filter(m -> !Modifier.isStatic(m.getModifiers())) + .forEach((m) -> invokeOn(logger, m)); + } + + public void testAllAPIMethods(java.lang.System.Logger logger) { + Stream.of(spiLoggerClass.getDeclaredMethods()) + .filter(m -> !Modifier.isStatic(m.getModifiers())) + .forEach((m) -> invokeOn(logger, m)); + } + + public void testAllBridgeMethods(java.lang.System.Logger logger) { + Stream.of(bridgeLoggerClass.getDeclaredMethods()) + .filter(m -> !Modifier.isStatic(m.getModifiers())) + .forEach((m) -> invokeOn(logger, m)); + } + + public void testAllLogProducerMethods(java.lang.System.Logger logger) { + Stream.of(julLogProducerClass.getDeclaredMethods()) + .filter(m -> !Modifier.isStatic(m.getModifiers())) + .forEach((m) -> invokeOn(logger, m)); + } + + public StringBuilder testGetLoggerOverriddenOnSpi() { + final StringBuilder errors = new StringBuilder(); + Stream.of(jdkLoggerClass.getDeclaredMethods()) + .filter(m -> Modifier.isStatic(m.getModifiers())) + .filter(m -> Modifier.isPublic(m.getModifiers())) + .filter(m -> !m.getName().equals("getLoggerFinder")) + .filter(m -> { + try { + final Method x = bridgeLoggerClass.getDeclaredMethod(m.getName(), m.getParameterTypes()); + return x == null; + } catch (NoSuchMethodException ex) { + return true; + } + }).forEach(m -> { + final String errorMsg = bridgeLoggerClass.getName() + " should override\n\t" + m.toString(); + System.err.println(errorMsg); + errors.append(errorMsg).append('\n'); + }); + if (errors.length() == 0) { + System.out.println(bridgeLoggerClass + " overrides all static methods from " + jdkLoggerClass); + } else { + if (errors.length() > 0) throw new RuntimeException(errors.toString()); + } + return errors; + } + + public static void main(String argv[]) throws Exception { + final LoggerFinderAPITest test = new LoggerFinderAPITest(false); + final StringBuilder errors = new StringBuilder(); + errors.append(test.testGetLoggerOverriddenOnSpi()); + java.lang.System.Logger julLogger = + java.lang.System.LoggerFinder.getLoggerFinder() + .getLogger("foo", LoggerFinderAPITest.class); + errors.append(test.testDefaultJULLogger(julLogger)); + if (errors.length() > 0) throw new RuntimeException(errors.toString()); + java.lang.System.Logger julSystemLogger = + java.lang.System.LoggerFinder.getLoggerFinder() + .getLogger("bar", Thread.class); + errors.append(test.testDefaultJULLogger(julSystemLogger)); + if (errors.length() > 0) throw new RuntimeException(errors.toString()); + java.lang.System.Logger julLocalizedLogger = + (java.lang.System.Logger) + System.getLogger("baz", bundleLocalized); + java.lang.System.Logger julLocalizedSystemLogger = + java.lang.System.LoggerFinder.getLoggerFinder() + .getLocalizedLogger("oof", bundleLocalized, Thread.class); + final String error = errors.toString(); + if (!error.isEmpty()) throw new RuntimeException(error); + for (java.lang.System.Logger logger : new java.lang.System.Logger[] { + julLogger, julSystemLogger, julLocalizedLogger, julLocalizedSystemLogger + }) { + test.testAllJdkExtensionMethods(logger); + test.testAllAPIMethods(logger); + test.testAllBridgeMethods(logger); + test.testAllLogProducerMethods(logger); + } + } + +} --- /dev/null 2015-11-20 17:44:38.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/internal/backend/LoggerFinderBackendTest.java 2015-11-20 17:44:38.000000000 +0100 @@ -0,0 +1,2316 @@ +/* + * 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. + */ + +/** + * @test + * @bug 8140364 + * @author danielfuchs + * @summary JDK implementation specific unit test for JDK internal artifacts. + * This test tests all the public API methods defined in the {@link + * java.lang.System.Logger} interface, as well as all the JDK + * internal methods defined in the + * {@link sun.util.logging.PlatformLogger.Bridge} + * interface, with loggers returned by {@link + * java.lang.System.LoggerFinder#getLogger(java.lang.String, java.lang.Class)} + * and {@link java.lang.System.LoggerFinder#getLocalizedLogger(java.lang.String, + * java.util.ResourceBundle, java.lang.Class)} + * (using both a null resource bundle and a non null resource bundle). + * It calls both the {@link java.lang.System} factory methods and + * {@link jdk.internal.logger.LazyLoggers} to obtains those loggers, + * and configure them with all possible known levels. + * @modules java.base/sun.util.logging + * java.base/jdk.internal.logger + * java.logging/sun.util.logging.internal + * @build LoggerFinderBackendTest SystemClassLoader + * @run main/othervm -Djava.system.class.loader=SystemClassLoader -Dtest.logger.hidesProvider=true LoggerFinderBackendTest + * @run main/othervm -Djava.system.class.loader=SystemClassLoader -Dtest.logger.hidesProvider=false LoggerFinderBackendTest + */ + + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.MethodType; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.ResourceBundle; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiFunction; +import java.util.function.BooleanSupplier; +import java.util.function.Function; +import java.util.function.Supplier; +import java.lang.System.LoggerFinder; +import java.util.logging.ConsoleHandler; +import java.util.logging.Handler; +import sun.util.logging.PlatformLogger.Level; +import java.util.logging.LogManager; +import java.util.logging.LogRecord; +import java.util.logging.Logger; +import sun.util.logging.internal.LoggingProviderImpl; + +/** + * @author danielfuchs + */ +public class LoggerFinderBackendTest { + + // whether the implementation of Logger try to do a best + // effort for logp... If the provider is not hidden, then + // the logp() implementation comes from LoggerWrapper - which does a + // best effort. Otherwise, it comes from the default provider + // which does support logp. + static final boolean BEST_EFFORT_FOR_LOGP = + !Boolean.getBoolean("test.logger.hidesProvider"); + static final boolean VERBOSE = false; + + static final Class spiLoggerClass = + java.lang.System.Logger.class; + static final Class jdkLoggerClass = + java.lang.System.Logger.class; + static final Class bridgeLoggerClass = + sun.util.logging.PlatformLogger.Bridge.class; + + /** Use to retrieve the log records that were produced by the JUL backend */ + static class LoggerTesterHandler extends Handler { + public final List records = + Collections.synchronizedList(new ArrayList<>()); + + @Override + public void publish(LogRecord record) { + record.getSourceClassName(); record.getSourceMethodName(); + records.add(record); + } + + @Override + public void flush() { + } + + @Override + public void close() throws SecurityException { + records.clear(); + } + + public void reset() { + records.clear(); + } + } + + /** The {@link LoggerTesterHandler} handler is added to the root logger. */ + static final LoggerTesterHandler handler = new LoggerTesterHandler(); + static { + for (Handler h : Logger.getLogger("").getHandlers()) { + if (h instanceof ConsoleHandler) { + Logger.getLogger("").removeHandler(h); + } + } + Logger.getLogger("").addHandler(handler); + } + + /** + * A resource handler parameter that will be used when calling out the + * logrb-like methods - as well as when calling the level-specific + * methods that take a ResourceBundle parameter. + */ + public static class ResourceBundeParam extends ResourceBundle { + Map map = Collections.synchronizedMap(new LinkedHashMap<>()); + @Override + protected Object handleGetObject(String key) { + map.putIfAbsent(key, "${"+key+"}"); + return map.get(key); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(new LinkedHashSet<>(map.keySet())); + } + + } + + final static ResourceBundle bundleParam = + ResourceBundle.getBundle(ResourceBundeParam.class.getName()); + + /** + * A resource handler parameter that will be used when creating localized + * loggers by calling {@link + * LoggerFinder#getLocalizedLogger(java.lang.String, java.util.ResourceBundle, java.lang.Class)}. + */ + public static class ResourceBundeLocalized extends ResourceBundle { + Map map = Collections.synchronizedMap(new LinkedHashMap<>()); + @Override + protected Object handleGetObject(String key) { + map.putIfAbsent(key, "Localized:${"+key+"}"); + return map.get(key); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(new LinkedHashSet<>(map.keySet())); + } + + } + + /** + * The Levels enum is used to call all the level-specific methods on + * a logger instance. To minimize the amount of code it uses reflection + * to do so. + */ + static Lookup lookup = MethodHandles.lookup(); + public enum Levels { + /** Used to call all forms of Logger.log?(SEVERE, ...) */ + SEVERE("severe", bridgeLoggerClass, Level.SEVERE, null, "error", false), + /** Used to call all forms of Logger.log?(WARNING,...) */ + WARNING("warning", bridgeLoggerClass, Level.WARNING, "warning", "warning", false), + /** Used to call all forms of Logger.log?(INFO,...) */ + INFO("info", bridgeLoggerClass, Level.INFO, "info", "info", false), + /** Used to call all forms of Logger.log?(CONFIG,...) */ + CONFIG("config", bridgeLoggerClass, Level.CONFIG, null, "debug", false), + /** Used to call all forms of Logger.log?(FINE,...) */ + FINE("fine", bridgeLoggerClass, Level.FINE, null, "debug", false), + /** Used to call all forms of Logger.log?(FINER,...) */ + FINER("finer", bridgeLoggerClass, Level.FINER, null, "trace", false), + /** Used to call all forms of Logger.log?(FINEST,...) */ + FINEST("finest", bridgeLoggerClass, Level.FINEST, null, "trace", false), + ; + public final String method; // The name of the level-specific method to call + public final Class definingClass; // which interface j.u.logger.Logger or j.u.logging.spi.Logger defines it + public final Level platformLevel; // The platform Level it will be mapped to in Jul when Jul is the backend + public final String jdkExtensionToJUL; // The name of the method called on the JUL logger when JUL is the backend + public final String julToJdkExtension; // The name of the method called in the jdk extension by the default impl in jdk.internal.logging.Logger + public final String enableMethod; // The name of the isXxxxEnabled method + public final boolean hasSpecificIsEnabled; + Levels(String method, Class definingClass, Level defaultMapping, + String jdkExtensionToJUL, String julToJdkExtension, + boolean hasSpecificIsEnabled) { + this.method = method; + this.definingClass = definingClass; + this.platformLevel = defaultMapping; + this.jdkExtensionToJUL = jdkExtensionToJUL; + this.julToJdkExtension = julToJdkExtension; + this.hasSpecificIsEnabled = hasSpecificIsEnabled; + if (hasSpecificIsEnabled) { + this.enableMethod = "is" + method.substring(0,1).toUpperCase() + + method.substring(1) + "Enabled"; + } else { + this.enableMethod = "isLoggable"; + } + } + + /* + * calls this level specific method - e.g. if this==INFO: logger.info(msg); + */ + public void level(Object logger, String msg) { + MethodType mt = MethodType.methodType(void.class, Level.class, String.class); + invoke("log", logger, mt, platformLevel, msg); + } + + /* + * calls this level specific method - e.g. if this==INFO: logger.info(msgSupplier); + */ + public void level(Object logger, Supplier msgSupplier) { + MethodType mt = MethodType.methodType(void.class, Level.class, Supplier.class); + invoke("log", logger, mt, platformLevel, msgSupplier); + } + + /* + * calls this level specific method - e.g. if this==INFO: logger.info(msg, params); + */ + public void level(Object logger, String msg, Object... params) { + MethodType mt = MethodType.methodType(void.class, Level.class, String.class, + Object[].class); + invoke("log", logger, mt, platformLevel, msg, params); + } + + /* + * calls this level specific method - e.g. if this==INFO: logger.info(msg, thrown); + */ + public void level(Object logger, String msg, Throwable thrown) { + MethodType mt = MethodType.methodType(void.class, Level.class, String.class, + Throwable.class); + invoke("log", logger, mt, platformLevel, msg, thrown); + } + + /* + * calls this level specific method - e.g. if this==INFO: logger.info(msgSupplier, thrown); + */ + public void level(Object logger, Supplier msgSupplier, Throwable thrown) { + MethodType mt = MethodType.methodType(void.class, Level.class, + Throwable.class, Supplier.class); + invoke("log", logger, mt, platformLevel, thrown, msgSupplier); + } + + /* + * calls this level specific method - e.g. if this==INFO: logger.info(bundle, msg); + */ + public void level(Object logger, String msg, ResourceBundle bundle) { + MethodType mt = MethodType.methodType(void.class, Level.class, + ResourceBundle.class, String.class, Object[].class); + invoke("logrb", logger, mt, platformLevel, bundle, msg, null); + } + + public void level(Object logger, String msg, ResourceBundle bundle, + Object... params) { + MethodType mt = MethodType.methodType(void.class, Level.class, + ResourceBundle.class, String.class, Object[].class); + invoke("logrb", logger, mt, platformLevel, bundle, msg, params); + } + + public void level(Object logger, String msg, ResourceBundle bundle, + Throwable thrown) { + MethodType mt = MethodType.methodType(void.class, Level.class, + ResourceBundle.class, String.class, Throwable.class); + invoke("logrb", logger, mt, platformLevel, bundle, msg, thrown); + } + + public boolean isEnabled(Object logger) { + try { + if (hasSpecificIsEnabled) { + MethodType mt = MethodType.methodType(boolean.class); + final MethodHandle handle = lookup.findVirtual(definingClass, + enableMethod, mt).bindTo(logger); + return Boolean.class.cast(handle.invoke()); + } else { + MethodType mt = MethodType.methodType(boolean.class, + Level.class); + final MethodHandle handle = lookup.findVirtual(definingClass, + enableMethod, mt).bindTo(logger); + return Boolean.class.cast(handle.invoke(platformLevel)); + } + } catch (Throwable ex) { + throw new RuntimeException(ex); + } + } + + private void invoke(String method, Object logger, MethodType mt, Object... args) { + try { + final int last = mt.parameterCount()-1; + boolean isVarargs = mt.parameterType(last).isArray(); + final MethodHandle handle = lookup.findVirtual(definingClass, + method, mt).bindTo(logger); + + final StringBuilder builder = new StringBuilder(); + builder.append(logger.getClass().getSimpleName()).append('.') + .append(method).append('('); + String sep = ""; + int offset = 0; + Object[] params = args; + for (int i=0; (i-offset) < params.length; i++) { + if (isVarargs && i == last) { + offset = last; + params = (Object[])args[i]; + if (params == null) break; + } + Object p = params[i - offset]; + String quote = (p instanceof String) ? "\"" : ""; + builder.append(sep).append(quote).append(p).append(quote); + sep = ", "; + } + builder.append(')'); + if (verbose) { + System.out.println(builder); + } + handle.invokeWithArguments(args); + } catch (Throwable ex) { + throw new RuntimeException(ex); + } + } + + }; + + static interface Checker extends BiFunction {} + static interface JdkLogTester + extends BiFunction {} + static interface SpiLogTester + extends BiFunction {} + + static interface MethodInvoker { + public void logX(LOGGER logger, LEVEL level, Object... args); + } + + public enum JdkLogMethodInvoker + implements MethodInvoker { + /** + * Tests {@link + * jdk.internal.logging.Logger#log(Level, String, Object...)}; + **/ + LOG_STRING_PARAMS("log", MethodType.methodType(void.class, + Level.class, String.class, Object[].class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#log(Level, String, Throwable)}; + **/ + LOG_STRING_THROWN("log", MethodType.methodType(void.class, + Level.class, String.class, Throwable.class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#log(Level, Supplier)}; + **/ + LOG_SUPPLIER("log", MethodType.methodType(void.class, + Level.class, Supplier.class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#log(Level, Throwable, Supplier)}; + **/ + LOG_SUPPLIER_THROWN("log", MethodType.methodType(void.class, + Level.class, Throwable.class, Supplier.class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#logp(Level, String, String, String)}; + **/ + LOGP_STRING("logp", MethodType.methodType(void.class, + Level.class, String.class, String.class, String.class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#logp(Level, String, String, String, Object...)}; + **/ + LOGP_STRING_PARAMS("logp", MethodType.methodType(void.class, + Level.class, String.class, String.class, String.class, Object[].class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#logp(Level, String, String, String, Throwable)}; + **/ + LOGP_STRING_THROWN("logp", MethodType.methodType(void.class, + Level.class, String.class, String.class, String.class, Throwable.class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#logp(Level, String, String, Supplier)}; + **/ + LOGP_SUPPLIER("logp", MethodType.methodType(void.class, + Level.class, String.class, String.class, Supplier.class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#logp(Level, String, String, Throwable, Supplier)}; + **/ + LOGP_SUPPLIER_THROWN("logp", MethodType.methodType(void.class, + Level.class, String.class, String.class, + Throwable.class, Supplier.class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#logrb(Level, ResourceBundle, String, Object...)}; + **/ + LOGRB_STRING_PARAMS("logrb", MethodType.methodType(void.class, + Level.class, ResourceBundle.class, String.class, Object[].class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#logrb(Level, ResourceBundle, String, Throwable)}; + **/ + LOGRB_STRING_THROWN("logrb", MethodType.methodType(void.class, + Level.class, ResourceBundle.class, String.class, Throwable.class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#logrb(Level, String, String, ResourceBundle, String, Object...)}; + **/ + LOGRBP_STRING_PARAMS("logrb", MethodType.methodType(void.class, + Level.class, String.class, String.class, ResourceBundle.class, + String.class, Object[].class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#logrb(Level, String, String, ResourceBundle, String, Throwable)}; + **/ + LOGRBP_STRING_THROWN("logrb", MethodType.methodType(void.class, + Level.class, String.class, String.class, ResourceBundle.class, + String.class, Throwable.class)), + ; + final MethodType mt; + final String method; + JdkLogMethodInvoker(String method, MethodType mt) { + this.mt = mt; + this.method = method; + } + Object[] makeArgs(Level level, Object... rest) { + List list = new ArrayList<>(rest == null ? 1 : rest.length + 1); + list.add(level); + if (rest != null) { + list.addAll(Arrays.asList(rest)); + } + return list.toArray(new Object[list.size()]); + } + + @Override + public void logX(sun.util.logging.PlatformLogger.Bridge logger, Level level, Object... args) { + try { + MethodHandle handle = lookup.findVirtual(bridgeLoggerClass, + method, mt).bindTo(logger); + final int last = mt.parameterCount()-1; + boolean isVarargs = mt.parameterType(last).isArray(); + + args = makeArgs(level, args); + + final StringBuilder builder = new StringBuilder(); + builder.append(logger.getClass().getSimpleName()).append('.') + .append(this.method).append('('); + String sep = ""; + int offset = 0; + Object[] params = args; + for (int i=0; (i-offset) < params.length; i++) { + if (isVarargs && i == last) { + offset = last; + params = (Object[])args[i]; + if (params == null) break; + } + Object p = params[i - offset]; + String quote = (p instanceof String) ? "\"" : ""; + p = p instanceof Level ? "Level."+p : p; + builder.append(sep).append(quote).append(p).append(quote); + sep = ", "; + } + builder.append(')'); + if (verbose) System.out.println(builder); + handle.invokeWithArguments(args); + } catch (Throwable ex) { + throw new RuntimeException(ex); + } + } + } + + + public enum SpiLogMethodInvoker implements MethodInvoker { + /** + * Tests {@link + * jdk.internal.logging.Logger#log(Level, String, Object...)}; + **/ + LOG_STRING_PARAMS("log", MethodType.methodType(void.class, + java.lang.System.Logger.Level.class, String.class, Object[].class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#log(Level, String, Throwable)}; + **/ + LOG_STRING_THROWN("log", MethodType.methodType(void.class, + java.lang.System.Logger.Level.class, String.class, Throwable.class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#log(Level, Supplier)}; + **/ + LOG_SUPPLIER("log", MethodType.methodType(void.class, + java.lang.System.Logger.Level.class, Supplier.class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#log(Level, Throwable, Supplier)}; + **/ + LOG_SUPPLIER_THROWN("log", MethodType.methodType(void.class, + java.lang.System.Logger.Level.class, Supplier.class, Throwable.class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#log(Level, Supplier)}; + **/ + LOG_OBJECT("log", MethodType.methodType(void.class, + java.lang.System.Logger.Level.class, Object.class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#logrb(Level, ResourceBundle, String, Object...)}; + **/ + LOGRB_STRING_PARAMS("log", MethodType.methodType(void.class, + java.lang.System.Logger.Level.class, ResourceBundle.class, + String.class, Object[].class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#logrb(Level, ResourceBundle, String, Throwable)}; + **/ + LOGRB_STRING_THROWN("log", MethodType.methodType(void.class, + java.lang.System.Logger.Level.class, ResourceBundle.class, + String.class, Throwable.class)), + ; + final MethodType mt; + final String method; + SpiLogMethodInvoker(String method, MethodType mt) { + this.mt = mt; + this.method = method; + } + Object[] makeArgs(java.lang.System.Logger.Level level, Object... rest) { + List list = new ArrayList<>(rest == null ? 1 : rest.length + 1); + list.add(level); + if (rest != null) { + list.addAll(Arrays.asList(rest)); + } + return list.toArray(new Object[list.size()]); + } + + @Override + public void logX(java.lang.System.Logger logger, + java.lang.System.Logger.Level level, Object... args) { + try { + MethodHandle handle = lookup.findVirtual(spiLoggerClass, + method, mt).bindTo(logger); + final int last = mt.parameterCount()-1; + boolean isVarargs = mt.parameterType(last).isArray(); + + args = makeArgs(level, args); + + final StringBuilder builder = new StringBuilder(); + builder.append(logger.getClass().getSimpleName()).append('.') + .append(this.method).append('('); + String sep = ""; + int offset = 0; + Object[] params = args; + for (int i=0; (i-offset) < params.length; i++) { + if (isVarargs && i == last) { + offset = last; + params = (Object[])args[i]; + if (params == null) break; + } + Object p = params[i - offset]; + String quote = (p instanceof String) ? "\"" : ""; + p = p instanceof Level ? "Level."+p : p; + builder.append(sep).append(quote).append(p).append(quote); + sep = ", "; + } + builder.append(')'); + if (verbose) System.out.println(builder); + handle.invokeWithArguments(args); + } catch (Throwable ex) { + throw new RuntimeException(ex); + } + } + } + + + public abstract static class BackendTester { + static final Level[] levelMap = {Level.ALL, Level.FINER, Level.FINE, + Level.INFO, Level.WARNING, Level.SEVERE, Level.OFF}; + + abstract class BackendAdaptor { + public abstract String getLoggerName(BackendRecord res); + public abstract Object getLevel(BackendRecord res); + public abstract String getMessage(BackendRecord res); + public abstract String getSourceClassName(BackendRecord res); + public abstract String getSourceMethodName(BackendRecord res); + public abstract Throwable getThrown(BackendRecord res); + public abstract ResourceBundle getResourceBundle(BackendRecord res); + public abstract void setLevel(java.lang.System.Logger logger, + Level level); + public abstract void setLevel(java.lang.System.Logger logger, + java.lang.System.Logger.Level level); + public abstract List getBackendRecords(); + public abstract void resetBackendRecords(); + public boolean shouldBeLoggable(Levels level, Level loggerLevel) { + final Level logLevel = level.platformLevel; + return shouldBeLoggable(logLevel, loggerLevel); + } + public boolean shouldBeLoggable(Level logLevel, Level loggerLevel) { + return loggerLevel.intValue() != Level.OFF.intValue() + && logLevel.intValue() >= loggerLevel.intValue(); + } + public boolean shouldBeLoggable(java.lang.System.Logger.Level logLevel, + java.lang.System.Logger.Level loggerLevel) { + return loggerLevel != java.lang.System.Logger.Level.OFF + && logLevel.ordinal() >= loggerLevel.ordinal(); + } + public boolean isLoggable(java.lang.System.Logger logger, Level l) { + return bridgeLoggerClass.cast(logger).isLoggable(l); + } + public String getCallerClassName(Levels level, String clazz) { + return clazz != null ? clazz : Levels.class.getName(); + } + public String getCallerClassName(MethodInvoker logMethod, + String clazz) { + return clazz != null ? clazz : logMethod.getClass().getName(); + } + public String getCallerMethodName(Levels level, String method) { + return method != null ? method : "invoke"; + } + public String getCallerMethodName(MethodInvoker logMethod, + String method) { + return method != null ? method : "logX"; + } + public Object getMappedLevel(Object level) { + return level; + } + + public Level toJUL(java.lang.System.Logger.Level level) { + return levelMap[level.ordinal()]; + } + } + + public final boolean isSystem; + public final Class restrictedTo; + public final ResourceBundle localized; + public BackendTester(boolean isSystem) { + this(isSystem,null,null); + } + public BackendTester(boolean isSystem, ResourceBundle localized) { + this(isSystem,null,localized); + } + public BackendTester(boolean isSystem, + Class restrictedTo) { + this(isSystem, restrictedTo, null); + } + public BackendTester(boolean isSystem, + Class restrictedTo, + ResourceBundle localized) { + this.isSystem = isSystem; + this.restrictedTo = restrictedTo; + this.localized = localized; + } + + public java.lang.System.Logger convert(java.lang.System.Logger logger) { + return logger; + } + + public static Level[] LEVELS = { + Level.OFF, + Level.SEVERE, Level.WARNING, Level.INFO, Level.CONFIG, + Level.FINE, Level.FINER, Level.FINEST, + Level.ALL + }; + + abstract BackendAdaptor adaptor(); + + protected void checkRecord(Levels test, BackendRecord res, String loggerName, + Level level, String msg, String className, String methodName, + Throwable thrown, ResourceBundle bundle, Object... params) { + checkRecord(test, res, loggerName, level, ()->msg, className, + methodName, thrown, bundle, params); + + } + protected void checkRecord(Levels test, BackendRecord res, String loggerName, + Level level, Supplier msg, String className, String methodName, + Throwable thrown, ResourceBundle bundle, Object... params) { + checkRecord(test.method, res, loggerName, level, msg, + className, methodName, thrown, bundle, params); + } + protected void checkRecord(String logMethod, BackendRecord res, String loggerName, + L level, Supplier msg, String className, String methodName, + Throwable thrown, ResourceBundle bundle, Object... params) { + final BackendAdaptor analyzer = adaptor(); + if (! Objects.equals(analyzer.getLoggerName(res), loggerName)) { + throw new RuntimeException(logMethod+": expected logger name " + + loggerName + " got " + analyzer.getLoggerName(res)); + } + if (!Objects.equals(analyzer.getLevel(res), analyzer.getMappedLevel(level))) { + throw new RuntimeException(logMethod+": expected level " + + analyzer.getMappedLevel(level) + " got " + analyzer.getLevel(res)); + } + if (!Objects.equals(analyzer.getMessage(res), msg.get())) { + throw new RuntimeException(logMethod+": expected message \"" + + msg.get() + "\" got \"" + analyzer.getMessage(res) +"\""); + } + if (!Objects.equals(analyzer.getSourceClassName(res), className)) { + throw new RuntimeException(logMethod + + ": expected class name \"" + className + + "\" got \"" + analyzer.getSourceClassName(res) +"\""); + } + if (!Objects.equals(analyzer.getSourceMethodName(res), methodName)) { + throw new RuntimeException(logMethod + + ": expected method name \"" + methodName + + "\" got \"" + analyzer.getSourceMethodName(res) +"\""); + } + final Throwable thrownRes = analyzer.getThrown(res); + if (!Objects.equals(thrownRes, thrown)) { + throw new RuntimeException(logMethod + + ": expected throwable \"" + thrown + + "\" got \"" + thrownRes + "\""); + } + if (!Objects.equals(analyzer.getResourceBundle(res), bundle)) { + throw new RuntimeException(logMethod + + ": expected bundle \"" + bundle + + "\" got \"" + analyzer.getResourceBundle(res) +"\""); + } + } + + public void testLevel(Levels level, java.lang.System.Logger logger, + String msg) { + Runnable test = () -> level.level(logger, msg); + Checker check = (res, l) -> { + checkRecord(level, res, logger.getName(), l, msg, + adaptor().getCallerClassName(level, Levels.class.getName()), + adaptor().getCallerMethodName(level, "invoke"), + null, localized); + return null; + }; + test("msg", level, logger, test, check); + } + + public void testLevel(Levels level, java.lang.System.Logger logger, + String msg, Object... params) { + Runnable test = () -> level.level(logger, msg, (Object[])params); + Checker check = (res, l) -> { + checkRecord(level, res, logger.getName(), l, msg, + adaptor().getCallerClassName(level, Levels.class.getName()), + adaptor().getCallerMethodName(level, "invoke"), + null, localized, (Object[])params); + return null; + }; + test("msg, params", level, logger, test, check); + } + + public void testLevel(Levels level, java.lang.System.Logger logger, + String msg, Throwable thrown) { + Runnable test = () -> level.level(logger, msg, thrown); + Checker check = (res, l) -> { + checkRecord(level, res, logger.getName(), l, msg, + adaptor().getCallerClassName(level, Levels.class.getName()), + adaptor().getCallerMethodName(level, "invoke"), + thrown, localized); + return null; + }; + test("msg, thrown", level, logger, test, check); + } + + public void testLevel(Levels level, java.lang.System.Logger logger, + Supplier msg) { + Runnable test = () -> level.level(logger, msg); + Checker check = (res, l) -> { + checkRecord(level, res, logger.getName(), l, msg, + adaptor().getCallerClassName(level, Levels.class.getName()), + adaptor().getCallerMethodName(level, "invoke"), + null, null); + return null; + }; + test("msgSupplier", level, logger, test, check); + } + + public void testLevel(Levels level, java.lang.System.Logger logger, + Supplier msg, Throwable thrown) { + Runnable test = () -> level.level(logger, msg, thrown); + Checker check = (res, l) -> { + checkRecord(level, res, logger.getName(), l, msg, + adaptor().getCallerClassName(level, Levels.class.getName()), + adaptor().getCallerMethodName(level, "invoke"), + thrown, null); + return null; + }; + test("throw, msgSupplier", level, logger, test, check); + } + + public void testLevel(Levels level, java.lang.System.Logger logger, + String msg, ResourceBundle bundle) { + Runnable test = () -> level.level(logger, msg, bundle); + Checker check = (res, l) -> { + checkRecord(level, res, logger.getName(), l, msg, + adaptor().getCallerClassName(level, Levels.class.getName()), + adaptor().getCallerMethodName(level, "invoke"), + null, bundle); + return null; + }; + test("bundle, msg", level, logger, test, check); + } + + public void testLevel(Levels level, java.lang.System.Logger logger, + String msg, ResourceBundle bundle, Object... params) { + Runnable test = () -> level.level(logger, msg, bundle, (Object[])params); + Checker check = (res, l) -> { + checkRecord(level, res, logger.getName(), l, msg, + adaptor().getCallerClassName(level, Levels.class.getName()), + adaptor().getCallerMethodName(level, "invoke"), + null, bundle, (Object[])params); + return null; + }; + test("bundle, msg, params", level, logger, test, check); + } + + public void testLevel(Levels level, java.lang.System.Logger logger, + String msg, ResourceBundle bundle, Throwable thrown) { + Runnable test = () -> level.level(logger, msg, bundle, thrown); + Checker check = (res, l) -> { + checkRecord(level, res, logger.getName(), l, msg, + adaptor().getCallerClassName(level, Levels.class.getName()), + adaptor().getCallerMethodName(level, "invoke"), + thrown, bundle); + return null; + }; + test("bundle, msg, throwable", level, logger, test, check); + } + + // System.Logger + public void testSpiLog(java.lang.System.Logger logger, String msg) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + SpiLogMethodInvoker.LOG_STRING_PARAMS, + SpiLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + SpiLogMethodInvoker.LOG_STRING_PARAMS, + "logX"), null, localized); + return null; + }; + SpiLogTester tester = (x, level) -> { + SpiLogMethodInvoker.LOG_STRING_PARAMS.logX(x, level, msg, (Object[])null); + return null; + }; + Function nameProducer = (l) -> "log(Level." + l + ", \"" + msg + "\")"; + testSpiLog(logger, tester, check, nameProducer); + } + + public void testSpiLog(java.lang.System.Logger logger, + ResourceBundle bundle, String msg) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + SpiLogMethodInvoker.LOGRB_STRING_PARAMS, + SpiLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + SpiLogMethodInvoker.LOGRB_STRING_PARAMS, + "logX"), null, bundle); + return null; + }; + SpiLogTester tester = (x, level) -> { + SpiLogMethodInvoker.LOGRB_STRING_PARAMS.logX(x, level, bundle, msg, (Object[])null); + return null; + }; + Function nameProducer = (l) -> "log(Level." + l + + ", bundle, \"" + msg + "\")"; + testSpiLog(logger, tester, check, nameProducer); + } + + public void testSpiLog(java.lang.System.Logger logger, String msg, Object... params) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + SpiLogMethodInvoker.LOG_STRING_PARAMS, + SpiLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + SpiLogMethodInvoker.LOG_STRING_PARAMS, + "logX"), null, localized, params); + return null; + }; + SpiLogTester tester = (x, level) -> { + SpiLogMethodInvoker.LOG_STRING_PARAMS.logX(x, level, msg, params); + return null; + }; + Function nameProducer = (l) -> "log(Level." + l + ", \"" + msg + "\", params...)"; + testSpiLog(logger, tester, check, nameProducer); + } + + public void testSpiLog(java.lang.System.Logger logger, + ResourceBundle bundle, String msg, Object... params) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + SpiLogMethodInvoker.LOGRB_STRING_PARAMS, + SpiLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + SpiLogMethodInvoker.LOGRB_STRING_PARAMS, + "logX"), null, bundle, params); + return null; + }; + SpiLogTester tester = (x, level) -> { + SpiLogMethodInvoker.LOGRB_STRING_PARAMS.logX(x, level, bundle, msg, params); + return null; + }; + Function nameProducer = (l) -> "log(Level." + l + + ", bundle, \"" + msg + "\", params...)"; + testSpiLog(logger, tester, check, nameProducer); + } + + public void testSpiLog(java.lang.System.Logger logger, String msg, Throwable thrown) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + SpiLogMethodInvoker.LOG_STRING_THROWN, + SpiLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + SpiLogMethodInvoker.LOG_STRING_THROWN, + "logX"), thrown, localized); + return null; + }; + SpiLogTester tester = (x, level) -> { + SpiLogMethodInvoker.LOG_STRING_THROWN.logX(x, level, msg, thrown); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", \"" + msg + "\", thrown)"; + testSpiLog(logger, tester, check, nameProducer); + } + + public void testSpiLog(java.lang.System.Logger logger, + ResourceBundle bundle, String msg, Throwable thrown) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + SpiLogMethodInvoker.LOGRB_STRING_THROWN, + SpiLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + SpiLogMethodInvoker.LOGRB_STRING_THROWN, + "logX"), thrown, bundle); + return null; + }; + SpiLogTester tester = (x, level) -> { + SpiLogMethodInvoker.LOGRB_STRING_THROWN.logX(x, level, bundle, msg, thrown); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", bundle, \"" + msg + "\", thrown)"; + testSpiLog(logger, tester, check, nameProducer); + } + + public void testSpiLog(java.lang.System.Logger logger, Supplier msg) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, msg, + adaptor().getCallerClassName( + SpiLogMethodInvoker.LOG_SUPPLIER, + SpiLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + SpiLogMethodInvoker.LOG_SUPPLIER, + "logX"), null, null); + return null; + }; + SpiLogTester tester = (x, level) -> { + SpiLogMethodInvoker.LOG_SUPPLIER.logX(x, level, msg); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", () -> \"" + msg.get() + "\")"; + testSpiLog(logger, tester, check, nameProducer); + } + + public void testSpiLog(java.lang.System.Logger logger, Object obj) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> obj.toString(), + adaptor().getCallerClassName( + SpiLogMethodInvoker.LOG_OBJECT, + SpiLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + SpiLogMethodInvoker.LOG_OBJECT, + "logX"), null, null); + return null; + }; + SpiLogTester tester = (x, level) -> { + SpiLogMethodInvoker.LOG_OBJECT.logX(x, level, obj); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", new "+obj.getClass().getSimpleName()+"(\"" + + obj.toString() + "\"))"; + testSpiLog(logger, tester, check, nameProducer); + } + + public void testSpiLog(java.lang.System.Logger logger, Throwable thrown, Supplier msg) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, msg, + adaptor().getCallerClassName( + SpiLogMethodInvoker.LOG_SUPPLIER_THROWN, + SpiLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + SpiLogMethodInvoker.LOG_SUPPLIER_THROWN, + "logX"), thrown, null); + return null; + }; + SpiLogTester tester = (x, level) -> { + SpiLogMethodInvoker.LOG_SUPPLIER_THROWN.logX(x, level, msg, thrown); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", () -> \"" + msg.get() + "\", thrown)"; + testSpiLog(logger, tester, check, nameProducer); + } + + + // JDK + + public void testLog(java.lang.System.Logger logger, String msg) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOG_STRING_PARAMS, + JdkLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + JdkLogMethodInvoker.LOG_STRING_PARAMS, + "logX"), null, localized); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOG_STRING_PARAMS.logX(x, level, msg, (Object[])null); + return null; + }; + Function nameProducer = (l) -> "log(Level." + l + ", \"" + msg + "\")"; + testJdkLog(logger, tester, check, nameProducer); + } + + public void testLogrb(java.lang.System.Logger logger, + ResourceBundle bundle, String msg) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGRB_STRING_PARAMS, + JdkLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + JdkLogMethodInvoker.LOGRB_STRING_PARAMS, + "logX"), null, bundle); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOGRB_STRING_PARAMS.logX(x, level, bundle, msg, (Object[])null); + return null; + }; + Function nameProducer = (l) -> "logrb(Level." + l + + ", bundle, \"" + msg + "\")"; + testJdkLog(logger, tester, check, nameProducer); + } + + public void testLog(java.lang.System.Logger logger, String msg, Object... params) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOG_STRING_PARAMS, + JdkLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + JdkLogMethodInvoker.LOG_STRING_PARAMS, + "logX"), null, localized, params); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOG_STRING_PARAMS.logX(x, level, msg, params); + return null; + }; + Function nameProducer = (l) -> "log(Level." + l + ", \"" + msg + "\", params...)"; + testJdkLog(logger, tester, check, nameProducer); + } + + public void testLogrb(java.lang.System.Logger logger, + ResourceBundle bundle, String msg, Object... params) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGRB_STRING_PARAMS, + JdkLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + JdkLogMethodInvoker.LOGRB_STRING_PARAMS, + "logX"), null, bundle, params); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOGRB_STRING_PARAMS.logX(x, level, bundle, msg, params); + return null; + }; + Function nameProducer = (l) -> "log(Level." + l + + ", bundle, \"" + msg + "\", params...)"; + testJdkLog(logger, tester, check, nameProducer); + } + + public void testLog(java.lang.System.Logger logger, String msg, Throwable thrown) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOG_STRING_THROWN, + JdkLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + JdkLogMethodInvoker.LOG_STRING_THROWN, + "logX"), thrown, localized); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOG_STRING_THROWN.logX(x, level, msg, thrown); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", \"" + msg + "\", thrown)"; + testJdkLog(logger, tester, check, nameProducer); + } + + public void testLogrb(java.lang.System.Logger logger, + ResourceBundle bundle, String msg, Throwable thrown) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGRB_STRING_THROWN, + JdkLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + JdkLogMethodInvoker.LOGRB_STRING_THROWN, + "logX"), thrown, bundle); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOGRB_STRING_THROWN.logX(x, level, bundle, msg, thrown); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", bundle, \"" + msg + "\", thrown)"; + testJdkLog(logger, tester, check, nameProducer); + } + + public void testLog(java.lang.System.Logger logger, Supplier msg) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, msg, + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOG_SUPPLIER, + JdkLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + JdkLogMethodInvoker.LOG_SUPPLIER, + "logX"), null, null); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOG_SUPPLIER.logX(x, level, msg); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", () -> \"" + msg.get() + "\")"; + testJdkLog(logger, tester, check, nameProducer); + } + + public void testLog(java.lang.System.Logger logger, Throwable thrown, Supplier msg) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, msg, + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOG_SUPPLIER_THROWN, + JdkLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + JdkLogMethodInvoker.LOG_SUPPLIER_THROWN, + "logX"), thrown, null); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOG_SUPPLIER_THROWN.logX(x, level, thrown, msg); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", () -> \"" + msg.get() + "\", thrown)"; + testJdkLog(logger, tester, check, nameProducer); + } + + static Supplier logpMessage(ResourceBundle bundle, + String className, String methodName, Supplier msg) { + if (BEST_EFFORT_FOR_LOGP && bundle == null + && (className != null || methodName != null)) { + final String cName = className == null ? "" : className; + final String mName = methodName == null ? "" : methodName; + return () -> { + String m = msg.get(); + return String.format("[%s %s] %s", cName, mName, m == null ? "" : m); + }; + } else { + return msg; + } + } + + public void testLogp(java.lang.System.Logger logger, String className, + String methodName, String msg) { + Checker check = (res, l) -> { + checkRecord("logp", res, logger.getName(), l, + logpMessage(localized, className, methodName, () -> msg), + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGP_STRING, className), + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGP_STRING, methodName), + null, localized); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOGP_STRING.logX(x, level, + className, methodName, msg); + return null; + }; + Function nameProducer = (l) -> + "logp(Level." + l + ", class, method, \"" + msg + "\")"; + testJdkLog(logger, tester, check, nameProducer); + } + + public void testLogrb(java.lang.System.Logger logger, String className, + String methodName, ResourceBundle bundle, String msg) { + Checker check = (res, l) -> { + checkRecord("logp", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGRBP_STRING_PARAMS, className), + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGRBP_STRING_PARAMS, methodName), + null, bundle); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOGRBP_STRING_PARAMS.logX(x, level, + className, methodName, bundle, msg, (Object[])null); + return null; + }; + Function nameProducer = (l) -> + "logp(Level." + l + ", class, method, bundle, \"" + msg + "\")"; + testJdkLog(logger, tester, check, nameProducer); + } + + public void testLogp(java.lang.System.Logger logger, String className, + String methodName, String msg, Object... params) { + Checker check = (res, l) -> { + checkRecord("logp", res, logger.getName(), l, + logpMessage(localized, className, methodName, () -> msg), + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGP_STRING_PARAMS, className), + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGP_STRING_PARAMS, methodName), + null, localized, params); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOGP_STRING_PARAMS.logX(x, level, + className, methodName, msg, params); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", class, method, \"" + msg + "\", params...)"; + testJdkLog(logger, tester, check, nameProducer); + } + + public void testLogrb(java.lang.System.Logger logger, String className, + String methodName, ResourceBundle bundle, String msg, + Object... params) { + Checker check = (res, l) -> { + checkRecord("logp", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGRBP_STRING_PARAMS, className), + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGRBP_STRING_PARAMS, methodName), + null, bundle, params); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOGRBP_STRING_PARAMS.logX(x, level, + className, methodName, bundle, msg, params); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", class, method, bundle, \"" + + msg + "\", params...)"; + testJdkLog(logger, tester, check, nameProducer); + } + + public void testLogp(java.lang.System.Logger logger, String className, + String methodName, String msg, Throwable thrown) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, + logpMessage(localized, className, methodName, () -> msg), + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGP_STRING_THROWN, className), + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGP_STRING_THROWN, methodName), + thrown, localized); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOGP_STRING_THROWN.logX(x, level, + className, methodName, msg, thrown); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", class, method, \"" + msg + "\", thrown)"; + testJdkLog(logger, tester, check, nameProducer); + } + + public void testLogrb(java.lang.System.Logger logger, String className, + String methodName, ResourceBundle bundle, + String msg, Throwable thrown) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGRBP_STRING_THROWN, className), + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGRBP_STRING_THROWN, methodName), + thrown, bundle); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOGRBP_STRING_THROWN.logX(x, level, + className, methodName, bundle, msg, thrown); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", class, method, bundle, \"" + msg + "\", thrown)"; + testJdkLog(logger, tester, check, nameProducer); + + } + + public void testLogp(java.lang.System.Logger logger, String className, + String methodName, Supplier msg) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, + logpMessage(null, className, methodName, msg), + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGP_SUPPLIER, className), + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGP_SUPPLIER, methodName), + null, null); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOGP_SUPPLIER.logX(x, level, + className, methodName, msg); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", class, method, () -> \"" + msg.get() + "\")"; + testJdkLog(logger, tester, check, nameProducer); + } + + public void testLogp(java.lang.System.Logger logger, String className, + String methodName, Throwable thrown, Supplier msg) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, + logpMessage(null, className, methodName, msg), + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGP_SUPPLIER_THROWN, className), + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGP_SUPPLIER_THROWN, methodName), + thrown, null); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOGP_SUPPLIER_THROWN.logX(x, level, + className, methodName, thrown, msg); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", class, method, () -> \"" + msg.get() + "\", thrown)"; + testJdkLog(logger, tester, check, nameProducer); + } + + private void testJdkLog(java.lang.System.Logger logger, + JdkLogTester log, Checker check, + Function nameProducer) { + if (restrictedTo != null) { + if (!bridgeLoggerClass.isAssignableFrom(restrictedTo)) { + if (VERBOSE) { + System.out.println("Skipping method from " + + bridgeLoggerClass); + } + return; + } + } + System.out.println("Testing Logger." + nameProducer.apply("*") + + " on " + logger); + final BackendAdaptor adaptor = adaptor(); + for (Level loggerLevel : LEVELS) { + adaptor.setLevel(logger, loggerLevel); + for (Level l : LEVELS) { + check(logger, () -> log.apply(bridgeLoggerClass.cast(logger), l), + check, () -> adaptor.isLoggable(logger, l), + () -> adaptor.shouldBeLoggable(l, loggerLevel), + l, loggerLevel, nameProducer.apply(l.toString())); + } + } + } + + private void testSpiLog(java.lang.System.Logger logger, + SpiLogTester log, Checker check, + Function nameProducer) { + System.out.println("Testing System.Logger." + nameProducer.apply("*") + + " on " + logger); + final BackendAdaptor adaptor = adaptor(); + for (java.lang.System.Logger.Level loggerLevel : java.lang.System.Logger.Level.values()) { + + adaptor.setLevel(logger, loggerLevel); + for (java.lang.System.Logger.Level l : java.lang.System.Logger.Level.values()) { + check(logger, () -> log.apply(logger, l), + check, () -> logger.isLoggable(l), + () -> adaptor.shouldBeLoggable(l, loggerLevel), + l, loggerLevel, nameProducer.apply(l.toString())); + } + } + } + + private void test(String args, Levels level, java.lang.System.Logger logger, + Runnable test, Checker check) { + if (restrictedTo != null) { + if (!level.definingClass.isAssignableFrom(restrictedTo)) { + if (VERBOSE) { + System.out.println("Skipping method from " + + level.definingClass); + } + return; + } + } + String method = args.contains("bundle") ? "logrb" : "log"; + System.out.println("Testing Logger." + + method + "(Level." + level.platformLevel + + ", "+ args + ")" + " on " + logger); + final BackendAdaptor adaptor = adaptor(); + for (Level loggerLevel : LEVELS) { + adaptor.setLevel(logger, loggerLevel); + check(logger, test, check, + () -> level.isEnabled(logger), + () -> adaptor.shouldBeLoggable(level, loggerLevel), + level.platformLevel, loggerLevel, level.method); + } + } + + private void check(java.lang.System.Logger logger, + Runnable test, Checker check, + BooleanSupplier checkLevelEnabled, + BooleanSupplier shouldBeLoggable, + L logLevel, L loggerLevel, String logMethod) { + final BackendAdaptor adaptor = adaptor(); + adaptor.resetBackendRecords(); + test.run(); + final List records = adaptor.getBackendRecords(); + if (shouldBeLoggable.getAsBoolean()) { + if (!checkLevelEnabled.getAsBoolean()) { + throw new RuntimeException("Logger is not enabled for " + + logMethod + + " although logger level is " + loggerLevel); + } + if (records.size() != 1) { + throw new RuntimeException(loggerLevel + " [" + + logLevel + "] : Unexpected record sizes: " + + records.toString()); + } + BackendRecord res = records.get(0); + check.apply(res, logLevel); + } else { + if (checkLevelEnabled.getAsBoolean()) { + throw new RuntimeException("Logger is enabled for " + + logMethod + + " although logger level is " + loggerLevel); + } + if (!records.isEmpty()) { + throw new RuntimeException(loggerLevel + " [" + + logLevel + "] : Unexpected record sizes: " + + records.toString()); + } + } + } + } + + public static class JULBackendTester extends BackendTester{ + + public JULBackendTester(boolean isSystem) { + this(isSystem,null,null); + } + public JULBackendTester(boolean isSystem, ResourceBundle localized) { + this(isSystem,null,localized); + } + public JULBackendTester(boolean isSystem, + Class restrictedTo) { + this(isSystem, restrictedTo, null); + } + public JULBackendTester(boolean isSystem, + Class restrictedTo, + ResourceBundle localized) { + super(isSystem, restrictedTo, localized); + } + + Logger getBackendLogger(String name) { + if (isSystem) { + return LoggingProviderImpl.getLogManagerAccess().demandLoggerFor( + LogManager.getLogManager(), name, Thread.class); + } else { + return Logger.getLogger(name); + } + } + + class JULBackendAdaptor extends BackendAdaptor { + @Override + public String getLoggerName(LogRecord res) { + return res.getLoggerName(); + } + @Override + public Level getLevel(LogRecord res) { + return Level.valueOf(res.getLevel().getName()); + } + @Override + public String getMessage(LogRecord res) { + return res.getMessage(); + } + @Override + public String getSourceClassName(LogRecord res) { + return res.getSourceClassName(); + } + @Override + public String getSourceMethodName(LogRecord res) { + return res.getSourceMethodName(); + } + @Override + public Throwable getThrown(LogRecord res) { + return res.getThrown(); + } + @Override + public ResourceBundle getResourceBundle(LogRecord res) { + return res.getResourceBundle(); + } + @Override + public void setLevel(java.lang.System.Logger logger, Level level) { + Logger backend = getBackendLogger(logger.getName()); + backend.setLevel(java.util.logging.Level.parse(level.name())); + } + @Override + public void setLevel(java.lang.System.Logger logger, java.lang.System.Logger.Level level) { + setLevel(logger, toJUL(level)); + } + @Override + public List getBackendRecords() { + return handler.records; + } + @Override + public void resetBackendRecords() { + handler.reset(); + } + @Override + public Level getMappedLevel(Object level) { + if (level instanceof java.lang.System.Logger.Level) { + return toJUL((java.lang.System.Logger.Level)level); + } + return (Level)level; + } + } + + final JULBackendAdaptor julAdaptor = new JULBackendAdaptor(); + + @Override + BackendAdaptor adaptor() { + return julAdaptor; + } + + } + + public abstract static class BackendTesterFactory { + public abstract BackendTester createBackendTester(boolean isSystem); + public abstract BackendTester createBackendTester(boolean isSystem, + Class restrictedTo); + public abstract BackendTester createBackendTester(boolean isSystem, + Class restrictedTo, + ResourceBundle bundle); + public abstract BackendTester createBackendTester(boolean isSystem, + ResourceBundle bundle); + } + + public static class JULBackendTesterFactory extends BackendTesterFactory { + + @Override + public BackendTester createBackendTester(boolean isSystem) { + return new JULBackendTester(isSystem); + } + + @Override + public BackendTester createBackendTester(boolean isSystem, + Class restrictedTo) { + return new JULBackendTester(isSystem, restrictedTo); + } + + @Override + public BackendTester createBackendTester(boolean isSystem, + Class restrictedTo, + ResourceBundle bundle) { + return new JULBackendTester(isSystem, restrictedTo, bundle); + } + + @Override + public BackendTester createBackendTester(boolean isSystem, + ResourceBundle bundle) { + return new JULBackendTester(isSystem, bundle); + } + } + + public static class CustomLoggerFinder extends LoggerFinder { + + static enum CustomLevel { OFF, FATAL, ERROR, WARN, INFO, DEBUG, TRACE, ALL }; + static CustomLevel[] customLevelMap = { CustomLevel.ALL, + CustomLevel.TRACE, CustomLevel.DEBUG, CustomLevel.INFO, + CustomLevel.WARN, CustomLevel.ERROR, CustomLevel.OFF + }; + static class CustomLogRecord { + public final CustomLevel logLevel; + public final java.lang.System.Logger logger; + public final String msg; + public final Object[] params; + public final Throwable thrown; + public final ResourceBundle bundle; + + CustomLogRecord(java.lang.System.Logger producer, + CustomLevel level, String msg) { + this(producer, level, msg, (ResourceBundle)null, (Throwable)null, (Object[])null); + } + + CustomLogRecord(java.lang.System.Logger producer, + CustomLevel level, String msg, ResourceBundle bundle, + Throwable thrown, Object... params) { + this.logger = producer; + this.logLevel = level; + this.msg = msg; + this.params = params; + this.thrown = thrown; + this.bundle = bundle; + } + } + + static final List records = + Collections.synchronizedList(new ArrayList<>()); + + static class CustomLogger implements java.lang.System.Logger { + + final String name; + volatile CustomLevel level; + CustomLogger(String name) { + this.name = name; + this.level = CustomLevel.INFO; + } + + @Override + public String getName() { + return name; + } + + public void setLevel(CustomLevel level) { + this.level = level; + } + + + @Override + public boolean isLoggable(java.lang.System.Logger.Level level) { + + return this.level != CustomLevel.OFF && this.level.ordinal() + >= customLevelMap[level.ordinal()].ordinal(); + } + + @Override + public void log(java.lang.System.Logger.Level level, ResourceBundle bundle, String key, Throwable thrown) { + if (isLoggable(level)) { + records.add(new CustomLogRecord(this, customLevelMap[level.ordinal()], + key, bundle, thrown)); + } + } + + @Override + public void log(java.lang.System.Logger.Level level, ResourceBundle bundle, String format, Object... params) { + if (isLoggable(level)) { + records.add(new CustomLogRecord(this, customLevelMap[level.ordinal()], + format, bundle, null, params)); + } + } + + } + + final Map applicationLoggers = + Collections.synchronizedMap(new HashMap<>()); + final Map systemLoggers = + Collections.synchronizedMap(new HashMap<>()); + + @Override + public java.lang.System.Logger getLogger(String name, Class caller) { + ClassLoader callerLoader = caller.getClassLoader(); + if (callerLoader == null) { + systemLoggers.putIfAbsent(name, new CustomLogger(name)); + return systemLoggers.get(name); + } else { + applicationLoggers.putIfAbsent(name, new CustomLogger(name)); + return applicationLoggers.get(name); + } + } + + CustomLevel fromJul(Level level) { + if (level.intValue() == Level.OFF.intValue()) { + return CustomLevel.OFF; + } else if (level.intValue() > Level.SEVERE.intValue()) { + return CustomLevel.ERROR; + } else if (level.intValue() > Level.WARNING.intValue()) { + return CustomLevel.ERROR; + } else if (level.intValue() > Level.INFO.intValue()) { + return CustomLevel.WARN; + } else if (level.intValue() > Level.CONFIG.intValue()) { + return CustomLevel.INFO; + } else if (level.intValue() > Level.FINER.intValue()) { + return CustomLevel.DEBUG; + } else if (level.intValue() > Level.FINEST.intValue()) { + return CustomLevel.TRACE; + } else if (level.intValue() == Level.ALL.intValue()) { + return CustomLevel.ALL; + } else { + return CustomLevel.TRACE; + } + } + + Level toJul(CustomLevel level) { + switch(level) { + case OFF: return Level.OFF; + case FATAL: return Level.SEVERE; + case ERROR: return Level.SEVERE; + case WARN: return Level.WARNING; + case INFO: return Level.INFO; + case DEBUG: return Level.FINE; + case TRACE: return Level.FINER; + case ALL: return Level.ALL; + default: throw new InternalError("No such level: "+level); + } + } + + } + + public static class CustomBackendTester extends + BackendTester { + + public final CustomLoggerFinder provider; + + public CustomBackendTester(boolean isSystem) { + this(isSystem, null, null); + } + + public CustomBackendTester(boolean isSystem, + Class restrictedTo) { + this(isSystem, restrictedTo, null); + } + + public CustomBackendTester(boolean isSystem, + ResourceBundle localized) { + this(isSystem, null, localized); + } + + public CustomBackendTester(boolean isSystem, + Class restrictedTo, + ResourceBundle localized) { + super(isSystem, restrictedTo, localized); + provider = (CustomLoggerFinder)java.lang.System.LoggerFinder.getLoggerFinder(); + } + + @Override + public java.lang.System.Logger convert(java.lang.System.Logger logger) { + if (restrictedTo != null && restrictedTo.isInstance(logger)) { + return logger; + } else if (restrictedTo == jdkLoggerClass) { + return logger; + } else { + return java.lang.System.Logger.class.cast( + sun.util.logging.PlatformLogger.Bridge.convert(logger)); + } + } + + class CustomBackendAdaptor extends BackendAdaptor { + + @Override + public String getLoggerName(CustomLoggerFinder.CustomLogRecord res) { + return res.logger.getName(); + } + + @Override + public CustomLoggerFinder.CustomLevel getLevel(CustomLoggerFinder.CustomLogRecord res) { + return res.logLevel; + } + + @Override + public String getMessage(CustomLoggerFinder.CustomLogRecord res) { + return res.msg; + } + + @Override // we don't support source class name in our custom provider implementation + public String getSourceClassName(CustomLoggerFinder.CustomLogRecord res) { + return null; + } + + @Override // we don't support source method name in our custom provider implementation + public String getSourceMethodName(CustomLoggerFinder.CustomLogRecord res) { + return null; + } + + @Override + public Throwable getThrown(CustomLoggerFinder.CustomLogRecord res) { + return res.thrown; + } + + @Override + public ResourceBundle getResourceBundle(CustomLoggerFinder.CustomLogRecord res) { + return res.bundle; + } + + @Override + public void setLevel(java.lang.System.Logger logger, Level level) { + final CustomLoggerFinder.CustomLogger l = + (CustomLoggerFinder.CustomLogger) + (isSystem ? provider.getLogger(logger.getName(), Thread.class) : + provider.getLogger(logger.getName(), LoggerFinderBackendTest.class)); + l.setLevel(provider.fromJul(level)); + } + @Override + public void setLevel(java.lang.System.Logger logger, + java.lang.System.Logger.Level level) { + setLevel(logger, toJUL(level)); + } + + CustomLoggerFinder.CustomLevel getLevel(java.lang.System.Logger logger) { + final CustomLoggerFinder.CustomLogger l = + (CustomLoggerFinder.CustomLogger) + (isSystem ? provider.getLogger(logger.getName(), Thread.class) : + provider.getLogger(logger.getName(), LoggerFinderBackendTest.class)); + return l.level; + } + + @Override + public List getBackendRecords() { + return CustomLoggerFinder.records; + } + + @Override + public void resetBackendRecords() { + CustomLoggerFinder.records.clear(); + } + + @Override + public boolean shouldBeLoggable(Levels level, Level loggerLevel) { + return loggerLevel != Level.OFF && + fromLevels(level).ordinal() <= provider.fromJul(loggerLevel).ordinal(); + } + + @Override + public boolean isLoggable(java.lang.System.Logger logger, Level l) { + return super.isLoggable(logger, l); + } + + @Override + public boolean shouldBeLoggable(Level logLevel, Level loggerLevel) { + return loggerLevel != Level.OFF && + provider.fromJul(logLevel).ordinal() <= provider.fromJul(loggerLevel).ordinal(); + } + + @Override // we don't support source class name in our custom provider implementation + public String getCallerClassName(Levels level, String clazz) { + return null; + } + + @Override // we don't support source method name in our custom provider implementation + public String getCallerMethodName(Levels level, String method) { + return null; + } + + @Override // we don't support source class name in our custom provider implementation + public String getCallerClassName(MethodInvoker logMethod, String clazz) { + return null; + } + + @Override // we don't support source method name in our custom provider implementation + public String getCallerMethodName(MethodInvoker logMethod, String method) { + return null; + } + + @Override + public CustomLoggerFinder.CustomLevel getMappedLevel(Object level) { + if (level instanceof java.lang.System.Logger.Level) { + final int index = ((java.lang.System.Logger.Level)level).ordinal(); + return CustomLoggerFinder.customLevelMap[index]; + } else if (level instanceof Level) { + return provider.fromJul((Level)level); + } + return (CustomLoggerFinder.CustomLevel) level; + } + + CustomLoggerFinder.CustomLevel fromLevels(Levels level) { + switch(level) { + case SEVERE: + return CustomLoggerFinder.CustomLevel.ERROR; + case WARNING: + return CustomLoggerFinder.CustomLevel.WARN; + case INFO: + return CustomLoggerFinder.CustomLevel.INFO; + case CONFIG: case FINE: + return CustomLoggerFinder.CustomLevel.DEBUG; + case FINER: case FINEST: + return CustomLoggerFinder.CustomLevel.TRACE; + } + throw new InternalError("No such level "+level); + } + + } + + @Override + BackendAdaptor adaptor() { + return new CustomBackendAdaptor(); + } + + } + + public static class CustomBackendTesterFactory extends BackendTesterFactory { + + @Override + public BackendTester createBackendTester(boolean isSystem) { + return new CustomBackendTester(isSystem); + } + + @Override + public BackendTester createBackendTester(boolean isSystem, + Class restrictedTo) { + return new CustomBackendTester(isSystem, restrictedTo); + } + + @Override + public BackendTester createBackendTester(boolean isSystem, + Class restrictedTo, + ResourceBundle bundle) { + return new CustomBackendTester(isSystem, restrictedTo, bundle); + } + + @Override + public BackendTester createBackendTester(boolean isSystem, + ResourceBundle bundle) { + return new CustomBackendTester(isSystem, bundle); + } + } + + static final Method getLazyLogger; + static final Method accessLoggerFinder; + static { + // jdk.internal.logger.LazyLoggers.getLazyLogger(name, caller); + try { + Class lazyLoggers = jdk.internal.logger.LazyLoggers.class; + getLazyLogger = lazyLoggers.getMethod("getLazyLogger", + String.class, Class.class); + getLazyLogger.setAccessible(true); + Class loggerFinderLoader = + Class.forName("java.lang.System$LoggerFinder"); + accessLoggerFinder = loggerFinderLoader.getDeclaredMethod("accessProvider"); + accessLoggerFinder.setAccessible(true); + } catch (Throwable ex) { + throw new ExceptionInInitializerError(ex); + } + } + + static java.lang.System.Logger getSystemLogger(String name, Class caller) throws Exception { + try { + return java.lang.System.Logger.class.cast(getLazyLogger.invoke(null, name, caller)); + } catch (InvocationTargetException x) { + Throwable t = x.getTargetException(); + if (t instanceof Exception) { + throw (Exception)t; + } else { + throw (Error)t; + } + } + } + static java.lang.System.Logger getSystemLogger(String name, + ResourceBundle bundle, Class caller) throws Exception { + try { + LoggerFinder provider = LoggerFinder.class.cast(accessLoggerFinder.invoke(null)); + return provider.getLocalizedLogger(name, bundle, caller); + } catch (InvocationTargetException x) { + Throwable t = x.getTargetException(); + if (t instanceof Exception) { + throw (Exception)t; + } else { + throw (Error)t; + } + } + } + + // Change this to 'true' to get more traces... + public static boolean verbose = false; + + public static void main(String[] argv) throws Exception { + + final AtomicInteger nb = new AtomicInteger(0); + final boolean hidesProvider = Boolean.getBoolean("test.logger.hidesProvider"); + System.out.println(ClassLoader.getSystemClassLoader()); + final BackendTesterFactory factory; + if (java.lang.System.LoggerFinder.getLoggerFinder() instanceof CustomLoggerFinder) { + if (hidesProvider) { + System.err.println("Custom backend " + + java.lang.System.LoggerFinder.getLoggerFinder() + + " should have been hidden!"); + throw new RuntimeException( + "Custom backend should have been hidden: " + + "check value of java.system.class.loader property"); + } + System.out.println("Using custom backend"); + factory = new CustomBackendTesterFactory(); + } else { + if (!hidesProvider) { + System.err.println("Default JUL backend " + + java.lang.System.LoggerFinder.getLoggerFinder() + + " should have been hidden!"); + throw new RuntimeException( + "Default JUL backend should have been hidden: " + + "check value of java.system.class.loader property"); + } + System.out.println("Using JUL backend"); + factory = new JULBackendTesterFactory(); + } + + testBackend(nb, factory); + } + + public static void testBackend(AtomicInteger nb, BackendTesterFactory factory) throws Exception { + + // Tests all level specifics methods with loggers configured with + // all possible levels and loggers obtained with all possible + // entry points from LoggerFactory and JdkLoggerFactory, with + // JUL as backend. + + // Test a simple application logger with JUL backend + final BackendTester tester = factory.createBackendTester(false); + final java.lang.System.Logger logger = + java.lang.System.LoggerFinder.getLoggerFinder() + .getLogger("foo", LoggerFinderBackendTest.class); + + testLogger(tester, logger, nb); + + // Test a simple system logger with JUL backend + final java.lang.System.Logger system = + java.lang.System.LoggerFinder.getLoggerFinder() + .getLogger("bar", Thread.class); + final BackendTester systemTester = factory.createBackendTester(true); + testLogger(systemTester, system, nb); + + // Test a localized application logger with null resource bundle and + // JUL backend + final java.lang.System.Logger noBundleLogger = + java.lang.System.LoggerFinder.getLoggerFinder() + .getLocalizedLogger("baz", null, LoggerFinderBackendTest.class); + final BackendTester noBundleTester = + factory.createBackendTester(false, spiLoggerClass); + testLogger(noBundleTester, noBundleLogger, nb); + + // Test a localized system logger with null resource bundle and JUL + // backend + final java.lang.System.Logger noBundleSysLogger = + java.lang.System.LoggerFinder.getLoggerFinder() + .getLocalizedLogger("oof", null, Thread.class); + final BackendTester noBundleSysTester = + factory.createBackendTester(true, spiLoggerClass); + testLogger(noBundleSysTester, noBundleSysLogger, nb); + + // Test a localized application logger with null resource bundle and + // JUL backend + try { + System.getLogger("baz", null); + throw new RuntimeException("Expected NullPointerException not thrown"); + } catch (NullPointerException x) { + System.out.println("System.Loggers.getLogger(\"baz\", null): got expected " + x); + } + final java.lang.System.Logger noBundleExtensionLogger = + getSystemLogger("baz", null, LoggerFinderBackendTest.class); + final BackendTester noBundleExtensionTester = + factory.createBackendTester(false, jdkLoggerClass); + testLogger(noBundleExtensionTester, noBundleExtensionLogger, nb); + + // Test a simple system logger with JUL backend + final java.lang.System.Logger sysExtensionLogger = + getSystemLogger("oof", Thread.class); + final BackendTester sysExtensionTester = + factory.createBackendTester(true, jdkLoggerClass); + testLogger(sysExtensionTester, sysExtensionLogger, nb); + + // Test a localized system logger with null resource bundle and JUL + // backend + final java.lang.System.Logger noBundleSysExtensionLogger = + getSystemLogger("oof", null, Thread.class); + final BackendTester noBundleSysExtensionTester = + factory.createBackendTester(true, jdkLoggerClass); + testLogger(noBundleSysExtensionTester, noBundleSysExtensionLogger, nb); + + // Test a localized application logger converted to JDK with null + // resource bundle and JUL backend + final java.lang.System.Logger noBundleConvertedLogger = + (java.lang.System.Logger) + sun.util.logging.PlatformLogger.Bridge.convert(noBundleLogger); + final BackendTester noBundleJdkTester = factory.createBackendTester(false); + testLogger(noBundleJdkTester, noBundleConvertedLogger, nb); + + // Test a localized system logger converted to JDK with null resource + // bundle and JUL backend + final java.lang.System.Logger noBundleConvertedSysLogger = + (java.lang.System.Logger) + sun.util.logging.PlatformLogger.Bridge.convert(noBundleSysLogger); + final BackendTester noBundleJdkSysTester = factory.createBackendTester(true); + testLogger(noBundleJdkSysTester, noBundleConvertedSysLogger, nb); + + // Test a localized application logger with resource bundle and JUL + // backend + final ResourceBundle bundle = + ResourceBundle.getBundle(ResourceBundeLocalized.class.getName()); + final java.lang.System.Logger bundleLogger = + java.lang.System.LoggerFinder.getLoggerFinder() + .getLocalizedLogger("toto", bundle, LoggerFinderBackendTest.class); + final BackendTester bundleTester = + factory.createBackendTester(false, spiLoggerClass, bundle); + testLogger(bundleTester, bundleLogger, nb); + + // Test a localized system logger with resource bundle and JUL backend + final java.lang.System.Logger bundleSysLogger = + java.lang.System.LoggerFinder.getLoggerFinder() + .getLocalizedLogger("titi", bundle, Thread.class); + final BackendTester bundleSysTester = + factory.createBackendTester(true, spiLoggerClass, bundle); + testLogger(bundleSysTester, bundleSysLogger, nb); + + // Test a localized Jdk application logger with resource bundle and JUL + // backend + final java.lang.System.Logger bundleExtensionLogger = + System.getLogger("tita", bundle); + final BackendTester bundleExtensionTester = + factory.createBackendTester(false, jdkLoggerClass, bundle); + testLogger(bundleExtensionTester, bundleExtensionLogger, nb); + + // Test a localized Jdk system logger with resource bundle and JUL + // backend + final java.lang.System.Logger bundleExtensionSysLogger = + getSystemLogger("titu", bundle, Thread.class); + final BackendTester bundleExtensionSysTester = + factory.createBackendTester(true, jdkLoggerClass, bundle); + testLogger(bundleExtensionSysTester, bundleExtensionSysLogger, nb); + + // Test a localized application logger converted to JDK with resource + // bundle and JUL backend + final BackendTester bundleJdkTester = + factory.createBackendTester(false, bundle); + final java.lang.System.Logger bundleConvertedLogger = + (java.lang.System.Logger) + sun.util.logging.PlatformLogger.Bridge.convert(bundleLogger); + testLogger(bundleJdkTester, bundleConvertedLogger, nb); + + // Test a localized Jdk system logger converted to JDK with resource + // bundle and JUL backend + final BackendTester bundleJdkSysTester = + factory.createBackendTester(true, bundle); + final java.lang.System.Logger bundleConvertedSysLogger = + (java.lang.System.Logger) + sun.util.logging.PlatformLogger.Bridge.convert(bundleSysLogger); + testLogger(bundleJdkSysTester, bundleConvertedSysLogger, nb); + + // Now need to add tests for all the log/logp/logrb methods... + + } + + private static class FooObj { + final String s; + FooObj(String s) { + this.s = s; + } + + @Override + public String toString() { + return super.toString() +": "+s; + } + + } + + public static void testLogger(BackendTester tester, + java.lang.System.Logger spiLogger, AtomicInteger nb) { + + // Test all level-specific method forms: + // fatal(...) error(...) severe(...) etc... + java.lang.System.Logger jdkLogger = tester.convert(spiLogger); + for (Levels l : Levels.values()) { + java.lang.System.Logger logger = + l.definingClass.equals(spiLoggerClass) ? spiLogger : jdkLogger; + tester.testLevel(l, logger, l.method + "[" + logger.getName()+ "]-" + + nb.incrementAndGet()); + tester.testLevel(l, logger, l.method + "[" + logger.getName()+ "]-" + + nb.incrementAndGet(), + bundleParam); + final int nbb = nb.incrementAndGet(); + tester.testLevel(l, logger, () -> l.method + "[" + logger.getName() + + "]-" + nbb); + } + for (Levels l : Levels.values()) { + java.lang.System.Logger logger = + l.definingClass.equals(spiLoggerClass) ? spiLogger : jdkLogger; + tester.testLevel(l, logger, + l.method + "[" + logger.getName()+ "]({0},{1})-" + + nb.incrementAndGet(), + "One", "Two"); + tester.testLevel(l, logger, + l.method + "[" + logger.getName()+ "]({0},{1})-" + + nb.incrementAndGet(), + bundleParam, "One", "Two"); + } + final Throwable thrown = new RuntimeException("Test"); + for (Levels l : Levels.values()) { + java.lang.System.Logger logger = + l.definingClass.equals(spiLoggerClass) ? spiLogger : jdkLogger; + tester.testLevel(l, logger, l.method + "[" + logger.getName()+ "]-" + + nb.incrementAndGet(), + thrown); + tester.testLevel(l, logger, l.method + "[" + logger.getName()+ "]-" + + nb.incrementAndGet(), + bundleParam, thrown); + final int nbb = nb.incrementAndGet(); + tester.testLevel(l, logger, ()->l.method + "[" + logger.getName()+ "]-" + + nbb, thrown); + } + + java.lang.System.Logger logger = jdkLogger; + + // test System.Logger methods + tester.testSpiLog(logger, "[" + logger.getName()+ "]-" + + nb.incrementAndGet()); + tester.testSpiLog(logger, bundleParam, "[" + logger.getName()+ "]-" + + nb.incrementAndGet()); + tester.testSpiLog(logger, "[" + logger.getName()+ "]-({0},{1})" + + nb.incrementAndGet(), "One", "Two"); + tester.testSpiLog(logger, bundleParam, "[" + logger.getName()+ "]-({0},{1})" + + nb.incrementAndGet(), "One", "Two"); + tester.testSpiLog(logger, "[" + logger.getName()+ "]-" + + nb.incrementAndGet(), thrown); + tester.testSpiLog(logger, bundleParam, "[" + logger.getName()+ "]-" + + nb.incrementAndGet(), thrown); + final int nbb01 = nb.incrementAndGet(); + tester.testSpiLog(logger, () -> "[" + logger.getName()+ "]-" + nbb01); + final int nbb02 = nb.incrementAndGet(); + tester.testSpiLog(logger, thrown, () -> "[" + logger.getName()+ "]-" + nbb02); + final int nbb03 = nb.incrementAndGet(); + tester.testSpiLog(logger, new FooObj("[" + logger.getName()+ "]-" + nbb03)); + + // Test all log method forms: + // jdk.internal.logging.Logger.log(...) + tester.testLog(logger, "[" + logger.getName()+ "]-" + + nb.incrementAndGet()); + tester.testLogrb(logger, bundleParam, "[" + logger.getName()+ "]-" + + nb.incrementAndGet()); + tester.testLog(logger, "[" + logger.getName()+ "]-({0},{1})" + + nb.incrementAndGet(), "One", "Two"); + tester.testLogrb(logger, bundleParam, "[" + logger.getName()+ "]-({0},{1})" + + nb.incrementAndGet(), "One", "Two"); + tester.testLog(logger, "[" + logger.getName()+ "]-" + + nb.incrementAndGet(), thrown); + tester.testLogrb(logger, bundleParam, "[" + logger.getName()+ "]-" + + nb.incrementAndGet(), thrown); + final int nbb1 = nb.incrementAndGet(); + tester.testLog(logger, () -> "[" + logger.getName()+ "]-" + nbb1); + final int nbb2 = nb.incrementAndGet(); + tester.testLog(logger, thrown, () -> "[" + logger.getName()+ "]-" + nbb2); + + // Test all logp method forms + // jdk.internal.logging.Logger.logp(...) + tester.testLogp(logger, "clazz" + nb.incrementAndGet(), + "method" + nb.incrementAndGet(), + "[" + logger.getName()+ "]-" + + nb.incrementAndGet()); + tester.testLogrb(logger, "clazz" + nb.incrementAndGet(), + "method" + nb.incrementAndGet(), bundleParam, + "[" + logger.getName()+ "]-" + + nb.incrementAndGet()); + tester.testLogp(logger, "clazz" + nb.incrementAndGet(), + "method" + nb.incrementAndGet(), + "[" + logger.getName()+ "]-({0},{1})" + + nb.incrementAndGet(), "One", "Two"); + tester.testLogrb(logger, "clazz" + nb.incrementAndGet(), + "method" + nb.incrementAndGet(), bundleParam, + "[" + logger.getName()+ "]-({0},{1})" + + nb.incrementAndGet(), "One", "Two"); + tester.testLogp(logger, "clazz" + nb.incrementAndGet(), + "method" + nb.incrementAndGet(), + "[" + logger.getName()+ "]-" + + nb.incrementAndGet(), thrown); + tester.testLogrb(logger, "clazz" + nb.incrementAndGet(), + "method" + nb.incrementAndGet(), bundleParam, + "[" + logger.getName()+ "]-" + + nb.incrementAndGet(), thrown); + final int nbb3 = nb.incrementAndGet(); + tester.testLogp(logger, "clazz" + nb.incrementAndGet(), + "method" + nb.incrementAndGet(), + () -> "[" + logger.getName()+ "]-" + nbb3); + final int nbb4 = nb.incrementAndGet(); + tester.testLogp(logger, "clazz" + nb.incrementAndGet(), + "method" + nb.incrementAndGet(), + thrown, () -> "[" + logger.getName()+ "]-" + nbb4); + } + +} --- /dev/null 2015-11-20 17:44:39.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/internal/backend/META-INF/services/java.lang.System$LoggerFinder 2015-11-20 17:44:39.000000000 +0100 @@ -0,0 +1,2 @@ +LoggerFinderBackendTest$CustomLoggerFinder + --- /dev/null 2015-11-20 17:44:40.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/internal/backend/SystemClassLoader.java 2015-11-20 17:44:39.000000000 +0100 @@ -0,0 +1,98 @@ +/* + * 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.IOException; +import java.net.URL; +import java.util.Enumeration; +import java.lang.System.LoggerFinder; + +/** + * A custom class loader which can hide the registered LoggerProvider + * depending on the value of a test.logger.hidesProvider system property. + * @author danielfuchs + */ +public class SystemClassLoader extends ClassLoader { + + final public boolean hidesProvider; + + public SystemClassLoader() { + hidesProvider = Boolean.getBoolean("test.logger.hidesProvider"); + } + public SystemClassLoader(ClassLoader parent) { + super(parent); + hidesProvider = Boolean.getBoolean("test.logger.hidesProvider"); + } + + boolean accept(String name) { + final boolean res = !name.endsWith(LoggerFinder.class.getName()); + if (res == false) { + System.out.println("Hiding " + name); + } + return res; + } + + @Override + public URL getResource(String name) { + if (hidesProvider && !accept(name)) { + return null; + } else { + return super.getResource(name); + } + } + + class Enumerator implements Enumeration { + final Enumeration enumeration; + volatile URL next; + Enumerator(Enumeration enumeration) { + this.enumeration = enumeration; + } + + @Override + public boolean hasMoreElements() { + if (next != null) return true; + if (!enumeration.hasMoreElements()) return false; + if (hidesProvider == false) return true; + next = enumeration.nextElement(); + if (accept(next.getPath())) return true; + next = null; + return hasMoreElements(); + } + + @Override + public URL nextElement() { + final URL res = next == null ? enumeration.nextElement() : next; + next = null; + if (hidesProvider == false || accept(res.getPath())) return res; + return nextElement(); + } + } + + @Override + public Enumeration getResources(String name) throws IOException { + final Enumeration enumeration = super.getResources(name); + return hidesProvider ? new Enumerator(enumeration) : enumeration; + } + + + +} --- /dev/null 2015-11-20 17:44:40.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/jdk/DefaultLoggerBridgeTest/DefaultLoggerBridgeTest.java 2015-11-20 17:44:40.000000000 +0100 @@ -0,0 +1,850 @@ +/* + * 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.security.AccessControlException; +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.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.ResourceBundle; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import java.util.logging.Handler; +import java.util.logging.LogManager; +import sun.util.logging.PlatformLogger; +import java.util.logging.LogRecord; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.util.stream.Stream; +import sun.util.logging.internal.LoggingProviderImpl; + +/** + * @test + * @bug 8140364 + * @summary JDK implementation specific unit test for JDK internal artifacts. + * Tests all internal bridge methods with the default LoggerFinder + * JUL backend. + * @modules java.base/sun.util.logging + * java.base/jdk.internal.logger + * java.logging/sun.util.logging.internal + * @run main/othervm DefaultLoggerBridgeTest + * @author danielfuchs + */ +public class DefaultLoggerBridgeTest { + + final static AtomicLong sequencer = new AtomicLong(); + final static boolean VERBOSE = false; + static final ThreadLocal allowControl = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAccess = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAll = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + + public static final Queue eventQueue = new ArrayBlockingQueue<>(128); + + public static final class LogEvent implements Cloneable { + + public LogEvent() { + this(sequencer.getAndIncrement()); + } + + LogEvent(long sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + long sequenceNumber; + boolean isLoggable; + String loggerName; + java.util.logging.Level level; + ResourceBundle bundle; + Throwable thrown; + Object[] args; + String msg; + String className; + String methodName; + + Object[] toArray() { + return new Object[] { + sequenceNumber, + isLoggable, + loggerName, + level, + bundle, + thrown, + args, + msg, + className, + methodName, + }; + } + + @Override + public String toString() { + return Arrays.deepToString(toArray()); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof LogEvent + && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray()); + } + + @Override + public int hashCode() { + return Objects.hash(toArray()); + } + + public LogEvent cloneWith(long sequenceNumber) + throws CloneNotSupportedException { + LogEvent cloned = (LogEvent)super.clone(); + cloned.sequenceNumber = sequenceNumber; + return cloned; + } + + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + java.util.logging.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + return LogEvent.of(sequenceNumber, isLoggable, name, + DefaultLoggerBridgeTest.class.getName(), + "testLogger", level, bundle, key, + thrown, params); + } + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + String className, String methodName, + java.util.logging.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + LogEvent evt = new LogEvent(sequenceNumber); + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = thrown; + evt.msg = key; + evt.isLoggable = isLoggable; + evt.className = className; + evt.methodName = methodName; + return evt; + } + + } + + static final java.util.logging.Level[] julLevels = { + java.util.logging.Level.ALL, + java.util.logging.Level.FINEST, + java.util.logging.Level.FINER, + java.util.logging.Level.FINE, + java.util.logging.Level.CONFIG, + java.util.logging.Level.INFO, + java.util.logging.Level.WARNING, + java.util.logging.Level.SEVERE, + java.util.logging.Level.OFF, + }; + + + public static class MyBundle extends ResourceBundle { + + final ConcurrentHashMap map = new ConcurrentHashMap<>(); + + @Override + protected Object handleGetObject(String key) { + if (key.contains(" (translated)")) { + throw new RuntimeException("Unexpected key: " + key); + } + return map.computeIfAbsent(key, k -> k + " (translated)"); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(map.keySet()); + } + + } + + public static class MyHandler extends Handler { + + @Override + public java.util.logging.Level getLevel() { + return java.util.logging.Level.ALL; + } + + @Override + public void publish(LogRecord record) { + eventQueue.add(LogEvent.of(sequencer.getAndIncrement(), + true, record.getLoggerName(), + record.getSourceClassName(), + record.getSourceMethodName(), + record.getLevel(), + record.getResourceBundle(), record.getMessage(), + record.getThrown(), record.getParameters())); + } + @Override + public void flush() { + } + @Override + public void close() throws SecurityException { + } + + } + + public static class MyLoggerBundle extends MyBundle { + + } + + static PlatformLogger.Bridge convert(Logger logger) { + boolean old = allowAccess.get().get(); + allowAccess.get().set(true); + try { + return PlatformLogger.Bridge.convert(logger); + } finally { + allowAccess.get().set(old); + } + } + + static Logger getLogger(String name, Class caller) { + boolean old = allowAccess.get().get(); + allowAccess.get().set(true); + try { + return jdk.internal.logger.LazyLoggers.getLogger(name, caller); + } finally { + allowAccess.get().set(old); + } + } + + static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS}; + + static void setSecurityManager() { + if (System.getSecurityManager() == null) { + Policy.setPolicy(new SimplePolicy(allowControl, allowAccess, allowAll)); + System.setSecurityManager(new SecurityManager()); + } + } + + public static void main(String[] args) { + if (args.length == 0) + args = new String[] { + "NOSECURITY", + "NOPERMISSIONS", + "WITHPERMISSIONS" + }; + + Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> { + LoggerFinder provider; + switch (testCase) { + case NOSECURITY: + System.out.println("\n*** Without Security Manager\n"); + test(true); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case NOPERMISSIONS: + System.out.println("\n*** With Security Manager, without permissions\n"); + setSecurityManager(); + test(false); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case WITHPERMISSIONS: + System.out.println("\n*** With Security Manager, with control permission\n"); + setSecurityManager(); + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + test(true); + } finally { + allowControl.get().set(control); + } + break; + default: + throw new RuntimeException("Unknown test case: " + testCase); + } + }); + System.out.println("\nPASSED: Tested " + sequencer.get() + " cases."); + } + + public static void test(boolean hasRequiredPermissions) { + + ResourceBundle loggerBundle = + ResourceBundle.getBundle(MyLoggerBundle.class.getName()); + final Map loggerDescMap = new HashMap<>(); + + Logger sysLogger1a = getLogger("foo", Thread.class); + loggerDescMap.put(sysLogger1a, "jdk.internal.logger.LazyLoggers.getLogger(\"foo\", Thread.class)"); + + Logger appLogger1 = System.getLogger("foo"); + loggerDescMap.put(appLogger1, "System.getLogger(\"foo\")"); + + LoggerFinder provider; + try { + provider = LoggerFinder.getLoggerFinder(); + if (!hasRequiredPermissions) { + throw new RuntimeException("Expected exception not raised"); + } + } catch (AccessControlException x) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected permission check", x); + } + if (!SimplePolicy.LOGGERFINDER_PERMISSION.equals(x.getPermission())) { + throw new RuntimeException("Unexpected permission in exception: " + x, x); + } + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = LoggerFinder.getLoggerFinder(); + } finally { + allowControl.get().set(control); + } + } + + Logger sysLogger1b = null; + try { + sysLogger1b = provider.getLogger("foo", Thread.class); + if (sysLogger1b != sysLogger1a) { + loggerDescMap.put(sysLogger1b, "provider.getLogger(\"foo\", Thread.class)"); + } + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a system logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(SimplePolicy.LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for system logger: " + acx); + } + + Logger appLogger2 = System.getLogger("foo", loggerBundle); + loggerDescMap.put(appLogger2, "System.getLogger(\"foo\", loggerBundle)"); + + if (appLogger2 == appLogger1) { + throw new RuntimeException("identical loggers"); + } + + Logger sysLogger2 = null; + try { + sysLogger2 = provider.getLocalizedLogger("foo", loggerBundle, Thread.class); + loggerDescMap.put(sysLogger2, "provider.getLocalizedLogger(\"foo\", loggerBundle, Thread.class)"); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a system logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(SimplePolicy.LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for localized system logger: " + acx); + } + if (hasRequiredPermissions && appLogger2 == sysLogger2) { + throw new RuntimeException("identical loggers"); + } + if (hasRequiredPermissions && sysLogger2 == sysLogger1a) { + throw new RuntimeException("identical loggers"); + } + + final java.util.logging.Logger appSink; + final java.util.logging.Logger sysSink; + final MyHandler appHandler; + final MyHandler sysHandler; + final boolean old = allowAll.get().get(); + allowAll.get().set(true); + try { + sysSink = LoggingProviderImpl.getLogManagerAccess().demandLoggerFor( + LogManager.getLogManager(), "foo", Thread.class); + appSink = LoggingProviderImpl.getLogManagerAccess().demandLoggerFor( + LogManager.getLogManager(), "foo", DefaultLoggerBridgeTest.class); + if (appSink == sysSink) { + throw new RuntimeException("identical backend loggers"); + } + appSink.addHandler(appHandler = new MyHandler()); + sysSink.addHandler(sysHandler = new MyHandler()); + appSink.setUseParentHandlers(VERBOSE); + sysSink.setUseParentHandlers(VERBOSE); + } finally { + allowAll.get().set(old); + } + + try { + testLogger(provider, loggerDescMap, "foo", null, convert(sysLogger1a), sysSink); + testLogger(provider, loggerDescMap, "foo", null, convert(appLogger1), appSink); + testLogger(provider, loggerDescMap, "foo", loggerBundle, convert(appLogger2), appSink); + if (sysLogger1b != null && sysLogger1b != sysLogger1a) { + testLogger(provider, loggerDescMap, "foo", null, convert(sysLogger1b), sysSink); + } + if (sysLogger2 != null) { + testLogger(provider, loggerDescMap, "foo", loggerBundle, convert(sysLogger2), sysSink); + } + } finally { + allowAll.get().set(true); + try { + appSink.removeHandler(appHandler); + sysSink.removeHandler(sysHandler); + } finally { + allowAll.get().set(old); + } + } + } + + public static class Foo { + + } + + static void verbose(String msg) { + if (VERBOSE) { + System.out.println(msg); + } + } + + static void checkLogEvent(LoggerFinder provider, String desc, + LogEvent expected) { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + + static void checkLogEvent(LoggerFinder provider, String desc, + LogEvent expected, boolean expectNotNull) { + LogEvent actual = eventQueue.poll(); + if (actual == null && !expectNotNull) return; + if (actual != null && !expectNotNull) { + throw new RuntimeException("Unexpected log event found for " + desc + + "\n\tgot: " + actual); + } + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + + static void setLevel(java.util.logging.Logger sink, java.util.logging.Level loggerLevel) { + boolean before = allowAll.get().get(); + try { + allowAll.get().set(true); + sink.setLevel(loggerLevel); + } finally { + allowAll.get().set(before); + } + } + + static sun.util.logging.PlatformLogger.Level toPlatformLevel(java.util.logging.Level level) { + boolean old = allowAccess.get().get(); + allowAccess.get().set(true); + try { + return sun.util.logging.PlatformLogger.Level.valueOf(level.getName()); + } finally { + allowAccess.get().set(old); + } + } + + // Calls the methods defined on LogProducer and verify the + // parameters received by the underlying logger. + private static void testLogger(LoggerFinder provider, + Map loggerDescMap, + String name, + ResourceBundle loggerBundle, + PlatformLogger.Bridge logger, + java.util.logging.Logger sink) { + + if (loggerDescMap.get(logger) == null) { + throw new RuntimeException("Missing description for " + logger); + } + System.out.println("Testing " + loggerDescMap.get(logger) + " [" + logger + "]"); + final java.util.logging.Level OFF = java.util.logging.Level.OFF; + + Foo foo = new Foo(); + String fooMsg = foo.toString(); + System.out.println("\tlogger.log(messageLevel, fooMsg)"); + System.out.println("\tlogger.(fooMsg)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, loggerBundle, + fooMsg, (Throwable)null, (Object[])null); + logger.log(toPlatformLevel(messageLevel), fooMsg); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + + Supplier supplier = new Supplier() { + @Override + public String get() { + return this.toString(); + } + }; + System.out.println("\tlogger.log(messageLevel, supplier)"); + System.out.println("\tlogger.(supplier)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, supplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, null, + supplier.get(), (Throwable)null, (Object[])null); + logger.log(toPlatformLevel(messageLevel), supplier); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + + String format = "two params [{1} {2}]"; + Object arg1 = foo; + Object arg2 = fooMsg; + System.out.println("\tlogger.log(messageLevel, format, arg1, arg2)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, format, foo, fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, loggerBundle, + format, (Throwable)null, arg1, arg2); + logger.log(toPlatformLevel(messageLevel), format, arg1, arg2); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + + Throwable thrown = new Exception("OK: log me!"); + System.out.println("\tlogger.log(messageLevel, fooMsg, thrown)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, fooMsg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, loggerBundle, + fooMsg, thrown, (Object[])null); + logger.log(toPlatformLevel(messageLevel), fooMsg, thrown); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + + System.out.println("\tlogger.log(messageLevel, thrown, supplier)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, thrown, supplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, null, + supplier.get(), thrown, (Object[])null); + logger.log(toPlatformLevel(messageLevel), thrown, supplier); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + + String sourceClass = "blah.Blah"; + String sourceMethod = "blih"; + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, fooMsg)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, loggerBundle, + fooMsg, (Throwable)null, (Object[])null); + logger.logp(toPlatformLevel(messageLevel), sourceClass, sourceMethod, fooMsg); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, supplier)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, supplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, null, + supplier.get(), (Throwable)null, (Object[])null); + logger.logp(toPlatformLevel(messageLevel), sourceClass, sourceMethod, supplier); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, format, arg1, arg2)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, format, arg1, arg2): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, loggerBundle, + format, (Throwable)null, arg1, arg2); + logger.logp(toPlatformLevel(messageLevel), sourceClass, sourceMethod, format, arg1, arg2); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, fooMsg, thrown)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, loggerBundle, + fooMsg, thrown, (Object[])null); + logger.logp(toPlatformLevel(messageLevel), sourceClass, sourceMethod, fooMsg, thrown); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, thrown, supplier)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, thrown, supplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, null, + supplier.get(), thrown, (Object[])null); + logger.logp(toPlatformLevel(messageLevel), sourceClass, sourceMethod, thrown, supplier); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + + ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName()); + System.out.println("\tlogger.logrb(messageLevel, bundle, format, arg1, arg2)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.logrb(messageLevel, bundle, format, arg1, arg2): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, bundle, + format, (Throwable)null, arg1, arg2); + logger.logrb(toPlatformLevel(messageLevel), bundle, format, arg1, arg2); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + + System.out.println("\tlogger.logrb(messageLevel, bundle, msg, thrown)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.logrb(messageLevel, bundle, msg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, bundle, + fooMsg, thrown, (Object[])null); + logger.logrb(toPlatformLevel(messageLevel), bundle, fooMsg, thrown); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + + System.out.println("\tlogger.logrb(messageLevel, sourceClass, sourceMethod, bundle, format, arg1, arg2)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, format, arg1, arg2): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, bundle, + format, (Throwable)null, arg1, arg2); + logger.logrb(toPlatformLevel(messageLevel), sourceClass, sourceMethod, bundle, format, arg1, arg2); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + + System.out.println("\tlogger.logrb(messageLevel, sourceClass, sourceMethod, bundle, msg, thrown)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, msg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, bundle, + fooMsg, thrown, (Object[])null); + logger.logrb(toPlatformLevel(messageLevel), sourceClass, sourceMethod, bundle, fooMsg, thrown); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + } + + 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 { + public static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + final static RuntimePermission ACCESS_LOGGER = new RuntimePermission("accessClassInPackage.jdk.internal.logger"); + final static RuntimePermission ACCESS_LOGGING = new RuntimePermission("accessClassInPackage.sun.util.logging"); + + final Permissions permissions; + final Permissions allPermissions; + final ThreadLocal allowControl; + final ThreadLocal allowAccess; + final ThreadLocal allowAll; + public SimplePolicy(ThreadLocal allowControl, + ThreadLocal allowAccess, + ThreadLocal allowAll) { + this.allowControl = allowControl; + this.allowAccess = allowAccess; + this.allowAll = allowAll; + permissions = new Permissions(); + allPermissions = new PermissionsBuilder() + .add(new java.security.AllPermission()) + .toPermissions(); + } + + Permissions getPermissions() { + if (allowControl.get().get() || allowAccess.get().get() || allowAll.get().get()) { + PermissionsBuilder builder = new PermissionsBuilder() + .addAll(permissions); + if (allowControl.get().get()) { + builder.add(LOGGERFINDER_PERMISSION); + } + if (allowAccess.get().get()) { + builder.add(ACCESS_LOGGER); + builder.add(ACCESS_LOGGING); + } + if (allowAll.get().get()) { + builder.addAll(allPermissions); + } + return builder.toPermissions(); + } + return permissions; + } + + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + return getPermissions().implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + } +} --- /dev/null 2015-11-20 17:44:41.000000000 +0100 +++ new/jdk/test/java/lang/System/LoggerFinder/jdk/DefaultPlatformLoggerTest/DefaultPlatformLoggerTest.java 2015-11-20 17:44:41.000000000 +0100 @@ -0,0 +1,544 @@ +/* + * 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.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.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.ResourceBundle; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.logging.Handler; +import java.util.logging.LogManager; +import java.util.logging.LogRecord; +import java.lang.System.LoggerFinder; +import sun.util.logging.PlatformLogger; +import sun.util.logging.internal.LoggingProviderImpl; + +/** + * @test + * @bug 8140364 + * @summary Tests all PlatformLogger methods with the default LoggerFinder JUL backend. + * @modules java.base/sun.util.logging java.logging/sun.util.logging.internal + * @run main/othervm DefaultPlatformLoggerTest + * @author danielfuchs + */ +public class DefaultPlatformLoggerTest { + + final static AtomicLong sequencer = new AtomicLong(); + final static boolean VERBOSE = false; + static final ThreadLocal allowControl = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAll = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + + public static final Queue eventQueue = new ArrayBlockingQueue<>(128); + + public static final class LogEvent implements Cloneable { + + public LogEvent() { + this(sequencer.getAndIncrement()); + } + + LogEvent(long sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + long sequenceNumber; + boolean isLoggable; + String loggerName; + java.util.logging.Level level; + ResourceBundle bundle; + Throwable thrown; + Object[] args; + String msg; + String className; + String methodName; + + Object[] toArray() { + return new Object[] { + sequenceNumber, + isLoggable, + loggerName, + level, + bundle, + thrown, + args, + msg, + className, + methodName, + }; + } + + @Override + public String toString() { + return Arrays.deepToString(toArray()); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof LogEvent + && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray()); + } + + @Override + public int hashCode() { + return Objects.hash(toArray()); + } + + public LogEvent cloneWith(long sequenceNumber) + throws CloneNotSupportedException { + LogEvent cloned = (LogEvent)super.clone(); + cloned.sequenceNumber = sequenceNumber; + return cloned; + } + + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + java.util.logging.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + return LogEvent.of(sequenceNumber, isLoggable, name, + DefaultPlatformLoggerTest.class.getName(), + "testLogger", level, bundle, key, + thrown, params); + } + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + String className, String methodName, + java.util.logging.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + LogEvent evt = new LogEvent(sequenceNumber); + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = thrown; + evt.msg = key; + evt.isLoggable = isLoggable; + evt.className = className; + evt.methodName = methodName; + return evt; + } + + } + + static final java.util.logging.Level[] julLevels = { + java.util.logging.Level.ALL, + new java.util.logging.Level("FINER_THAN_FINEST", java.util.logging.Level.FINEST.intValue() - 10) {}, + java.util.logging.Level.FINEST, + new java.util.logging.Level("FINER_THAN_FINER", java.util.logging.Level.FINER.intValue() - 10) {}, + java.util.logging.Level.FINER, + new java.util.logging.Level("FINER_THAN_FINE", java.util.logging.Level.FINE.intValue() - 10) {}, + java.util.logging.Level.FINE, + new java.util.logging.Level("FINER_THAN_CONFIG", java.util.logging.Level.FINE.intValue() + 10) {}, + java.util.logging.Level.CONFIG, + new java.util.logging.Level("FINER_THAN_INFO", java.util.logging.Level.INFO.intValue() - 10) {}, + java.util.logging.Level.INFO, + new java.util.logging.Level("FINER_THAN_WARNING", java.util.logging.Level.INFO.intValue() + 10) {}, + java.util.logging.Level.WARNING, + new java.util.logging.Level("FINER_THAN_SEVERE", java.util.logging.Level.SEVERE.intValue() - 10) {}, + java.util.logging.Level.SEVERE, + new java.util.logging.Level("FATAL", java.util.logging.Level.SEVERE.intValue() + 10) {}, + java.util.logging.Level.OFF, + }; + + static final java.util.logging.Level[] julPlatformLevels = { + java.util.logging.Level.FINEST, + java.util.logging.Level.FINER, + java.util.logging.Level.FINE, + java.util.logging.Level.CONFIG, + java.util.logging.Level.INFO, + java.util.logging.Level.WARNING, + java.util.logging.Level.SEVERE, + }; + + + public static class MyBundle extends ResourceBundle { + + final ConcurrentHashMap map = new ConcurrentHashMap<>(); + + @Override + protected Object handleGetObject(String key) { + if (key.contains(" (translated)")) { + throw new RuntimeException("Unexpected key: " + key); + } + return map.computeIfAbsent(key, k -> k + " (translated)"); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(map.keySet()); + } + + } + + public static class MyHandler extends Handler { + + @Override + public java.util.logging.Level getLevel() { + return java.util.logging.Level.ALL; + } + + @Override + public void publish(LogRecord record) { + eventQueue.add(LogEvent.of(sequencer.getAndIncrement(), + true, record.getLoggerName(), + record.getSourceClassName(), + record.getSourceMethodName(), + record.getLevel(), + record.getResourceBundle(), record.getMessage(), + record.getThrown(), record.getParameters())); + } + @Override + public void flush() { + } + @Override + public void close() throws SecurityException { + } + + } + + public static class MyLoggerBundle extends MyBundle { + + } + + public static void main(String[] args) throws Exception { + LoggerFinder provider = LoggerFinder.getLoggerFinder(); + java.util.logging.Logger appSink = LoggingProviderImpl.getLogManagerAccess() + .demandLoggerFor(LogManager.getLogManager(), "foo", + DefaultPlatformLoggerTest.class); + java.util.logging.Logger sysSink = LoggingProviderImpl.getLogManagerAccess() + .demandLoggerFor(LogManager.getLogManager(),"foo", Thread.class); + appSink.addHandler(new MyHandler()); + sysSink.addHandler(new MyHandler()); + appSink.setUseParentHandlers(VERBOSE); + sysSink.setUseParentHandlers(VERBOSE); + + System.out.println("\n*** Without Security Manager\n"); + test(provider, true, appSink, sysSink); + System.out.println("Tetscase count: " + sequencer.get()); + + Policy.setPolicy(new SimplePolicy(allowAll, allowControl)); + System.setSecurityManager(new SecurityManager()); + + System.out.println("\n*** With Security Manager, without permissions\n"); + test(provider, false, appSink, sysSink); + System.out.println("Tetscase count: " + sequencer.get()); + + System.out.println("\n*** With Security Manager, with control permission\n"); + allowControl.get().set(true); + test(provider, true, appSink, sysSink); + + System.out.println("\nPASSED: Tested " + sequencer.get() + " cases."); + } + + public static void test(LoggerFinder provider, boolean hasRequiredPermissions, + java.util.logging.Logger appSink, java.util.logging.Logger sysSink) throws Exception { + + // No way to giva a resource bundle to a platform logger. + // ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName()); + final Map loggerDescMap = new HashMap<>(); + + PlatformLogger platform = PlatformLogger.getLogger("foo"); + loggerDescMap.put(platform, "PlatformLogger.getLogger(\"foo\")"); + + testLogger(provider, loggerDescMap, "foo", null, platform, sysSink); + } + + public static class Foo { + + } + + static void verbose(String msg) { + if (VERBOSE) { + System.out.println(msg); + } + } + + static void checkLogEvent(LoggerFinder provider, String desc, + LogEvent expected) { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + + static void checkLogEvent(LoggerFinder provider, String desc, + LogEvent expected, boolean expectNotNull) { + LogEvent actual = eventQueue.poll(); + if (actual == null && !expectNotNull) return; + if (actual != null && !expectNotNull) { + throw new RuntimeException("Unexpected log event found for " + desc + + "\n\tgot: " + actual); + } + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + + static void setLevel(java.util.logging.Logger sink, java.util.logging.Level loggerLevel) { + boolean before = allowAll.get().get(); + try { + allowAll.get().set(true); + sink.setLevel(loggerLevel); + } finally { + allowAll.get().set(before); + } + } + + // Calls the methods defined on LogProducer and verify the + // parameters received by the underlying logger. + private static void testLogger(LoggerFinder provider, + Map loggerDescMap, + String name, + ResourceBundle loggerBundle, + PlatformLogger logger, + java.util.logging.Logger sink) throws Exception { + + System.out.println("Testing " + loggerDescMap.get(logger)); + final java.util.logging.Level OFF = java.util.logging.Level.OFF; + + Foo foo = new Foo(); + String fooMsg = foo.toString(); + System.out.println("\tlogger.(fooMsg)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel : julPlatformLevels) { + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, loggerBundle, + fooMsg, (Throwable)null, (Object[])null); + String desc2 = "logger." + messageLevel.toString().toLowerCase() + + "(fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + if (messageLevel == java.util.logging.Level.FINEST) { + logger.finest(fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.FINER) { + logger.finer(fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.FINE) { + logger.fine(fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.CONFIG) { + logger.config(fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.INFO) { + logger.info(fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.WARNING) { + logger.warning(fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.SEVERE) { + logger.severe(fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } + } + } + + Throwable thrown = new Exception("OK: log me!"); + System.out.println("\tlogger.(msg, thrown)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julPlatformLevels) { + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, loggerBundle, + fooMsg, thrown, (Object[])null); + String desc2 = "logger." + messageLevel.toString().toLowerCase() + + "(msg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + if (messageLevel == java.util.logging.Level.FINEST) { + logger.finest(fooMsg, thrown); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.FINER) { + logger.finer(fooMsg, thrown); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.FINE) { + logger.fine(fooMsg, thrown); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.CONFIG) { + logger.config(fooMsg, thrown); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.INFO) { + logger.info(fooMsg, thrown); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.WARNING) { + logger.warning(fooMsg, thrown); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.SEVERE) { + logger.severe(fooMsg, thrown); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } + } + } + + String format = "two params [{1} {2}]"; + Object arg1 = foo; + Object arg2 = fooMsg; + System.out.println("\tlogger.(format, arg1, arg2)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel : julPlatformLevels) { + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, loggerBundle, + format, (Throwable)null, foo, fooMsg); + String desc2 = "logger." + messageLevel.toString().toLowerCase() + + "(format, foo, fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + if (messageLevel == java.util.logging.Level.FINEST) { + logger.finest(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.FINER) { + logger.finer(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.FINE) { + logger.fine(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.CONFIG) { + logger.config(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.INFO) { + logger.info(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.WARNING) { + logger.warning(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.SEVERE) { + logger.severe(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } + } + } + + } + + 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 { + public static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + + final Permissions permissions; + final Permissions withControlPermissions; + final Permissions allPermissions; + final ThreadLocal allowAll; + final ThreadLocal allowControl; + public SimplePolicy(ThreadLocal allowAll, + ThreadLocal allowControl) { + this.allowAll = allowAll; + this.allowControl = allowControl; + permissions = new Permissions(); + + withControlPermissions = new Permissions(); + withControlPermissions.add(LOGGERFINDER_PERMISSION); + + // 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); + if (allowControl.get().get()) return withControlPermissions.implies(permission); + return permissions.implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll( + allowAll.get().get() ? allPermissions : + allowControl.get().get() + ? withControlPermissions : permissions).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll( + allowAll.get().get() ? allPermissions : + allowControl.get().get() + ? withControlPermissions : permissions).toPermissions(); + } + } +} --- old/jdk/src/java.base/share/classes/sun/util/logging/LoggingProxy.java 2015-11-20 17:44:41.000000000 +0100 +++ /dev/null 2015-11-20 17:44:41.000000000 +0100 @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2009, 2013, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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. - */ - - -package sun.util.logging; - -/** - * A proxy interface for the java.util.logging support. - * - * @see sun.util.logging.LoggingSupport - */ -public interface LoggingProxy { - // Methods to bridge java.util.logging.Logger methods - public Object getLogger(String name); - - public Object getLevel(Object logger); - - public void setLevel(Object logger, Object newLevel); - - public boolean isLoggable(Object logger, Object level); - - public void log(Object logger, Object level, String msg); - - public void log(Object logger, Object level, String msg, Throwable t); - - public void log(Object logger, Object level, String msg, Object... params); - - // Methods to bridge java.util.logging.LoggingMXBean methods - public java.util.List getLoggerNames(); - - public String getLoggerLevel(String loggerName); - - public void setLoggerLevel(String loggerName, String levelName); - - public String getParentLoggerName(String loggerName); - - // Methods to bridge Level.parse() and Level.getName() method - public Object parseLevel(String levelName); - - public String getLevelName(Object level); - - public int getLevelValue(Object level); - - // return the logging property - public String getProperty(String key); -} --- old/jdk/src/java.base/share/classes/sun/util/logging/LoggingSupport.java 2015-11-20 17:44:42.000000000 +0100 +++ /dev/null 2015-11-20 17:44:42.000000000 +0100 @@ -1,190 +0,0 @@ -/* - * Copyright (c) 2009, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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. - */ - - -package sun.util.logging; - -import java.lang.reflect.Field; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.time.ZonedDateTime; - -/** - * Internal API to support JRE implementation to detect if the java.util.logging - * support is available but with no dependency on the java.util.logging - * classes. This LoggingSupport class provides several static methods to - * access the java.util.logging functionality that requires the caller - * to ensure that the logging support is {@linkplain #isAvailable available} - * before invoking it. - * - * @see sun.util.logging.PlatformLogger if you want to log messages even - * if the logging support is not available - */ -public class LoggingSupport { - private LoggingSupport() { } - - private static final LoggingProxy proxy = - AccessController.doPrivileged(new PrivilegedAction() { - public LoggingProxy run() { - try { - // create a LoggingProxyImpl instance when - // java.util.logging classes exist - Class c = Class.forName("java.util.logging.LoggingProxyImpl", true, null); - Field f = c.getDeclaredField("INSTANCE"); - f.setAccessible(true); - return (LoggingProxy) f.get(null); - } catch (ClassNotFoundException cnf) { - return null; - } catch (NoSuchFieldException e) { - throw new AssertionError(e); - } catch (IllegalAccessException e) { - throw new AssertionError(e); - } - }}); - - /** - * Returns true if java.util.logging support is available. - */ - public static boolean isAvailable() { - return proxy != null; - } - - private static void ensureAvailable() { - if (proxy == null) - throw new AssertionError("Should not here"); - } - - public static java.util.List getLoggerNames() { - ensureAvailable(); - return proxy.getLoggerNames(); - } - public static String getLoggerLevel(String loggerName) { - ensureAvailable(); - return proxy.getLoggerLevel(loggerName); - } - - public static void setLoggerLevel(String loggerName, String levelName) { - ensureAvailable(); - proxy.setLoggerLevel(loggerName, levelName); - } - - public static String getParentLoggerName(String loggerName) { - ensureAvailable(); - return proxy.getParentLoggerName(loggerName); - } - - public static Object getLogger(String name) { - ensureAvailable(); - return proxy.getLogger(name); - } - - public static Object getLevel(Object logger) { - ensureAvailable(); - return proxy.getLevel(logger); - } - - public static void setLevel(Object logger, Object newLevel) { - ensureAvailable(); - proxy.setLevel(logger, newLevel); - } - - public static boolean isLoggable(Object logger, Object level) { - ensureAvailable(); - return proxy.isLoggable(logger,level); - } - - public static void log(Object logger, Object level, String msg) { - ensureAvailable(); - proxy.log(logger, level, msg); - } - - public static void log(Object logger, Object level, String msg, Throwable t) { - ensureAvailable(); - proxy.log(logger, level, msg, t); - } - - public static void log(Object logger, Object level, String msg, Object... params) { - ensureAvailable(); - proxy.log(logger, level, msg, params); - } - - public static Object parseLevel(String levelName) { - ensureAvailable(); - return proxy.parseLevel(levelName); - } - - public static String getLevelName(Object level) { - ensureAvailable(); - return proxy.getLevelName(level); - } - - public static int getLevelValue(Object level) { - ensureAvailable(); - return proxy.getLevelValue(level); - } - - // Since JDK 9, logging uses java.time to get more precise time stamps. - // It is possible to configure the simple format to print nano seconds (.%1$tN) - // by specifying: - // java.util.logging.SimpleFormatter.format=%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS.%1$tN %1$Tp %2$s%n%4$s: %5$s%6$s%n - // in the logging configuration - private static final String DEFAULT_FORMAT = - "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n"; - - private static final String FORMAT_PROP_KEY = "java.util.logging.SimpleFormatter.format"; - public static String getSimpleFormat() { - return getSimpleFormat(true); - } - - // useProxy if true will cause initialization of - // java.util.logging and read its configuration - static String getSimpleFormat(boolean useProxy) { - String format = - AccessController.doPrivileged( - new PrivilegedAction() { - public String run() { - return System.getProperty(FORMAT_PROP_KEY); - } - }); - - if (useProxy && proxy != null && format == null) { - format = proxy.getProperty(FORMAT_PROP_KEY); - } - - if (format != null) { - try { - // validate the user-defined format string - String.format(format, ZonedDateTime.now(), "", "", "", "", ""); - } catch (IllegalArgumentException e) { - // illegal syntax; fall back to the default format - format = DEFAULT_FORMAT; - } - } else { - format = DEFAULT_FORMAT; - } - return format; - } - -} --- old/jdk/src/java.logging/share/classes/java/util/logging/LoggingProxyImpl.java 2015-11-20 17:44:42.000000000 +0100 +++ /dev/null 2015-11-20 17:44:42.000000000 +0100 @@ -1,117 +0,0 @@ -/* - * Copyright (c) 2009, 2013, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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. - */ - -package java.util.logging; - -import sun.util.logging.LoggingProxy; - -/** - * Implementation of LoggingProxy when java.util.logging classes exist. - */ -class LoggingProxyImpl implements LoggingProxy { - static final LoggingProxy INSTANCE = new LoggingProxyImpl(); - - private LoggingProxyImpl() { } - - @Override - public Object getLogger(String name) { - // always create a platform logger with the resource bundle name - return Logger.getPlatformLogger(name); - } - - @Override - public Object getLevel(Object logger) { - return ((Logger) logger).getLevel(); - } - - @Override - public void setLevel(Object logger, Object newLevel) { - ((Logger) logger).setLevel((Level) newLevel); - } - - @Override - public boolean isLoggable(Object logger, Object level) { - return ((Logger) logger).isLoggable((Level) level); - } - - @Override - public void log(Object logger, Object level, String msg) { - ((Logger) logger).log((Level) level, msg); - } - - @Override - public void log(Object logger, Object level, String msg, Throwable t) { - ((Logger) logger).log((Level) level, msg, t); - } - - @Override - public void log(Object logger, Object level, String msg, Object... params) { - ((Logger) logger).log((Level) level, msg, params); - } - - @Override - public java.util.List getLoggerNames() { - return LogManager.getLoggingMXBean().getLoggerNames(); - } - - @Override - public String getLoggerLevel(String loggerName) { - return LogManager.getLoggingMXBean().getLoggerLevel(loggerName); - } - - @Override - public void setLoggerLevel(String loggerName, String levelName) { - LogManager.getLoggingMXBean().setLoggerLevel(loggerName, levelName); - } - - @Override - public String getParentLoggerName(String loggerName) { - return LogManager.getLoggingMXBean().getParentLoggerName(loggerName); - } - - @Override - public Object parseLevel(String levelName) { - Level level = Level.findLevel(levelName); - if (level == null) { - throw new IllegalArgumentException("Unknown level \"" + levelName + "\""); - } - return level; - } - - @Override - public String getLevelName(Object level) { - return ((Level) level).getLevelName(); - } - - @Override - public int getLevelValue(Object level) { - return ((Level) level).intValue(); - } - - @Override - public String getProperty(String key) { - return LogManager.getLogManager().getProperty(key); - } -}