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