1 /*
   2  * Copyright (c) 2015, 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.AccessController;
  25 import java.security.CodeSource;
  26 import java.security.Permission;
  27 import java.security.PermissionCollection;
  28 import java.security.Permissions;
  29 import java.security.Policy;
  30 import java.security.PrivilegedAction;
  31 import java.security.ProtectionDomain;
  32 import java.util.Arrays;
  33 import java.util.Collections;
  34 import java.util.Enumeration;
  35 import java.util.HashMap;
  36 import java.util.Map;
  37 import java.util.Objects;
  38 import java.util.Queue;
  39 import java.util.ResourceBundle;
  40 import java.util.concurrent.ArrayBlockingQueue;
  41 import java.util.concurrent.ConcurrentHashMap;
  42 import java.util.concurrent.atomic.AtomicBoolean;
  43 import java.util.concurrent.atomic.AtomicLong;
  44 import java.util.function.Supplier;
  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     8046565
  53  * @summary Tests loggers returned by System.getLogger with a naive implementation
  54  *          of LoggerFinder, and in particular the default body of
  55  *          System.Logger methods.
  56  * @build CustomLoggerTest AccessSystemLogger
  57  * @run driver AccessSystemLogger
  58  * @run main/othervm -Xbootclasspath/a:boot CustomLoggerTest NOSECURITY
  59  * @run main/othervm -Xbootclasspath/a:boot CustomLoggerTest NOPERMISSIONS
  60  * @run main/othervm -Xbootclasspath/a:boot CustomLoggerTest WITHPERMISSIONS
  61  * @author danielfuchs
  62  */
  63 public class CustomLoggerTest {
  64 
  65     final static AtomicLong sequencer = new AtomicLong();
  66     final static boolean VERBOSE = false;
  67     static final ThreadLocal<AtomicBoolean> allowControl = new ThreadLocal<AtomicBoolean>() {
  68         @Override
  69         protected AtomicBoolean initialValue() {
  70             return  new AtomicBoolean(false);
  71         }
  72     };
  73 
  74     public static class MyBundle extends ResourceBundle {
  75 
  76         final ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();
  77 
  78         @Override
  79         protected Object handleGetObject(String key) {
  80             if (key.contains(" (translated)")) {
  81                 throw new RuntimeException("Unexpected key: " + key);
  82             }
  83             return map.computeIfAbsent(key, k -> k + " (translated)");
  84         }
  85 
  86         @Override
  87         public Enumeration<String> getKeys() {
  88             return Collections.enumeration(map.keySet());
  89         }
  90 
  91     }
  92     public static class MyLoggerBundle extends MyBundle {
  93 
  94     }
  95 
  96 
  97     public static class BaseLoggerFinder extends LoggerFinder {
  98         final ConcurrentHashMap<String, LoggerImpl> system = new ConcurrentHashMap<>();
  99         final ConcurrentHashMap<String, LoggerImpl> user = new ConcurrentHashMap<>();
 100         public Queue<LogEvent> eventQueue = new ArrayBlockingQueue<>(128);
 101 
 102         // changing this to true requires changing the logic in the
 103         // test in order to load this class with a protection domain
 104         // that has the CONTROL_PERMISSION (e.g. by using a custom
 105         // system class loader.
 106         final boolean doChecks = false;
 107 
 108         public static final class LogEvent {
 109 
 110             public LogEvent() {
 111                 this(sequencer.getAndIncrement());
 112             }
 113 
 114             LogEvent(long sequenceNumber) {
 115                 this.sequenceNumber = sequenceNumber;
 116             }
 117 
 118             long sequenceNumber;
 119             boolean isLoggable;
 120             String loggerName;
 121             Level level;
 122             ResourceBundle bundle;
 123             Throwable thrown;
 124             Object[] args;
 125             Supplier<String> supplier;
 126             String msg;
 127 
 128             Object[] toArray() {
 129                 return new Object[] {
 130                     sequenceNumber,
 131                     isLoggable,
 132                     loggerName,
 133                     level,
 134                     bundle,
 135                     thrown,
 136                     args,
 137                     supplier,
 138                     msg,
 139                 };
 140             }
 141 
 142             @Override
 143             public String toString() {
 144                 return Arrays.deepToString(toArray());
 145             }
 146 
 147 
 148 
 149             @Override
 150             public boolean equals(Object obj) {
 151                 return obj instanceof LogEvent
 152                         && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray());
 153             }
 154 
 155             @Override
 156             public int hashCode() {
 157                 return Objects.hash(toArray());
 158             }
 159 
 160 
 161             public static LogEvent of(boolean isLoggable, String name,
 162                     Level level, ResourceBundle bundle,
 163                     String key, Throwable thrown) {
 164                 LogEvent evt = new LogEvent();
 165                 evt.isLoggable = isLoggable;
 166                 evt.loggerName = name;
 167                 evt.level = level;
 168                 evt.args = null;
 169                 evt.bundle = bundle;
 170                 evt.thrown = thrown;
 171                 evt.supplier = null;
 172                 evt.msg = key;
 173                 return evt;
 174             }
 175 
 176             public static LogEvent of(boolean isLoggable, String name,
 177                     Level level, ResourceBundle bundle,
 178                     String key, Object... params) {
 179                 LogEvent evt = new LogEvent();
 180                 evt.isLoggable = isLoggable;
 181                 evt.loggerName = name;
 182                 evt.level = level;
 183                 evt.args = params;
 184                 evt.bundle = bundle;
 185                 evt.thrown = null;
 186                 evt.supplier = null;
 187                 evt.msg = key;
 188                 return evt;
 189             }
 190 
 191             public static LogEvent of(long sequenceNumber,
 192                     boolean isLoggable, String name,
 193                     Level level, ResourceBundle bundle,
 194                     String key, Supplier<String> supplier,
 195                     Throwable thrown, Object... params) {
 196                 LogEvent evt = new LogEvent(sequenceNumber);
 197                 evt.loggerName = name;
 198                 evt.level = level;
 199                 evt.args = params;
 200                 evt.bundle = bundle;
 201                 evt.thrown = thrown;
 202                 evt.supplier = supplier;
 203                 evt.msg = key;
 204                 evt.isLoggable = isLoggable;
 205                 return evt;
 206             }
 207 
 208         }
 209 
 210         public class LoggerImpl implements Logger {
 211             private final String name;
 212             private Level level = Level.INFO;
 213 
 214             public LoggerImpl(String name) {
 215                 this.name = name;
 216             }
 217 
 218             @Override
 219             public String getName() {
 220                 return name;
 221             }
 222 
 223             @Override
 224             public boolean isLoggable(Level level) {
 225                 return this.level != Level.OFF && this.level.getSeverity() <= level.getSeverity();
 226             }
 227 
 228             @Override
 229             public void log(Level level, ResourceBundle bundle, String key, Throwable thrown) {
 230                 log(LogEvent.of(isLoggable(level), this.name, level, bundle, key, thrown));
 231             }
 232 
 233             @Override
 234             public void log(Level level, ResourceBundle bundle, String format, Object... params) {
 235                 log(LogEvent.of(isLoggable(level), name, level, bundle, format, params));
 236             }
 237 
 238             void log(LogEvent event) {
 239                 eventQueue.add(event);
 240             }
 241         }
 242 
 243         @Override
 244         public Logger getLogger(String name, Class<?> caller) {
 245             // We should check the permission to obey the API contract, but
 246             // what happens if we don't?
 247             // This is the main difference compared with what we test in
 248             // java/lang/System/LoggerFinder/BaseLoggerFinderTest
 249             SecurityManager sm = System.getSecurityManager();
 250             if (sm != null && doChecks) {
 251                 sm.checkPermission(LOGGERFINDER_PERMISSION);
 252             }
 253 
 254             PrivilegedAction<ClassLoader> pa = () -> caller.getClassLoader();
 255             ClassLoader callerLoader = AccessController.doPrivileged(pa);
 256             if (callerLoader == null) {
 257                 return system.computeIfAbsent(name, (n) -> new LoggerImpl(n));
 258             } else {
 259                 return user.computeIfAbsent(name, (n) -> new LoggerImpl(n));
 260             }
 261         }
 262     }
 263 
 264     static final AccessSystemLogger accessSystemLogger = new AccessSystemLogger();
 265 
 266     static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS};
 267 
 268     static void setSecurityManager() {
 269         if (System.getSecurityManager() == null) {
 270             Policy.setPolicy(new SimplePolicy(allowControl));
 271             System.setSecurityManager(new SecurityManager());
 272         }
 273     }
 274     public static void main(String[] args) {
 275         if (args.length == 0)
 276             args = new String[] {
 277                 "NOSECURITY",
 278                 "NOPERMISSIONS",
 279                 "WITHPERMISSIONS"
 280             };
 281 
 282         // 1. Obtain destination loggers directly from the LoggerFinder
 283         //   - LoggerFinder.getLogger("foo", type)
 284         BaseLoggerFinder provider =
 285                 BaseLoggerFinder.class.cast(LoggerFinder.getLoggerFinder());
 286         BaseLoggerFinder.LoggerImpl appSink =
 287                 BaseLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", CustomLoggerTest.class));
 288         BaseLoggerFinder.LoggerImpl sysSink =
 289                 BaseLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", Thread.class));
 290 
 291 
 292         Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> {
 293             switch (testCase) {
 294                 case NOSECURITY:
 295                     System.out.println("\n*** Without Security Manager\n");
 296                     test(provider, true, appSink, sysSink);
 297                     System.out.println("Tetscase count: " + sequencer.get());
 298                     break;
 299                 case NOPERMISSIONS:
 300                     System.out.println("\n*** With Security Manager, without permissions\n");
 301                     setSecurityManager();
 302                     test(provider, false, appSink, sysSink);
 303                     System.out.println("Tetscase count: " + sequencer.get());
 304                     break;
 305                 case WITHPERMISSIONS:
 306                     System.out.println("\n*** With Security Manager, with control permission\n");
 307                     setSecurityManager();
 308                     final boolean control = allowControl.get().get();
 309                     try {
 310                         allowControl.get().set(true);
 311                         test(provider, true, appSink, sysSink);
 312                     } finally {
 313                         allowControl.get().set(control);
 314                     }
 315                     break;
 316                 default:
 317                     throw new RuntimeException("Unknown test case: " + testCase);
 318             }
 319         });
 320         System.out.println("\nPASSED: Tested " + sequencer.get() + " cases.");
 321     }
 322 
 323     public static void test(BaseLoggerFinder provider, boolean hasRequiredPermissions,
 324             BaseLoggerFinder.LoggerImpl appSink, BaseLoggerFinder.LoggerImpl sysSink) {
 325 
 326         ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName());
 327         final Map<Logger, String> loggerDescMap = new HashMap<>();
 328 
 329 
 330         // 1. Test loggers returned by:
 331         //   - System.getLogger("foo")
 332         //   - and AccessSystemLogger.getLogger("foo")
 333         Logger appLogger1 = System.getLogger("foo");
 334         loggerDescMap.put(appLogger1, "System.getLogger(\"foo\");");
 335 
 336         Logger sysLogger1 = null;
 337         try {
 338             sysLogger1 = accessSystemLogger.getLogger("foo");
 339             loggerDescMap.put(sysLogger1, "AccessSystemLogger.getLogger(\"foo\")");
 340         } catch (AccessControlException acx) {
 341             if (hasRequiredPermissions) {
 342                 throw new RuntimeException("Unexpected security exception: ", acx);
 343             }
 344             if (!acx.getPermission().equals(LoggerFinder.LOGGERFINDER_PERMISSION)) {
 345                 throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
 346             }
 347             throw new RuntimeException("unexpected exception: " + acx, acx);
 348         }
 349 
 350         if (appLogger1 == sysLogger1) {
 351             throw new RuntimeException("identical loggers");
 352         }
 353 
 354         if (provider.system.contains(appLogger1)) {
 355             throw new RuntimeException("app logger in system map");
 356         }
 357         if (provider.user.contains(sysLogger1)) {
 358             throw new RuntimeException("sys logger in appplication map");
 359         }
 360         if (provider.system.contains(sysLogger1)) {
 361             // sysLogger should be a a LazyLoggerWrapper
 362             throw new RuntimeException("sys logger is in system map (should be wrapped)");
 363         }
 364 
 365 
 366         // 2. Test loggers returned by:
 367         //   - System.getLogger(\"foo\", loggerBundle)
 368         //   - and AccessSystemLogger.getLogger(\"foo\", loggerBundle)
 369         Logger appLogger2 =
 370                 System.getLogger("foo", loggerBundle);
 371         loggerDescMap.put(appLogger2, "System.getLogger(\"foo\", loggerBundle)");
 372 
 373         Logger sysLogger2 = null;
 374         try {
 375             sysLogger2 = accessSystemLogger.getLogger("foo", loggerBundle);
 376             loggerDescMap.put(sysLogger2, "AccessSystemLogger.getLogger(\"foo\", loggerBundle)");
 377         } catch (AccessControlException acx) {
 378             if (hasRequiredPermissions) {
 379                 throw new RuntimeException("Unexpected security exception: ", acx);
 380             }
 381             if (!acx.getPermission().equals(LoggerFinder.LOGGERFINDER_PERMISSION)) {
 382                 throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
 383             }
 384             throw new RuntimeException("unexpected exception: " + acx, acx);
 385         }
 386         if (appLogger2 == sysLogger2) {
 387             throw new RuntimeException("identical loggers");
 388         }
 389         if (appLogger2 == appSink) {
 390             throw new RuntimeException("identical loggers");
 391         }
 392         if (sysLogger2 == sysSink) {
 393             throw new RuntimeException("identical loggers");
 394         }
 395 
 396         if (provider.system.contains(appLogger2)) {
 397             throw new RuntimeException("localized app logger in system map");
 398         }
 399         if (provider.user.contains(appLogger2)) {
 400             throw new RuntimeException("localized app logger  in appplication map");
 401         }
 402         if (provider.user.contains(sysLogger2)) {
 403             throw new RuntimeException("localized sys logger in appplication map");
 404         }
 405         if (provider.system.contains(sysLogger2)) {
 406             throw new RuntimeException("localized sys logger not in system map");
 407         }
 408 
 409         testLogger(provider, loggerDescMap, "foo", null, appLogger1, appSink);
 410         testLogger(provider, loggerDescMap, "foo", null, sysLogger1, sysSink);
 411         testLogger(provider, loggerDescMap, "foo", loggerBundle, appLogger2, appSink);
 412         testLogger(provider, loggerDescMap, "foo", loggerBundle, sysLogger2, sysSink);
 413     }
 414 
 415     public static class Foo {
 416 
 417     }
 418 
 419     static void verbose(String msg) {
 420        if (VERBOSE) {
 421            System.out.println(msg);
 422        }
 423     }
 424 
 425     // Calls the 8 methods defined on Logger and verify the
 426     // parameters received by the underlying BaseLoggerFinder.LoggerImpl
 427     // logger.
 428     private static void testLogger(BaseLoggerFinder provider,
 429             Map<Logger, String> loggerDescMap,
 430             String name,
 431             ResourceBundle loggerBundle,
 432             Logger logger,
 433             BaseLoggerFinder.LoggerImpl sink) {
 434 
 435         System.out.println("Testing " + loggerDescMap.get(logger));
 436 
 437         Foo foo = new Foo();
 438         String fooMsg = foo.toString();
 439         for (Level loggerLevel : Level.values()) {
 440             sink.level = loggerLevel;
 441             for (Level messageLevel : Level.values()) {
 442                 String desc = "logger.log(messageLevel, foo): loggerLevel="
 443                         + loggerLevel+", messageLevel="+messageLevel;
 444                 BaseLoggerFinder.LogEvent expected =
 445                         BaseLoggerFinder.LogEvent.of(
 446                             sequencer.get(),
 447                             messageLevel.compareTo(loggerLevel) >= 0,
 448                             name, messageLevel, (ResourceBundle)null,
 449                             fooMsg, null, (Throwable)null, (Object[])null);
 450                 logger.log(messageLevel, foo);
 451                 if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
 452                     if (provider.eventQueue.poll() != null) {
 453                         throw new RuntimeException("unexpected event in queue for " + desc);
 454                     }
 455                 } else {
 456                     BaseLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
 457                     if (!expected.equals(actual)) {
 458                         throw new RuntimeException("mismatch for " + desc
 459                                 + "\n\texpected=" + expected
 460                                 + "\n\t  actual=" + actual);
 461                     } else {
 462                         verbose("Got expected results for "
 463                                 + desc + "\n\t" + expected);
 464                     }
 465                 }
 466             }
 467         }
 468 
 469         String msg = "blah";
 470         for (Level loggerLevel : Level.values()) {
 471             sink.level = loggerLevel;
 472             for (Level messageLevel : Level.values()) {
 473                 String desc = "logger.log(messageLevel, \"blah\"): loggerLevel="
 474                         + loggerLevel+", messageLevel="+messageLevel;
 475                 BaseLoggerFinder.LogEvent expected =
 476                         BaseLoggerFinder.LogEvent.of(
 477                             sequencer.get(),
 478                             messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
 479                             name, messageLevel, loggerBundle,
 480                             msg, null, (Throwable)null, (Object[])null);
 481                 logger.log(messageLevel, msg);
 482                 BaseLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
 483                 if (!expected.equals(actual)) {
 484                     throw new RuntimeException("mismatch for " + desc
 485                             + "\n\texpected=" + expected
 486                             + "\n\t  actual=" + actual);
 487                 } else {
 488                     verbose("Got expected results for "
 489                             + desc + "\n\t" + expected);
 490                 }
 491             }
 492         }
 493 
 494         Supplier<String> fooSupplier = new Supplier<String>() {
 495             @Override
 496             public String get() {
 497                 return this.toString();
 498             }
 499         };
 500 
 501         for (Level loggerLevel : Level.values()) {
 502             sink.level = loggerLevel;
 503             for (Level messageLevel : Level.values()) {
 504                 String desc = "logger.log(messageLevel, fooSupplier): loggerLevel="
 505                         + loggerLevel+", messageLevel="+messageLevel;
 506                 BaseLoggerFinder.LogEvent expected =
 507                         BaseLoggerFinder.LogEvent.of(
 508                             sequencer.get(),
 509                             messageLevel.compareTo(loggerLevel) >= 0,
 510                             name, messageLevel, (ResourceBundle)null,
 511                             fooSupplier.get(), null,
 512                             (Throwable)null, (Object[])null);
 513                 logger.log(messageLevel, fooSupplier);
 514                 if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
 515                     if (provider.eventQueue.poll() != null) {
 516                         throw new RuntimeException("unexpected event in queue for " + desc);
 517                     }
 518                 } else {
 519                     BaseLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
 520                     if (!expected.equals(actual)) {
 521                         throw new RuntimeException("mismatch for " + desc
 522                                 + "\n\texpected=" + expected
 523                                 + "\n\t  actual=" + actual);
 524                     } else {
 525                         verbose("Got expected results for "
 526                                 + desc + "\n\t" + expected);
 527                     }
 528                 }
 529             }
 530         }
 531 
 532         String format = "two params [{1} {2}]";
 533         Object arg1 = foo;
 534         Object arg2 = msg;
 535         for (Level loggerLevel : Level.values()) {
 536             sink.level = loggerLevel;
 537             for (Level messageLevel : Level.values()) {
 538                 String desc = "logger.log(messageLevel, format, params...): loggerLevel="
 539                         + loggerLevel+", messageLevel="+messageLevel;
 540                 BaseLoggerFinder.LogEvent expected =
 541                         BaseLoggerFinder.LogEvent.of(
 542                             sequencer.get(),
 543                             messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
 544                             name, messageLevel, loggerBundle,
 545                             format, null, (Throwable)null, new Object[] {arg1, arg2});
 546                 logger.log(messageLevel, format, arg1, arg2);
 547                 BaseLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
 548                 if (!expected.equals(actual)) {
 549                     throw new RuntimeException("mismatch for " + desc
 550                             + "\n\texpected=" + expected
 551                             + "\n\t  actual=" + actual);
 552                 } else {
 553                     verbose("Got expected results for "
 554                             + desc + "\n\t" + expected);
 555                 }
 556             }
 557         }
 558 
 559         Throwable thrown = new Exception("OK: log me!");
 560         for (Level loggerLevel : Level.values()) {
 561             sink.level = loggerLevel;
 562             for (Level messageLevel : Level.values()) {
 563                 String desc = "logger.log(messageLevel, \"blah\", thrown): loggerLevel="
 564                         + loggerLevel+", messageLevel="+messageLevel;
 565                 BaseLoggerFinder.LogEvent expected =
 566                         BaseLoggerFinder.LogEvent.of(
 567                             sequencer.get(),
 568                             messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
 569                             name, messageLevel, loggerBundle,
 570                             msg, null, thrown, (Object[]) null);
 571                 logger.log(messageLevel, msg, thrown);
 572                 BaseLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
 573                 if (!expected.equals(actual)) {
 574                     throw new RuntimeException("mismatch for " + desc
 575                             + "\n\texpected=" + expected
 576                             + "\n\t  actual=" + actual);
 577                 } else {
 578                     verbose("Got expected results for "
 579                             + desc + "\n\t" + expected);
 580                 }
 581             }
 582         }
 583 
 584 
 585         for (Level loggerLevel : Level.values()) {
 586             sink.level = loggerLevel;
 587             for (Level messageLevel : Level.values()) {
 588                 String desc = "logger.log(messageLevel, thrown, fooSupplier): loggerLevel="
 589                         + loggerLevel+", messageLevel="+messageLevel;
 590                 BaseLoggerFinder.LogEvent expected =
 591                         BaseLoggerFinder.LogEvent.of(
 592                             sequencer.get(),
 593                             messageLevel.compareTo(loggerLevel) >= 0,
 594                             name, messageLevel, (ResourceBundle)null,
 595                             fooSupplier.get(), null,
 596                             (Throwable)thrown, (Object[])null);
 597                 logger.log(messageLevel, fooSupplier, thrown);
 598                 if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
 599                     if (provider.eventQueue.poll() != null) {
 600                         throw new RuntimeException("unexpected event in queue for " + desc);
 601                     }
 602                 } else {
 603                     BaseLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
 604                     if (!expected.equals(actual)) {
 605                         throw new RuntimeException("mismatch for " + desc
 606                                 + "\n\texpected=" + expected
 607                                 + "\n\t  actual=" + actual);
 608                     } else {
 609                         verbose("Got expected results for "
 610                                 + desc + "\n\t" + expected);
 611                     }
 612                 }
 613             }
 614         }
 615 
 616         ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName());
 617         for (Level loggerLevel : Level.values()) {
 618             sink.level = loggerLevel;
 619             for (Level messageLevel : Level.values()) {
 620                 String desc = "logger.log(messageLevel, bundle, format, params...): loggerLevel="
 621                         + loggerLevel+", messageLevel="+messageLevel;
 622                 BaseLoggerFinder.LogEvent expected =
 623                         BaseLoggerFinder.LogEvent.of(
 624                             sequencer.get(),
 625                             messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
 626                             name, messageLevel, bundle,
 627                             format, null, (Throwable)null, new Object[] {foo, msg});
 628                 logger.log(messageLevel, bundle, format, foo, msg);
 629                 BaseLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
 630                 if (!expected.equals(actual)) {
 631                     throw new RuntimeException("mismatch for " + desc
 632                             + "\n\texpected=" + expected
 633                             + "\n\t  actual=" + actual);
 634                 } else {
 635                     verbose("Got expected results for "
 636                             + desc + "\n\t" + expected);
 637                 }
 638             }
 639         }
 640 
 641         for (Level loggerLevel : Level.values()) {
 642             sink.level = loggerLevel;
 643             for (Level messageLevel : Level.values()) {
 644                 String desc = "logger.log(messageLevel, bundle, \"blah\", thrown): loggerLevel="
 645                         + loggerLevel+", messageLevel="+messageLevel;
 646                 BaseLoggerFinder.LogEvent expected =
 647                         BaseLoggerFinder.LogEvent.of(
 648                             sequencer.get(),
 649                             messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
 650                             name, messageLevel, bundle,
 651                             msg, null, thrown, (Object[]) null);
 652                 logger.log(messageLevel, bundle, msg, thrown);
 653                 BaseLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
 654                 if (!expected.equals(actual)) {
 655                     throw new RuntimeException("mismatch for " + desc
 656                             + "\n\texpected=" + expected
 657                             + "\n\t  actual=" + actual);
 658                 } else {
 659                     verbose("Got expected results for "
 660                             + desc + "\n\t" + expected);
 661                 }
 662             }
 663         }
 664     }
 665 
 666     final static class PermissionsBuilder {
 667         final Permissions perms;
 668         public PermissionsBuilder() {
 669             this(new Permissions());
 670         }
 671         public PermissionsBuilder(Permissions perms) {
 672             this.perms = perms;
 673         }
 674         public PermissionsBuilder add(Permission p) {
 675             perms.add(p);
 676             return this;
 677         }
 678         public PermissionsBuilder addAll(PermissionCollection col) {
 679             if (col != null) {
 680                 for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) {
 681                     perms.add(e.nextElement());
 682                 }
 683             }
 684             return this;
 685         }
 686         public Permissions toPermissions() {
 687             final PermissionsBuilder builder = new PermissionsBuilder();
 688             builder.addAll(perms);
 689             return builder.perms;
 690         }
 691     }
 692 
 693     public static class SimplePolicy extends Policy {
 694 
 695         final Permissions permissions;
 696         final Permissions allPermissions;
 697         final ThreadLocal<AtomicBoolean> allowControl;
 698         public SimplePolicy(ThreadLocal<AtomicBoolean> allowControl) {
 699             this.allowControl = allowControl;
 700             permissions = new Permissions();
 701 
 702             // these are used for configuring the test itself...
 703             allPermissions = new Permissions();
 704             allPermissions.add(LoggerFinder.LOGGERFINDER_PERMISSION);
 705 
 706         }
 707 
 708         @Override
 709         public boolean implies(ProtectionDomain domain, Permission permission) {
 710             if (allowControl.get().get()) return allPermissions.implies(permission);
 711             return permissions.implies(permission);
 712         }
 713 
 714         @Override
 715         public PermissionCollection getPermissions(CodeSource codesource) {
 716             return new PermissionsBuilder().addAll(allowControl.get().get()
 717                     ? allPermissions : permissions).toPermissions();
 718         }
 719 
 720         @Override
 721         public PermissionCollection getPermissions(ProtectionDomain domain) {
 722             return new PermissionsBuilder().addAll(allowControl.get().get()
 723                     ? allPermissions : permissions).toPermissions();
 724         }
 725     }
 726 }