001: /*
002: * Copyright 2001-2002 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package sun.rmi.runtime;
027:
028: import java.io.ByteArrayOutputStream;
029: import java.io.IOException;
030: import java.io.PrintStream;
031: import java.io.OutputStream;
032: import java.rmi.server.LogStream;
033: import java.util.logging.ConsoleHandler;
034: import java.util.logging.Handler;
035: import java.util.logging.Formatter;
036: import java.util.logging.SimpleFormatter;
037: import java.util.logging.StreamHandler;
038: import java.util.logging.Level;
039: import java.util.logging.Logger;
040: import java.util.logging.LogManager;
041: import java.util.logging.LogRecord;
042: import java.util.logging.StreamHandler;
043: import java.util.Map;
044: import java.util.HashMap;
045:
046: /**
047: * Utility which provides an abstract "logger" like RMI internal API
048: * which can be directed to use one of two types of logging
049: * infrastructure: the java.util.logging API or the
050: * java.rmi.server.LogStream API. The default behavior is to use the
051: * java.util.logging API. The LogStream API may be used instead by
052: * setting the system property sun.rmi.log.useOld to true.
053: *
054: * For backwards compatibility, supports the RMI system logging
055: * properties which pre-1.4 comprised the only way to configure RMI
056: * logging. If the java.util.logging API is used and RMI system log
057: * properties are set, the system properties override initial RMI
058: * logger values as appropriate. If the java.util.logging API is
059: * turned off, pre-1.4 logging behavior is used.
060: *
061: * @author Laird Dornin
062: * 1.15, 07/05/05
063: * @since 1.4
064: */
065: public abstract class Log {
066:
067: /** Logger re-definition of old RMI log values */
068: public static final Level BRIEF = Level.FINE;
069: public static final Level VERBOSE = Level.FINER;
070:
071: /* selects log implementation */
072: private static final LogFactory logFactory;
073: static {
074: boolean useOld = Boolean
075: .valueOf(
076: (String) java.security.AccessController
077: .doPrivileged(new sun.security.action.GetPropertyAction(
078: "sun.rmi.log.useOld")))
079: .booleanValue();
080:
081: /* set factory to select the logging facility to use */
082: logFactory = (useOld ? (LogFactory) new LogStreamLogFactory()
083: : (LogFactory) new LoggerLogFactory());
084: }
085:
086: /** "logger like" API to be used by RMI implementation */
087: public abstract boolean isLoggable(Level level);
088:
089: public abstract void log(Level level, String message);
090:
091: public abstract void log(Level level, String message,
092: Throwable thrown);
093:
094: /** get and set the RMI server call output stream */
095: public abstract void setOutputStream(OutputStream stream);
096:
097: public abstract PrintStream getPrintStream();
098:
099: /** factory interface enables Logger and LogStream implementations */
100: private static interface LogFactory {
101: Log createLog(String loggerName, String oldLogName, Level level);
102: }
103:
104: /* access log objects */
105:
106: /**
107: * Access log for a tri-state system property.
108: *
109: * Need to first convert override value to a log level, taking
110: * care to interpret a range of values between BRIEF, VERBOSE and
111: * SILENT.
112: *
113: * An override < 0 is interpreted to mean that the logging
114: * configuration should not be overridden. The level passed to the
115: * factories createLog method will be null in this case.
116: *
117: * Note that if oldLogName is null and old logging is on, the
118: * returned LogStreamLog will ignore the override parameter - the
119: * log will never log messages. This permits new logs that only
120: * write to Loggers to do nothing when old logging is active.
121: *
122: * Do not call getLog multiple times on the same logger name.
123: * Since this is an internal API, no checks are made to ensure
124: * that multiple logs do not exist for the same logger.
125: */
126: public static Log getLog(String loggerName, String oldLogName,
127: int override) {
128: Level level;
129:
130: if (override < 0) {
131: level = null;
132: } else if (override == LogStream.SILENT) {
133: level = Level.OFF;
134: } else if ((override > LogStream.SILENT)
135: && (override <= LogStream.BRIEF)) {
136: level = BRIEF;
137: } else if ((override > LogStream.BRIEF)
138: && (override <= LogStream.VERBOSE)) {
139: level = VERBOSE;
140: } else {
141: level = Level.FINEST;
142: }
143: return logFactory.createLog(loggerName, oldLogName, level);
144: }
145:
146: /**
147: * Access logs associated with boolean properties
148: *
149: * Do not call getLog multiple times on the same logger name.
150: * Since this is an internal API, no checks are made to ensure
151: * that multiple logs do not exist for the same logger.
152: */
153: public static Log getLog(String loggerName, String oldLogName,
154: boolean override) {
155: Level level = (override ? VERBOSE : null);
156: return logFactory.createLog(loggerName, oldLogName, level);
157: }
158:
159: /**
160: * Factory to create Log objects which deliver log messages to the
161: * java.util.logging API.
162: */
163: private static class LoggerLogFactory implements LogFactory {
164: LoggerLogFactory() {
165: }
166:
167: /*
168: * Accessor to obtain an arbitrary RMI logger with name
169: * loggerName. If the level of the logger is greater than the
170: * level for the system property with name, the logger level
171: * will be set to the value of system property.
172: */
173: public Log createLog(final String loggerName,
174: String oldLogName, final Level level) {
175: Logger logger = Logger.getLogger(loggerName);
176: return new LoggerLog(logger, level);
177: }
178: }
179:
180: /**
181: * Class specialized to log messages to the java.util.logging API
182: */
183: private static class LoggerLog extends Log {
184:
185: /* alternate console handler for RMI loggers */
186: private static final Handler alternateConsole = (Handler) java.security.AccessController
187: .doPrivileged(new java.security.PrivilegedAction() {
188: public Object run() {
189: InternalStreamHandler alternate = new InternalStreamHandler(
190: System.err);
191: alternate.setLevel(Level.ALL);
192: return alternate;
193: }
194: });
195:
196: /** handler to which messages are copied */
197: private InternalStreamHandler copyHandler = null;
198:
199: /* logger to which log messages are written */
200: private final Logger logger;
201:
202: /* used as return value of RemoteServer.getLog */
203: private LoggerPrintStream loggerSandwich;
204:
205: /** creates a Log which will delegate to the given logger */
206: private LoggerLog(final Logger logger, final Level level) {
207: this .logger = logger;
208:
209: if (level != null) {
210: java.security.AccessController
211: .doPrivileged(new java.security.PrivilegedAction() {
212: public Object run() {
213: if (!logger.isLoggable(level)) {
214: logger.setLevel(level);
215: }
216: logger.addHandler(alternateConsole);
217: return null;
218: }
219: });
220: }
221: }
222:
223: public boolean isLoggable(Level level) {
224: return logger.isLoggable(level);
225: }
226:
227: public void log(Level level, String message) {
228: if (isLoggable(level)) {
229: String[] source = getSource();
230: logger.logp(level, source[0], source[1], Thread
231: .currentThread().getName()
232: + ": " + message);
233: }
234: }
235:
236: public void log(Level level, String message, Throwable thrown) {
237: if (isLoggable(level)) {
238: String[] source = getSource();
239: logger.logp(level, source[0], source[1], Thread
240: .currentThread().getName()
241: + ": " + message, thrown);
242: }
243: }
244:
245: /**
246: * Set the output stream associated with the RMI server call
247: * logger.
248: *
249: * Calling code needs LoggingPermission "control".
250: */
251: public synchronized void setOutputStream(OutputStream out) {
252: if (out != null) {
253: if (!logger.isLoggable(VERBOSE)) {
254: logger.setLevel(VERBOSE);
255: }
256: copyHandler = new InternalStreamHandler(out);
257: copyHandler.setLevel(Log.VERBOSE);
258: logger.addHandler(copyHandler);
259: } else {
260: /* ensure that messages are not logged */
261: if (copyHandler != null) {
262: logger.removeHandler(copyHandler);
263: }
264: copyHandler = null;
265: }
266: }
267:
268: public synchronized PrintStream getPrintStream() {
269: if (loggerSandwich == null) {
270: loggerSandwich = new LoggerPrintStream(logger);
271: }
272: return loggerSandwich;
273: }
274: }
275:
276: /**
277: * Subclass of StreamHandler for redirecting log output. flush
278: * must be called in the publish and close methods.
279: */
280: private static class InternalStreamHandler extends StreamHandler {
281: InternalStreamHandler(OutputStream out) {
282: super (out, new SimpleFormatter());
283: }
284:
285: public void publish(LogRecord record) {
286: super .publish(record);
287: flush();
288: }
289:
290: public void close() {
291: flush();
292: }
293: }
294:
295: /**
296: * PrintStream which forwards log messages to the logger. Class
297: * is needed to maintain backwards compatibility with
298: * RemoteServer.{set|get}Log().
299: */
300: private static class LoggerPrintStream extends PrintStream {
301:
302: /** logger where output of this log is sent */
303: private final Logger logger;
304:
305: /** record the last character written to this stream */
306: private int last = -1;
307:
308: /** stream used for buffering lines */
309: private final ByteArrayOutputStream bufOut;
310:
311: private LoggerPrintStream(Logger logger) {
312: super (new ByteArrayOutputStream());
313: bufOut = (ByteArrayOutputStream) super .out;
314: this .logger = logger;
315: }
316:
317: public void write(int b) {
318: if ((last == '\r') && (b == '\n')) {
319: last = -1;
320: return;
321: } else if ((b == '\n') || (b == '\r')) {
322: try {
323: /* write the converted bytes of the log message */
324: String message = Thread.currentThread().getName()
325: + ": " + bufOut.toString();
326: logger.logp(Level.INFO, "LogStream", "print",
327: message);
328: } finally {
329: bufOut.reset();
330: }
331: } else {
332: super .write(b);
333: }
334: last = b;
335: }
336:
337: public void write(byte b[], int off, int len) {
338: if (len < 0) {
339: throw new ArrayIndexOutOfBoundsException(len);
340: }
341: for (int i = 0; i < len; i++) {
342: write(b[off + i]);
343: }
344: }
345:
346: public String toString() {
347: return "RMI";
348: }
349: }
350:
351: /**
352: * Factory to create Log objects which deliver log messages to the
353: * java.rmi.server.LogStream API
354: */
355: private static class LogStreamLogFactory implements LogFactory {
356: LogStreamLogFactory() {
357: }
358:
359: /* create a new LogStreamLog for the specified log */
360: public Log createLog(String loggerName, String oldLogName,
361: Level level) {
362: LogStream stream = null;
363: if (oldLogName != null) {
364: stream = LogStream.log(oldLogName);
365: }
366: return new LogStreamLog(stream, level);
367: }
368: }
369:
370: /**
371: * Class specialized to log messages to the
372: * java.rmi.server.LogStream API
373: */
374: private static class LogStreamLog extends Log {
375: /** Log stream to which log messages are written */
376: private final LogStream stream;
377:
378: /** the level of the log as set by associated property */
379: private int levelValue = Level.OFF.intValue();
380:
381: private LogStreamLog(LogStream stream, Level level) {
382: if ((stream != null) && (level != null)) {
383: /* if the stream or level is null, dont log any
384: * messages
385: */
386: levelValue = level.intValue();
387: }
388: this .stream = stream;
389: }
390:
391: public synchronized boolean isLoggable(Level level) {
392: return (level.intValue() >= levelValue);
393: }
394:
395: public void log(Level messageLevel, String message) {
396: if (isLoggable(messageLevel)) {
397: String[] source = getSource();
398: stream.println(unqualifiedName(source[0]) + "."
399: + source[1] + ": " + message);
400: }
401: }
402:
403: public void log(Level level, String message, Throwable thrown) {
404: if (isLoggable(level)) {
405: /*
406: * keep output contiguous and maintain the contract of
407: * RemoteServer.getLog
408: */
409: synchronized (stream) {
410: String[] source = getSource();
411: stream.println(unqualifiedName(source[0]) + "."
412: + source[1] + ": " + message);
413: thrown.printStackTrace(stream);
414: }
415: }
416: }
417:
418: public PrintStream getPrintStream() {
419: return stream;
420: }
421:
422: public synchronized void setOutputStream(OutputStream out) {
423: if (out != null) {
424: if (VERBOSE.intValue() < levelValue) {
425: levelValue = VERBOSE.intValue();
426: }
427: stream.setOutputStream(out);
428: } else {
429: /* ensure that messages are not logged */
430: levelValue = Level.OFF.intValue();
431: }
432: }
433:
434: /*
435: * Mimic old log messages that only contain unqualified names.
436: */
437: private static String unqualifiedName(String name) {
438: int lastDot = name.lastIndexOf(".");
439: if (lastDot >= 0) {
440: name = name.substring(lastDot + 1);
441: }
442: name = name.replace('$', '.');
443: return name;
444: }
445: }
446:
447: /**
448: * Obtain class and method names of code calling a log method.
449: */
450: private static String[] getSource() {
451: StackTraceElement[] trace = (new Exception()).getStackTrace();
452: return new String[] { trace[3].getClassName(),
453: trace[3].getMethodName() };
454: }
455: }
|