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