001: /*
002: * Copyright 1999-2004 The Apache Software Foundation
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.coyote.tomcat4;
018:
019: import java.io.IOException;
020: import java.io.Writer;
021: import java.util.Hashtable;
022:
023: import org.apache.catalina.connector.ClientAbortException;
024: import org.apache.coyote.ActionCode;
025: import org.apache.coyote.Response;
026: import org.apache.tomcat.util.buf.ByteChunk;
027: import org.apache.tomcat.util.buf.C2BConverter;
028: import org.apache.tomcat.util.buf.CharChunk;
029:
030: /**
031: * The buffer used by Tomcat response. This is a derivative of the Tomcat 3.3
032: * OutputBuffer, with the removal of some of the state handling (which in
033: * Coyote is mostly the Processor's responsability).
034: *
035: * @author Costin Manolache
036: * @author Remy Maucherat
037: */
038: public class OutputBuffer extends Writer implements
039: ByteChunk.ByteOutputChannel, CharChunk.CharOutputChannel {
040:
041: // -------------------------------------------------------------- Constants
042:
043: public static final String DEFAULT_ENCODING = org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING;
044: public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;
045: static final int debug = 0;
046:
047: // The buffer can be used for byte[] and char[] writing
048: // ( this is needed to support ServletOutputStream and for
049: // efficient implementations of templating systems )
050: public final int INITIAL_STATE = 0;
051: public final int CHAR_STATE = 1;
052: public final int BYTE_STATE = 2;
053:
054: // ----------------------------------------------------- Instance Variables
055:
056: /**
057: * The byte buffer.
058: */
059: private ByteChunk bb;
060:
061: /**
062: * The chunk buffer.
063: */
064: private CharChunk cb;
065:
066: /**
067: * State of the output buffer.
068: */
069: private int state = 0;
070:
071: /**
072: * Number of bytes written.
073: */
074: private int bytesWritten = 0;
075:
076: /**
077: * Number of chars written.
078: */
079: private int charsWritten = 0;
080:
081: /**
082: * Flag which indicates if the output buffer is closed.
083: */
084: private boolean closed = false;
085:
086: /**
087: * Do a flush on the next operation.
088: */
089: private boolean doFlush = false;
090:
091: /**
092: * Byte chunk used to output bytes.
093: */
094: private ByteChunk outputChunk = new ByteChunk();
095:
096: /**
097: * Encoding to use.
098: */
099: private String enc;
100:
101: /**
102: * Encoder is set.
103: */
104: private boolean gotEnc = false;
105:
106: /**
107: * List of encoders.
108: */
109: protected Hashtable encoders = new Hashtable();
110:
111: /**
112: * Current char to byte converter.
113: */
114: protected C2BConverter conv;
115:
116: /**
117: * Associated Coyote response.
118: */
119: private Response coyoteResponse;
120:
121: /**
122: * Suspended flag. All output bytes will be swallowed if this is true.
123: */
124: private boolean suspended = false;
125:
126: // ----------------------------------------------------------- Constructors
127:
128: /**
129: * Default constructor. Allocate the buffer with the default buffer size.
130: */
131: public OutputBuffer() {
132:
133: this (DEFAULT_BUFFER_SIZE);
134:
135: }
136:
137: /**
138: * Alternate constructor which allows specifying the initial buffer size.
139: *
140: * @param size Buffer size to use
141: */
142: public OutputBuffer(int size) {
143:
144: bb = new ByteChunk(size);
145: bb.setLimit(size);
146: bb.setByteOutputChannel(this );
147: cb = new CharChunk(size);
148: cb.setCharOutputChannel(this );
149: cb.setLimit(size);
150:
151: }
152:
153: // ------------------------------------------------------------- Properties
154:
155: /**
156: * Associated Coyote response.
157: *
158: * @param coyoteResponse Associated Coyote response
159: */
160: public void setResponse(Response coyoteResponse) {
161: this .coyoteResponse = coyoteResponse;
162: }
163:
164: /**
165: * Get associated Coyote response.
166: *
167: * @return the associated Coyote response
168: */
169: public Response getResponse() {
170: return this .coyoteResponse;
171: }
172:
173: /**
174: * Is the response output suspended ?
175: *
176: * @return suspended flag value
177: */
178: public boolean isSuspended() {
179: return this .suspended;
180: }
181:
182: /**
183: * Set the suspended flag.
184: *
185: * @param suspended New suspended flag value
186: */
187: public void setSuspended(boolean suspended) {
188: this .suspended = suspended;
189: }
190:
191: // --------------------------------------------------------- Public Methods
192:
193: /**
194: * Recycle the output buffer.
195: */
196: public void recycle() {
197:
198: if (debug > 0)
199: log("recycle()");
200:
201: state = INITIAL_STATE;
202: bytesWritten = 0;
203: charsWritten = 0;
204:
205: cb.recycle();
206: bb.recycle();
207: closed = false;
208: suspended = false;
209:
210: if (conv != null) {
211: conv.recycle();
212: }
213:
214: gotEnc = false;
215: enc = null;
216:
217: }
218:
219: /**
220: * Close the output buffer. This tries to calculate the response size if
221: * the response has not been committed yet.
222: *
223: * @throws IOException An underlying IOException occurred
224: */
225: public void close() throws IOException {
226:
227: if (closed)
228: return;
229: if (suspended)
230: return;
231:
232: if ((!coyoteResponse.isCommitted())
233: && (coyoteResponse.getContentLength() == -1)) {
234: // Flushing the char buffer
235: if (state == CHAR_STATE) {
236: cb.flushBuffer();
237: state = BYTE_STATE;
238: }
239: // If this didn't cause a commit of the response, the final content
240: // length can be calculated
241: if (!coyoteResponse.isCommitted()) {
242: coyoteResponse.setContentLength(bb.getLength());
243: }
244: }
245:
246: doFlush(false);
247: closed = true;
248:
249: coyoteResponse.finish();
250:
251: }
252:
253: /**
254: * Flush bytes or chars contained in the buffer.
255: *
256: * @throws IOException An underlying IOException occurred
257: */
258: public void flush() throws IOException {
259: doFlush(true);
260: }
261:
262: /**
263: * Flush bytes or chars contained in the buffer.
264: *
265: * @throws IOException An underlying IOException occurred
266: */
267: protected void doFlush(boolean realFlush) throws IOException {
268:
269: if (suspended)
270: return;
271:
272: doFlush = true;
273: if (state == CHAR_STATE) {
274: cb.flushBuffer();
275: bb.flushBuffer();
276: state = BYTE_STATE;
277: } else if (state == BYTE_STATE) {
278: bb.flushBuffer();
279: } else if (state == INITIAL_STATE)
280: realWriteBytes(null, 0, 0); // nothing written yet
281: doFlush = false;
282:
283: if (realFlush) {
284: coyoteResponse.action(ActionCode.ACTION_CLIENT_FLUSH,
285: coyoteResponse);
286: // If some exception occurred earlier, or if some IOE occurred
287: // here, notify the servlet with an IOE
288: if (coyoteResponse.isExceptionPresent()) {
289: throw new ClientAbortException(coyoteResponse
290: .getErrorException());
291: }
292: }
293:
294: }
295:
296: // ------------------------------------------------- Bytes Handling Methods
297:
298: /**
299: * Sends the buffer data to the client output, checking the
300: * state of Response and calling the right interceptors.
301: *
302: * @param buf Byte buffer to be written to the response
303: * @param off Offset
304: * @param cnt Length
305: *
306: * @throws IOException An underlying IOException occurred
307: */
308: public void realWriteBytes(byte buf[], int off, int cnt)
309: throws IOException {
310:
311: if (debug > 2)
312: log("realWrite(b, " + off + ", " + cnt + ") "
313: + coyoteResponse);
314:
315: if (closed)
316: return;
317: if (coyoteResponse == null)
318: return;
319:
320: // If we really have something to write
321: if (cnt > 0) {
322: // real write to the adapter
323: outputChunk.setBytes(buf, off, cnt);
324: try {
325: coyoteResponse.doWrite(outputChunk);
326: } catch (IOException e) {
327: // An IOException on a write is almost always due to
328: // the remote client aborting the request. Wrap this
329: // so that it can be handled better by the error dispatcher.
330: throw new ClientAbortException(e);
331: }
332: }
333:
334: }
335:
336: public void write(byte b[], int off, int len) throws IOException {
337:
338: if (suspended)
339: return;
340:
341: if (state == CHAR_STATE)
342: cb.flushBuffer();
343: state = BYTE_STATE;
344: writeBytes(b, off, len);
345:
346: }
347:
348: private void writeBytes(byte b[], int off, int len)
349: throws IOException {
350:
351: if (closed)
352: return;
353: if (debug > 0)
354: log("write(b,off,len)");
355:
356: bb.append(b, off, len);
357: bytesWritten += len;
358:
359: // if called from within flush(), then immediately flush
360: // remaining bytes
361: if (doFlush) {
362: bb.flushBuffer();
363: }
364:
365: }
366:
367: // XXX Char or byte ?
368: public void writeByte(int b) throws IOException {
369:
370: if (suspended)
371: return;
372:
373: if (state == CHAR_STATE)
374: cb.flushBuffer();
375: state = BYTE_STATE;
376:
377: if (debug > 0)
378: log("write(b)");
379:
380: bb.append((byte) b);
381: bytesWritten++;
382:
383: }
384:
385: // ------------------------------------------------- Chars Handling Methods
386:
387: public void write(int c) throws IOException {
388:
389: if (suspended)
390: return;
391:
392: state = CHAR_STATE;
393:
394: if (debug > 0)
395: log("writeChar(b)");
396:
397: cb.append((char) c);
398: charsWritten++;
399:
400: }
401:
402: public void write(char c[]) throws IOException {
403:
404: if (suspended)
405: return;
406:
407: write(c, 0, c.length);
408:
409: }
410:
411: public void write(char c[], int off, int len) throws IOException {
412:
413: if (suspended)
414: return;
415:
416: state = CHAR_STATE;
417:
418: if (debug > 0)
419: log("write(c,off,len)" + cb.getLength() + " "
420: + cb.getLimit());
421:
422: cb.append(c, off, len);
423: charsWritten += len;
424:
425: }
426:
427: public void write(StringBuffer sb) throws IOException {
428:
429: if (suspended)
430: return;
431:
432: state = CHAR_STATE;
433:
434: if (debug > 1)
435: log("write(s,off,len)");
436:
437: int len = sb.length();
438: charsWritten += len;
439: cb.append(sb);
440:
441: }
442:
443: /**
444: * Append a string to the buffer
445: */
446: public void write(String s, int off, int len) throws IOException {
447:
448: if (suspended)
449: return;
450:
451: state = CHAR_STATE;
452:
453: if (debug > 1)
454: log("write(s,off,len)");
455:
456: charsWritten += len;
457: if (s == null)
458: s = "null";
459: cb.append(s, off, len);
460:
461: }
462:
463: public void write(String s) throws IOException {
464:
465: if (suspended)
466: return;
467:
468: state = CHAR_STATE;
469: if (s == null)
470: s = "null";
471: write(s, 0, s.length());
472:
473: }
474:
475: public void flushChars() throws IOException {
476:
477: if (debug > 0)
478: log("flushChars() " + cb.getLength());
479:
480: cb.flushBuffer();
481: state = BYTE_STATE;
482:
483: }
484:
485: public boolean flushCharsNeeded() {
486: return state == CHAR_STATE;
487: }
488:
489: public void setEncoding(String s) {
490: enc = s;
491: }
492:
493: public void realWriteChars(char c[], int off, int len)
494: throws IOException {
495:
496: if (debug > 0)
497: log("realWrite(c,o,l) " + cb.getOffset() + " " + len);
498:
499: if (!gotEnc)
500: setConverter();
501:
502: if (debug > 0)
503: log("encoder: " + conv + " " + gotEnc);
504:
505: conv.convert(c, off, len);
506: conv.flushBuffer(); // ???
507:
508: }
509:
510: protected void setConverter() {
511:
512: if (coyoteResponse != null)
513: enc = coyoteResponse.getCharacterEncoding();
514:
515: if (debug > 0)
516: log("Got encoding: " + enc);
517:
518: gotEnc = true;
519: if (enc == null)
520: enc = DEFAULT_ENCODING;
521: conv = (C2BConverter) encoders.get(enc);
522: if (conv == null) {
523: try {
524: conv = new C2BConverter(bb, enc);
525: encoders.put(enc, conv);
526: } catch (IOException e) {
527: conv = (C2BConverter) encoders.get(DEFAULT_ENCODING);
528: if (conv == null) {
529: try {
530: conv = new C2BConverter(bb, DEFAULT_ENCODING);
531: encoders.put(DEFAULT_ENCODING, conv);
532: } catch (IOException ex) {
533: // Ignore
534: }
535: }
536: }
537: }
538: }
539:
540: // -------------------- BufferedOutputStream compatibility
541:
542: /**
543: * Real write - this buffer will be sent to the client
544: */
545: public void flushBytes() throws IOException {
546:
547: if (debug > 0)
548: log("flushBytes() " + bb.getLength());
549: bb.flushBuffer();
550:
551: }
552:
553: public int getBytesWritten() {
554: return bytesWritten;
555: }
556:
557: public int getCharsWritten() {
558: return charsWritten;
559: }
560:
561: public int getContentWritten() {
562: return bytesWritten + charsWritten;
563: }
564:
565: /**
566: * True if this buffer hasn't been used ( since recycle() ) -
567: * i.e. no chars or bytes have been added to the buffer.
568: */
569: public boolean isNew() {
570: return (bytesWritten == 0) && (charsWritten == 0);
571: }
572:
573: public void setBufferSize(int size) {
574: if (size > bb.getLimit()) {// ??????
575: bb.setLimit(size);
576: }
577: }
578:
579: public void reset() {
580:
581: //count=0;
582: bb.recycle();
583: bytesWritten = 0;
584: cb.recycle();
585: charsWritten = 0;
586: gotEnc = false;
587: enc = null;
588:
589: }
590:
591: public int getBufferSize() {
592: return bb.getLimit();
593: }
594:
595: protected void log(String s) {
596: System.out.println("OutputBuffer: " + s);
597: }
598:
599: }
|