1 /*
2 * Copyright 1999-2004 The Apache Software Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.apache.coyote.tomcat4;
18
19 import java.io.IOException;
20 import java.io.Writer;
21 import java.util.Hashtable;
22
23 import org.apache.catalina.connector.ClientAbortException;
24 import org.apache.coyote.ActionCode;
25 import org.apache.coyote.Response;
26 import org.apache.tomcat.util.buf.ByteChunk;
27 import org.apache.tomcat.util.buf.C2BConverter;
28 import org.apache.tomcat.util.buf.CharChunk;
29
30
31 /**
32 * The buffer used by Tomcat response. This is a derivative of the Tomcat 3.3
33 * OutputBuffer, with the removal of some of the state handling (which in
34 * Coyote is mostly the Processor's responsability).
35 *
36 * @author Costin Manolache
37 * @author Remy Maucherat
38 */
39 public class OutputBuffer extends Writer
40 implements ByteChunk.ByteOutputChannel, CharChunk.CharOutputChannel {
41
42
43 private static org.apache.commons.logging.Log log=
44 org.apache.commons.logging.LogFactory.getLog( OutputBuffer.class );
45
46 // -------------------------------------------------------------- Constants
47
48
49 public static final String DEFAULT_ENCODING =
50 org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING;
51 public static final int DEFAULT_BUFFER_SIZE = 8*1024;
52 static final int debug = 0;
53
54
55 // The buffer can be used for byte[] and char[] writing
56 // ( this is needed to support ServletOutputStream and for
57 // efficient implementations of templating systems )
58 public final int INITIAL_STATE = 0;
59 public final int CHAR_STATE = 1;
60 public final int BYTE_STATE = 2;
61
62
63 // ----------------------------------------------------- Instance Variables
64
65
66 /**
67 * The byte buffer.
68 */
69 private ByteChunk bb;
70
71
72 /**
73 * The chunk buffer.
74 */
75 private CharChunk cb;
76
77
78 /**
79 * State of the output buffer.
80 */
81 private int state = 0;
82
83
84 /**
85 * Number of bytes written.
86 */
87 private int bytesWritten = 0;
88
89
90 /**
91 * Number of chars written.
92 */
93 private int charsWritten = 0;
94
95
96 /**
97 * Flag which indicates if the output buffer is closed.
98 */
99 private boolean closed = false;
100
101
102 /**
103 * Do a flush on the next operation.
104 */
105 private boolean doFlush = false;
106
107
108 /**
109 * Byte chunk used to output bytes.
110 */
111 private ByteChunk outputChunk = new ByteChunk();
112
113
114 /**
115 * Encoding to use.
116 */
117 private String enc;
118
119
120 /**
121 * Encoder is set.
122 */
123 private boolean gotEnc = false;
124
125
126 /**
127 * List of encoders.
128 */
129 protected Hashtable encoders = new Hashtable();
130
131
132 /**
133 * Current char to byte converter.
134 */
135 protected C2BConverter conv;
136
137
138 /**
139 * Associated Coyote response.
140 */
141 private Response coyoteResponse;
142
143
144 /**
145 * Suspended flag. All output bytes will be swallowed if this is true.
146 */
147 private boolean suspended = false;
148
149
150 // ----------------------------------------------------------- Constructors
151
152
153 /**
154 * Default constructor. Allocate the buffer with the default buffer size.
155 */
156 public OutputBuffer() {
157
158 this(DEFAULT_BUFFER_SIZE);
159
160 }
161
162
163 /**
164 * Alternate constructor which allows specifying the initial buffer size.
165 *
166 * @param size Buffer size to use
167 */
168 public OutputBuffer(int size) {
169
170 bb = new ByteChunk(size);
171 bb.setLimit(size);
172 bb.setByteOutputChannel(this);
173 cb = new CharChunk(size);
174 cb.setCharOutputChannel(this);
175 cb.setLimit(size);
176
177 }
178
179
180 // ------------------------------------------------------------- Properties
181
182
183 /**
184 * Associated Coyote response.
185 *
186 * @param coyoteResponse Associated Coyote response
187 */
188 public void setResponse(Response coyoteResponse) {
189 this.coyoteResponse = coyoteResponse;
190 }
191
192
193 /**
194 * Get associated Coyote response.
195 *
196 * @return the associated Coyote response
197 */
198 public Response getResponse() {
199 return this.coyoteResponse;
200 }
201
202
203 /**
204 * Is the response output suspended ?
205 *
206 * @return suspended flag value
207 */
208 public boolean isSuspended() {
209 return this.suspended;
210 }
211
212
213 /**
214 * Set the suspended flag.
215 *
216 * @param suspended New suspended flag value
217 */
218 public void setSuspended(boolean suspended) {
219 this.suspended = suspended;
220 }
221
222
223 // --------------------------------------------------------- Public Methods
224
225
226 /**
227 * Recycle the output buffer.
228 */
229 public void recycle() {
230
231 if (debug > 0)
232 log("recycle()");
233
234 state = INITIAL_STATE;
235 bytesWritten = 0;
236 charsWritten = 0;
237
238 cb.recycle();
239 bb.recycle();
240 closed = false;
241 suspended = false;
242
243 if (conv!= null) {
244 conv.recycle();
245 }
246
247 gotEnc = false;
248 enc = null;
249
250 }
251
252
253 /**
254 * Close the output buffer. This tries to calculate the response size if
255 * the response has not been committed yet.
256 *
257 * @throws IOException An underlying IOException occurred
258 */
259 public void close()
260 throws IOException {
261
262 if (closed)
263 return;
264 if (suspended)
265 return;
266
267 if ((!coyoteResponse.isCommitted())
268 && (coyoteResponse.getContentLengthLong() == -1)) {
269 // Flushing the char buffer
270 if (state == CHAR_STATE) {
271 cb.flushBuffer();
272 state = BYTE_STATE;
273 }
274 // If this didn't cause a commit of the response, the final content
275 // length can be calculated
276 if (!coyoteResponse.isCommitted()) {
277 coyoteResponse.setContentLength(bb.getLength());
278 }
279 }
280
281 doFlush(false);
282 closed = true;
283
284 coyoteResponse.finish();
285
286 }
287
288
289 /**
290 * Flush bytes or chars contained in the buffer.
291 *
292 * @throws IOException An underlying IOException occurred
293 */
294 public void flush()
295 throws IOException {
296 doFlush(true);
297 }
298
299
300 /**
301 * Flush bytes or chars contained in the buffer.
302 *
303 * @throws IOException An underlying IOException occurred
304 */
305 protected void doFlush(boolean realFlush)
306 throws IOException {
307
308 if (suspended)
309 return;
310
311 doFlush = true;
312 if (state == CHAR_STATE) {
313 cb.flushBuffer();
314 bb.flushBuffer();
315 state = BYTE_STATE;
316 } else if (state == BYTE_STATE) {
317 bb.flushBuffer();
318 } else if (state == INITIAL_STATE)
319 realWriteBytes(null, 0, 0); // nothing written yet
320 doFlush = false;
321
322 if (realFlush) {
323 coyoteResponse.action(ActionCode.ACTION_CLIENT_FLUSH,
324 coyoteResponse);
325 // If some exception occurred earlier, or if some IOE occurred
326 // here, notify the servlet with an IOE
327 if (coyoteResponse.isExceptionPresent()) {
328 throw new ClientAbortException
329 (coyoteResponse.getErrorException());
330 }
331 }
332
333 }
334
335
336 // ------------------------------------------------- Bytes Handling Methods
337
338
339 /**
340 * Sends the buffer data to the client output, checking the
341 * state of Response and calling the right interceptors.
342 *
343 * @param buf Byte buffer to be written to the response
344 * @param off Offset
345 * @param cnt Length
346 *
347 * @throws IOException An underlying IOException occurred
348 */
349 public void realWriteBytes(byte buf[], int off, int cnt)
350 throws IOException {
351
352 if (debug > 2)
353 log("realWrite(b, " + off + ", " + cnt + ") " + coyoteResponse);
354
355 if (closed)
356 return;
357 if (coyoteResponse == null)
358 return;
359
360 // If we really have something to write
361 if (cnt > 0) {
362 // real write to the adapter
363 outputChunk.setBytes(buf, off, cnt);
364 try {
365 coyoteResponse.doWrite(outputChunk);
366 } catch (IOException e) {
367 // An IOException on a write is almost always due to
368 // the remote client aborting the request. Wrap this
369 // so that it can be handled better by the error dispatcher.
370 throw new ClientAbortException(e);
371 }
372 }
373
374 }
375
376
377 public void write(byte b[], int off, int len) throws IOException {
378
379 if (suspended)
380 return;
381
382 if (state == CHAR_STATE)
383 cb.flushBuffer();
384 state = BYTE_STATE;
385 writeBytes(b, off, len);
386
387 }
388
389
390 private void writeBytes(byte b[], int off, int len)
391 throws IOException {
392
393 if (closed)
394 return;
395 if (debug > 0)
396 log("write(b,off,len)");
397
398 bb.append(b, off, len);
399 bytesWritten += len;
400
401 // if called from within flush(), then immediately flush
402 // remaining bytes
403 if (doFlush) {
404 bb.flushBuffer();
405 }
406
407 }
408
409
410 // XXX Char or byte ?
411 public void writeByte(int b)
412 throws IOException {
413
414 if (suspended)
415 return;
416
417 if (state == CHAR_STATE)
418 cb.flushBuffer();
419 state = BYTE_STATE;
420
421 if (debug > 0)
422 log("write(b)");
423
424 bb.append( (byte)b );
425 bytesWritten++;
426
427 }
428
429
430 // ------------------------------------------------- Chars Handling Methods
431
432
433 public void write(int c)
434 throws IOException {
435
436 if (suspended)
437 return;
438
439 state = CHAR_STATE;
440
441 if (debug > 0)
442 log("writeChar(b)");
443
444 cb.append((char) c);
445 charsWritten++;
446
447 }
448
449
450 public void write(char c[])
451 throws IOException {
452
453 if (suspended)
454 return;
455
456 write(c, 0, c.length);
457
458 }
459
460
461 public void write(char c[], int off, int len)
462 throws IOException {
463
464 if (suspended)
465 return;
466
467 state = CHAR_STATE;
468
469 if (debug > 0)
470 log("write(c,off,len)" + cb.getLength() + " " + cb.getLimit());
471
472 cb.append(c, off, len);
473 charsWritten += len;
474
475 }
476
477
478 public void write(StringBuffer sb)
479 throws IOException {
480
481 if (suspended)
482 return;
483
484 state = CHAR_STATE;
485
486 if (debug > 1)
487 log("write(s,off,len)");
488
489 int len = sb.length();
490 charsWritten += len;
491 cb.append(sb);
492
493 }
494
495
496 /**
497 * Append a string to the buffer
498 */
499 public void write(String s, int off, int len)
500 throws IOException {
501
502 if (suspended)
503 return;
504
505 state=CHAR_STATE;
506
507 if (debug > 1)
508 log("write(s,off,len)");
509
510 charsWritten += len;
511 if (s==null)
512 s="null";
513 cb.append( s, off, len );
514
515 }
516
517
518 public void write(String s)
519 throws IOException {
520
521 if (suspended)
522 return;
523
524 state = CHAR_STATE;
525 if (s==null)
526 s="null";
527 write(s, 0, s.length());
528
529 }
530
531
532 public void flushChars()
533 throws IOException {
534
535 if (debug > 0)
536 log("flushChars() " + cb.getLength());
537
538 cb.flushBuffer();
539 state = BYTE_STATE;
540
541 }
542
543
544 public boolean flushCharsNeeded() {
545 return state == CHAR_STATE;
546 }
547
548
549 public void setEncoding(String s) {
550 enc = s;
551 }
552
553
554 public void realWriteChars(char c[], int off, int len)
555 throws IOException {
556
557 if (debug > 0)
558 log("realWrite(c,o,l) " + cb.getOffset() + " " + len);
559
560 if (!gotEnc)
561 setConverter();
562
563 if (debug > 0)
564 log("encoder: " + conv + " " + gotEnc);
565
566 conv.convert(c, off, len);
567 conv.flushBuffer(); // ???
568
569 }
570
571
572 protected void setConverter() {
573
574 if (coyoteResponse != null)
575 enc = coyoteResponse.getCharacterEncoding();
576
577 if (debug > 0)
578 log("Got encoding: " + enc);
579
580 gotEnc = true;
581 if (enc == null)
582 enc = DEFAULT_ENCODING;
583 conv = (C2BConverter) encoders.get(enc);
584 if (conv == null) {
585 try {
586 conv = new C2BConverter(bb, enc);
587 encoders.put(enc, conv);
588 } catch (IOException e) {
589 conv = (C2BConverter) encoders.get(DEFAULT_ENCODING);
590 if (conv == null) {
591 try {
592 conv = new C2BConverter(bb, DEFAULT_ENCODING);
593 encoders.put(DEFAULT_ENCODING, conv);
594 } catch (IOException ex) {
595 // Ignore
596 }
597 }
598 }
599 }
600 }
601
602
603 // -------------------- BufferedOutputStream compatibility
604
605
606 /**
607 * Real write - this buffer will be sent to the client
608 */
609 public void flushBytes()
610 throws IOException {
611
612 if (debug > 0)
613 log("flushBytes() " + bb.getLength());
614 bb.flushBuffer();
615
616 }
617
618
619 public int getBytesWritten() {
620 return bytesWritten;
621 }
622
623
624 public int getCharsWritten() {
625 return charsWritten;
626 }
627
628
629 public int getContentWritten() {
630 return bytesWritten + charsWritten;
631 }
632
633
634 /**
635 * True if this buffer hasn't been used ( since recycle() ) -
636 * i.e. no chars or bytes have been added to the buffer.
637 */
638 public boolean isNew() {
639 return (bytesWritten == 0) && (charsWritten == 0);
640 }
641
642
643 public void setBufferSize(int size) {
644 if (size > bb.getLimit()) {// ??????
645 bb.setLimit(size);
646 }
647 }
648
649
650 public void reset() {
651
652 //count=0;
653 bb.recycle();
654 bytesWritten = 0;
655 cb.recycle();
656 charsWritten = 0;
657 gotEnc = false;
658 enc = null;
659
660 }
661
662
663 public int getBufferSize() {
664 return bb.getLimit();
665 }
666
667
668
669 protected void log( String s ) {
670 if (log.isDebugEnabled())
671 log.debug("OutputBuffer: " + s);
672 }
673
674
675 }