001: /*
002: * Copyright (c) 2002-2003 by OpenSymphony
003: * All rights reserved.
004: */
005: package com.opensymphony.oscache.web.filter;
006:
007: import java.io.*;
008:
009: import java.util.Locale;
010: import java.util.zip.GZIPInputStream;
011:
012: import javax.servlet.ServletResponse;
013: import javax.servlet.http.HttpServletResponse;
014:
015: /**
016: * Holds the servlet response in a byte array so that it can be held
017: * in the cache (and, since this class is serializable, optionally
018: * persisted to disk).
019: *
020: * @version $Revision: 362 $
021: * @author <a href="mailto:sergek@lokitech.com">Serge Knystautas</a>
022: */
023: public class ResponseContent implements Serializable {
024: private transient ByteArrayOutputStream bout = new ByteArrayOutputStream(
025: 1000);
026: private Locale locale = null;
027: private String contentEncoding = null;
028: private String contentType = null;
029: private byte[] content = null;
030: private long expires = Long.MAX_VALUE;
031: private long lastModified = -1;
032: private long maxAge = -60;
033:
034: public String getContentType() {
035: return contentType;
036: }
037:
038: /**
039: * Set the content type. We capture this so that when we serve this
040: * data from cache, we can set the correct content type on the response.
041: */
042: public void setContentType(String value) {
043: contentType = value;
044: }
045:
046: public long getLastModified() {
047: return lastModified;
048: }
049:
050: public void setLastModified(long value) {
051: lastModified = value;
052: }
053:
054: public String getContentEncoding() {
055: return contentEncoding;
056: }
057:
058: public void setContentEncoding(String contentEncoding) {
059: this .contentEncoding = contentEncoding;
060: }
061:
062: /**
063: * Set the Locale. We capture this so that when we serve this data from
064: * cache, we can set the correct locale on the response.
065: */
066: public void setLocale(Locale value) {
067: locale = value;
068: }
069:
070: /**
071: * @return the expires date and time in miliseconds when the content will be stale
072: */
073: public long getExpires() {
074: return expires;
075: }
076:
077: /**
078: * Sets the expires date and time in miliseconds.
079: * @param value time in miliseconds when the content will expire
080: */
081: public void setExpires(long value) {
082: expires = value;
083: }
084:
085: /**
086: * Returns the max age of the content in miliseconds. If expires header and cache control are
087: * enabled both, both will be equal.
088: * @return the max age of the content in miliseconds, if -1 max-age is disabled
089: */
090: public long getMaxAge() {
091: return maxAge;
092: }
093:
094: /**
095: * Sets the max age date and time in miliseconds. If the parameter is -1, the max-age parameter
096: * won't be set by default in the Cache-Control header.
097: * @param value sets the intial
098: */
099: public void setMaxAge(long value) {
100: maxAge = value;
101: }
102:
103: /**
104: * Get an output stream. This is used by the {@link SplitServletOutputStream}
105: * to capture the original (uncached) response into a byte array.
106: * @return the original (uncached) response, returns null if response is already committed.
107: */
108: public OutputStream getOutputStream() {
109: return bout;
110: }
111:
112: /**
113: * Gets the size of this cached content.
114: *
115: * @return The size of the content, in bytes. If no content
116: * exists, this method returns <code>-1</code>.
117: */
118: public int getSize() {
119: return (content != null) ? content.length : (-1);
120: }
121:
122: /**
123: * Called once the response has been written in its entirety. This
124: * method commits the response output stream by converting the output
125: * stream into a byte array.
126: */
127: public void commit() {
128: if (bout != null) {
129: content = bout.toByteArray();
130: bout = null;
131: }
132: }
133:
134: /**
135: * Writes this cached data out to the supplied <code>ServletResponse</code>.
136: *
137: * @param response The servlet response to output the cached content to.
138: * @throws IOException
139: */
140: public void writeTo(ServletResponse response) throws IOException {
141: writeTo(response, false, false);
142: }
143:
144: /**
145: * Writes this cached data out to the supplied <code>ServletResponse</code>.
146: *
147: * @param response The servlet response to output the cached content to.
148: * @param fragment is true if this content a fragment or part of a page
149: * @param acceptsGZip is true if client browser supports gzip compression
150: * @throws IOException
151: */
152: public void writeTo(ServletResponse response, boolean fragment,
153: boolean acceptsGZip) throws IOException {
154: //Send the content type and data to this response
155: if (contentType != null) {
156: response.setContentType(contentType);
157: }
158:
159: if (fragment) {
160: // Don't support gzip compression if the content is a fragment of a page
161: acceptsGZip = false;
162: } else {
163: // add special headers for a complete page
164: if (response instanceof HttpServletResponse) {
165: HttpServletResponse httpResponse = (HttpServletResponse) response;
166:
167: // add the last modified header
168: if (lastModified != -1) {
169: httpResponse.setDateHeader(
170: CacheFilter.HEADER_LAST_MODIFIED,
171: lastModified);
172: }
173:
174: // add the expires header
175: if (expires != Long.MAX_VALUE) {
176: httpResponse.setDateHeader(
177: CacheFilter.HEADER_EXPIRES, expires);
178: }
179:
180: // add the cache-control header for max-age
181: if (maxAge == CacheFilter.MAX_AGE_NO_INIT
182: || maxAge == CacheFilter.MAX_AGE_TIME) {
183: // do nothing
184: } else if (maxAge > 0) { // set max-age based on life time
185: long currentMaxAge = maxAge / 1000
186: - System.currentTimeMillis() / 1000;
187: if (currentMaxAge < 0) {
188: currentMaxAge = 0;
189: }
190: httpResponse.addHeader(
191: CacheFilter.HEADER_CACHE_CONTROL,
192: "max-age=" + currentMaxAge);
193: } else {
194: httpResponse.addHeader(
195: CacheFilter.HEADER_CACHE_CONTROL,
196: "max-age=" + (-maxAge));
197: }
198:
199: }
200: }
201:
202: if (locale != null) {
203: response.setLocale(locale);
204: }
205:
206: OutputStream out = new BufferedOutputStream(response
207: .getOutputStream());
208:
209: if (isContentGZiped()) {
210: if (acceptsGZip) {
211: ((HttpServletResponse) response).addHeader(
212: CacheFilter.HEADER_CONTENT_ENCODING, "gzip");
213: response.setContentLength(content.length);
214: out.write(content);
215: } else {
216: // client doesn't support, so we have to uncompress it
217: ByteArrayInputStream bais = new ByteArrayInputStream(
218: content);
219: GZIPInputStream zis = new GZIPInputStream(bais);
220:
221: ByteArrayOutputStream baos = new ByteArrayOutputStream();
222: int numBytesRead = 0;
223: byte[] tempBytes = new byte[4196];
224:
225: while ((numBytesRead = zis.read(tempBytes, 0,
226: tempBytes.length)) != -1) {
227: baos.write(tempBytes, 0, numBytesRead);
228: }
229:
230: byte[] result = baos.toByteArray();
231:
232: response.setContentLength(result.length);
233: out.write(result);
234: }
235: } else {
236: // the content isn't compressed
237: // regardless if the client browser supports gzip we will just return the content
238: response.setContentLength(content.length);
239: out.write(content);
240: }
241: out.flush();
242: }
243:
244: /**
245: * @return true if the content is GZIP compressed
246: */
247: public boolean isContentGZiped() {
248: return "gzip".equals(contentEncoding);
249: }
250:
251: }
|