Frames | No Frames |
1: /* StreamHandler.java -- 2: A class for publishing log messages to instances of java.io.OutputStream 3: Copyright (C) 2002 Free Software Foundation, Inc. 4: 5: This file is part of GNU Classpath. 6: 7: GNU Classpath is free software; you can redistribute it and/or modify 8: it under the terms of the GNU General Public License as published by 9: the Free Software Foundation; either version 2, or (at your option) 10: any later version. 11: 12: GNU Classpath is distributed in the hope that it will be useful, but 13: WITHOUT ANY WARRANTY; without even the implied warranty of 14: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15: General Public License for more details. 16: 17: You should have received a copy of the GNU General Public License 18: along with GNU Classpath; see the file COPYING. If not, write to the 19: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 20: 02110-1301 USA. 21: 22: Linking this library statically or dynamically with other modules is 23: making a combined work based on this library. Thus, the terms and 24: conditions of the GNU General Public License cover the whole 25: combination. 26: 27: As a special exception, the copyright holders of this library give you 28: permission to link this library with independent modules to produce an 29: executable, regardless of the license terms of these independent 30: modules, and to copy and distribute the resulting executable under 31: terms of your choice, provided that you also meet, for each linked 32: independent module, the terms and conditions of the license of that 33: module. An independent module is a module which is not derived from 34: or based on this library. If you modify this library, you may extend 35: this exception to your version of the library, but you are not 36: obligated to do so. If you do not wish to do so, delete this 37: exception statement from your version. */ 38: 39: 40: package java.util.logging; 41: 42: import java.io.OutputStream; 43: import java.io.OutputStreamWriter; 44: import java.io.UnsupportedEncodingException; 45: import java.io.Writer; 46: 47: /** 48: * A <code>StreamHandler</code> publishes <code>LogRecords</code> to 49: * a instances of <code>java.io.OutputStream</code>. 50: * 51: * @author Sascha Brawer (brawer@acm.org) 52: */ 53: public class StreamHandler 54: extends Handler 55: { 56: private OutputStream out; 57: private Writer writer; 58: 59: 60: /** 61: * Indicates the current state of this StreamHandler. The value 62: * should be one of STATE_FRESH, STATE_PUBLISHED, or STATE_CLOSED. 63: */ 64: private int streamState = STATE_FRESH; 65: 66: 67: /** 68: * streamState having this value indicates that the StreamHandler 69: * has been created, but the publish(LogRecord) method has not been 70: * called yet. If the StreamHandler has been constructed without an 71: * OutputStream, writer will be null, otherwise it is set to a 72: * freshly created OutputStreamWriter. 73: */ 74: private static final int STATE_FRESH = 0; 75: 76: 77: /** 78: * streamState having this value indicates that the publish(LocRecord) 79: * method has been called at least once. 80: */ 81: private static final int STATE_PUBLISHED = 1; 82: 83: 84: /** 85: * streamState having this value indicates that the close() method 86: * has been called. 87: */ 88: private static final int STATE_CLOSED = 2; 89: 90: 91: /** 92: * Creates a <code>StreamHandler</code> without an output stream. 93: * Subclasses can later use {@link 94: * #setOutputStream(java.io.OutputStream)} to associate an output 95: * stream with this StreamHandler. 96: */ 97: public StreamHandler() 98: { 99: this(null, null); 100: } 101: 102: 103: /** 104: * Creates a <code>StreamHandler</code> that formats log messages 105: * with the specified Formatter and publishes them to the specified 106: * output stream. 107: * 108: * @param out the output stream to which the formatted log messages 109: * are published. 110: * 111: * @param formatter the <code>Formatter</code> that will be used 112: * to format log messages. 113: */ 114: public StreamHandler(OutputStream out, Formatter formatter) 115: { 116: this(out, "java.util.logging.StreamHandler", Level.INFO, 117: formatter, SimpleFormatter.class); 118: } 119: 120: 121: StreamHandler( 122: OutputStream out, 123: String propertyPrefix, 124: Level defaultLevel, 125: Formatter formatter, Class defaultFormatterClass) 126: { 127: this.level = LogManager.getLevelProperty(propertyPrefix + ".level", 128: defaultLevel); 129: 130: this.filter = (Filter) LogManager.getInstanceProperty( 131: propertyPrefix + ".filter", 132: /* must be instance of */ Filter.class, 133: /* default: new instance of */ null); 134: 135: if (formatter != null) 136: this.formatter = formatter; 137: else 138: this.formatter = (Formatter) LogManager.getInstanceProperty( 139: propertyPrefix + ".formatter", 140: /* must be instance of */ Formatter.class, 141: /* default: new instance of */ defaultFormatterClass); 142: 143: try 144: { 145: String enc = LogManager.getLogManager().getProperty(propertyPrefix 146: + ".encoding"); 147: 148: /* make sure enc actually is a valid encoding */ 149: if ((enc != null) && (enc.length() > 0)) 150: new String(new byte[0], enc); 151: 152: this.encoding = enc; 153: } 154: catch (Exception _) 155: { 156: } 157: 158: if (out != null) 159: { 160: try 161: { 162: changeWriter(out, getEncoding()); 163: } 164: catch (UnsupportedEncodingException uex) 165: { 166: /* This should never happen, since the validity of the encoding 167: * name has been checked above. 168: */ 169: throw new RuntimeException(uex.getMessage()); 170: } 171: } 172: } 173: 174: 175: private void checkOpen() 176: { 177: if (streamState == STATE_CLOSED) 178: throw new IllegalStateException(this.toString() + " has been closed"); 179: } 180: 181: private void checkFresh() 182: { 183: checkOpen(); 184: if (streamState != STATE_FRESH) 185: throw new IllegalStateException("some log records have been published to " + this); 186: } 187: 188: 189: private void changeWriter(OutputStream out, String encoding) 190: throws UnsupportedEncodingException 191: { 192: OutputStreamWriter writer; 193: 194: /* The logging API says that a null encoding means the default 195: * platform encoding. However, java.io.OutputStreamWriter needs 196: * another constructor for the default platform encoding, 197: * passing null would throw an exception. 198: */ 199: if (encoding == null) 200: writer = new OutputStreamWriter(out); 201: else 202: writer = new OutputStreamWriter(out, encoding); 203: 204: /* Closing the stream has side effects -- do this only after 205: * creating a new writer has been successful. 206: */ 207: if ((streamState != STATE_FRESH) || (this.writer != null)) 208: close(); 209: 210: this.writer = writer; 211: this.out = out; 212: this.encoding = encoding; 213: streamState = STATE_FRESH; 214: } 215: 216: 217: /** 218: * Sets the character encoding which this handler uses for publishing 219: * log records. The encoding of a <code>StreamHandler</code> must be 220: * set before any log records have been published. 221: * 222: * @param encoding the name of a character encoding, or <code>null</code> 223: * for the default encoding. 224: * 225: * @throws SecurityException if a security manager exists and 226: * the caller is not granted the permission to control the 227: * the logging infrastructure. 228: * 229: * @exception IllegalStateException if any log records have been 230: * published to this <code>StreamHandler</code> before. Please 231: * be aware that this is a pecularity of the GNU implementation. 232: * While the API specification indicates that it is an error 233: * if the encoding is set after records have been published, 234: * it does not mandate any specific behavior for that case. 235: */ 236: public void setEncoding(String encoding) 237: throws SecurityException, UnsupportedEncodingException 238: { 239: /* The inherited implementation first checks whether the invoking 240: * code indeed has the permission to control the logging infra- 241: * structure, and throws a SecurityException if this was not the 242: * case. 243: * 244: * Next, it verifies that the encoding is supported and throws 245: * an UnsupportedEncodingExcpetion otherwise. Finally, it remembers 246: * the name of the encoding. 247: */ 248: super.setEncoding(encoding); 249: 250: checkFresh(); 251: 252: /* If out is null, setEncoding is being called before an output 253: * stream has been set. In that case, we need to check that the 254: * encoding is valid, and remember it if this is the case. Since 255: * this is exactly what the inherited implementation of 256: * Handler.setEncoding does, we can delegate. 257: */ 258: if (out != null) 259: { 260: /* The logging API says that a null encoding means the default 261: * platform encoding. However, java.io.OutputStreamWriter needs 262: * another constructor for the default platform encoding, passing 263: * null would throw an exception. 264: */ 265: if (encoding == null) 266: writer = new OutputStreamWriter(out); 267: else 268: writer = new OutputStreamWriter(out, encoding); 269: } 270: } 271: 272: 273: /** 274: * Changes the output stream to which this handler publishes 275: * logging records. 276: * 277: * @throws SecurityException if a security manager exists and 278: * the caller is not granted the permission to control 279: * the logging infrastructure. 280: * 281: * @throws NullPointerException if <code>out</code> 282: * is <code>null</code>. 283: */ 284: protected void setOutputStream(OutputStream out) 285: throws SecurityException 286: { 287: LogManager.getLogManager().checkAccess(); 288: 289: /* Throw a NullPointerException if out is null. */ 290: out.getClass(); 291: 292: try 293: { 294: changeWriter(out, getEncoding()); 295: } 296: catch (UnsupportedEncodingException ex) 297: { 298: /* This seems quite unlikely to happen, unless the underlying 299: * implementation of java.io.OutputStreamWriter changes its 300: * mind (at runtime) about the set of supported character 301: * encodings. 302: */ 303: throw new RuntimeException(ex.getMessage()); 304: } 305: } 306: 307: 308: /** 309: * Publishes a <code>LogRecord</code> to the associated output 310: * stream, provided the record passes all tests for being loggable. 311: * The <code>StreamHandler</code> will localize the message of the 312: * log record and substitute any message parameters. 313: * 314: * <p>Most applications do not need to call this method directly. 315: * Instead, they will use use a {@link Logger}, which will create 316: * LogRecords and distribute them to registered handlers. 317: * 318: * <p>In case of an I/O failure, the <code>ErrorManager</code> 319: * of this <code>Handler</code> will be informed, but the caller 320: * of this method will not receive an exception. 321: * 322: * <p>If a log record is being published to a 323: * <code>StreamHandler</code> that has been closed earlier, the Sun 324: * J2SE 1.4 reference can be observed to silently ignore the 325: * call. The GNU implementation, however, intentionally behaves 326: * differently by informing the <code>ErrorManager</code> associated 327: * with this <code>StreamHandler</code>. Since the condition 328: * indicates a programming error, the programmer should be 329: * informed. It also seems extremely unlikely that any application 330: * would depend on the exact behavior in this rather obscure, 331: * erroneous case -- especially since the API specification does not 332: * prescribe what is supposed to happen. 333: * 334: * @param record the log event to be published. 335: */ 336: public void publish(LogRecord record) 337: { 338: String formattedMessage; 339: 340: if (!isLoggable(record)) 341: return; 342: 343: if (streamState == STATE_FRESH) 344: { 345: try 346: { 347: writer.write(formatter.getHead(this)); 348: } 349: catch (java.io.IOException ex) 350: { 351: reportError(null, ex, ErrorManager.WRITE_FAILURE); 352: return; 353: } 354: catch (Exception ex) 355: { 356: reportError(null, ex, ErrorManager.GENERIC_FAILURE); 357: return; 358: } 359: 360: streamState = STATE_PUBLISHED; 361: } 362: 363: try 364: { 365: formattedMessage = formatter.format(record); 366: } 367: catch (Exception ex) 368: { 369: reportError(null, ex, ErrorManager.FORMAT_FAILURE); 370: return; 371: } 372: 373: try 374: { 375: writer.write(formattedMessage); 376: } 377: catch (Exception ex) 378: { 379: reportError(null, ex, ErrorManager.WRITE_FAILURE); 380: } 381: } 382: 383: 384: /** 385: * Checks whether or not a <code>LogRecord</code> would be logged 386: * if it was passed to this <code>StreamHandler</code> for publication. 387: * 388: * <p>The <code>StreamHandler</code> implementation first checks 389: * whether a writer is present and the handler's level is greater 390: * than or equal to the severity level threshold. In a second step, 391: * if a {@link Filter} has been installed, its {@link 392: * Filter#isLoggable(LogRecord) isLoggable} method is 393: * invoked. Subclasses of <code>StreamHandler</code> can override 394: * this method to impose their own constraints. 395: * 396: * @param record the <code>LogRecord</code> to be checked. 397: * 398: * @return <code>true</code> if <code>record</code> would 399: * be published by {@link #publish(LogRecord) publish}, 400: * <code>false</code> if it would be discarded. 401: * 402: * @see #setLevel(Level) 403: * @see #setFilter(Filter) 404: * @see Filter#isLoggable(LogRecord) 405: * 406: * @throws NullPointerException if <code>record</code> is 407: * <code>null</code>. */ 408: public boolean isLoggable(LogRecord record) 409: { 410: return (writer != null) && super.isLoggable(record); 411: } 412: 413: 414: /** 415: * Forces any data that may have been buffered to the underlying 416: * output device. 417: * 418: * <p>In case of an I/O failure, the <code>ErrorManager</code> 419: * of this <code>Handler</code> will be informed, but the caller 420: * of this method will not receive an exception. 421: * 422: * <p>If a <code>StreamHandler</code> that has been closed earlier 423: * is closed a second time, the Sun J2SE 1.4 reference can be 424: * observed to silently ignore the call. The GNU implementation, 425: * however, intentionally behaves differently by informing the 426: * <code>ErrorManager</code> associated with this 427: * <code>StreamHandler</code>. Since the condition indicates a 428: * programming error, the programmer should be informed. It also 429: * seems extremely unlikely that any application would depend on the 430: * exact behavior in this rather obscure, erroneous case -- 431: * especially since the API specification does not prescribe what is 432: * supposed to happen. 433: */ 434: public void flush() 435: { 436: try 437: { 438: checkOpen(); 439: if (writer != null) 440: writer.flush(); 441: } 442: catch (Exception ex) 443: { 444: reportError(null, ex, ErrorManager.FLUSH_FAILURE); 445: } 446: } 447: 448: 449: /** 450: * Closes this <code>StreamHandler</code> after having forced any 451: * data that may have been buffered to the underlying output 452: * device. 453: * 454: * <p>As soon as <code>close</code> has been called, 455: * a <code>Handler</code> should not be used anymore. Attempts 456: * to publish log records, to flush buffers, or to modify the 457: * <code>Handler</code> in any other way may throw runtime 458: * exceptions after calling <code>close</code>.</p> 459: * 460: * <p>In case of an I/O failure, the <code>ErrorManager</code> 461: * of this <code>Handler</code> will be informed, but the caller 462: * of this method will not receive an exception.</p> 463: * 464: * <p>If a <code>StreamHandler</code> that has been closed earlier 465: * is closed a second time, the Sun J2SE 1.4 reference can be 466: * observed to silently ignore the call. The GNU implementation, 467: * however, intentionally behaves differently by informing the 468: * <code>ErrorManager</code> associated with this 469: * <code>StreamHandler</code>. Since the condition indicates a 470: * programming error, the programmer should be informed. It also 471: * seems extremely unlikely that any application would depend on the 472: * exact behavior in this rather obscure, erroneous case -- 473: * especially since the API specification does not prescribe what is 474: * supposed to happen. 475: * 476: * @throws SecurityException if a security manager exists and 477: * the caller is not granted the permission to control 478: * the logging infrastructure. 479: */ 480: public void close() 481: throws SecurityException 482: { 483: LogManager.getLogManager().checkAccess(); 484: 485: try 486: { 487: /* Although flush also calls checkOpen, it catches 488: * any exceptions and reports them to the ErrorManager 489: * as flush failures. However, we want to report 490: * a closed stream as a close failure, not as a 491: * flush failure here. Therefore, we call checkOpen() 492: * before flush(). 493: */ 494: checkOpen(); 495: flush(); 496: 497: if (writer != null) 498: { 499: if (formatter != null) 500: { 501: /* Even if the StreamHandler has never published a record, 502: * it emits head and tail upon closing. An earlier version 503: * of the GNU Classpath implementation did not emitted 504: * anything. However, this had caused XML log files to be 505: * entirely empty instead of containing no log records. 506: */ 507: if (streamState == STATE_FRESH) 508: writer.write(formatter.getHead(this)); 509: if (streamState != STATE_CLOSED) 510: writer.write(formatter.getTail(this)); 511: } 512: streamState = STATE_CLOSED; 513: writer.close(); 514: } 515: } 516: catch (Exception ex) 517: { 518: reportError(null, ex, ErrorManager.CLOSE_FAILURE); 519: } 520: } 521: }