001/* StreamHandler.java -- 002 A class for publishing log messages to instances of java.io.OutputStream 003 Copyright (C) 2002 Free Software Foundation, Inc. 004 005This file is part of GNU Classpath. 006 007GNU Classpath is free software; you can redistribute it and/or modify 008it under the terms of the GNU General Public License as published by 009the Free Software Foundation; either version 2, or (at your option) 010any later version. 011 012GNU Classpath is distributed in the hope that it will be useful, but 013WITHOUT ANY WARRANTY; without even the implied warranty of 014MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015General Public License for more details. 016 017You should have received a copy of the GNU General Public License 018along with GNU Classpath; see the file COPYING. If not, write to the 019Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02002110-1301 USA. 021 022Linking this library statically or dynamically with other modules is 023making a combined work based on this library. Thus, the terms and 024conditions of the GNU General Public License cover the whole 025combination. 026 027As a special exception, the copyright holders of this library give you 028permission to link this library with independent modules to produce an 029executable, regardless of the license terms of these independent 030modules, and to copy and distribute the resulting executable under 031terms of your choice, provided that you also meet, for each linked 032independent module, the terms and conditions of the license of that 033module. An independent module is a module which is not derived from 034or based on this library. If you modify this library, you may extend 035this exception to your version of the library, but you are not 036obligated to do so. If you do not wish to do so, delete this 037exception statement from your version. */ 038 039 040package java.util.logging; 041 042import java.io.OutputStream; 043import java.io.OutputStreamWriter; 044import java.io.UnsupportedEncodingException; 045import java.io.Writer; 046 047/** 048 * A <code>StreamHandler</code> publishes <code>LogRecords</code> to 049 * a instances of <code>java.io.OutputStream</code>. 050 * 051 * @author Sascha Brawer (brawer@acm.org) 052 */ 053public class StreamHandler 054 extends Handler 055{ 056 private OutputStream out; 057 private Writer writer; 058 059 060 /** 061 * Indicates the current state of this StreamHandler. The value 062 * should be one of STATE_FRESH, STATE_PUBLISHED, or STATE_CLOSED. 063 */ 064 private int streamState = STATE_FRESH; 065 066 067 /** 068 * streamState having this value indicates that the StreamHandler 069 * has been created, but the publish(LogRecord) method has not been 070 * called yet. If the StreamHandler has been constructed without an 071 * OutputStream, writer will be null, otherwise it is set to a 072 * freshly created OutputStreamWriter. 073 */ 074 private static final int STATE_FRESH = 0; 075 076 077 /** 078 * streamState having this value indicates that the publish(LocRecord) 079 * method has been called at least once. 080 */ 081 private static final int STATE_PUBLISHED = 1; 082 083 084 /** 085 * streamState having this value indicates that the close() method 086 * has been called. 087 */ 088 private static final int STATE_CLOSED = 2; 089 090 091 /** 092 * Creates a <code>StreamHandler</code> without an output stream. 093 * Subclasses can later use {@link 094 * #setOutputStream(java.io.OutputStream)} to associate an output 095 * stream with this StreamHandler. 096 */ 097 public StreamHandler() 098 { 099 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}