| /* |
| * Copyright 1999-2004 The Apache Software Foundation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.coyote; |
| |
| import java.io.IOException; |
| import java.util.Locale; |
| |
| import org.apache.tomcat.util.buf.ByteChunk; |
| import org.apache.tomcat.util.http.MimeHeaders; |
| |
| /** |
| * Response object. |
| * |
| * @author James Duncan Davidson [duncan@eng.sun.com] |
| * @author Jason Hunter [jch@eng.sun.com] |
| * @author James Todd [gonzo@eng.sun.com] |
| * @author Harish Prabandham |
| * @author Hans Bergsten <hans@gefionsoftware.com> |
| * @author Remy Maucherat |
| */ |
| public final class Response { |
| |
| |
| // ----------------------------------------------------------- Constructors |
| |
| |
| public Response() { |
| } |
| |
| |
| // ----------------------------------------------------- Class Variables |
| |
| /** |
| * Default locale as mandated by the spec. |
| */ |
| private static Locale DEFAULT_LOCALE = Locale.getDefault(); |
| |
| |
| // ----------------------------------------------------- Instance Variables |
| |
| /** |
| * Status code. |
| */ |
| protected int status = 200; |
| |
| |
| /** |
| * Status message. |
| */ |
| protected String message = null; |
| |
| |
| /** |
| * Response headers. |
| */ |
| protected MimeHeaders headers = new MimeHeaders(); |
| |
| |
| /** |
| * Associated output buffer. |
| */ |
| protected OutputBuffer outputBuffer; |
| |
| |
| /** |
| * Notes. |
| */ |
| protected Object notes[] = new Object[Constants.MAX_NOTES]; |
| |
| |
| /** |
| * Committed flag. |
| */ |
| protected boolean commited = false; |
| |
| |
| /** |
| * Action hook. |
| */ |
| public ActionHook hook; |
| |
| |
| /** |
| * HTTP specific fields. |
| */ |
| protected String contentType = null; |
| protected String contentLanguage = null; |
| protected String characterEncoding = Constants.DEFAULT_CHARACTER_ENCODING; |
| protected int contentLength = -1; |
| private Locale locale = DEFAULT_LOCALE; |
| |
| // General informations |
| private long bytesWritten=0; |
| |
| /** |
| * Holds request error exception. |
| */ |
| protected Exception errorException = null; |
| |
| /** |
| * Has the charset been explicitly set. |
| */ |
| protected boolean charsetSet = false; |
| |
| /** |
| * Request error URI. |
| */ |
| protected String errorURI = null; |
| |
| protected Request req; |
| |
| // ------------------------------------------------------------- Properties |
| |
| public Request getRequest() { |
| return req; |
| } |
| |
| public void setRequest( Request req ) { |
| this.req=req; |
| } |
| |
| public OutputBuffer getOutputBuffer() { |
| return outputBuffer; |
| } |
| |
| |
| public void setOutputBuffer(OutputBuffer outputBuffer) { |
| this.outputBuffer = outputBuffer; |
| } |
| |
| |
| public MimeHeaders getMimeHeaders() { |
| return headers; |
| } |
| |
| |
| public ActionHook getHook() { |
| return hook; |
| } |
| |
| |
| public void setHook(ActionHook hook) { |
| this.hook = hook; |
| } |
| |
| |
| // -------------------- Per-Response "notes" -------------------- |
| |
| |
| public final void setNote(int pos, Object value) { |
| notes[pos] = value; |
| } |
| |
| |
| public final Object getNote(int pos) { |
| return notes[pos]; |
| } |
| |
| |
| // -------------------- Actions -------------------- |
| |
| |
| public void action(ActionCode actionCode, Object param) { |
| if (hook != null) { |
| if( param==null ) |
| hook.action(actionCode, this); |
| else |
| hook.action(actionCode, param); |
| } |
| } |
| |
| |
| // -------------------- State -------------------- |
| |
| |
| public int getStatus() { |
| return status; |
| } |
| |
| |
| /** |
| * Set the response status |
| */ |
| public void setStatus( int status ) { |
| this.status = status; |
| } |
| |
| |
| /** |
| * Get the status message. |
| */ |
| public String getMessage() { |
| return message; |
| } |
| |
| |
| /** |
| * Set the status message. |
| */ |
| public void setMessage(String message) { |
| this.message = message; |
| } |
| |
| |
| public boolean isCommitted() { |
| return commited; |
| } |
| |
| |
| public void setCommitted(boolean v) { |
| this.commited = v; |
| } |
| |
| |
| // -----------------Error State -------------------- |
| |
| |
| /** |
| * Set the error Exception that occurred during |
| * request processing. |
| */ |
| public void setErrorException(Exception ex) { |
| errorException = ex; |
| } |
| |
| |
| /** |
| * Get the Exception that occurred during request |
| * processing. |
| */ |
| public Exception getErrorException() { |
| return errorException; |
| } |
| |
| |
| public boolean isExceptionPresent() { |
| return ( errorException != null ); |
| } |
| |
| |
| /** |
| * Set request URI that caused an error during |
| * request processing. |
| */ |
| public void setErrorURI(String uri) { |
| errorURI = uri; |
| } |
| |
| |
| /** Get the request URI that caused the original error. |
| */ |
| public String getErrorURI() { |
| return errorURI; |
| } |
| |
| |
| // -------------------- Methods -------------------- |
| |
| |
| public void reset() |
| throws IllegalStateException { |
| |
| // Reset the headers only if this is the main request, |
| // not for included |
| contentType = null;; |
| locale = DEFAULT_LOCALE; |
| contentLanguage = null; |
| characterEncoding = Constants.DEFAULT_CHARACTER_ENCODING; |
| contentLength = -1; |
| charsetSet = false; |
| |
| status = 200; |
| message = null; |
| headers.clear(); |
| |
| // Force the PrintWriter to flush its data to the output |
| // stream before resetting the output stream |
| // |
| // Reset the stream |
| if (commited) { |
| //String msg = sm.getString("servletOutputStreamImpl.reset.ise"); |
| throw new IllegalStateException(); |
| } |
| |
| action(ActionCode.ACTION_RESET, this); |
| } |
| |
| |
| public void finish() throws IOException { |
| action(ActionCode.ACTION_CLOSE, this); |
| } |
| |
| |
| public void acknowledge() throws IOException { |
| action(ActionCode.ACTION_ACK, this); |
| } |
| |
| |
| // -------------------- Headers -------------------- |
| public boolean containsHeader(String name) { |
| return headers.getHeader(name) != null; |
| } |
| |
| |
| public void setHeader(String name, String value) { |
| char cc=name.charAt(0); |
| if( cc=='C' || cc=='c' ) { |
| if( checkSpecialHeader(name, value) ) |
| return; |
| } |
| headers.setValue(name).setString( value); |
| } |
| |
| |
| public void addHeader(String name, String value) { |
| char cc=name.charAt(0); |
| if( cc=='C' || cc=='c' ) { |
| if( checkSpecialHeader(name, value) ) |
| return; |
| } |
| headers.addValue(name).setString( value ); |
| } |
| |
| |
| /** |
| * Set internal fields for special header names. |
| * Called from set/addHeader. |
| * Return true if the header is special, no need to set the header. |
| */ |
| private boolean checkSpecialHeader( String name, String value) { |
| // XXX Eliminate redundant fields !!! |
| // ( both header and in special fields ) |
| if( name.equalsIgnoreCase( "Content-Type" ) ) { |
| setContentType( value ); |
| return true; |
| } |
| if( name.equalsIgnoreCase( "Content-Length" ) ) { |
| try { |
| int cL=Integer.parseInt( value ); |
| setContentLength( cL ); |
| return true; |
| } catch( NumberFormatException ex ) { |
| // Do nothing - the spec doesn't have any "throws" |
| // and the user might know what he's doing |
| return false; |
| } |
| } |
| if( name.equalsIgnoreCase( "Content-Language" ) ) { |
| // XXX XXX Need to construct Locale or something else |
| } |
| return false; |
| } |
| |
| |
| /** Signal that we're done with the headers, and body will follow. |
| * Any implementation needs to notify ContextManager, to allow |
| * interceptors to fix headers. |
| */ |
| public void sendHeaders() throws IOException { |
| action(ActionCode.ACTION_COMMIT, this); |
| commited = true; |
| } |
| |
| |
| // -------------------- I18N -------------------- |
| |
| |
| public Locale getLocale() { |
| return locale; |
| } |
| |
| /** |
| * Called explicitely by user to set the Content-Language and |
| * the default encoding |
| */ |
| public void setLocale(Locale locale) { |
| |
| if (locale == null) { |
| return; // throw an exception? |
| } |
| |
| // Save the locale for use by getLocale() |
| this.locale = locale; |
| |
| // Set the contentLanguage for header output |
| contentLanguage = locale.getLanguage(); |
| if ((contentLanguage != null) && (contentLanguage.length() > 0)) { |
| String country = locale.getCountry(); |
| StringBuffer value = new StringBuffer(contentLanguage); |
| if ((country != null) && (country.length() > 0)) { |
| value.append('-'); |
| value.append(country); |
| } |
| contentLanguage = value.toString(); |
| } |
| |
| } |
| |
| /** |
| * Return the content language. |
| */ |
| public String getContentLanguage() { |
| return contentLanguage; |
| } |
| |
| /* |
| * Overrides the name of the character encoding used in the body |
| * of the response. This method must be called prior to writing output |
| * using getWriter(). |
| * |
| * @param charset String containing the name of the chararacter encoding. |
| */ |
| public void setCharacterEncoding(String charset) { |
| |
| if (isCommitted()) |
| return; |
| if (charset == null) |
| return; |
| |
| characterEncoding = charset; |
| charsetSet=true; |
| } |
| |
| public String getCharacterEncoding() { |
| return characterEncoding; |
| } |
| |
| /** |
| * Sets the content type. |
| * |
| * This method must preserve any response charset that may already have |
| * been set via a call to response.setContentType(), response.setLocale(), |
| * or response.setCharacterEncoding(). |
| * |
| * @param type the content type |
| */ |
| public void setContentType(String type) { |
| |
| int semicolonIndex = -1; |
| |
| if (type == null) { |
| this.contentType = null; |
| return; |
| } |
| |
| /* |
| * Remove the charset param (if any) from the Content-Type, and use it |
| * to set the response encoding. |
| * The most recent response encoding setting will be appended to the |
| * response's Content-Type (as its charset param) by getContentType(); |
| */ |
| boolean hasCharset = false; |
| int len = type.length(); |
| int index = type.indexOf(';'); |
| while (index != -1) { |
| semicolonIndex = index; |
| index++; |
| while (index < len && Character.isSpace(type.charAt(index))) { |
| index++; |
| } |
| if (index+8 < len |
| && type.charAt(index) == 'c' |
| && type.charAt(index+1) == 'h' |
| && type.charAt(index+2) == 'a' |
| && type.charAt(index+3) == 'r' |
| && type.charAt(index+4) == 's' |
| && type.charAt(index+5) == 'e' |
| && type.charAt(index+6) == 't' |
| && type.charAt(index+7) == '=') { |
| hasCharset = true; |
| break; |
| } |
| index = type.indexOf(';', index); |
| } |
| |
| if (!hasCharset) { |
| this.contentType = type; |
| return; |
| } |
| |
| this.contentType = type.substring(0, semicolonIndex); |
| String tail = type.substring(index+8); |
| int nextParam = tail.indexOf(';'); |
| String charsetValue = null; |
| if (nextParam != -1) { |
| this.contentType += tail.substring(nextParam); |
| charsetValue = tail.substring(0, nextParam); |
| } else { |
| charsetValue = tail; |
| } |
| |
| // The charset value may be quoted, but must not contain any quotes. |
| if (charsetValue != null && charsetValue.length() > 0) { |
| charsetSet=true; |
| charsetValue = charsetValue.replace('"', ' '); |
| this.characterEncoding = charsetValue.trim(); |
| } |
| } |
| |
| public String getContentType() { |
| |
| String ret = contentType; |
| |
| if (ret != null |
| && characterEncoding != null |
| && charsetSet) { |
| ret = ret + ";charset=" + characterEncoding; |
| } |
| |
| return ret; |
| } |
| |
| public void setContentLength(int contentLength) { |
| this.contentLength = contentLength; |
| } |
| |
| public int getContentLength() { |
| return contentLength; |
| } |
| |
| |
| /** |
| * Write a chunk of bytes. |
| */ |
| public void doWrite(ByteChunk chunk/*byte buffer[], int pos, int count*/) |
| throws IOException |
| { |
| outputBuffer.doWrite(chunk, this); |
| bytesWritten+=chunk.getLength(); |
| } |
| |
| // -------------------- |
| |
| public void recycle() { |
| |
| contentType = null; |
| contentLanguage = null; |
| locale = DEFAULT_LOCALE; |
| characterEncoding = Constants.DEFAULT_CHARACTER_ENCODING; |
| charsetSet = false; |
| contentLength = -1; |
| status = 200; |
| message = null; |
| commited = false; |
| errorException = null; |
| errorURI = null; |
| headers.clear(); |
| |
| // update counters |
| bytesWritten=0; |
| } |
| |
| public long getBytesWritten() { |
| return bytesWritten; |
| } |
| |
| public void setBytesWritten(long bytesWritten) { |
| this.bytesWritten = bytesWritten; |
| } |
| } |