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.io.ByteArrayOutputStream;
24  import java.io.IOException;
25  import java.io.PrintStream;
26  import java.io.UncheckedIOException;
27  import java.security.AccessControlException;
28  import java.security.CodeSource;
29  import java.security.Permission;
30  import java.security.PermissionCollection;
31  import java.security.Permissions;
32  import java.security.Policy;
33  import java.security.ProtectionDomain;
34  import java.util.Collections;
35  import java.util.Enumeration;
36  import java.util.HashMap;
37  import java.util.Map;
38  import java.util.ResourceBundle;
39  import java.util.stream.Stream;
40  import java.util.concurrent.ConcurrentHashMap;
41  import java.util.concurrent.atomic.AtomicBoolean;
42  import java.util.concurrent.atomic.AtomicLong;
43  import java.util.function.Supplier;
44  import java.lang.System.LoggerFinder;
45  import java.lang.System.Logger;
46  import java.lang.System.Logger.Level;
47  import java.security.AccessController;
48  import java.security.PrivilegedAction;
49  import java.util.EnumSet;
50  import java.util.Iterator;
51  import java.util.Locale;
52  import java.util.ServiceConfigurationError;
53  import java.util.ServiceLoader;
54  import java.util.concurrent.atomic.AtomicReference;
55  import jdk.internal.logger.SimpleConsoleLogger;
56  
57  /**
58   * @test
59   * @bug     8140364
60   * @summary JDK implementation specific unit test for LoggerFinderLoader.
61   *          Tests the behavior of LoggerFinderLoader with respect to the
62   *          value of the internal diagnosability switches. Also test the
63   *          DefaultLoggerFinder and SimpleConsoleLogger implementation.
64   * @modules java.base/sun.util.logging
65   *          java.base/jdk.internal.logger
66   * @build AccessSystemLogger LoggerFinderLoaderTest CustomSystemClassLoader
67   * @run  driver AccessSystemLogger
68   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader LoggerFinderLoaderTest NOSECURITY
69   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader LoggerFinderLoaderTest NOPERMISSIONS
70   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader LoggerFinderLoaderTest WITHPERMISSIONS
71   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true LoggerFinderLoaderTest NOSECURITY
72   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true LoggerFinderLoaderTest NOPERMISSIONS
73   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true LoggerFinderLoaderTest WITHPERMISSIONS
74   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=ERROR LoggerFinderLoaderTest NOSECURITY
75   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=ERROR LoggerFinderLoaderTest NOPERMISSIONS
76   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=ERROR LoggerFinderLoaderTest WITHPERMISSIONS
77   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=DEBUG LoggerFinderLoaderTest NOSECURITY
78   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=DEBUG LoggerFinderLoaderTest NOPERMISSIONS
79   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=DEBUG LoggerFinderLoaderTest WITHPERMISSIONS
80   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=QUIET LoggerFinderLoaderTest NOSECURITY
81   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=QUIET LoggerFinderLoaderTest NOPERMISSIONS
82   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=QUIET LoggerFinderLoaderTest WITHPERMISSIONS
83   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true LoggerFinderLoaderTest NOSECURITY
84   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true LoggerFinderLoaderTest NOPERMISSIONS
85   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true LoggerFinderLoaderTest WITHPERMISSIONS
86   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=ERROR LoggerFinderLoaderTest NOSECURITY
87   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=ERROR LoggerFinderLoaderTest NOPERMISSIONS
88   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=ERROR LoggerFinderLoaderTest WITHPERMISSIONS
89   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=DEBUG LoggerFinderLoaderTest NOSECURITY
90   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=DEBUG LoggerFinderLoaderTest NOPERMISSIONS
91   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=DEBUG LoggerFinderLoaderTest WITHPERMISSIONS
92   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=QUIET LoggerFinderLoaderTest NOSECURITY
93   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=QUIET LoggerFinderLoaderTest NOPERMISSIONS
94   * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=QUIET LoggerFinderLoaderTest WITHPERMISSIONS
95   * @author danielfuchs
96   */
97  public class LoggerFinderLoaderTest {
98  
99      static final RuntimePermission LOGGERFINDER_PERMISSION =
100                 new RuntimePermission("loggerFinder");
101     final static boolean VERBOSE = false;
102     static final ThreadLocal<AtomicBoolean> allowControl = new ThreadLocal<AtomicBoolean>() {
103         @Override
104         protected AtomicBoolean initialValue() {
105             return  new AtomicBoolean(false);
106         }
107     };
108     static final ThreadLocal<AtomicBoolean> allowAccess = new ThreadLocal<AtomicBoolean>() {
109         @Override
110         protected AtomicBoolean initialValue() {
111             return  new AtomicBoolean(false);
112         }
113     };
114 
115     final static AccessSystemLogger accessSystemLogger = new AccessSystemLogger();
116     static final Class<?>[] providerClass;
117     static {
118         try {
119             providerClass = new Class<?>[] {
120                 ClassLoader.getSystemClassLoader().loadClass("LoggerFinderLoaderTest$BaseLoggerFinder"),
121                 ClassLoader.getSystemClassLoader().loadClass("LoggerFinderLoaderTest$BaseLoggerFinder2")
122             };
123         } catch (ClassNotFoundException ex) {
124             throw new ExceptionInInitializerError(ex);
125         }
126     }
127 
128     /**
129      * What our test provider needs to implement.
130      */
131     public static interface TestLoggerFinder {
132         public final static AtomicBoolean fails = new AtomicBoolean();
133         public final static AtomicReference<String> conf = new AtomicReference<>("");
134         public final static AtomicLong sequencer = new AtomicLong();
135         public final ConcurrentHashMap<String, LoggerImpl> system = new ConcurrentHashMap<>();
136         public final ConcurrentHashMap<String, LoggerImpl> user = new ConcurrentHashMap<>();
137 
138         public class LoggerImpl implements System.Logger {
139             final String name;
140             final Logger logger;
141 
142             public LoggerImpl(String name, Logger logger) {
143                 this.name = name;
144                 this.logger = logger;
145             }
146 
147             @Override
148             public String getName() {
149                 return name;
150             }
151 
152             @Override
153             public boolean isLoggable(Logger.Level level) {
154                 return logger.isLoggable(level);
155             }
156 
157             @Override
158             public void log(Logger.Level level, ResourceBundle bundle, String key, Throwable thrown) {
159                 logger.log(level, bundle, key, thrown);
160             }
161 
162             @Override
163             public void log(Logger.Level level, ResourceBundle bundle, String format, Object... params) {
164                 logger.log(level, bundle, format, params);
165             }
166 
167         }
168 
169         public Logger getLogger(String name, Module caller);
170         public Logger getLocalizedLogger(String name, ResourceBundle bundle, Module caller);
171     }
172 
173     public static class BaseLoggerFinder extends LoggerFinder implements TestLoggerFinder {
174 
175         static final RuntimePermission LOGGERFINDER_PERMISSION =
176                     new RuntimePermission("loggerFinder");
177         public BaseLoggerFinder() {
178             if (fails.get()) {
179                 throw new RuntimeException("Simulate exception while loading provider");
180             }
181         }
182 
183         System.Logger createSimpleLogger(String name) {
184             PrivilegedAction<System.Logger> pa = () -> SimpleConsoleLogger.makeSimpleLogger(name);
185             return AccessController.doPrivileged(pa);
186         }
187 
188 
189         @Override
190         public Logger getLogger(String name, Module caller) {
191             SecurityManager sm = System.getSecurityManager();
192             if (sm != null) {
193                 sm.checkPermission(LOGGERFINDER_PERMISSION);
194             }
195             PrivilegedAction<ClassLoader> pa = () -> caller.getClassLoader();
196             ClassLoader callerLoader = AccessController.doPrivileged(pa);
197             if (callerLoader == null) {
198                 return system.computeIfAbsent(name, (n) -> new LoggerImpl(n, createSimpleLogger(name)));
199             } else {
200                 return user.computeIfAbsent(name, (n) -> new LoggerImpl(n, createSimpleLogger(name)));
201             }
202         }
203     }
204 
205     public static class BaseLoggerFinder2 extends LoggerFinder implements TestLoggerFinder {
206 
207         static final RuntimePermission LOGGERFINDER_PERMISSION =
208                     new RuntimePermission("loggerFinder");
209         public BaseLoggerFinder2() {
210             throw new ServiceConfigurationError("Should not come here");
211         }
212         @Override
213         public Logger getLogger(String name, Module caller) {
214             throw new ServiceConfigurationError("Should not come here");
215         }
216     }
217 
218     public static class MyBundle extends ResourceBundle {
219 
220         final ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();
221 
222         @Override
223         protected Object handleGetObject(String key) {
224             if (key.contains(" (translated)")) {
225                 throw new RuntimeException("Unexpected key: " + key);
226             }
227             return map.computeIfAbsent(key, k -> k.toUpperCase(Locale.ROOT) + " (translated)");
228         }
229 
230         @Override
231         public Enumeration<String> getKeys() {
232             return Collections.enumeration(map.keySet());
233         }
234 
235     }
236     public static class MyLoggerBundle extends MyBundle {
237 
238     }
239 
240     static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS};
241 
242     static void setSecurityManager() {
243         if (System.getSecurityManager() == null) {
244             Policy.setPolicy(new SimplePolicy(allowControl, allowAccess));
245             System.setSecurityManager(new SecurityManager());
246         }
247     }
248 
249     static LoggerFinder getLoggerFinder(Class<?> expectedClass,
250             String errorPolicy, boolean singleton) {
251         LoggerFinder provider = null;
252         try {
253             TestLoggerFinder.sequencer.incrementAndGet();
254             provider = LoggerFinder.getLoggerFinder();
255             if (TestLoggerFinder.fails.get() || singleton) {
256                 if ("ERROR".equals(errorPolicy.toUpperCase(Locale.ROOT))) {
257                     throw new RuntimeException("Expected exception not thrown");
258                 } else if ("WARNING".equals(errorPolicy.toUpperCase(Locale.ROOT))) {
259                     String warning = ErrorStream.errorStream.peek();
260                     if (!warning.contains("WARNING: Failed to instantiate LoggerFinder provider; Using default.")) {
261                         throw new RuntimeException("Expected message not found. Error stream contained: " + warning);
262                     }
263                 } else if ("DEBUG".equals(errorPolicy.toUpperCase(Locale.ROOT))) {
264                     String warning = ErrorStream.errorStream.peek();
265                     if (!warning.contains("WARNING: Failed to instantiate LoggerFinder provider; Using default.")) {
266                         throw new RuntimeException("Expected message not found. Error stream contained: " + warning);
267                     }
268                     if (!warning.contains("WARNING: Exception raised trying to instantiate LoggerFinder")) {
269                         throw new RuntimeException("Expected message not found. Error stream contained: " + warning);
270                     }
271                     if (TestLoggerFinder.fails.get()) {
272                         if (!warning.contains("java.util.ServiceConfigurationError: java.lang.System$LoggerFinder: Provider LoggerFinderLoaderTest$BaseLoggerFinder could not be instantiated")) {
273                             throw new RuntimeException("Expected message not found. Error stream contained: " + warning);
274                         }
275                     } else if (singleton) {
276                         if (!warning.contains("java.util.ServiceConfigurationError: More than on LoggerFinder implementation")) {
277                             throw new RuntimeException("Expected message not found. Error stream contained: " + warning);
278                         }
279                     }
280                 } else if ("QUIET".equals(errorPolicy.toUpperCase(Locale.ROOT))) {
281                     if (!ErrorStream.errorStream.peek().isEmpty()) {
282                         throw new RuntimeException("Unexpected error message found: "
283                                 + ErrorStream.errorStream.peek());
284                     }
285                 }
286             }
287         } catch(AccessControlException a) {
288             throw a;
289         } catch(Throwable t) {
290             if (TestLoggerFinder.fails.get() || singleton) {
291                 // must check System.err
292                 if ("ERROR".equals(errorPolicy.toUpperCase(Locale.ROOT))) {
293                     provider = LoggerFinder.getLoggerFinder();
294                 } else {
295                     Throwable orig = t.getCause();
296                     while (orig != null && orig.getCause() != null) orig = orig.getCause();
297                     if (orig != null) orig.printStackTrace(ErrorStream.err);
298                     throw new RuntimeException("Unexpected exception: " + t, t);
299                 }
300             } else {
301                 throw new RuntimeException("Unexpected exception: " + t, t);
302             }
303         }
304         expectedClass.cast(provider);
305         ErrorStream.errorStream.store();
306         System.out.println("*** Actual LoggerFinder class is: " + provider.getClass().getName());
307         return provider;
308     }
309 
310 
311     static class ErrorStream extends PrintStream {
312 
313         static AtomicBoolean forward = new AtomicBoolean();
314         ByteArrayOutputStream out;
315         String saved = "";
316         public ErrorStream(ByteArrayOutputStream out) {
317             super(out);
318             this.out = out;
319         }
320 
321         @Override
322         public void write(int b) {
323             super.write(b);
324             if (forward.get()) err.write(b);
325         }
326 
327         @Override
328         public void write(byte[] b) throws IOException {
329             super.write(b);
330             if (forward.get()) err.write(b);
331         }
332 
333         @Override
334         public void write(byte[] buf, int off, int len) {
335             super.write(buf, off, len);
336             if (forward.get()) err.write(buf, off, len);
337         }
338 
339         public String peek() {
340             flush();
341             return out.toString();
342         }
343 
344         public String drain() {
345             flush();
346             String res = out.toString();
347             out.reset();
348             return res;
349         }
350 
351         public void store() {
352             flush();
353             saved = out.toString();
354             out.reset();
355         }
356 
357         public void restore() {
358             out.reset();
359             try {
360                 out.write(saved.getBytes());
361             } catch(IOException io) {
362                 throw new UncheckedIOException(io);
363             }
364         }
365 
366         static final PrintStream err = System.err;
367         static final ErrorStream errorStream = new ErrorStream(new ByteArrayOutputStream());
368     }
369 
370     private static StringBuilder appendProperty(StringBuilder b, String name) {
371         String value = System.getProperty(name);
372         if (value == null) return b;
373         return b.append(name).append("=").append(value).append('\n');
374     }
375 
376     public static void main(String[] args) {
377         if (args.length == 0) {
378             args = new String[] {
379                 "NOSECURITY",
380                 "NOPERMISSIONS",
381                 "WITHPERMISSIONS"
382             };
383         }
384         Locale.setDefault(Locale.ENGLISH);
385         System.setErr(ErrorStream.errorStream);
386         System.setProperty("jdk.logger.packages", TestLoggerFinder.LoggerImpl.class.getName());
387         //System.setProperty("jdk.logger.finder.error", "ERROR");
388         //System.setProperty("jdk.logger.finder.singleton", "true");
389         //System.setProperty("test.fails", "true");
390         TestLoggerFinder.fails.set(Boolean.getBoolean("test.fails"));
391         StringBuilder c = new StringBuilder();
392         appendProperty(c, "jdk.logger.packages");
393         appendProperty(c, "jdk.logger.finder.error");
394         appendProperty(c, "jdk.logger.finder.singleton");
395         appendProperty(c, "test.fails");
396         TestLoggerFinder.conf.set(c.toString());
397         try {
398             test(args);
399         } finally {
400             try {
401                 System.setErr(ErrorStream.err);
402             } catch (Error | RuntimeException x) {
403                 x.printStackTrace(ErrorStream.err);
404             }
405         }
406     }
407 
408 
409     public static void test(String[] args) {
410 
411         final String errorPolicy =  System.getProperty("jdk.logger.finder.error", "WARNING");
412         final Boolean ensureSingleton = Boolean.getBoolean("jdk.logger.finder.singleton");
413 
414         final Class<?> expectedClass =
415                 TestLoggerFinder.fails.get() || ensureSingleton
416                 ? jdk.internal.logger.DefaultLoggerFinder.class
417                 : TestLoggerFinder.class;
418 
419         System.out.println("Declared provider class: " + providerClass[0]
420                 + "[" + providerClass[0].getClassLoader() + "]");
421 
422         if (!TestLoggerFinder.fails.get()) {
423             ServiceLoader<LoggerFinder> serviceLoader =
424                 ServiceLoader.load(LoggerFinder.class, ClassLoader.getSystemClassLoader());
425             Iterator<LoggerFinder> iterator = serviceLoader.iterator();
426             Object firstProvider = iterator.next();
427             if (!firstProvider.getClass().getName().equals("LoggerFinderLoaderTest$BaseLoggerFinder")) {
428                 throw new RuntimeException("Unexpected provider: " + firstProvider.getClass().getName());
429             }
430             if (!iterator.hasNext()) {
431                 throw new RuntimeException("Expected two providers");
432             }
433         }
434 
435         Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> {
436             LoggerFinder provider;
437             ErrorStream.errorStream.restore();
438             switch (testCase) {
439                 case NOSECURITY:
440                     System.out.println("\n*** Without Security Manager\n");
441                     System.out.println(TestLoggerFinder.conf.get());
442                     provider = getLoggerFinder(expectedClass, errorPolicy, ensureSingleton);
443                     test(provider, true);
444                     System.out.println("Tetscase count: " + TestLoggerFinder.sequencer.get());
445                     break;
446                 case NOPERMISSIONS:
447                     System.out.println("\n*** With Security Manager, without permissions\n");
448                     System.out.println(TestLoggerFinder.conf.get());
449                     setSecurityManager();
450                     try {
451                         provider = getLoggerFinder(expectedClass, errorPolicy, ensureSingleton);
452                         throw new RuntimeException("Expected exception not raised");
453                     } catch (AccessControlException x) {
454                         if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) {
455                             throw new RuntimeException("Unexpected permission check", x);
456                         }
457                         final boolean control = allowControl.get().get();
458                         try {
459                             allowControl.get().set(true);
460                             provider = getLoggerFinder(expectedClass, errorPolicy, ensureSingleton);
461                         } finally {
462                             allowControl.get().set(control);
463                         }
464                     }
465                     test(provider, false);
466                     System.out.println("Tetscase count: " + TestLoggerFinder.sequencer.get());
467                     break;
468                 case WITHPERMISSIONS:
469                     System.out.println("\n*** With Security Manager, with control permission\n");
470                     System.out.println(TestLoggerFinder.conf.get());
471                     setSecurityManager();
472                     final boolean control = allowControl.get().get();
473                     try {
474                         allowControl.get().set(true);
475                         provider = getLoggerFinder(expectedClass, errorPolicy, ensureSingleton);
476                         test(provider, true);
477                     } finally {
478                         allowControl.get().set(control);
479                     }
480                     break;
481                 default:
482                     throw new RuntimeException("Unknown test case: " + testCase);
483             }
484         });
485         System.out.println("\nPASSED: Tested " + TestLoggerFinder.sequencer.get() + " cases.");
486     }
487 
488     public static void test(LoggerFinder provider, boolean hasRequiredPermissions) {
489 
490         ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName());
491         final Map<Logger, String> loggerDescMap = new HashMap<>();
492 
493         System.Logger sysLogger = accessSystemLogger.getLogger("foo");
494         loggerDescMap.put(sysLogger, "accessSystemLogger.getLogger(\"foo\")");
495         System.Logger localizedSysLogger = accessSystemLogger.getLogger("fox", loggerBundle);
496         loggerDescMap.put(localizedSysLogger, "accessSystemLogger.getLogger(\"fox\", loggerBundle)");
497         System.Logger appLogger = System.getLogger("bar");
498         loggerDescMap.put(appLogger,"System.getLogger(\"bar\")");
499         System.Logger localizedAppLogger = System.getLogger("baz", loggerBundle);
500         loggerDescMap.put(localizedAppLogger,"System.getLogger(\"baz\", loggerBundle)");
501 
502         testLogger(provider, loggerDescMap, "foo", null, sysLogger);
503         testLogger(provider, loggerDescMap, "foo", loggerBundle, localizedSysLogger);
504         testLogger(provider, loggerDescMap, "foo", null, appLogger);
505         testLogger(provider, loggerDescMap, "foo", loggerBundle, localizedAppLogger);
506     }
507 
508     public static class Foo {
509 
510     }
511 
512     static void verbose(String msg) {
513        if (VERBOSE) {
514            System.out.println(msg);
515        }
516     }
517 
518     // Calls the 8 methods defined on Logger and verify the
519     // parameters received by the underlying TestProvider.LoggerImpl
520     // logger.
521     private static void testLogger(LoggerFinder provider,
522             Map<Logger, String> loggerDescMap,
523             String name,
524             ResourceBundle loggerBundle,
525             Logger logger) {
526 
527         System.out.println("Testing " + loggerDescMap.get(logger) + " [" + logger +"]");
528         AtomicLong sequencer = TestLoggerFinder.sequencer;
529 
530         Foo foo = new Foo();
531         String fooMsg = foo.toString();
532         for (Level loggerLevel : EnumSet.of(Level.INFO)) {
533             for (Level messageLevel : Level.values()) {
534                 ErrorStream.errorStream.drain();
535                 String desc = "logger.log(messageLevel, foo): loggerLevel="
536                         + loggerLevel+", messageLevel="+messageLevel;
537                 sequencer.incrementAndGet();
538                 logger.log(messageLevel, foo);
539                 if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
540                     if (!ErrorStream.errorStream.peek().isEmpty()) {
541                         throw new RuntimeException("unexpected event in queue for "
542                                 + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
543                     }
544                 } else {
545                     String logged = ErrorStream.errorStream.drain();
546                     if (!logged.contains("LoggerFinderLoaderTest testLogger")
547                         || !logged.contains(messageLevel.getName() + ": " + fooMsg)) {
548                         throw new RuntimeException("mismatch for " + desc
549                                 + "\n\texpected:" + "\n<<<<\n"
550                                 + "[date] LoggerFinderLoaderTest testLogger\n"
551                                 + messageLevel.getName() + " " + fooMsg
552                                 + "\n>>>>"
553                                 + "\n\t  actual:"
554                                 + "\n<<<<\n" + logged + ">>>>\n");
555                     } else {
556                         verbose("Got expected results for "
557                                 + desc + "\n<<<<\n" + logged + ">>>>\n");
558                     }
559                 }
560             }
561         }
562 
563         String msg = "blah";
564         for (Level loggerLevel : EnumSet.of(Level.INFO)) {
565             for (Level messageLevel : Level.values()) {
566                 String desc = "logger.log(messageLevel, \"blah\"): loggerLevel="
567                         + loggerLevel+", messageLevel="+messageLevel;
568                 sequencer.incrementAndGet();
569                 logger.log(messageLevel, msg);
570                 if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
571                     if (!ErrorStream.errorStream.peek().isEmpty()) {
572                         throw new RuntimeException("unexpected event in queue for "
573                                 + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
574                     }
575                 } else {
576                     String logged = ErrorStream.errorStream.drain();
577                     String msgText = loggerBundle == null ? msg : loggerBundle.getString(msg);
578                     if (!logged.contains("LoggerFinderLoaderTest testLogger")
579                         || !logged.contains(messageLevel.getName() + ": " + msgText)) {
580                         throw new RuntimeException("mismatch for " + desc
581                                 + "\n\texpected:" + "\n<<<<\n"
582                                 + "[date] LoggerFinderLoaderTest testLogger\n"
583                                 + messageLevel.getName() + " " + msgText
584                                 + "\n>>>>"
585                                 + "\n\t  actual:"
586                                 + "\n<<<<\n" + logged + ">>>>\n");
587                     } else {
588                         verbose("Got expected results for "
589                                 + desc + "\n<<<<\n" + logged + ">>>>\n");
590                     }
591                 }
592             }
593         }
594 
595         Supplier<String> fooSupplier = new Supplier<String>() {
596             @Override
597             public String get() {
598                 return this.toString();
599             }
600         };
601 
602         for (Level loggerLevel : EnumSet.of(Level.INFO)) {
603             for (Level messageLevel : Level.values()) {
604                 String desc = "logger.log(messageLevel, fooSupplier): loggerLevel="
605                         + loggerLevel+", messageLevel="+messageLevel;
606                 sequencer.incrementAndGet();
607                 logger.log(messageLevel, fooSupplier);
608                 if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
609                     if (!ErrorStream.errorStream.peek().isEmpty()) {
610                         throw new RuntimeException("unexpected event in queue for "
611                                 + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
612                     }
613                 } else {
614                     String logged = ErrorStream.errorStream.drain();
615                     if (!logged.contains("LoggerFinderLoaderTest testLogger")
616                         || !logged.contains(messageLevel.getName() + ": " + fooSupplier.get())) {
617                         throw new RuntimeException("mismatch for " + desc
618                                 + "\n\texpected:" + "\n<<<<\n"
619                                 + "[date] LoggerFinderLoaderTest testLogger\n"
620                                 + messageLevel.getName() + " " + fooSupplier.get()
621                                 + "\n>>>>"
622                                 + "\n\t  actual:"
623                                 + "\n<<<<\n" + logged + ">>>>\n");
624                     } else {
625                         verbose("Got expected results for "
626                                 + desc + "\n<<<<\n" + logged + ">>>>\n");
627                     }
628                 }
629             }
630         }
631 
632 
633         String format = "two params [{1} {2}]";
634         Object arg1 = foo;
635         Object arg2 = msg;
636         for (Level loggerLevel : EnumSet.of(Level.INFO)) {
637             for (Level messageLevel : Level.values()) {
638                 String desc = "logger.log(messageLevel, format, params...): loggerLevel="
639                         + loggerLevel+", messageLevel="+messageLevel;
640                 sequencer.incrementAndGet();
641                 logger.log(messageLevel, format, foo, msg);
642                 if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
643                     if (!ErrorStream.errorStream.peek().isEmpty()) {
644                         throw new RuntimeException("unexpected event in queue for "
645                                 + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
646                     }
647                 } else {
648                     String logged = ErrorStream.errorStream.drain();
649                     String msgFormat = loggerBundle == null ? format : loggerBundle.getString(format);
650                     String text = java.text.MessageFormat.format(msgFormat, foo, msg);
651                     if (!logged.contains("LoggerFinderLoaderTest testLogger")
652                         || !logged.contains(messageLevel.getName() + ": " + text)) {
653                         throw new RuntimeException("mismatch for " + desc
654                                 + "\n\texpected:" + "\n<<<<\n"
655                                 + "[date] LoggerFinderLoaderTest testLogger\n"
656                                 + messageLevel.getName() + " " + text
657                                 + "\n>>>>"
658                                 + "\n\t  actual:"
659                                 + "\n<<<<\n" + logged + ">>>>\n");
660                     } else {
661                         verbose("Got expected results for "
662                                 + desc + "\n<<<<\n" + logged + ">>>>\n");
663                     }
664                 }
665             }
666         }
667 
668         Throwable thrown = new Exception("OK: log me!");
669         for (Level loggerLevel : EnumSet.of(Level.INFO)) {
670             for (Level messageLevel : Level.values()) {
671                 String desc = "logger.log(messageLevel, \"blah\", thrown): loggerLevel="
672                         + loggerLevel+", messageLevel="+messageLevel;
673                 sequencer.incrementAndGet();
674                 logger.log(messageLevel, msg, thrown);
675                 if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
676                     if (!ErrorStream.errorStream.peek().isEmpty()) {
677                         throw new RuntimeException("unexpected event in queue for "
678                                 + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
679                     }
680                 } else {
681                     String logged = ErrorStream.errorStream.drain();
682                     ByteArrayOutputStream baos = new ByteArrayOutputStream();
683                     thrown.printStackTrace(new PrintStream(baos));
684                     String text = baos.toString();
685                     String msgText = loggerBundle == null ? msg : loggerBundle.getString(msg);
686                     if (!logged.contains("LoggerFinderLoaderTest testLogger")
687                         || !logged.contains(messageLevel.getName() + ": " + msgText)
688                         || !logged.contains(text)) {
689                         throw new RuntimeException("mismatch for " + desc
690                                 + "\n\texpected:" + "\n<<<<\n"
691                                 + "[date] LoggerFinderLoaderTest testLogger\n"
692                                 + messageLevel.getName() + " " + msgText +"\n"
693                                 + text
694                                 + ">>>>"
695                                 + "\n\t  actual:"
696                                 + "\n<<<<\n" + logged + ">>>>\n");
697                     } else {
698                         verbose("Got expected results for "
699                                 + desc + "\n<<<<\n" + logged + ">>>>\n");
700                     }
701                 }
702             }
703         }
704 
705 
706         for (Level loggerLevel : EnumSet.of(Level.INFO)) {
707             for (Level messageLevel : Level.values()) {
708                 String desc = "logger.log(messageLevel, thrown, fooSupplier): loggerLevel="
709                         + loggerLevel+", messageLevel="+messageLevel;
710                 sequencer.incrementAndGet();
711                 logger.log(messageLevel, fooSupplier, thrown);
712                 if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
713                     if (!ErrorStream.errorStream.peek().isEmpty()) {
714                         throw new RuntimeException("unexpected event in queue for "
715                                 + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
716                     }
717                 } else {
718                     String logged = ErrorStream.errorStream.drain();
719                     ByteArrayOutputStream baos = new ByteArrayOutputStream();
720                     thrown.printStackTrace(new PrintStream(baos));
721                     String text = baos.toString();
722                     if (!logged.contains("LoggerFinderLoaderTest testLogger")
723                         || !logged.contains(messageLevel.getName() + ": " + fooSupplier.get())
724                         || !logged.contains(text)) {
725                         throw new RuntimeException("mismatch for " + desc
726                                 + "\n\texpected:" + "\n<<<<\n"
727                                 + "[date] LoggerFinderLoaderTest testLogger\n"
728                                 + messageLevel.getName() + " " + fooSupplier.get() +"\n"
729                                 + text
730                                 + ">>>>"
731                                 + "\n\t  actual:"
732                                 + "\n<<<<\n" + logged + ">>>>\n");
733                     } else {
734                         verbose("Got expected results for "
735                                 + desc + "\n<<<<\n" + logged + ">>>>\n");
736                     }
737                 }
738             }
739         }
740 
741         ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName());
742         for (Level loggerLevel : EnumSet.of(Level.INFO)) {
743             for (Level messageLevel : Level.values()) {
744                 String desc = "logger.log(messageLevel, bundle, format, params...): loggerLevel="
745                         + loggerLevel+", messageLevel="+messageLevel;
746                 sequencer.incrementAndGet();
747                 logger.log(messageLevel, bundle, format, foo, msg);
748                 if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
749                     if (!ErrorStream.errorStream.peek().isEmpty()) {
750                         throw new RuntimeException("unexpected event in queue for "
751                                 + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
752                     }
753                 } else {
754                     String logged = ErrorStream.errorStream.drain();
755                     String text = java.text.MessageFormat.format(bundle.getString(format), foo, msg);
756                     if (!logged.contains("LoggerFinderLoaderTest testLogger")
757                         || !logged.contains(messageLevel.getName() + ": " + text)) {
758                         throw new RuntimeException("mismatch for " + desc
759                                 + "\n\texpected:" + "\n<<<<\n"
760                                 + "[date] LoggerFinderLoaderTest testLogger\n"
761                                 + messageLevel.getName() + " " + text
762                                 + "\n>>>>"
763                                 + "\n\t  actual:"
764                                 + "\n<<<<\n" + logged + ">>>>\n");
765                     } else {
766                         verbose("Got expected results for "
767                                 + desc + "\n<<<<\n" + logged + ">>>>\n");
768                     }
769                 }
770             }
771         }
772 
773         for (Level loggerLevel : EnumSet.of(Level.INFO)) {
774             for (Level messageLevel : Level.values()) {
775                 String desc = "logger.log(messageLevel, bundle, \"blah\", thrown): loggerLevel="
776                         + loggerLevel+", messageLevel="+messageLevel;
777                 sequencer.incrementAndGet();
778                 logger.log(messageLevel, bundle, msg, thrown);
779                 if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
780                     if (!ErrorStream.errorStream.peek().isEmpty()) {
781                         throw new RuntimeException("unexpected event in queue for "
782                                 + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
783                     }
784                 } else {
785                     String logged = ErrorStream.errorStream.drain();
786                     String textMsg = bundle.getString(msg);
787                     ByteArrayOutputStream baos = new ByteArrayOutputStream();
788                     thrown.printStackTrace(new PrintStream(baos));
789                     String text = baos.toString();
790                     if (!logged.contains("LoggerFinderLoaderTest testLogger")
791                         || !logged.contains(messageLevel.getName() + ": " + textMsg)
792                         || !logged.contains(text)) {
793                         throw new RuntimeException("mismatch for " + desc
794                                 + "\n\texpected:" + "\n<<<<\n"
795                                 + "[date] LoggerFinderLoaderTest testLogger\n"
796                                 + messageLevel.getName() + " " + textMsg +"\n"
797                                 + text
798                                 + ">>>>"
799                                 + "\n\t  actual:"
800                                 + "\n<<<<\n" + logged + ">>>>\n");
801                     } else {
802                         verbose("Got expected results for "
803                                 + desc + "\n<<<<\n" + logged + ">>>>\n");
804                     }
805                 }
806             }
807         }
808 
809     }
810 
811     final static class PermissionsBuilder {
812         final Permissions perms;
813         public PermissionsBuilder() {
814             this(new Permissions());
815         }
816         public PermissionsBuilder(Permissions perms) {
817             this.perms = perms;
818         }
819         public PermissionsBuilder add(Permission p) {
820             perms.add(p);
821             return this;
822         }
823         public PermissionsBuilder addAll(PermissionCollection col) {
824             if (col != null) {
825                 for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) {
826                     perms.add(e.nextElement());
827                 }
828             }
829             return this;
830         }
831         public Permissions toPermissions() {
832             final PermissionsBuilder builder = new PermissionsBuilder();
833             builder.addAll(perms);
834             return builder.perms;
835         }
836     }
837 
838     public static class SimplePolicy extends Policy {
839         final static RuntimePermission CONTROL = LOGGERFINDER_PERMISSION;
840         final static RuntimePermission ACCESS = new RuntimePermission("accessClassInPackage.jdk.internal.logger");
841 
842         final Permissions permissions;
843         final ThreadLocal<AtomicBoolean> allowControl;
844         final ThreadLocal<AtomicBoolean> allowAccess;
845         public SimplePolicy(ThreadLocal<AtomicBoolean> allowControl, ThreadLocal<AtomicBoolean> allowAccess) {
846             this.allowControl = allowControl;
847             this.allowAccess = allowAccess;
848             permissions = new Permissions();
849             permissions.add(new RuntimePermission("setIO"));
850         }
851 
852         Permissions getPermissions() {
853             if (allowControl.get().get() || allowAccess.get().get()) {
854                 PermissionsBuilder builder =  new PermissionsBuilder()
855                         .addAll(permissions);
856                 if (allowControl.get().get()) {
857                     builder.add(CONTROL);
858                 }
859                 if (allowAccess.get().get()) {
860                     builder.add(ACCESS);
861                 }
862                 return builder.toPermissions();
863             }
864             return permissions;
865         }
866 
867         @Override
868         public boolean implies(ProtectionDomain domain, Permission permission) {
869             return getPermissions().implies(permission);
870         }
871 
872         @Override
873         public PermissionCollection getPermissions(CodeSource codesource) {
874             return new PermissionsBuilder().addAll(getPermissions()).toPermissions();
875         }
876 
877         @Override
878         public PermissionCollection getPermissions(ProtectionDomain domain) {
879             return new PermissionsBuilder().addAll(getPermissions()).toPermissions();
880         }
881     }
882 }