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