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;
018:
019: import java.io.IOException;
020: import java.util.Locale;
021:
022: import org.apache.tomcat.util.buf.ByteChunk;
023: import org.apache.tomcat.util.http.MimeHeaders;
024:
025: /**
026: * Response object.
027: *
028: * @author James Duncan Davidson [duncan@eng.sun.com]
029: * @author Jason Hunter [jch@eng.sun.com]
030: * @author James Todd [gonzo@eng.sun.com]
031: * @author Harish Prabandham
032: * @author Hans Bergsten <hans@gefionsoftware.com>
033: * @author Remy Maucherat
034: */
035: public final class Response {
036:
037: // ----------------------------------------------------------- Constructors
038:
039: public Response() {
040: }
041:
042: // ----------------------------------------------------- Class Variables
043:
044: /**
045: * Default locale as mandated by the spec.
046: */
047: private static Locale DEFAULT_LOCALE = Locale.getDefault();
048:
049: // ----------------------------------------------------- Instance Variables
050:
051: /**
052: * Status code.
053: */
054: protected int status = 200;
055:
056: /**
057: * Status message.
058: */
059: protected String message = null;
060:
061: /**
062: * Response headers.
063: */
064: protected MimeHeaders headers = new MimeHeaders();
065:
066: /**
067: * Associated output buffer.
068: */
069: protected OutputBuffer outputBuffer;
070:
071: /**
072: * Notes.
073: */
074: protected Object notes[] = new Object[Constants.MAX_NOTES];
075:
076: /**
077: * Committed flag.
078: */
079: protected boolean commited = false;
080:
081: /**
082: * Action hook.
083: */
084: public ActionHook hook;
085:
086: /**
087: * HTTP specific fields.
088: */
089: protected String contentType = null;
090: protected String contentLanguage = null;
091: protected String characterEncoding = Constants.DEFAULT_CHARACTER_ENCODING;
092: protected int contentLength = -1;
093: private Locale locale = DEFAULT_LOCALE;
094:
095: // General informations
096: private long bytesWritten = 0;
097:
098: /**
099: * Holds request error exception.
100: */
101: protected Exception errorException = null;
102:
103: /**
104: * Has the charset been explicitly set.
105: */
106: protected boolean charsetSet = false;
107:
108: /**
109: * Request error URI.
110: */
111: protected String errorURI = null;
112:
113: protected Request req;
114:
115: // ------------------------------------------------------------- Properties
116:
117: public Request getRequest() {
118: return req;
119: }
120:
121: public void setRequest(Request req) {
122: this .req = req;
123: }
124:
125: public OutputBuffer getOutputBuffer() {
126: return outputBuffer;
127: }
128:
129: public void setOutputBuffer(OutputBuffer outputBuffer) {
130: this .outputBuffer = outputBuffer;
131: }
132:
133: public MimeHeaders getMimeHeaders() {
134: return headers;
135: }
136:
137: public ActionHook getHook() {
138: return hook;
139: }
140:
141: public void setHook(ActionHook hook) {
142: this .hook = hook;
143: }
144:
145: // -------------------- Per-Response "notes" --------------------
146:
147: public final void setNote(int pos, Object value) {
148: notes[pos] = value;
149: }
150:
151: public final Object getNote(int pos) {
152: return notes[pos];
153: }
154:
155: // -------------------- Actions --------------------
156:
157: public void action(ActionCode actionCode, Object param) {
158: if (hook != null) {
159: if (param == null)
160: hook.action(actionCode, this );
161: else
162: hook.action(actionCode, param);
163: }
164: }
165:
166: // -------------------- State --------------------
167:
168: public int getStatus() {
169: return status;
170: }
171:
172: /**
173: * Set the response status
174: */
175: public void setStatus(int status) {
176: this .status = status;
177: }
178:
179: /**
180: * Get the status message.
181: */
182: public String getMessage() {
183: return message;
184: }
185:
186: /**
187: * Set the status message.
188: */
189: public void setMessage(String message) {
190: this .message = message;
191: }
192:
193: public boolean isCommitted() {
194: return commited;
195: }
196:
197: public void setCommitted(boolean v) {
198: this .commited = v;
199: }
200:
201: // -----------------Error State --------------------
202:
203: /**
204: * Set the error Exception that occurred during
205: * request processing.
206: */
207: public void setErrorException(Exception ex) {
208: errorException = ex;
209: }
210:
211: /**
212: * Get the Exception that occurred during request
213: * processing.
214: */
215: public Exception getErrorException() {
216: return errorException;
217: }
218:
219: public boolean isExceptionPresent() {
220: return (errorException != null);
221: }
222:
223: /**
224: * Set request URI that caused an error during
225: * request processing.
226: */
227: public void setErrorURI(String uri) {
228: errorURI = uri;
229: }
230:
231: /** Get the request URI that caused the original error.
232: */
233: public String getErrorURI() {
234: return errorURI;
235: }
236:
237: // -------------------- Methods --------------------
238:
239: public void reset() throws IllegalStateException {
240:
241: // Reset the headers only if this is the main request,
242: // not for included
243: contentType = null;
244: ;
245: locale = DEFAULT_LOCALE;
246: contentLanguage = null;
247: characterEncoding = Constants.DEFAULT_CHARACTER_ENCODING;
248: contentLength = -1;
249: charsetSet = false;
250:
251: status = 200;
252: message = null;
253: headers.clear();
254:
255: // Force the PrintWriter to flush its data to the output
256: // stream before resetting the output stream
257: //
258: // Reset the stream
259: if (commited) {
260: //String msg = sm.getString("servletOutputStreamImpl.reset.ise");
261: throw new IllegalStateException();
262: }
263:
264: action(ActionCode.ACTION_RESET, this );
265: }
266:
267: public void finish() throws IOException {
268: action(ActionCode.ACTION_CLOSE, this );
269: }
270:
271: public void acknowledge() throws IOException {
272: action(ActionCode.ACTION_ACK, this );
273: }
274:
275: // -------------------- Headers --------------------
276: public boolean containsHeader(String name) {
277: return headers.getHeader(name) != null;
278: }
279:
280: public void setHeader(String name, String value) {
281: char cc = name.charAt(0);
282: if (cc == 'C' || cc == 'c') {
283: if (checkSpecialHeader(name, value))
284: return;
285: }
286: headers.setValue(name).setString(value);
287: }
288:
289: public void addHeader(String name, String value) {
290: char cc = name.charAt(0);
291: if (cc == 'C' || cc == 'c') {
292: if (checkSpecialHeader(name, value))
293: return;
294: }
295: headers.addValue(name).setString(value);
296: }
297:
298: /**
299: * Set internal fields for special header names.
300: * Called from set/addHeader.
301: * Return true if the header is special, no need to set the header.
302: */
303: private boolean checkSpecialHeader(String name, String value) {
304: // XXX Eliminate redundant fields !!!
305: // ( both header and in special fields )
306: if (name.equalsIgnoreCase("Content-Type")) {
307: setContentType(value);
308: return true;
309: }
310: if (name.equalsIgnoreCase("Content-Length")) {
311: try {
312: int cL = Integer.parseInt(value);
313: setContentLength(cL);
314: return true;
315: } catch (NumberFormatException ex) {
316: // Do nothing - the spec doesn't have any "throws"
317: // and the user might know what he's doing
318: return false;
319: }
320: }
321: if (name.equalsIgnoreCase("Content-Language")) {
322: // XXX XXX Need to construct Locale or something else
323: }
324: return false;
325: }
326:
327: /** Signal that we're done with the headers, and body will follow.
328: * Any implementation needs to notify ContextManager, to allow
329: * interceptors to fix headers.
330: */
331: public void sendHeaders() throws IOException {
332: action(ActionCode.ACTION_COMMIT, this );
333: commited = true;
334: }
335:
336: // -------------------- I18N --------------------
337:
338: public Locale getLocale() {
339: return locale;
340: }
341:
342: /**
343: * Called explicitely by user to set the Content-Language and
344: * the default encoding
345: */
346: public void setLocale(Locale locale) {
347:
348: if (locale == null) {
349: return; // throw an exception?
350: }
351:
352: // Save the locale for use by getLocale()
353: this .locale = locale;
354:
355: // Set the contentLanguage for header output
356: contentLanguage = locale.getLanguage();
357: if ((contentLanguage != null) && (contentLanguage.length() > 0)) {
358: String country = locale.getCountry();
359: StringBuffer value = new StringBuffer(contentLanguage);
360: if ((country != null) && (country.length() > 0)) {
361: value.append('-');
362: value.append(country);
363: }
364: contentLanguage = value.toString();
365: }
366:
367: }
368:
369: /**
370: * Return the content language.
371: */
372: public String getContentLanguage() {
373: return contentLanguage;
374: }
375:
376: /*
377: * Overrides the name of the character encoding used in the body
378: * of the response. This method must be called prior to writing output
379: * using getWriter().
380: *
381: * @param charset String containing the name of the chararacter encoding.
382: */
383: public void setCharacterEncoding(String charset) {
384:
385: if (isCommitted())
386: return;
387: if (charset == null)
388: return;
389:
390: characterEncoding = charset;
391: charsetSet = true;
392: }
393:
394: public String getCharacterEncoding() {
395: return characterEncoding;
396: }
397:
398: /**
399: * Sets the content type.
400: *
401: * This method must preserve any response charset that may already have
402: * been set via a call to response.setContentType(), response.setLocale(),
403: * or response.setCharacterEncoding().
404: *
405: * @param type the content type
406: */
407: public void setContentType(String type) {
408:
409: int semicolonIndex = -1;
410:
411: if (type == null) {
412: this .contentType = null;
413: return;
414: }
415:
416: /*
417: * Remove the charset param (if any) from the Content-Type, and use it
418: * to set the response encoding.
419: * The most recent response encoding setting will be appended to the
420: * response's Content-Type (as its charset param) by getContentType();
421: */
422: boolean hasCharset = false;
423: int len = type.length();
424: int index = type.indexOf(';');
425: while (index != -1) {
426: semicolonIndex = index;
427: index++;
428: while (index < len && Character.isSpace(type.charAt(index))) {
429: index++;
430: }
431: if (index + 8 < len && type.charAt(index) == 'c'
432: && type.charAt(index + 1) == 'h'
433: && type.charAt(index + 2) == 'a'
434: && type.charAt(index + 3) == 'r'
435: && type.charAt(index + 4) == 's'
436: && type.charAt(index + 5) == 'e'
437: && type.charAt(index + 6) == 't'
438: && type.charAt(index + 7) == '=') {
439: hasCharset = true;
440: break;
441: }
442: index = type.indexOf(';', index);
443: }
444:
445: if (!hasCharset) {
446: this .contentType = type;
447: return;
448: }
449:
450: this .contentType = type.substring(0, semicolonIndex);
451: String tail = type.substring(index + 8);
452: int nextParam = tail.indexOf(';');
453: String charsetValue = null;
454: if (nextParam != -1) {
455: this .contentType += tail.substring(nextParam);
456: charsetValue = tail.substring(0, nextParam);
457: } else {
458: charsetValue = tail;
459: }
460:
461: // The charset value may be quoted, but must not contain any quotes.
462: if (charsetValue != null && charsetValue.length() > 0) {
463: charsetSet = true;
464: charsetValue = charsetValue.replace('"', ' ');
465: this .characterEncoding = charsetValue.trim();
466: }
467: }
468:
469: public String getContentType() {
470:
471: String ret = contentType;
472:
473: if (ret != null && characterEncoding != null && charsetSet) {
474: ret = ret + ";charset=" + characterEncoding;
475: }
476:
477: return ret;
478: }
479:
480: public void setContentLength(int contentLength) {
481: this .contentLength = contentLength;
482: }
483:
484: public int getContentLength() {
485: return contentLength;
486: }
487:
488: /**
489: * Write a chunk of bytes.
490: */
491: public void doWrite(ByteChunk chunk/*byte buffer[], int pos, int count*/)
492: throws IOException {
493: outputBuffer.doWrite(chunk, this );
494: bytesWritten += chunk.getLength();
495: }
496:
497: // --------------------
498:
499: public void recycle() {
500:
501: contentType = null;
502: contentLanguage = null;
503: locale = DEFAULT_LOCALE;
504: characterEncoding = Constants.DEFAULT_CHARACTER_ENCODING;
505: charsetSet = false;
506: contentLength = -1;
507: status = 200;
508: message = null;
509: commited = false;
510: errorException = null;
511: errorURI = null;
512: headers.clear();
513:
514: // update counters
515: bytesWritten = 0;
516: }
517:
518: public long getBytesWritten() {
519: return bytesWritten;
520: }
521:
522: public void setBytesWritten(long bytesWritten) {
523: this.bytesWritten = bytesWritten;
524: }
525: }
|