1 /*
   2  * Copyright (c) 2001, 2013, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.rmi.runtime;
  27 
  28 import java.io.ByteArrayOutputStream;
  29 import java.io.PrintStream;
  30 import java.io.OutputStream;
  31 import java.rmi.server.LogStream;
  32 import java.security.PrivilegedAction;
  33 import java.util.logging.Handler;
  34 import java.util.logging.SimpleFormatter;
  35 import java.util.logging.Level;
  36 import java.util.logging.Logger;
  37 import java.util.logging.LogRecord;
  38 import java.util.logging.StreamHandler;
  39 
  40 /**
  41  * Utility which provides an abstract "logger" like RMI internal API
  42  * which can be directed to use one of two types of logging
  43  * infrastructure: the java.util.logging API or the
  44  * java.rmi.server.LogStream API.  The default behavior is to use the
  45  * java.util.logging API.  The LogStream API may be used instead by
  46  * setting the system property sun.rmi.log.useOld to true.
  47  *
  48  * For backwards compatibility, supports the RMI system logging
  49  * properties which pre-1.4 comprised the only way to configure RMI
  50  * logging.  If the java.util.logging API is used and RMI system log
  51  * properties are set, the system properties override initial RMI
  52  * logger values as appropriate. If the java.util.logging API is
  53  * turned off, pre-1.4 logging behavior is used.
  54  *
  55  * @author Laird Dornin
  56  * @since 1.4
  57  */
  58 @SuppressWarnings("deprecation")
  59 public abstract class Log {
  60 
  61     /** Logger re-definition of old RMI log values */
  62     public static final Level BRIEF = Level.FINE;
  63     public static final Level VERBOSE = Level.FINER;
  64 
  65     /* selects log implementation */
  66     private static final LogFactory logFactory;
  67     static {
  68         boolean useOld = java.security.AccessController.doPrivileged(
  69             (PrivilegedAction<Boolean>) () -> Boolean.getBoolean("sun.rmi.log.useOld"));
  70 
  71         /* set factory to select the logging facility to use */
  72         logFactory = (useOld ? (LogFactory) new LogStreamLogFactory() :
  73                       (LogFactory) new LoggerLogFactory());
  74     }
  75 
  76     /** "logger like" API to be used by RMI implementation */
  77     public abstract boolean isLoggable(Level level);
  78     public abstract void log(Level level, String message);
  79     public abstract void log(Level level, String message, Throwable thrown);
  80 
  81     /** get and set the RMI server call output stream */
  82     public abstract void setOutputStream(OutputStream stream);
  83     public abstract PrintStream getPrintStream();
  84 
  85     /** factory interface enables Logger and LogStream implementations */
  86     private static interface LogFactory {
  87         Log createLog(String loggerName, String oldLogName, Level level);
  88     }
  89 
  90     /* access log objects */
  91 
  92     /**
  93      * Access log for a tri-state system property.
  94      *
  95      * Need to first convert override value to a log level, taking
  96      * care to interpret a range of values between BRIEF, VERBOSE and
  97      * SILENT.
  98      *
  99      * An override < 0 is interpreted to mean that the logging
 100      * configuration should not be overridden. The level passed to the
 101      * factories createLog method will be null in this case.
 102      *
 103      * Note that if oldLogName is null and old logging is on, the
 104      * returned LogStreamLog will ignore the override parameter - the
 105      * log will never log messages.  This permits new logs that only
 106      * write to Loggers to do nothing when old logging is active.
 107      *
 108      * Do not call getLog multiple times on the same logger name.
 109      * Since this is an internal API, no checks are made to ensure
 110      * that multiple logs do not exist for the same logger.
 111      */
 112     public static Log getLog(String loggerName, String oldLogName,
 113                              int override)
 114     {
 115         Level level;
 116 
 117         if (override < 0) {
 118             level = null;
 119         } else if (override == LogStream.SILENT) {
 120             level = Level.OFF;
 121         } else if ((override > LogStream.SILENT) &&
 122                    (override <= LogStream.BRIEF)) {
 123             level = BRIEF;
 124         } else if ((override > LogStream.BRIEF) &&
 125                    (override <= LogStream.VERBOSE))
 126         {
 127             level = VERBOSE;
 128         } else {
 129             level = Level.FINEST;
 130         }
 131         return logFactory.createLog(loggerName, oldLogName, level);
 132     }
 133 
 134     /**
 135      * Access logs associated with boolean properties
 136      *
 137      * Do not call getLog multiple times on the same logger name.
 138      * Since this is an internal API, no checks are made to ensure
 139      * that multiple logs do not exist for the same logger.
 140      */
 141     public static Log getLog(String loggerName, String oldLogName,
 142                              boolean override)
 143     {
 144         Level level = (override ? VERBOSE : null);
 145         return logFactory.createLog(loggerName, oldLogName, level);
 146     }
 147 
 148     /**
 149      * Factory to create Log objects which deliver log messages to the
 150      * java.util.logging API.
 151      */
 152     private static class LoggerLogFactory implements LogFactory {
 153         LoggerLogFactory() {}
 154 
 155         /*
 156          * Accessor to obtain an arbitrary RMI logger with name
 157          * loggerName.  If the level of the logger is greater than the
 158          * level for the system property with name, the logger level
 159          * will be set to the value of system property.
 160          */
 161         public Log createLog(final String loggerName, String oldLogName,
 162                              final Level level)
 163         {
 164             Logger logger = Logger.getLogger(loggerName);
 165             return new LoggerLog(logger, level);
 166         }
 167     }
 168 
 169     /**
 170      * Class specialized to log messages to the java.util.logging API
 171      */
 172     private static class LoggerLog extends Log {
 173 
 174         /* alternate console handler for RMI loggers */
 175         private static final Handler alternateConsole =
 176                 java.security.AccessController.doPrivileged(
 177                 new java.security.PrivilegedAction<Handler>() {
 178                     public Handler run() {
 179                             InternalStreamHandler alternate =
 180                                 new InternalStreamHandler(System.err);
 181                             alternate.setLevel(Level.ALL);
 182                             return alternate;
 183                         }
 184                 });
 185 
 186         /** handler to which messages are copied */
 187         private InternalStreamHandler copyHandler = null;
 188 
 189         /* logger to which log messages are written */
 190         private final Logger logger;
 191 
 192         /* used as return value of RemoteServer.getLog */
 193         private LoggerPrintStream loggerSandwich;
 194 
 195         /** creates a Log which will delegate to the given logger */
 196         private LoggerLog(final Logger logger, final Level level) {
 197             this.logger = logger;
 198 
 199             if (level != null){
 200                 java.security.AccessController.doPrivileged(
 201                     new java.security.PrivilegedAction<Void>() {
 202                         public Void run() {
 203                             if (!logger.isLoggable(level)) {
 204                                 logger.setLevel(level);
 205                             }
 206                             logger.addHandler(alternateConsole);
 207                             return null;
 208                         }
 209                     }
 210                 );
 211             }
 212         }
 213 
 214         public boolean isLoggable(Level level) {
 215             return logger.isLoggable(level);
 216         }
 217 
 218         public void log(Level level, String message) {
 219             if (isLoggable(level)) {
 220                 String[] source = getSource();
 221                 logger.logp(level, source[0], source[1],
 222                            Thread.currentThread().getName() + ": " + message);
 223             }
 224         }
 225 
 226         public void log(Level level, String message, Throwable thrown) {
 227             if (isLoggable(level)) {
 228                 String[] source = getSource();
 229                 logger.logp(level, source[0], source[1],
 230                     Thread.currentThread().getName() + ": " +
 231                            message, thrown);
 232             }
 233         }
 234 
 235         /**
 236          * Set the output stream associated with the RMI server call
 237          * logger.
 238          *
 239          * Calling code needs LoggingPermission "control".
 240          */
 241         public synchronized void setOutputStream(OutputStream out) {
 242             if (out != null) {
 243                 if (!logger.isLoggable(VERBOSE)) {
 244                     logger.setLevel(VERBOSE);
 245                 }
 246                 copyHandler = new InternalStreamHandler(out);
 247                 copyHandler.setLevel(Log.VERBOSE);
 248                 logger.addHandler(copyHandler);
 249             } else {
 250                 /* ensure that messages are not logged */
 251                 if (copyHandler != null) {
 252                     logger.removeHandler(copyHandler);
 253                 }
 254                 copyHandler = null;
 255             }
 256         }
 257 
 258         public synchronized PrintStream getPrintStream() {
 259             if (loggerSandwich == null) {
 260                 loggerSandwich = new LoggerPrintStream(logger);
 261             }
 262             return loggerSandwich;
 263         }
 264     }
 265 
 266     /**
 267      * Subclass of StreamHandler for redirecting log output.  flush
 268      * must be called in the publish and close methods.
 269      */
 270     private static class InternalStreamHandler extends StreamHandler {
 271         InternalStreamHandler(OutputStream out) {
 272             super(out, new SimpleFormatter());
 273         }
 274 
 275         public void publish(LogRecord record) {
 276             super.publish(record);
 277             flush();
 278         }
 279 
 280         public void close() {
 281             flush();
 282         }
 283     }
 284 
 285     /**
 286      * PrintStream which forwards log messages to the logger.  Class
 287      * is needed to maintain backwards compatibility with
 288      * RemoteServer.{set|get}Log().
 289      */
 290     private static class LoggerPrintStream extends PrintStream {
 291 
 292         /** logger where output of this log is sent */
 293         private final Logger logger;
 294 
 295         /** record the last character written to this stream */
 296         private int last = -1;
 297 
 298         /** stream used for buffering lines */
 299         private final ByteArrayOutputStream bufOut;
 300 
 301         private LoggerPrintStream(Logger logger)
 302         {
 303             super(new ByteArrayOutputStream());
 304             bufOut = (ByteArrayOutputStream) super.out;
 305             this.logger = logger;
 306         }
 307 
 308         public void write(int b) {
 309             if ((last == '\r') && (b == '\n')) {
 310                 last = -1;
 311                 return;
 312             } else if ((b == '\n') || (b == '\r')) {
 313                 try {
 314                     /* write the converted bytes of the log message */
 315                     String message =
 316                         Thread.currentThread().getName() + ": " +
 317                         bufOut.toString();
 318                     logger.logp(Level.INFO, "LogStream", "print", message);
 319                 } finally {
 320                     bufOut.reset();
 321                 }
 322             } else {
 323                 super.write(b);
 324             }
 325             last = b;
 326         }
 327 
 328         public void write(byte b[], int off, int len) {
 329             if (len < 0) {
 330                 throw new ArrayIndexOutOfBoundsException(len);
 331             }
 332             for (int i = 0; i < len; i++) {
 333                 write(b[off + i]);
 334             }
 335         }
 336 
 337         public String toString() {
 338             return "RMI";
 339         }
 340     }
 341 
 342     /**
 343      * Factory to create Log objects which deliver log messages to the
 344      * java.rmi.server.LogStream API
 345      */
 346     private static class LogStreamLogFactory implements LogFactory {
 347         LogStreamLogFactory() {}
 348 
 349         /* create a new LogStreamLog for the specified log */
 350         public Log createLog(String loggerName, String oldLogName,
 351                              Level level)
 352         {
 353             LogStream stream = null;
 354             if (oldLogName != null) {
 355                 stream = LogStream.log(oldLogName);
 356             }
 357             return new LogStreamLog(stream, level);
 358         }
 359     }
 360 
 361     /**
 362      * Class specialized to log messages to the
 363      * java.rmi.server.LogStream API
 364      */
 365     private static class LogStreamLog extends Log {
 366         /** Log stream to which log messages are written */
 367         private final LogStream stream;
 368 
 369         /** the level of the log as set by associated property */
 370         private int levelValue = Level.OFF.intValue();
 371 
 372         private LogStreamLog(LogStream stream, Level level) {
 373             if ((stream != null) && (level != null)) {
 374                 /* if the stream or level is null, don't log any
 375                  * messages
 376                  */
 377                 levelValue = level.intValue();
 378             }
 379             this.stream = stream;
 380         }
 381 
 382         public synchronized boolean isLoggable(Level level) {
 383             return (level.intValue() >= levelValue);
 384         }
 385 
 386         public void log(Level messageLevel, String message) {
 387             if (isLoggable(messageLevel)) {
 388                 String[] source = getSource();
 389                 stream.println(unqualifiedName(source[0]) +
 390                                "." + source[1] + ": " + message);
 391             }
 392         }
 393 
 394         public void log(Level level, String message, Throwable thrown) {
 395             if (isLoggable(level)) {
 396                 /*
 397                  * keep output contiguous and maintain the contract of
 398                  * RemoteServer.getLog
 399                  */
 400                 synchronized (stream) {
 401                     String[] source = getSource();
 402                     stream.println(unqualifiedName(source[0]) + "." +
 403                                    source[1] + ": " + message);
 404                     thrown.printStackTrace(stream);
 405                 }
 406             }
 407         }
 408 
 409         public PrintStream getPrintStream() {
 410             return stream;
 411         }
 412 
 413         public synchronized void setOutputStream(OutputStream out) {
 414             if (out != null) {
 415                 if (VERBOSE.intValue() < levelValue) {
 416                     levelValue = VERBOSE.intValue();
 417                 }
 418                 stream.setOutputStream(out);
 419             } else {
 420                 /* ensure that messages are not logged */
 421                 levelValue = Level.OFF.intValue();
 422             }
 423         }
 424 
 425         /*
 426          * Mimic old log messages that only contain unqualified names.
 427          */
 428         private static String unqualifiedName(String name) {
 429             int lastDot = name.lastIndexOf('.');
 430             if (lastDot >= 0) {
 431                 name = name.substring(lastDot + 1);
 432             }
 433             name = name.replace('$', '.');
 434             return name;
 435         }
 436     }
 437 
 438     /**
 439      * Obtain class and method names of code calling a log method.
 440      */
 441     private static String[] getSource() {
 442         StackTraceElement[] trace = (new Exception()).getStackTrace();
 443         return new String[] {
 444             trace[3].getClassName(),
 445             trace[3].getMethodName()
 446         };
 447     }
 448 }