KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mortbay > http > HttpOutputStream


1 // ========================================================================
2
// $Id: HttpOutputStream.java,v 1.28 2006/10/08 14:13:05 gregwilkins Exp $
3
// Copyright 199-2004 Mort Bay Consulting Pty. Ltd.
4
// ------------------------------------------------------------------------
5
// Licensed under the Apache License, Version 2.0 (the "License");
6
// you may not use this file except in compliance with the License.
7
// You may obtain a copy of the License at
8
// http://www.apache.org/licenses/LICENSE-2.0
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
// ========================================================================
15

16 package org.mortbay.http;
17
18 import java.io.IOException JavaDoc;
19 import java.io.InputStream JavaDoc;
20 import java.io.OutputStream JavaDoc;
21 import java.io.OutputStreamWriter JavaDoc;
22 import java.io.Writer JavaDoc;
23 import java.util.ArrayList JavaDoc;
24
25 import org.apache.commons.logging.Log;
26 import org.mortbay.log.LogFactory;
27 import org.mortbay.util.ByteArrayPool;
28 import org.mortbay.util.IO;
29 import org.mortbay.util.LogSupport;
30 import org.mortbay.util.OutputObserver;
31 import org.mortbay.util.StringUtil;
32
33
34 /* ---------------------------------------------------------------- */
35 /** HTTP Http OutputStream.
36  * Acts as a BufferedOutputStream until setChunking() is called.
37  * Once chunking is enabled, the raw stream is chunk encoded as per RFC2616.
38  *
39  * Implements the following HTTP and Servlet features: <UL>
40  * <LI>Filters for content and transfer encodings.
41  * <LI>Allows output to be reset if not committed (buffer never flushed).
42  * <LI>Notification of significant output events for filter triggering,
43  * header flushing, etc.
44  * </UL>
45  *
46  * This class is not synchronized and should be synchronized
47  * explicitly if an instance is used by multiple threads.
48  *
49  * @version $Id: HttpOutputStream.java,v 1.28 2006/10/08 14:13:05 gregwilkins Exp $
50  * @author Greg Wilkins
51 */

52 public class HttpOutputStream extends OutputStream JavaDoc
53     implements OutputObserver, HttpMessage.HeaderWriter
54 {
55     private static Log log = LogFactory.getLog(HttpOutputStream.class);
56
57     /* ------------------------------------------------------------ */
58     final static int __BUFFER_SIZE=4096;
59     final static int __FIRST_RESERVE=512;
60     
61     public final static Class JavaDoc[] __filterArg = {java.io.OutputStream JavaDoc.class};
62     
63     /* ------------------------------------------------------------ */
64     private OutputStream JavaDoc _out;
65     private OutputStream JavaDoc _realOut;
66     private BufferedOutputStream _bufferedOut;
67     private boolean _written;
68     private ArrayList JavaDoc _observers;
69     private int _bufferSize;
70     private int _headerReserve;
71     private HttpWriter _iso8859writer;
72     private HttpWriter _utf8writer;
73     private HttpWriter _asciiwriter;
74     private boolean _nulled;
75     private boolean _closing=false;
76     private int _contentLength=-1;
77     private int _bytes;
78     private boolean _disableFlush;
79     
80     /* ------------------------------------------------------------ */
81     /** Constructor.
82      * @param outputStream The outputStream to buffer or chunk to.
83      */

84     public HttpOutputStream(OutputStream JavaDoc outputStream)
85     {
86         this (outputStream,__BUFFER_SIZE,__FIRST_RESERVE);
87     }
88     
89     /* ------------------------------------------------------------ */
90     /** Constructor.
91      * @param outputStream The outputStream to buffer or chunk to.
92      */

93     public HttpOutputStream(OutputStream JavaDoc outputStream, int bufferSize)
94     {
95         this (outputStream,bufferSize,__FIRST_RESERVE);
96     }
97     
98     /* ------------------------------------------------------------ */
99     /** Constructor.
100      * @param outputStream The outputStream to buffer or chunk to.
101      */

102     public HttpOutputStream(OutputStream JavaDoc outputStream,
103                             int bufferSize,
104                             int headerReserve)
105     {
106         _written=false;
107         _bufferSize=bufferSize;
108         _headerReserve=headerReserve;
109         _realOut=outputStream;
110         _out=_realOut;
111     }
112
113     /* ------------------------------------------------------------ */
114     public void setContentLength(int length)
115     {
116         if (length>=0 && length<_bytes)
117             throw new IllegalStateException JavaDoc();
118         _contentLength=length;
119     }
120     
121     /* ------------------------------------------------------------ */
122     public void setBufferedOutputStream(BufferedOutputStream bos)
123     {
124         _bufferedOut=bos;
125         _bufferedOut.setCommitObserver(this);
126         if (_out!=null && _out!=_realOut)
127             _out=_bufferedOut;
128     }
129     
130     /* ------------------------------------------------------------ */
131     /** Get the backing output stream.
132      * A stream without filters or chunking is returned.
133      * @return Raw OutputStream.
134      */

135     public OutputStream JavaDoc getOutputStream()
136     {
137         return _realOut;
138     }
139     
140     /* ------------------------------------------------------------ */
141     /** Get the buffered output stream.
142      */

143     public OutputStream JavaDoc getBufferedOutputStream()
144     {
145         return _out;
146     }
147     
148     /* ------------------------------------------------------------ */
149     /** Has any data been written to the stream.
150      * @return True if write has been called.
151      */

152     public boolean isWritten()
153     {
154         return _written;
155     }
156         
157     /* ------------------------------------------------------------ */
158     /** Get the output buffer capacity.
159      * @return Buffer capacity in bytes.
160      */

161     public int getBufferSize()
162     {
163         return _bufferSize;
164     }
165     
166     /* ------------------------------------------------------------ */
167     /** Set the output buffer size.
168      * Note that this is the minimal buffer size and that installed
169      * filters may perform their own buffering and are likely to change
170      * the size of the output. Also the pre and post reserve buffers may be
171      * allocated within the buffer for headers and chunking.
172      * @param size Minimum buffer size in bytes
173      * @exception IllegalStateException If output has been written.
174      */

175     public void setBufferSize(int size)
176         throws IllegalStateException JavaDoc
177     {
178         if (size<=_bufferSize)
179             return;
180         
181         if (_bufferedOut!=null && _bufferedOut.size()>0)
182             throw new IllegalStateException JavaDoc("Not Reset");
183
184         try
185         {
186             _bufferSize=size;
187             if (_bufferedOut!=null)
188             {
189                 boolean fixed=_bufferedOut.isFixed();
190                 _bufferedOut.setFixed(false);
191                 _bufferedOut.ensureSize(size);
192                 _bufferedOut.setFixed(fixed);
193             }
194             
195         }
196         catch (IOException JavaDoc e){log.warn(LogSupport.EXCEPTION,e);}
197     }
198
199     /* ------------------------------------------------------------ */
200     public int getBytesWritten()
201     {
202         return _bytes;
203     }
204     
205     /* ------------------------------------------------------------ */
206     /** Reset Buffered output.
207      * If no data has been committed, the buffer output is discarded and
208      * the filters may be reinitialized.
209      * @exception IllegalStateException
210      */

211     public void resetBuffer()
212         throws IllegalStateException JavaDoc
213     {
214         // Shutdown filters without observation
215

216         if (_out!=null && _out!=_realOut)
217         {
218             ArrayList JavaDoc save_observers=_observers;
219             _observers=null;
220             _nulled=true;
221             try
222             {
223                 // discard current buffer and set it to output
224
if (_bufferedOut!=null)
225                 {
226                     _bufferedOut.resetStream();
227                     if (_bufferedOut instanceof ChunkingOutputStream)
228                         ((ChunkingOutputStream)_bufferedOut).setChunking(false);
229                 }
230             }
231             catch(Exception JavaDoc e)
232             {
233                 LogSupport.ignore(log,e);
234             }
235             finally
236             {
237                 _observers=save_observers;
238             }
239         }
240         _contentLength=-1;
241         _nulled=false;
242         _bytes=0;
243         _written=false;
244         _out=_realOut;
245         try
246         {
247             notify(OutputObserver.__RESET_BUFFER);
248         }
249         catch(IOException JavaDoc e)
250         {
251             LogSupport.ignore(log,e);
252         }
253     }
254
255     /* ------------------------------------------------------------ */
256     /** Add an Output Observer.
257      * Output Observers get notified of significant events on the
258      * output stream. Observers are called in the reverse order they
259      * were added.
260      * They are removed when the stream is closed.
261      * @param observer The observer.
262      */

263     public void addObserver(OutputObserver observer)
264     {
265         if (_observers==null)
266             _observers=new ArrayList JavaDoc(4);
267         _observers.add(observer);
268         _observers.add(null);
269     }
270     
271     /* ------------------------------------------------------------ */
272     /** Add an Output Observer.
273      * Output Observers get notified of significant events on the
274      * output stream. Observers are called in the reverse order they
275      * were added.
276      * They are removed when the stream is closed.
277      * @param observer The observer.
278      * @param data Data to be passed wit notify calls.
279      */

280     public void addObserver(OutputObserver observer, Object JavaDoc data)
281     {
282         if (_observers==null)
283             _observers=new ArrayList JavaDoc(4);
284         _observers.add(observer);
285         _observers.add(data);
286     }
287     
288     /* ------------------------------------------------------------ */
289     /** Reset the observers.
290      */

291     public void resetObservers()
292     {
293         _observers=null;
294     }
295     
296     /* ------------------------------------------------------------ */
297     /** Null the output.
298      * All output written is discarded until the stream is reset. Used
299      * for HEAD requests.
300      */

301     public void nullOutput()
302         throws IOException JavaDoc
303     {
304         _nulled=true;
305     }
306     
307     /* ------------------------------------------------------------ */
308     /** is the output Nulled?
309      */

310     public boolean isNullOutput()
311         throws IOException JavaDoc
312     {
313         return _nulled;
314     }
315     
316     /* ------------------------------------------------------------ */
317     /** Set chunking mode.
318      */

319     public void setChunking()
320     {
321         checkOutput();
322         if (_bufferedOut instanceof ChunkingOutputStream)
323             ((ChunkingOutputStream)_bufferedOut).setChunking(true);
324         else
325             throw new IllegalStateException JavaDoc(_bufferedOut.getClass().toString());
326     }
327     
328     /* ------------------------------------------------------------ */
329     /** Get chunking mode
330      */

331     public boolean isChunking()
332     {
333         return (_bufferedOut instanceof ChunkingOutputStream) &&
334             ((ChunkingOutputStream)_bufferedOut).isChunking();
335     }
336     
337     /* ------------------------------------------------------------ */
338     /** Reset the stream.
339      * Turn disable all filters.
340      * @exception IllegalStateException The stream cannot be
341      * reset if chunking is enabled.
342      */

343     public void resetStream()
344         throws IOException JavaDoc, IllegalStateException JavaDoc
345     {
346         if (isChunking())
347             close();
348         
349         _out=null;
350         _nulled=true;
351         if (_bufferedOut!=null)
352         {
353             _bufferedOut.resetStream();
354             if (_bufferedOut instanceof ChunkingOutputStream)
355                 ((ChunkingOutputStream)_bufferedOut).setChunking(false);
356         }
357         if (_iso8859writer!=null)
358             _iso8859writer.flush();
359         if (_utf8writer!=null)
360             _utf8writer.flush();
361         if (_asciiwriter!=null)
362             _asciiwriter.flush();
363
364         _bytes=0;
365         _written=false;
366         _out=_realOut;
367         _closing=false;
368         _contentLength=-1;
369         _nulled=false;
370         
371         if (_observers!=null)
372             _observers.clear();
373     }
374
375     /* ------------------------------------------------------------ */
376     public void destroy()
377     {
378         if (_bufferedOut!=null)
379             _bufferedOut.destroy();
380         _bufferedOut=null;
381         if (_iso8859writer!=null)
382             _iso8859writer.destroy();
383         _iso8859writer=null;
384         if (_utf8writer!=null)
385             _utf8writer.destroy();
386         _utf8writer=null;
387         if (_asciiwriter!=null)
388             _asciiwriter.destroy();
389         _asciiwriter=null;
390     }
391     
392     
393     /* ------------------------------------------------------------ */
394     public void writeHeader(HttpMessage httpMessage)
395         throws IOException JavaDoc
396     {
397         checkOutput();
398         _bufferedOut.writeHeader(httpMessage);
399     }
400     
401     /* ------------------------------------------------------------ */
402     public void write(int b) throws IOException JavaDoc
403     {
404         prepareOutput(1);
405         if (!_nulled)
406             _out.write(b);
407         if (_bytes==_contentLength)
408             flush();
409     }
410
411     /* ------------------------------------------------------------ */
412     public void write(byte b[]) throws IOException JavaDoc
413     {
414         write(b,0,b.length);
415     }
416
417     /* ------------------------------------------------------------ */
418     public void write(byte b[], int off, int len)
419         throws IOException JavaDoc
420     {
421         len=prepareOutput(len);
422         if (!_nulled)
423             _out.write(b,off,len);
424         if (_bytes==_contentLength)
425             flush();
426     }
427
428     /* ------------------------------------------------------------ */
429     protected void checkOutput()
430     {
431         if (_out==_realOut)
432         {
433             if (_bufferedOut==null)
434             {
435                 _bufferedOut=new ChunkingOutputStream(_realOut,
436                                                       _bufferSize,
437                                                       _headerReserve,
438                                                       false);
439                 _bufferedOut.setCommitObserver(this);
440                 _bufferedOut.setBypassBuffer(true);
441                 _bufferedOut.setFixed(true);
442             }
443             _out=_bufferedOut;
444         }
445     }
446     
447     /* ------------------------------------------------------------ */
448     protected int prepareOutput(int length)
449         throws IOException JavaDoc
450     {
451         if (_out==null)
452             throw new IOException JavaDoc("closed");
453         checkOutput();
454         if (!_written)
455         {
456             _written=true;
457             notify(OutputObserver.__FIRST_WRITE);
458         }
459         
460         if (_contentLength>=0)
461         {
462             if (_bytes+length>=_contentLength)
463             {
464                 length=_contentLength-_bytes;
465                 if (length==0)
466                     _nulled=true;
467             }
468         }
469         _bytes+=length;
470         return length;
471     }
472     
473     /* ------------------------------------------------------------ */
474     public void flush()
475         throws IOException JavaDoc
476     {
477        if (!_disableFlush && _out!=null && !_closing)
478           _out.flush();
479     }
480     
481     /* ------------------------------------------------------------ */
482     /** Close the stream.
483      * @exception IOException
484      */

485     public boolean isClosed()
486         throws IOException JavaDoc
487     {
488         return _out==null;
489     }
490     
491     /* ------------------------------------------------------------ */
492     /** Close the stream.
493      * @exception IOException
494      */

495     public void close()
496         throws IOException JavaDoc
497     {
498         // Are we already closed?
499
if (_out==null)
500             return;
501         _closing=true;
502         // Close
503
try {
504             notify(OutputObserver.__CLOSING);
505
506             OutputStream JavaDoc out =_out;
507             _out=null;
508             
509             if (out!=_bufferedOut)
510                 out.close();
511             else
512                 _bufferedOut.close();
513             
514             notify(OutputObserver.__CLOSED);
515         }
516         catch (IOException JavaDoc e)
517         {
518             LogSupport.ignore(log,e);
519         }
520     }
521
522     /* ------------------------------------------------------------ */
523     /** Output Notification.
524      * Called by the internal Buffered Output and the event is passed on to
525      * this streams observers.
526      */

527     public void outputNotify(OutputStream JavaDoc out, int action, Object JavaDoc ignoredData)
528         throws IOException JavaDoc
529     {
530         notify(action);
531     }
532
533     /* ------------------------------------------------------------ */
534     /* Notify observers of action.
535      * @see OutputObserver
536      * @param action the action.
537      */

538     private void notify(int action)
539         throws IOException JavaDoc
540     {
541         if (_observers!=null)
542         {
543             for (int i=_observers.size();i-->0;)
544             {
545                 Object JavaDoc data=_observers.get(i--);
546                 ((OutputObserver)_observers.get(i)).outputNotify(this,action,data);
547             }
548         }
549     }
550
551     /* ------------------------------------------------------------ */
552     public void write(InputStream JavaDoc in, int len)
553         throws IOException JavaDoc
554     {
555         IO.copy(in,this,len);
556     }
557
558     /* ------------------------------------------------------------ */
559     private Writer JavaDoc getISO8859Writer()
560         throws IOException JavaDoc
561     {
562         if (_iso8859writer==null)
563             _iso8859writer=new HttpWriter(StringUtil.__ISO_8859_1,
564                                           getBufferSize());
565         return _iso8859writer;
566     }
567     
568     /* ------------------------------------------------------------ */
569     private Writer JavaDoc getUTF8Writer()
570         throws IOException JavaDoc
571     {
572         if (_utf8writer==null)
573             _utf8writer=new HttpWriter("UTF-8",getBufferSize());
574         return _utf8writer;
575     }
576     
577     /* ------------------------------------------------------------ */
578     private Writer JavaDoc getASCIIWriter()
579         throws IOException JavaDoc
580     {
581         if (_asciiwriter==null)
582             _asciiwriter=new HttpWriter("US-ASCII",getBufferSize());
583         return _asciiwriter;
584     }
585     
586     /* ------------------------------------------------------------ */
587     public Writer JavaDoc getWriter(String JavaDoc encoding)
588         throws IOException JavaDoc
589     {
590         if (encoding==null ||
591             StringUtil.__ISO_8859_1.equalsIgnoreCase(encoding) ||
592             "ISO8859_1".equalsIgnoreCase(encoding))
593             return getISO8859Writer();
594
595         if ("UTF-8".equalsIgnoreCase(encoding) ||
596             "UTF8".equalsIgnoreCase(encoding))
597             return getUTF8Writer();
598         
599         if ("US-ASCII".equalsIgnoreCase(encoding))
600             return getASCIIWriter();
601
602         return new OutputStreamWriter JavaDoc(this,encoding);
603     }
604     
605     /* ------------------------------------------------------------ */
606     public String JavaDoc toString()
607     {
608         return super.toString() +
609             "\nout="+_out+
610             "\nrealOut="+_realOut+
611             "\nbufferedOut="+_bufferedOut;
612     }
613     
614     /* ------------------------------------------------------------ */
615     /* ------------------------------------------------------------ */
616     private class HttpWriter extends Writer JavaDoc
617     {
618         private OutputStreamWriter JavaDoc _writer=null;
619         private boolean _writting=false;
620         private byte[] _buf;
621         private String JavaDoc _encoding;
622         
623         /* -------------------------------------------------------- */
624         HttpWriter(String JavaDoc encoding,int bufferSize)
625         {
626             _buf = ByteArrayPool.getByteArray(bufferSize);
627             _encoding=encoding;
628         }
629         
630         /* -------------------------------------------------------- */
631         public Object JavaDoc getLock()
632         {
633             return lock;
634         }
635         
636         /* -------------------------------------------------------- */
637         public void write(char c)
638             throws IOException JavaDoc
639         {
640             HttpOutputStream.this.prepareOutput(1);
641             if (!_nulled)
642             {
643                 if (_writting)
644                     _writer.write(c);
645                 else if (c>=0&&c<=0x7f)
646                     HttpOutputStream.this.write((int)c);
647                 else
648                 {
649                     char[] ca ={c};
650                     writeEncoded(ca,0,1);
651                 }
652                 
653                 if (_bytes==_contentLength)
654                     flush();
655             }
656         }
657     
658         /* ------------------------------------------------------------ */
659         public void write(char[] ca)
660             throws IOException JavaDoc
661         {
662             this.write(ca,0,ca.length);
663         }
664         
665         /* ------------------------------------------------------------ */
666         public void write(char[] ca,int offset, int len)
667             throws IOException JavaDoc
668         {
669             if (_writting)
670                 _writer.write(ca,offset,len);
671             else
672             {
673                 int s=0;
674                 for (int i=0;i<len;i++)
675                 {
676                     char c=ca[offset+i];
677                     if (c>=0&&c<=0x7f)
678                     {
679                         _buf[s++]=(byte)c;
680                         if (s==_buf.length)
681                         {
682                             s=HttpOutputStream.this.prepareOutput(s);
683                             if (!_nulled)
684                                 HttpOutputStream.this._out.write(_buf,0,s);
685                             s=0;
686                         }
687                     }
688                     else
689                     {
690                         if (s>0)
691                         {
692                             s=HttpOutputStream.this.prepareOutput(s);
693                             if (!_nulled)
694                                 HttpOutputStream.this._out.write(_buf,0,s);
695                             s=0;
696                         }
697                         writeEncoded(ca,offset+i,len-i);
698                         break;
699                     }
700                 }
701                 
702                 if (s>0)
703                 {
704                     s=HttpOutputStream.this.prepareOutput(s);
705                     if (!_nulled)
706                         HttpOutputStream.this._out.write(_buf,0,s);
707                     s=0;
708                 }
709             }
710
711             if (!_nulled && _bytes==_contentLength)
712                 flush();
713         }
714     
715         /* ------------------------------------------------------------ */
716         public void write(String JavaDoc s)
717             throws IOException JavaDoc
718         {
719             this.write(s,0,s.length());
720         }
721     
722         /* ------------------------------------------------------------ */
723         public void write(String JavaDoc str,int offset, int len)
724             throws IOException JavaDoc
725         {
726             if (_writting)
727                 _writer.write(str,offset,len);
728             else
729             {
730                 int s=0;
731                 for (int i=0;i<len;i++)
732                 {
733                     char c=str.charAt(offset+i);
734                     if (c>=0&&c<=0x7f)
735                     {
736                         _buf[s++]=(byte)c;
737                         if (s==_buf.length)
738                         {
739                             s=HttpOutputStream.this.prepareOutput(s);
740                             if (!_nulled)
741                                 HttpOutputStream.this._out.write(_buf,0,s);
742                             s=0;
743                         }
744                     }
745                     else
746                     {
747                         if (s>0)
748                         {
749                             s=HttpOutputStream.this.prepareOutput(s);
750                             if (!_nulled)
751                                 HttpOutputStream.this._out.write(_buf,0,s);
752                             s=0;
753                         }
754                         char[] chars = str.toCharArray();
755                         writeEncoded(chars,offset+i,len-i);
756                         break;
757                     }
758                 }
759                 if (s>0)
760                 {
761                     s=HttpOutputStream.this.prepareOutput(s);
762                     if (!_nulled)
763                         HttpOutputStream.this._out.write(_buf,0,s);
764                     s=0;
765                 }
766             }
767
768             if (_bytes==_contentLength)
769                 flush();
770         }
771
772         /* ------------------------------------------------------------ */
773         private void writeEncoded(char[] ca,int offset, int length)
774             throws IOException JavaDoc
775         {
776             _writting=true;
777             if (_writer==null)
778                 _writer = new OutputStreamWriter JavaDoc(HttpOutputStream.this,_encoding);
779       
780             try
781             {
782                 HttpOutputStream.this._disableFlush=true;
783                 _writer.write(ca,offset,length);
784                 if (HttpOutputStream.this._contentLength>=0)
785                     _writer.flush();
786             }
787             finally
788             {
789                 HttpOutputStream.this._disableFlush=false;
790             }
791         }
792         
793         /* ------------------------------------------------------------ */
794         public void flush()
795             throws IOException JavaDoc
796         {
797             if (_writting)
798                 _writer.flush();
799             else
800                 HttpOutputStream.this.flush();
801             _writting=false;
802         }
803         
804         /* ------------------------------------------------------------ */
805         public void close()
806             throws IOException JavaDoc
807         {
808             _closing=true;
809             if (_writting)
810                 _writer.flush();
811             HttpOutputStream.this.close();
812             _writting=false;
813         }
814         
815         /* ------------------------------------------------------------ */
816         public void destroy()
817         {
818             ByteArrayPool.returnByteArray(_buf);
819             _buf=null;
820             _writer=null;
821             _encoding=null;
822         }
823     }
824     /**
825      * @return Returns the disableFlush.
826      */

827 }
828
Popular Tags