1 /*
   2  * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 import java.security.AccessControlException;
  24 import java.security.CodeSource;
  25 import java.security.Permission;
  26 import java.security.PermissionCollection;
  27 import java.security.Permissions;
  28 import java.security.Policy;
  29 import java.security.ProtectionDomain;
  30 import java.util.Arrays;
  31 import java.util.Collections;
  32 import java.util.Enumeration;
  33 import java.util.HashMap;
  34 import java.util.Map;
  35 import java.util.Objects;
  36 import java.util.Queue;
  37 import java.util.ResourceBundle;
  38 import java.util.concurrent.ArrayBlockingQueue;
  39 import java.util.concurrent.ConcurrentHashMap;
  40 import java.util.concurrent.atomic.AtomicBoolean;
  41 import java.util.concurrent.atomic.AtomicLong;
  42 import java.util.function.Supplier;
  43 import java.util.logging.Handler;
  44 import java.util.logging.LogRecord;
  45 import java.lang.System.LoggerFinder;
  46 import java.lang.System.Logger;
  47 import java.lang.System.Logger.Level;
  48 import java.util.stream.Stream;
  49 
  50 /**
  51  * @test
  52  * @bug     8140364
  53  * @summary Tests the default implementation of System.Logger, when
  54  *          JUL is the default backend.
  55  * @modules java.logging
  56  * @build AccessSystemLogger DefaultLoggerFinderTest
  57  * @run  driver AccessSystemLogger
  58  * @run  main/othervm -Xbootclasspath/a:boot DefaultLoggerFinderTest NOSECURITY
  59  * @run  main/othervm -Xbootclasspath/a:boot DefaultLoggerFinderTest NOPERMISSIONS
  60  * @run  main/othervm -Xbootclasspath/a:boot DefaultLoggerFinderTest WITHPERMISSIONS
  61  * @author danielfuchs
  62  */
  63 public class DefaultLoggerFinderTest {
  64 
  65     static final RuntimePermission LOGGERFINDER_PERMISSION =
  66                 new RuntimePermission("loggerFinder");
  67     final static AtomicLong sequencer = new AtomicLong();
  68     final static boolean VERBOSE = false;
  69     static final ThreadLocal<AtomicBoolean> allowControl = new ThreadLocal<AtomicBoolean>() {
  70         @Override
  71         protected AtomicBoolean initialValue() {
  72             return  new AtomicBoolean(false);
  73         }
  74     };
  75     static final ThreadLocal<AtomicBoolean> allowAll = new ThreadLocal<AtomicBoolean>() {
  76         @Override
  77         protected AtomicBoolean initialValue() {
  78             return  new AtomicBoolean(false);
  79         }
  80     };
  81 
  82     static final AccessSystemLogger accessSystemLogger = new AccessSystemLogger();
  83 
  84     public static final Queue<LogEvent> eventQueue = new ArrayBlockingQueue<>(128);
  85 
  86     public static final class LogEvent {
  87 
  88         public LogEvent() {
  89             this(sequencer.getAndIncrement());
  90         }
  91 
  92         LogEvent(long sequenceNumber) {
  93             this.sequenceNumber = sequenceNumber;
  94         }
  95 
  96         long sequenceNumber;
  97         boolean isLoggable;
  98         String loggerName;
  99         java.util.logging.Level level;
 100         ResourceBundle bundle;
 101         Throwable thrown;
 102         Object[] args;
 103         String msg;
 104         String className;
 105         String methodName;
 106 
 107         Object[] toArray() {
 108             return new Object[] {
 109                 sequenceNumber,
 110                 isLoggable,
 111                 loggerName,
 112                 level,
 113                 bundle,
 114                 thrown,
 115                 args,
 116                 msg,
 117                 className,
 118                 methodName,
 119             };
 120         }
 121 
 122         @Override
 123         public String toString() {
 124             return Arrays.deepToString(toArray());
 125         }
 126 
 127         @Override
 128         public boolean equals(Object obj) {
 129             return obj instanceof LogEvent
 130                     && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray());
 131         }
 132 
 133         @Override
 134         public int hashCode() {
 135             return Objects.hash(toArray());
 136         }
 137         public static LogEvent of(long sequenceNumber,
 138                 boolean isLoggable, String name,
 139                 java.util.logging.Level level, ResourceBundle bundle,
 140                 String key, Throwable thrown, Object... params) {
 141             return LogEvent.of(sequenceNumber, isLoggable, name,
 142                     DefaultLoggerFinderTest.class.getName(),
 143                     "testLogger", level, bundle, key,
 144                     thrown, params);
 145         }
 146         public static LogEvent of(long sequenceNumber,
 147                 boolean isLoggable, String name,
 148                 String className, String methodName,
 149                 java.util.logging.Level level, ResourceBundle bundle,
 150                 String key, Throwable thrown, Object... params) {
 151             LogEvent evt = new LogEvent(sequenceNumber);
 152             evt.loggerName = name;
 153             evt.level = level;
 154             evt.args = params;
 155             evt.bundle = bundle;
 156             evt.thrown = thrown;
 157             evt.msg = key;
 158             evt.isLoggable = isLoggable;
 159             evt.className = className;
 160             evt.methodName = methodName;
 161             return evt;
 162         }
 163 
 164     }
 165 
 166     static java.util.logging.Level mapToJul(Level level) {
 167         switch (level) {
 168             case ALL: return java.util.logging.Level.ALL;
 169             case TRACE: return java.util.logging.Level.FINER;
 170             case DEBUG: return java.util.logging.Level.FINE;
 171             case INFO: return java.util.logging.Level.INFO;
 172             case WARNING: return java.util.logging.Level.WARNING;
 173             case ERROR: return java.util.logging.Level.SEVERE;
 174             case OFF: return java.util.logging.Level.OFF;
 175         }
 176         throw new InternalError("No such level: " + level);
 177     }
 178 
 179     static final java.util.logging.Level[] julLevels = {
 180         java.util.logging.Level.ALL,
 181         new java.util.logging.Level("FINER_THAN_FINEST", java.util.logging.Level.FINEST.intValue() - 10) {},
 182         java.util.logging.Level.FINEST,
 183         new java.util.logging.Level("FINER_THAN_FINER", java.util.logging.Level.FINER.intValue() - 10) {},
 184         java.util.logging.Level.FINER,
 185         new java.util.logging.Level("FINER_THAN_FINE", java.util.logging.Level.FINE.intValue() - 10) {},
 186         java.util.logging.Level.FINE,
 187         new java.util.logging.Level("FINER_THAN_CONFIG", java.util.logging.Level.FINE.intValue() + 10) {},
 188         java.util.logging.Level.CONFIG,
 189         new java.util.logging.Level("FINER_THAN_INFO", java.util.logging.Level.INFO.intValue() - 10) {},
 190         java.util.logging.Level.INFO,
 191         new java.util.logging.Level("FINER_THAN_WARNING", java.util.logging.Level.INFO.intValue() + 10) {},
 192         java.util.logging.Level.WARNING,
 193         new java.util.logging.Level("FINER_THAN_SEVERE", java.util.logging.Level.SEVERE.intValue() - 10) {},
 194         java.util.logging.Level.SEVERE,
 195         new java.util.logging.Level("FATAL", java.util.logging.Level.SEVERE.intValue() + 10) {},
 196         java.util.logging.Level.OFF,
 197     };
 198 
 199     static final Level[] mappedLevels = {
 200         Level.ALL,     // ALL
 201         Level.DEBUG,   // FINER_THAN_FINEST
 202         Level.DEBUG,   // FINEST
 203         Level.DEBUG,   // FINER_THAN_FINER
 204         Level.TRACE,   // FINER
 205         Level.TRACE,   // FINER_THAN_FINE
 206         Level.DEBUG,   // FINE
 207         Level.DEBUG,   // FINER_THAN_CONFIG
 208         Level.DEBUG,   // CONFIG
 209         Level.DEBUG,   // FINER_THAN_INFO
 210         Level.INFO,    // INFO
 211         Level.INFO,    // FINER_THAN_WARNING
 212         Level.WARNING, // WARNING
 213         Level.WARNING, // FINER_THAN_SEVERE
 214         Level.ERROR,   // SEVERE
 215         Level.ERROR,   // FATAL
 216         Level.OFF,     // OFF
 217     };
 218 
 219     final static Map<java.util.logging.Level, Level> julToSpiMap;
 220     static {
 221         Map<java.util.logging.Level, Level> map = new HashMap<>();
 222         if (mappedLevels.length != julLevels.length) {
 223             throw new ExceptionInInitializerError("Array lengths differ"
 224                 + "\n\tjulLevels=" + Arrays.deepToString(julLevels)
 225                 + "\n\tmappedLevels=" + Arrays.deepToString(mappedLevels));
 226         }
 227         for (int i=0; i<julLevels.length; i++) {
 228             map.put(julLevels[i], mappedLevels[i]);
 229         }
 230         julToSpiMap = Collections.unmodifiableMap(map);
 231     }
 232 
 233     public static class MyBundle extends ResourceBundle {
 234 
 235         final ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();
 236 
 237         @Override
 238         protected Object handleGetObject(String key) {
 239             if (key.contains(" (translated)")) {
 240                 throw new RuntimeException("Unexpected key: " + key);
 241             }
 242             return map.computeIfAbsent(key, k -> k + " (translated)");
 243         }
 244 
 245         @Override
 246         public Enumeration<String> getKeys() {
 247             return Collections.enumeration(map.keySet());
 248         }
 249 
 250     }
 251 
 252     public static class MyHandler extends Handler {
 253 
 254         @Override
 255         public java.util.logging.Level getLevel() {
 256             return java.util.logging.Level.ALL;
 257         }
 258 
 259         @Override
 260         public void publish(LogRecord record) {
 261             eventQueue.add(LogEvent.of(sequencer.getAndIncrement(),
 262                     true, record.getLoggerName(),
 263                     record.getSourceClassName(),
 264                     record.getSourceMethodName(),
 265                     record.getLevel(),
 266                     record.getResourceBundle(), record.getMessage(),
 267                     record.getThrown(), record.getParameters()));
 268         }
 269         @Override
 270         public void flush() {
 271         }
 272         @Override
 273         public void close() throws SecurityException {
 274         }
 275 
 276     }
 277 
 278     public static class MyLoggerBundle extends MyBundle {
 279 
 280     }
 281 
 282 
 283     static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS};
 284 
 285     static void setSecurityManager() {
 286         if (System.getSecurityManager() == null) {
 287             Policy.setPolicy(new SimplePolicy(allowAll, allowControl));
 288             System.setSecurityManager(new SecurityManager());
 289         }
 290     }
 291 
 292     public static void main(String[] args) {
 293         if (args.length == 0)
 294             args = new String[] {
 295                 "NOSECURITY",
 296                 "NOPERMISSIONS",
 297                 "WITHPERMISSIONS"
 298             };
 299 
 300         final java.util.logging.Logger appSink = java.util.logging.Logger.getLogger("foo");
 301         final java.util.logging.Logger sysSink = accessSystemLogger.demandSystemLogger("foo");
 302         appSink.addHandler(new MyHandler());
 303         sysSink.addHandler(new MyHandler());
 304         appSink.setUseParentHandlers(VERBOSE);
 305         sysSink.setUseParentHandlers(VERBOSE);
 306 
 307         Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> {
 308             LoggerFinder provider;
 309             switch (testCase) {
 310                 case NOSECURITY:
 311                     System.out.println("\n*** Without Security Manager\n");
 312                     provider = LoggerFinder.getLoggerFinder();
 313                     test(provider, true, appSink, sysSink);
 314                     System.out.println("Tetscase count: " + sequencer.get());
 315                     break;
 316                 case NOPERMISSIONS:
 317                     System.out.println("\n*** With Security Manager, without permissions\n");
 318                     setSecurityManager();
 319                     try {
 320                         provider = LoggerFinder.getLoggerFinder();
 321                         throw new RuntimeException("Expected exception not raised");
 322                     } catch (AccessControlException x) {
 323                         if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) {
 324                             throw new RuntimeException("Unexpected permission check", x);
 325                         }
 326                         final boolean control = allowControl.get().get();
 327                         try {
 328                             allowControl.get().set(true);
 329                             provider = LoggerFinder.getLoggerFinder();
 330                         } finally {
 331                             allowControl.get().set(control);
 332                         }
 333                     }
 334                     test(provider, false, appSink, sysSink);
 335                     System.out.println("Tetscase count: " + sequencer.get());
 336                     break;
 337                 case WITHPERMISSIONS:
 338                     System.out.println("\n*** With Security Manager, with control permission\n");
 339                     setSecurityManager();
 340                     final boolean control = allowControl.get().get();
 341                     try {
 342                         allowControl.get().set(true);
 343                         provider = LoggerFinder.getLoggerFinder();
 344                         test(provider, true, appSink, sysSink);
 345                     } finally {
 346                         allowControl.get().set(control);
 347                     }
 348                     break;
 349                 default:
 350                     throw new RuntimeException("Unknown test case: " + testCase);
 351             }
 352         });
 353         System.out.println("\nPASSED: Tested " + sequencer.get() + " cases.");
 354     }
 355 
 356     public static void test(LoggerFinder provider,
 357             boolean hasRequiredPermissions,
 358             java.util.logging.Logger appSink,
 359             java.util.logging.Logger sysSink) {
 360 
 361         ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName());
 362         final Map<Logger, String> loggerDescMap = new HashMap<>();
 363 
 364 
 365         Logger appLogger1 = null;
 366         try {
 367             appLogger1 = provider.getLogger("foo", DefaultLoggerFinderTest.class);
 368             loggerDescMap.put(appLogger1, "provider.getApplicationLogger(\"foo\")");
 369             if (!hasRequiredPermissions) {
 370                 throw new RuntimeException("Managed to obtain a logger without permission");
 371             }
 372         } catch (AccessControlException acx) {
 373             if (hasRequiredPermissions) {
 374                 throw new RuntimeException("Unexpected security exception: ", acx);
 375             }
 376             if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) {
 377                 throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
 378             }
 379             System.out.println("Got expected exception for logger: " + acx);
 380             boolean old = allowControl.get().get();
 381             allowControl.get().set(true);
 382             try {
 383                 appLogger1 =provider.getLogger("foo", DefaultLoggerFinderTest.class);
 384                 loggerDescMap.put(appLogger1, "provider.getApplicationLogger(\"foo\")");
 385             } finally {
 386                 allowControl.get().set(old);
 387             }
 388         }
 389 
 390         Logger sysLogger1 = null;
 391         try {
 392             sysLogger1 = provider.getLogger("foo", Thread.class);
 393             loggerDescMap.put(sysLogger1, "provider.getSystemLogger(\"foo\")");
 394             if (!hasRequiredPermissions) {
 395                 throw new RuntimeException("Managed to obtain a system logger without permission");
 396             }
 397         } catch (AccessControlException acx) {
 398             if (hasRequiredPermissions) {
 399                 throw new RuntimeException("Unexpected security exception: ", acx);
 400             }
 401             if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) {
 402                 throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
 403             }
 404             System.out.println("Got expected exception for system logger: " + acx);
 405             boolean old = allowControl.get().get();
 406             allowControl.get().set(true);
 407             try {
 408                 sysLogger1 = provider.getLogger("foo", Thread.class);
 409                 loggerDescMap.put(sysLogger1, "provider.getSystemLogger(\"foo\")");
 410             } finally {
 411                 allowControl.get().set(old);
 412             }
 413         }
 414         if (appLogger1 == sysLogger1) {
 415             throw new RuntimeException("identical loggers");
 416         }
 417 
 418         Logger appLogger2 = null;
 419         try {
 420             appLogger2 = provider.getLocalizedLogger("foo", loggerBundle, DefaultLoggerFinderTest.class);
 421             loggerDescMap.put(appLogger2, "provider.getLocalizedApplicationLogger(\"foo\", loggerBundle)");
 422             if (!hasRequiredPermissions) {
 423                 throw new RuntimeException("Managed to obtain a logger without permission");
 424             }
 425         } catch (AccessControlException acx) {
 426             if (hasRequiredPermissions) {
 427                 throw new RuntimeException("Unexpected security exception: ", acx);
 428             }
 429             if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) {
 430                 throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
 431             }
 432             System.out.println("Got expected exception for logger: " + acx);
 433             boolean old = allowControl.get().get();
 434             allowControl.get().set(true);
 435             try {
 436                 appLogger2 = provider.getLocalizedLogger("foo", loggerBundle, DefaultLoggerFinderTest.class);
 437                 loggerDescMap.put(appLogger2, "provider.getLocalizedApplicationLogger(\"foo\", loggerBundle)");
 438             } finally {
 439                 allowControl.get().set(old);
 440             }
 441         }
 442 
 443         Logger sysLogger2 = null;
 444         try {
 445             sysLogger2 = provider.getLocalizedLogger("foo", loggerBundle, Thread.class);
 446             loggerDescMap.put(sysLogger2, "provider.getLocalizedSystemLogger(\"foo\", loggerBundle)");
 447             if (!hasRequiredPermissions) {
 448                 throw new RuntimeException("Managed to obtain a system logger without permission");
 449             }
 450         } catch (AccessControlException acx) {
 451             if (hasRequiredPermissions) {
 452                 throw new RuntimeException("Unexpected security exception: ", acx);
 453             }
 454             if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) {
 455                 throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
 456             }
 457             System.out.println("Got expected exception for localized system logger: " + acx);
 458             boolean old = allowControl.get().get();
 459             allowControl.get().set(true);
 460             try {
 461                 sysLogger2 = provider.getLocalizedLogger("foo", loggerBundle, Thread.class);
 462                 loggerDescMap.put(sysLogger2, "provider.getLocalizedSystemLogger(\"foo\", loggerBundle)");
 463             } finally {
 464                 allowControl.get().set(old);
 465             }
 466         }
 467         if (appLogger2 == sysLogger2) {
 468             throw new RuntimeException("identical loggers");
 469         }
 470         if (appLogger2 == appLogger1) {
 471             throw new RuntimeException("identical loggers");
 472         }
 473         if (sysLogger2 == sysLogger1) {
 474             throw new RuntimeException("identical loggers");
 475         }
 476 
 477 
 478         testLogger(provider, loggerDescMap, "foo", null, appLogger1, appSink);
 479         testLogger(provider, loggerDescMap, "foo", null, sysLogger1, sysSink);
 480         testLogger(provider, loggerDescMap, "foo", loggerBundle, appLogger2, appSink);
 481         testLogger(provider, loggerDescMap, "foo", loggerBundle, sysLogger2, sysSink);
 482 
 483 
 484         Logger appLogger3 = System.getLogger("foo");
 485         loggerDescMap.put(appLogger3, "System.getLogger(\"foo\")");
 486 
 487         testLogger(provider, loggerDescMap, "foo", null, appLogger3, appSink);
 488 
 489         Logger appLogger4 =
 490                 System.getLogger("foo", loggerBundle);
 491         loggerDescMap.put(appLogger4, "System.getLogger(\"foo\", loggerBundle)");
 492 
 493         if (appLogger4 == appLogger1) {
 494             throw new RuntimeException("identical loggers");
 495         }
 496 
 497         testLogger(provider, loggerDescMap, "foo", loggerBundle, appLogger4, appSink);
 498 
 499         Logger sysLogger3 = accessSystemLogger.getLogger("foo");
 500         loggerDescMap.put(sysLogger3, "AccessSystemLogger.getLogger(\"foo\")");
 501 
 502         testLogger(provider, loggerDescMap, "foo", null, sysLogger3, sysSink);
 503 
 504         Logger sysLogger4 =
 505                 accessSystemLogger.getLogger("foo", loggerBundle);
 506         loggerDescMap.put(appLogger4, "AccessSystemLogger.getLogger(\"foo\", loggerBundle)");
 507 
 508         if (sysLogger4 == sysLogger1) {
 509             throw new RuntimeException("identical loggers");
 510         }
 511 
 512         testLogger(provider, loggerDescMap, "foo", loggerBundle, sysLogger4, sysSink);
 513 
 514     }
 515 
 516     public static class Foo {
 517 
 518     }
 519 
 520     static void verbose(String msg) {
 521        if (VERBOSE) {
 522            System.out.println(msg);
 523        }
 524     }
 525 
 526     static void setLevel(java.util.logging.Logger sink, java.util.logging.Level loggerLevel) {
 527         boolean before = allowAll.get().get();
 528         try {
 529             allowAll.get().set(true);
 530             sink.setLevel(loggerLevel);
 531         } finally {
 532             allowAll.get().set(before);
 533         }
 534     }
 535 
 536 
 537     // Calls the 8 methods defined on Logger and verify the
 538     // parameters received by the underlying Logger Impl
 539     // logger.
 540     private static void testLogger(LoggerFinder provider,
 541             Map<Logger, String> loggerDescMap,
 542             String name,
 543             ResourceBundle loggerBundle,
 544             Logger logger,
 545             java.util.logging.Logger sink) {
 546 
 547         System.out.println("Testing " + loggerDescMap.get(logger) + " [" + logger + "]");
 548         final java.util.logging.Level OFF = java.util.logging.Level.OFF;
 549 
 550         Foo foo = new Foo();
 551         String fooMsg = foo.toString();
 552         for (java.util.logging.Level loggerLevel : julLevels) {
 553             setLevel(sink, loggerLevel);
 554             for (Level messageLevel : Level.values()) {
 555                 java.util.logging.Level julLevel = mapToJul(messageLevel);
 556                 String desc = "logger.log(messageLevel, foo): loggerLevel="
 557                         + loggerLevel+", messageLevel="+messageLevel;
 558                 LogEvent expected =
 559                         LogEvent.of(
 560                             sequencer.get(),
 561                             julLevel.intValue() >= loggerLevel.intValue(),
 562                             name, julLevel, (ResourceBundle)null,
 563                             fooMsg, (Throwable)null, (Object[])null);
 564                 logger.log(messageLevel, foo);
 565                 if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) {
 566                     if (eventQueue.poll() != null) {
 567                         throw new RuntimeException("unexpected event in queue for " + desc);
 568                     }
 569                 } else {
 570                     LogEvent actual =  eventQueue.poll();
 571                     if (!expected.equals(actual)) {
 572                         throw new RuntimeException("mismatch for " + desc
 573                                 + "\n\texpected=" + expected
 574                                 + "\n\t  actual=" + actual);
 575                     } else {
 576                         verbose("Got expected results for "
 577                                 + desc + "\n\t" + expected);
 578                     }
 579                 }
 580             }
 581         }
 582 
 583         String msg = "blah";
 584         for (java.util.logging.Level loggerLevel : julLevels) {
 585             setLevel(sink, loggerLevel);
 586             for (Level messageLevel : Level.values()) {
 587                 java.util.logging.Level julLevel = mapToJul(messageLevel);
 588                 String desc = "logger.log(messageLevel, \"blah\"): loggerLevel="
 589                         + loggerLevel+", messageLevel="+messageLevel;
 590                 LogEvent expected =
 591                         LogEvent.of(
 592                             sequencer.get(),
 593                             julLevel.intValue() >= loggerLevel.intValue(),
 594                             name, julLevel, loggerBundle,
 595                             msg, (Throwable)null, (Object[])null);
 596                 logger.log(messageLevel, msg);
 597                 if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) {
 598                     if (eventQueue.poll() != null) {
 599                         throw new RuntimeException("unexpected event in queue for " + desc);
 600                     }
 601                 } else {
 602                     LogEvent actual =  eventQueue.poll();
 603                     if (!expected.equals(actual)) {
 604                         throw new RuntimeException("mismatch for " + desc
 605                                 + "\n\texpected=" + expected
 606                                 + "\n\t  actual=" + actual);
 607                     } else {
 608                         verbose("Got expected results for "
 609                                 + desc + "\n\t" + expected);
 610                     }
 611                 }
 612             }
 613         }
 614 
 615         Supplier<String> fooSupplier = new Supplier<String>() {
 616             @Override
 617             public String get() {
 618                 return this.toString();
 619             }
 620         };
 621 
 622         for (java.util.logging.Level loggerLevel : julLevels) {
 623             setLevel(sink, loggerLevel);
 624             for (Level messageLevel : Level.values()) {
 625                 java.util.logging.Level julLevel = mapToJul(messageLevel);
 626                 String desc = "logger.log(messageLevel, fooSupplier): loggerLevel="
 627                         + loggerLevel+", messageLevel="+messageLevel;
 628                 LogEvent expected =
 629                         LogEvent.of(
 630                             sequencer.get(),
 631                             julLevel.intValue() >= loggerLevel.intValue(),
 632                             name, julLevel, (ResourceBundle)null,
 633                             fooSupplier.get(),
 634                             (Throwable)null, (Object[])null);
 635                 logger.log(messageLevel, fooSupplier);
 636                 if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) {
 637                     if (eventQueue.poll() != null) {
 638                         throw new RuntimeException("unexpected event in queue for " + desc);
 639                     }
 640                 } else {
 641                     LogEvent actual =  eventQueue.poll();
 642                     if (!expected.equals(actual)) {
 643                         throw new RuntimeException("mismatch for " + desc
 644                                 + "\n\texpected=" + expected
 645                                 + "\n\t  actual=" + actual);
 646                     } else {
 647                         verbose("Got expected results for "
 648                                 + desc + "\n\t" + expected);
 649                     }
 650                 }
 651             }
 652         }
 653 
 654         String format = "two params [{1} {2}]";
 655         Object arg1 = foo;
 656         Object arg2 = msg;
 657         for (java.util.logging.Level loggerLevel : julLevels) {
 658             setLevel(sink, loggerLevel);
 659             for (Level messageLevel : Level.values()) {
 660                 java.util.logging.Level julLevel = mapToJul(messageLevel);
 661                 String desc = "logger.log(messageLevel, format, params...): loggerLevel="
 662                         + loggerLevel+", messageLevel="+messageLevel;
 663                 LogEvent expected =
 664                         LogEvent.of(
 665                             sequencer.get(),
 666                             julLevel.intValue() >= loggerLevel.intValue(),
 667                             name, julLevel, loggerBundle,
 668                             format, (Throwable)null, new Object[] {arg1, arg2});
 669                 logger.log(messageLevel, format, arg1, arg2);
 670                 if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) {
 671                     if (eventQueue.poll() != null) {
 672                         throw new RuntimeException("unexpected event in queue for " + desc);
 673                     }
 674                 } else {
 675                     LogEvent actual =  eventQueue.poll();
 676                     if (!expected.equals(actual)) {
 677                         throw new RuntimeException("mismatch for " + desc
 678                                 + "\n\texpected=" + expected
 679                                 + "\n\t  actual=" + actual);
 680                     } else {
 681                         verbose("Got expected results for "
 682                                 + desc + "\n\t" + expected);
 683                     }
 684                 }
 685             }
 686         }
 687 
 688         Throwable thrown = new Exception("OK: log me!");
 689         for (java.util.logging.Level loggerLevel : julLevels) {
 690             setLevel(sink, loggerLevel);
 691             for (Level messageLevel : Level.values()) {
 692                 java.util.logging.Level julLevel = mapToJul(messageLevel);
 693                 String desc = "logger.log(messageLevel, \"blah\", thrown): loggerLevel="
 694                         + loggerLevel+", messageLevel="+messageLevel;
 695                 LogEvent expected =
 696                         LogEvent.of(
 697                             sequencer.get(),
 698                             julLevel.intValue() >= loggerLevel.intValue(),
 699                             name, julLevel, loggerBundle,
 700                             msg, thrown, (Object[]) null);
 701                 logger.log(messageLevel, msg, thrown);
 702                 if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) {
 703                     if (eventQueue.poll() != null) {
 704                         throw new RuntimeException("unexpected event in queue for " + desc);
 705                     }
 706                 } else {
 707                     LogEvent actual =  eventQueue.poll();
 708                     if (!expected.equals(actual)) {
 709                         throw new RuntimeException("mismatch for " + desc
 710                                 + "\n\texpected=" + expected
 711                                 + "\n\t  actual=" + actual);
 712                     } else {
 713                         verbose("Got expected results for "
 714                                 + desc + "\n\t" + expected);
 715                     }
 716                 }
 717             }
 718         }
 719 
 720 
 721         for (java.util.logging.Level loggerLevel : julLevels) {
 722             setLevel(sink, loggerLevel);
 723             for (Level messageLevel : Level.values()) {
 724                 java.util.logging.Level julLevel = mapToJul(messageLevel);
 725                 String desc = "logger.log(messageLevel, thrown, fooSupplier): loggerLevel="
 726                         + loggerLevel+", messageLevel="+messageLevel;
 727                 LogEvent expected =
 728                         LogEvent.of(
 729                             sequencer.get(),
 730                             julLevel.intValue() >= loggerLevel.intValue(),
 731                             name, julLevel, (ResourceBundle)null,
 732                             fooSupplier.get(),
 733                             (Throwable)thrown, (Object[])null);
 734                 logger.log(messageLevel, fooSupplier, thrown);
 735                 if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) {
 736                     if (eventQueue.poll() != null) {
 737                         throw new RuntimeException("unexpected event in queue for " + desc);
 738                     }
 739                 } else {
 740                     LogEvent actual =  eventQueue.poll();
 741                     if (!expected.equals(actual)) {
 742                         throw new RuntimeException("mismatch for " + desc
 743                                 + "\n\texpected=" + expected
 744                                 + "\n\t  actual=" + actual);
 745                     } else {
 746                         verbose("Got expected results for "
 747                                 + desc + "\n\t" + expected);
 748                     }
 749                 }
 750             }
 751         }
 752 
 753         ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName());
 754         for (java.util.logging.Level loggerLevel : julLevels) {
 755             setLevel(sink, loggerLevel);
 756             for (Level messageLevel : Level.values()) {
 757                 java.util.logging.Level julLevel = mapToJul(messageLevel);
 758                 String desc = "logger.log(messageLevel, bundle, format, params...): loggerLevel="
 759                         + loggerLevel+", messageLevel="+messageLevel;
 760                 LogEvent expected =
 761                         LogEvent.of(
 762                             sequencer.get(),
 763                             julLevel.intValue() >= loggerLevel.intValue(),
 764                             name, julLevel, bundle,
 765                             format, (Throwable)null, new Object[] {foo, msg});
 766                 logger.log(messageLevel, bundle, format, foo, msg);
 767                 if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) {
 768                     if (eventQueue.poll() != null) {
 769                         throw new RuntimeException("unexpected event in queue for " + desc);
 770                     }
 771                 } else {
 772                     LogEvent actual =  eventQueue.poll();
 773                     if (!expected.equals(actual)) {
 774                         throw new RuntimeException("mismatch for " + desc
 775                                 + "\n\texpected=" + expected
 776                                 + "\n\t  actual=" + actual);
 777                     } else {
 778                         verbose("Got expected results for "
 779                                 + desc + "\n\t" + expected);
 780                     }
 781                 }
 782             }
 783         }
 784 
 785         for (java.util.logging.Level loggerLevel : julLevels) {
 786             setLevel(sink, loggerLevel);
 787             for (Level messageLevel : Level.values()) {
 788                 java.util.logging.Level julLevel = mapToJul(messageLevel);
 789                 String desc = "logger.log(messageLevel, bundle, \"blah\", thrown): loggerLevel="
 790                         + loggerLevel+", messageLevel="+messageLevel;
 791                 LogEvent expected =
 792                         LogEvent.of(
 793                             sequencer.get(),
 794                             julLevel.intValue() >= loggerLevel.intValue(),
 795                             name, julLevel, bundle,
 796                             msg, thrown, (Object[]) null);
 797                 logger.log(messageLevel, bundle, msg, thrown);
 798                 if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) {
 799                     if (eventQueue.poll() != null) {
 800                         throw new RuntimeException("unexpected event in queue for " + desc);
 801                     }
 802                 } else {
 803                     LogEvent actual =  eventQueue.poll();
 804                     if (!expected.equals(actual)) {
 805                         throw new RuntimeException("mismatch for " + desc
 806                                 + "\n\texpected=" + expected
 807                                 + "\n\t  actual=" + actual);
 808                     } else {
 809                         verbose("Got expected results for "
 810                                 + desc + "\n\t" + expected);
 811                     }
 812                 }
 813             }
 814         }
 815     }
 816 
 817     final static class PermissionsBuilder {
 818         final Permissions perms;
 819         public PermissionsBuilder() {
 820             this(new Permissions());
 821         }
 822         public PermissionsBuilder(Permissions perms) {
 823             this.perms = perms;
 824         }
 825         public PermissionsBuilder add(Permission p) {
 826             perms.add(p);
 827             return this;
 828         }
 829         public PermissionsBuilder addAll(PermissionCollection col) {
 830             if (col != null) {
 831                 for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) {
 832                     perms.add(e.nextElement());
 833                 }
 834             }
 835             return this;
 836         }
 837         public Permissions toPermissions() {
 838             final PermissionsBuilder builder = new PermissionsBuilder();
 839             builder.addAll(perms);
 840             return builder.perms;
 841         }
 842     }
 843 
 844     public static class SimplePolicy extends Policy {
 845 
 846         final Permissions permissions;
 847         final Permissions withControlPermissions;
 848         final Permissions allPermissions;
 849         final ThreadLocal<AtomicBoolean> allowAll;
 850         final ThreadLocal<AtomicBoolean> allowControl;
 851         public SimplePolicy(ThreadLocal<AtomicBoolean> allowAll,
 852                 ThreadLocal<AtomicBoolean> allowControl) {
 853             this.allowAll = allowAll;
 854             this.allowControl = allowControl;
 855             permissions = new Permissions();
 856 
 857             withControlPermissions = new Permissions();
 858             withControlPermissions.add(LOGGERFINDER_PERMISSION);
 859 
 860             // these are used for configuring the test itself...
 861             allPermissions = new Permissions();
 862             allPermissions.add(new java.security.AllPermission());
 863         }
 864 
 865         @Override
 866         public boolean implies(ProtectionDomain domain, Permission permission) {
 867             if (allowAll.get().get()) return allPermissions.implies(permission);
 868             if (allowControl.get().get()) return withControlPermissions.implies(permission);
 869             return permissions.implies(permission);
 870         }
 871 
 872         @Override
 873         public PermissionCollection getPermissions(CodeSource codesource) {
 874             return new PermissionsBuilder().addAll(
 875                     allowAll.get().get() ? allPermissions :
 876                     allowControl.get().get()
 877                     ? withControlPermissions : permissions).toPermissions();
 878         }
 879 
 880         @Override
 881         public PermissionCollection getPermissions(ProtectionDomain domain) {
 882             return new PermissionsBuilder().addAll(
 883                     allowAll.get().get() ? allPermissions :
 884                     allowControl.get().get()
 885                     ? withControlPermissions : permissions).toPermissions();
 886         }
 887     }
 888 }