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