001: /*
002: * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/connector/ResponseBase.java,v 1.25 2002/03/18 07:15:39 remm Exp $
003: * $Revision: 1.25 $
004: * $Date: 2002/03/18 07:15:39 $
005: *
006: * ====================================================================
007: *
008: * The Apache Software License, Version 1.1
009: *
010: * Copyright (c) 1999 The Apache Software Foundation. All rights
011: * reserved.
012: *
013: * Redistribution and use in source and binary forms, with or without
014: * modification, are permitted provided that the following conditions
015: * are met:
016: *
017: * 1. Redistributions of source code must retain the above copyright
018: * notice, this list of conditions and the following disclaimer.
019: *
020: * 2. Redistributions in binary form must reproduce the above copyright
021: * notice, this list of conditions and the following disclaimer in
022: * the documentation and/or other materials provided with the
023: * distribution.
024: *
025: * 3. The end-user documentation included with the redistribution, if
026: * any, must include the following acknowlegement:
027: * "This product includes software developed by the
028: * Apache Software Foundation (http://www.apache.org/)."
029: * Alternately, this acknowlegement may appear in the software itself,
030: * if and wherever such third-party acknowlegements normally appear.
031: *
032: * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
033: * Foundation" must not be used to endorse or promote products derived
034: * from this software without prior written permission. For written
035: * permission, please contact apache@apache.org.
036: *
037: * 5. Products derived from this software may not be called "Apache"
038: * nor may "Apache" appear in their names without prior written
039: * permission of the Apache Group.
040: *
041: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
042: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
043: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
044: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
045: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
046: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
047: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
048: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
049: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
050: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
051: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
052: * SUCH DAMAGE.
053: * ====================================================================
054: *
055: * This software consists of voluntary contributions made by many
056: * individuals on behalf of the Apache Software Foundation. For more
057: * information on the Apache Software Foundation, please see
058: * <http://www.apache.org/>.
059: *
060: * [Additional notices, if required by prior licensing conditions]
061: *
062: */
063:
064: package org.apache.catalina.connector;
065:
066: import java.io.IOException;
067: import java.io.OutputStream;
068: import java.io.OutputStreamWriter;
069: import java.io.PrintWriter;
070: import java.util.Locale;
071: import javax.servlet.ServletException;
072: import javax.servlet.ServletOutputStream;
073: import javax.servlet.ServletResponse;
074: import org.apache.catalina.Connector;
075: import org.apache.catalina.Context;
076: import org.apache.catalina.Request;
077: import org.apache.catalina.Response;
078: import org.apache.catalina.util.CharsetMapper;
079: import org.apache.catalina.util.RequestUtil;
080: import org.apache.catalina.util.StringManager;
081:
082: /**
083: * Convenience base implementation of the <b>Response</b> interface, which can
084: * be used for the Response implementation required by most Connectors. Only
085: * the connector-specific methods need to be implemented.
086: *
087: * @author Craig R. McClanahan
088: * @author Remy Maucherat
089: * @version $Revision: 1.25 $ $Date: 2002/03/18 07:15:39 $
090: * @deprecated
091: */
092:
093: public abstract class ResponseBase implements Response, ServletResponse {
094:
095: // ----------------------------------------------------- Instance Variables
096:
097: /**
098: * Has this response been committed by the application yet?
099: */
100: protected boolean appCommitted = false;
101:
102: /**
103: * The buffer through which all of our output bytes are passed.
104: */
105: protected byte[] buffer = new byte[1024];
106:
107: /**
108: * The number of data bytes currently in the buffer.
109: */
110: protected int bufferCount = 0;
111:
112: /**
113: * Has this response been committed yet?
114: */
115: protected boolean committed = false;
116:
117: /**
118: * The Connector through which this Response is returned.
119: */
120: protected Connector connector = null;
121:
122: /**
123: * The actual number of bytes written to this Response.
124: */
125: protected int contentCount = 0;
126:
127: /**
128: * The content length associated with this Response.
129: */
130: protected int contentLength = -1;
131:
132: /**
133: * The content type associated with this Response.
134: */
135: protected String contentType = null;
136:
137: /**
138: * The Context within which this Response is being produced.
139: */
140: protected Context context = null;
141:
142: /**
143: * The character encoding associated with this Response.
144: */
145: protected String encoding = null;
146:
147: /**
148: * The facade associated with this response.
149: */
150: protected ResponseFacade facade = new ResponseFacade(this );
151:
152: /**
153: * Are we currently processing inside a RequestDispatcher.include()?
154: */
155: protected boolean included = false;
156:
157: /**
158: * Descriptive information about this Response implementation.
159: */
160: protected static final String info = "org.apache.catalina.connector.ResponseBase/1.0";
161:
162: /**
163: * The Locale associated with this Response.
164: */
165: protected Locale locale = Locale.getDefault();
166:
167: /**
168: * The output stream associated with this Response.
169: */
170: protected OutputStream output = null;
171:
172: /**
173: * The Request with which this Response is associated.
174: */
175: protected Request request = null;
176:
177: /**
178: * The string manager for this package.
179: */
180: protected static StringManager sm = StringManager
181: .getManager(Constants.Package);
182:
183: /**
184: * The ServletOutputStream that has been returned by
185: * <code>getOutputStream()</code>, if any.
186: */
187: protected ServletOutputStream stream = null;
188:
189: /**
190: * Has this response output been suspended?
191: */
192: protected boolean suspended = false;
193:
194: /**
195: * The PrintWriter that has been returned by
196: * <code>getWriter()</code>, if any.
197: */
198: protected PrintWriter writer = null;
199:
200: /**
201: * Error flag. True if the response is an error report.
202: */
203: protected boolean error = false;
204:
205: // ------------------------------------------------------------- Properties
206:
207: /**
208: * Set the application commit flag.
209: */
210: public void setAppCommitted(boolean appCommitted) {
211:
212: this .appCommitted = appCommitted;
213:
214: }
215:
216: /**
217: * Application commit flag accessor.
218: */
219: public boolean isAppCommitted() {
220:
221: return (this .appCommitted || this .committed);
222:
223: }
224:
225: /**
226: * Return the Connector through which this Response will be transmitted.
227: */
228: public Connector getConnector() {
229:
230: return (this .connector);
231:
232: }
233:
234: /**
235: * Set the Connector through which this Response will be transmitted.
236: *
237: * @param connector The new connector
238: */
239: public void setConnector(Connector connector) {
240:
241: this .connector = connector;
242:
243: }
244:
245: /**
246: * Return the number of bytes actually written to the output stream.
247: */
248: public int getContentCount() {
249:
250: return (this .contentCount);
251:
252: }
253:
254: /**
255: * Return the Context with which this Response is associated.
256: */
257: public Context getContext() {
258:
259: return (this .context);
260:
261: }
262:
263: /**
264: * Set the Context with which this Response is associated. This should
265: * be called as soon as the appropriate Context is identified.
266: *
267: * @param context The associated Context
268: */
269: public void setContext(Context context) {
270:
271: this .context = context;
272:
273: }
274:
275: /**
276: * Return the "processing inside an include" flag.
277: */
278: public boolean getIncluded() {
279:
280: return (this .included);
281:
282: }
283:
284: /**
285: * Set the "processing inside an include" flag.
286: *
287: * @param included <code>true</code> if we are currently inside a
288: * RequestDispatcher.include(), else <code>false</code>
289: */
290: public void setIncluded(boolean included) {
291:
292: this .included = included;
293:
294: }
295:
296: /**
297: * Return descriptive information about this Response implementation and
298: * the corresponding version number, in the format
299: * <code><description>/<version></code>.
300: */
301: public String getInfo() {
302:
303: return (this .info);
304:
305: }
306:
307: /**
308: * Return the Request with which this Response is associated.
309: */
310: public Request getRequest() {
311:
312: return (this .request);
313:
314: }
315:
316: /**
317: * Set the Request with which this Response is associated.
318: *
319: * @param request The new associated request
320: */
321: public void setRequest(Request request) {
322:
323: this .request = request;
324:
325: }
326:
327: /**
328: * Return the <code>ServletResponse</code> for which this object
329: * is the facade.
330: */
331: public ServletResponse getResponse() {
332:
333: return (facade);
334:
335: }
336:
337: /**
338: * Return the output stream associated with this Response.
339: */
340: public OutputStream getStream() {
341:
342: return (this .output);
343:
344: }
345:
346: /**
347: * Set the output stream associated with this Response.
348: *
349: * @param stream The new output stream
350: */
351: public void setStream(OutputStream stream) {
352:
353: this .output = stream;
354:
355: }
356:
357: /**
358: * Set the suspended flag.
359: */
360: public void setSuspended(boolean suspended) {
361:
362: this .suspended = suspended;
363: if (stream != null)
364: ((ResponseStream) stream).setSuspended(suspended);
365:
366: }
367:
368: /**
369: * Suspended flag accessor.
370: */
371: public boolean isSuspended() {
372:
373: return (this .suspended);
374:
375: }
376:
377: /**
378: * Set the error flag.
379: */
380: public void setError() {
381:
382: this .error = true;
383:
384: }
385:
386: /**
387: * Error flag accessor.
388: */
389: public boolean isError() {
390:
391: return (this .error);
392:
393: }
394:
395: // --------------------------------------------------------- Public Methods
396:
397: /**
398: * Create and return a ServletOutputStream to write the content
399: * associated with this Response.
400: *
401: * @exception IOException if an input/output error occurs
402: */
403: public ServletOutputStream createOutputStream() throws IOException {
404:
405: return (new ResponseStream(this ));
406:
407: }
408:
409: /**
410: * Perform whatever actions are required to flush and close the output
411: * stream or writer, in a single operation.
412: *
413: * @exception IOException if an input/output error occurs
414: */
415: public void finishResponse() throws IOException {
416:
417: // If no stream has been requested yet, get one so we can
418: // flush the necessary headers
419: if (this .stream == null) {
420: ServletOutputStream sos = getOutputStream();
421: sos.flush();
422: sos.close();
423: return;
424: }
425:
426: // If our stream is closed, no action is necessary
427: if (((ResponseStream) stream).closed())
428: return;
429:
430: // Flush and close the appropriate output mechanism
431: if (writer != null) {
432: writer.flush();
433: writer.close();
434: } else {
435: stream.flush();
436: stream.close();
437: }
438:
439: // The underlying output stream (perhaps from a socket)
440: // is not our responsibility
441:
442: }
443:
444: /**
445: * Return the content length that was set or calculated for this Response.
446: */
447: public int getContentLength() {
448:
449: return (this .contentLength);
450:
451: }
452:
453: /**
454: * Return the content type that was set or calculated for this response,
455: * or <code>null</code> if no content type was set.
456: */
457: public String getContentType() {
458:
459: return (this .contentType);
460:
461: }
462:
463: /**
464: * Return a PrintWriter that can be used to render error messages,
465: * regardless of whether a stream or writer has already been acquired.
466: */
467: public PrintWriter getReporter() {
468:
469: if (isError()) {
470:
471: try {
472: if (this .stream == null)
473: this .stream = createOutputStream();
474: } catch (IOException e) {
475: return null;
476: }
477: return (new PrintWriter(this .stream));
478:
479: } else {
480:
481: if (this .stream != null) {
482: return null;
483: } else {
484: try {
485: return (new PrintWriter(getOutputStream()));
486: } catch (IOException e) {
487: return null;
488: }
489: }
490:
491: }
492:
493: }
494:
495: /**
496: * Release all object references, and initialize instance variables, in
497: * preparation for reuse of this object.
498: */
499: public void recycle() {
500:
501: // buffer is NOT reset when recycling
502: bufferCount = 0;
503: committed = false;
504: appCommitted = false;
505: suspended = false;
506: // connector is NOT reset when recycling
507: contentCount = 0;
508: contentLength = -1;
509: contentType = null;
510: context = null;
511: encoding = null;
512: included = false;
513: locale = Locale.getDefault();
514: output = null;
515: request = null;
516: stream = null;
517: writer = null;
518: error = false;
519:
520: }
521:
522: // -------------------------------------------------------- Package Methods
523:
524: /**
525: * Write the specified byte to our output stream, flushing if necessary.
526: *
527: * @param b The byte to be written
528: *
529: * @exception IOException if an input/output error occurs
530: */
531: public void write(int b) throws IOException {
532:
533: if (suspended)
534: throw new IOException(sm
535: .getString("responseBase.write.suspended"));
536:
537: if (bufferCount >= buffer.length)
538: flushBuffer();
539: buffer[bufferCount++] = (byte) b;
540: contentCount++;
541:
542: }
543:
544: /**
545: * Write <code>b.length</code> bytes from the specified byte array
546: * to our output stream. Flush the output stream as necessary.
547: *
548: * @param b The byte array to be written
549: *
550: * @exception IOException if an input/output error occurs
551: */
552: public void write(byte b[]) throws IOException {
553:
554: if (suspended)
555: throw new IOException(sm
556: .getString("responseBase.write.suspended"));
557:
558: write(b, 0, b.length);
559:
560: }
561:
562: /**
563: * Write <code>len</code> bytes from the specified byte array, starting
564: * at the specified offset, to our output stream. Flush the output
565: * stream as necessary.
566: *
567: * @param b The byte array containing the bytes to be written
568: * @param off Zero-relative starting offset of the bytes to be written
569: * @param len The number of bytes to be written
570: *
571: * @exception IOException if an input/output error occurs
572: */
573: public void write(byte b[], int off, int len) throws IOException {
574:
575: if (suspended)
576: throw new IOException(sm
577: .getString("responseBase.write.suspended"));
578:
579: // If the whole thing fits in the buffer, just put it there
580: if (len == 0)
581: return;
582: if (len <= (buffer.length - bufferCount)) {
583: System.arraycopy(b, off, buffer, bufferCount, len);
584: bufferCount += len;
585: contentCount += len;
586: return;
587: }
588:
589: // Flush the buffer and start writing full-buffer-size chunks
590: flushBuffer();
591: int iterations = len / buffer.length;
592: int leftoverStart = iterations * buffer.length;
593: int leftoverLen = len - leftoverStart;
594: for (int i = 0; i < iterations; i++)
595: write(b, off + (i * buffer.length), buffer.length);
596:
597: // Write the remainder (guaranteed to fit in the buffer)
598: if (leftoverLen > 0)
599: write(b, off + leftoverStart, leftoverLen);
600:
601: }
602:
603: // ------------------------------------------------ ServletResponse Methods
604:
605: /**
606: * Flush the buffer and commit this response.
607: *
608: * @exception IOException if an input/output error occurs
609: */
610: public void flushBuffer() throws IOException {
611:
612: committed = true;
613: if (bufferCount > 0) {
614: try {
615: output.write(buffer, 0, bufferCount);
616: } finally {
617: bufferCount = 0;
618: }
619: }
620:
621: }
622:
623: /**
624: * Return the actual buffer size used for this Response.
625: */
626: public int getBufferSize() {
627:
628: return (buffer.length);
629:
630: }
631:
632: /**
633: * Return the character encoding used for this Response.
634: */
635: public String getCharacterEncoding() {
636:
637: if (encoding == null)
638: return ("ISO-8859-1");
639: else
640: return (encoding);
641:
642: }
643:
644: /**
645: * Return the servlet output stream associated with this Response.
646: *
647: * @exception IllegalStateException if <code>getWriter</code> has
648: * already been called for this response
649: * @exception IOException if an input/output error occurs
650: */
651: public ServletOutputStream getOutputStream() throws IOException {
652:
653: if (writer != null)
654: throw new IllegalStateException(sm
655: .getString("responseBase.getOutputStream.ise"));
656:
657: if (stream == null)
658: stream = createOutputStream();
659: ((ResponseStream) stream).setCommit(true);
660: return (stream);
661:
662: }
663:
664: /**
665: * Return the Locale assigned to this response.
666: */
667: public Locale getLocale() {
668:
669: return (locale);
670:
671: }
672:
673: /**
674: * Return the writer associated with this Response.
675: *
676: * @exception IllegalStateException if <code>getOutputStream</code> has
677: * already been called for this response
678: * @exception IOException if an input/output error occurs
679: */
680: public PrintWriter getWriter() throws IOException {
681:
682: if (writer != null)
683: return (writer);
684:
685: if (stream != null)
686: throw new IllegalStateException(sm
687: .getString("responseBase.getWriter.ise"));
688:
689: ResponseStream newStream = (ResponseStream) createOutputStream();
690: newStream.setCommit(false);
691: OutputStreamWriter osr = new OutputStreamWriter(newStream,
692: getCharacterEncoding());
693: writer = new ResponseWriter(osr, newStream);
694: stream = newStream;
695: return (writer);
696:
697: }
698:
699: /**
700: * Has the output of this response already been committed?
701: */
702: public boolean isCommitted() {
703:
704: return (committed);
705:
706: }
707:
708: /**
709: * Clear any content written to the buffer.
710: *
711: * @exception IllegalStateException if this response has already
712: * been committed
713: */
714: public void reset() {
715:
716: if (committed)
717: throw new IllegalStateException(sm
718: .getString("responseBase.reset.ise"));
719:
720: if (included)
721: return; // Ignore any call from an included servlet
722:
723: if (stream != null)
724: ((ResponseStream) stream).reset();
725: bufferCount = 0;
726: contentLength = -1;
727: contentType = null;
728:
729: }
730:
731: /**
732: * Reset the data buffer but not any status or header information.
733: *
734: * @exception IllegalStateException if the response has already
735: * been committed
736: */
737: public void resetBuffer() {
738:
739: if (committed)
740: throw new IllegalStateException(sm
741: .getString("responseBase.resetBuffer.ise"));
742:
743: bufferCount = 0;
744:
745: }
746:
747: /**
748: * Set the buffer size to be used for this Response.
749: *
750: * @param size The new buffer size
751: *
752: * @exception IllegalStateException if this method is called after
753: * output has been committed for this response
754: */
755: public void setBufferSize(int size) {
756:
757: if (committed || (bufferCount > 0))
758: throw new IllegalStateException(sm
759: .getString("responseBase.setBufferSize.ise"));
760:
761: if (buffer.length >= size)
762: return;
763: buffer = new byte[size];
764:
765: }
766:
767: /**
768: * Set the content length (in bytes) for this Response.
769: *
770: * @param length The new content length
771: */
772: public void setContentLength(int length) {
773:
774: if (isCommitted())
775: return;
776:
777: if (included)
778: return; // Ignore any call from an included servlet
779:
780: this .contentLength = length;
781:
782: }
783:
784: /**
785: * Set the content type for this Response.
786: *
787: * @param type The new content type
788: */
789: public void setContentType(String type) {
790:
791: if (isCommitted())
792: return;
793:
794: if (included)
795: return; // Ignore any call from an included servlet
796:
797: this .contentType = type;
798: if (type.indexOf(';') >= 0) {
799: encoding = RequestUtil.parseCharacterEncoding(type);
800: if (encoding == null)
801: encoding = "ISO-8859-1";
802: } else {
803: if (encoding != null)
804: this .contentType = type + ";charset=" + encoding;
805: }
806:
807: }
808:
809: /**
810: * Set the Locale that is appropriate for this response, including
811: * setting the appropriate character encoding.
812: *
813: * @param locale The new locale
814: */
815: public void setLocale(Locale locale) {
816:
817: if (isCommitted())
818: return;
819:
820: if (included)
821: return; // Ignore any call from an included servlet
822:
823: this .locale = locale;
824: if (this .context != null) {
825: CharsetMapper mapper = context.getCharsetMapper();
826: this .encoding = mapper.getCharset(locale);
827: if (contentType != null) {
828: if (contentType.indexOf(';') < 0) {
829: contentType = contentType + ";charset=" + encoding;
830: } else {
831: // Replace the previous charset
832: int i = contentType.indexOf(';');
833: contentType = contentType.substring(0, i)
834: + ";charset=" + encoding;
835: }
836: }
837: }
838:
839: }
840:
841: }
|