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    
005    This file is part of GNU Classpath.
006    
007    GNU Classpath is free software; you can redistribute it and/or modify
008    it under the terms of the GNU General Public License as published by
009    the Free Software Foundation; either version 2, or (at your option)
010    any later version.
011    
012    GNU Classpath is distributed in the hope that it will be useful, but
013    WITHOUT ANY WARRANTY; without even the implied warranty of
014    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015    General Public License for more details.
016    
017    You should have received a copy of the GNU General Public License
018    along with GNU Classpath; see the file COPYING.  If not, write to the
019    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
020    02110-1301 USA.
021    
022    Linking this library statically or dynamically with other modules is
023    making a combined work based on this library.  Thus, the terms and
024    conditions of the GNU General Public License cover the whole
025    combination.
026    
027    As a special exception, the copyright holders of this library give you
028    permission to link this library with independent modules to produce an
029    executable, regardless of the license terms of these independent
030    modules, and to copy and distribute the resulting executable under
031    terms of your choice, provided that you also meet, for each linked
032    independent module, the terms and conditions of the license of that
033    module.  An independent module is a module which is not derived from
034    or based on this library.  If you modify this library, you may extend
035    this exception to your version of the library, but you are not
036    obligated to do so.  If you do not wish to do so, delete this
037    exception statement from your version. */
038    
039    
040    package java.util.logging;
041    
042    import java.io.OutputStream;
043    import java.io.OutputStreamWriter;
044    import java.io.UnsupportedEncodingException;
045    import 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     */
053    public 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    }