1 /*
2 * Copyright (c) 2002-2003 by OpenSymphony
3 * All rights reserved.
4 */
5 package com.opensymphony.oscache.web.filter;
6
7 import java.io;
8
9 import java.util.Locale;
10 import java.util.zip.GZIPInputStream;
11
12 import javax.servlet.ServletResponse;
13 import javax.servlet.http.HttpServletResponse;
14
15 /**
16 * Holds the servlet response in a byte array so that it can be held
17 * in the cache (and, since this class is serializable, optionally
18 * persisted to disk).
19 *
20 * @version $Revision: 362 $
21 * @author <a href="mailto:sergek@lokitech.com">Serge Knystautas</a>
22 */
23 public class ResponseContent implements Serializable {
24 private transient ByteArrayOutputStream bout = new ByteArrayOutputStream(1000);
25 private Locale locale = null;
26 private String contentEncoding = null;
27 private String contentType = null;
28 private byte[] content = null;
29 private long expires = Long.MAX_VALUE;
30 private long lastModified = -1;
31 private long maxAge = -60;
32
33 public String getContentType() {
34 return contentType;
35 }
36
37 /**
38 * Set the content type. We capture this so that when we serve this
39 * data from cache, we can set the correct content type on the response.
40 */
41 public void setContentType(String value) {
42 contentType = value;
43 }
44
45 public long getLastModified() {
46 return lastModified;
47 }
48
49 public void setLastModified(long value) {
50 lastModified = value;
51 }
52
53 public String getContentEncoding() {
54 return contentEncoding;
55 }
56
57 public void setContentEncoding(String contentEncoding) {
58 this.contentEncoding = contentEncoding;
59 }
60
61 /**
62 * Set the Locale. We capture this so that when we serve this data from
63 * cache, we can set the correct locale on the response.
64 */
65 public void setLocale(Locale value) {
66 locale = value;
67 }
68
69 /**
70 * @return the expires date and time in miliseconds when the content will be stale
71 */
72 public long getExpires() {
73 return expires;
74 }
75
76 /**
77 * Sets the expires date and time in miliseconds.
78 * @param value time in miliseconds when the content will expire
79 */
80 public void setExpires(long value) {
81 expires = value;
82 }
83
84 /**
85 * Returns the max age of the content in miliseconds. If expires header and cache control are
86 * enabled both, both will be equal.
87 * @return the max age of the content in miliseconds, if -1 max-age is disabled
88 */
89 public long getMaxAge() {
90 return maxAge;
91 }
92
93 /**
94 * Sets the max age date and time in miliseconds. If the parameter is -1, the max-age parameter
95 * won't be set by default in the Cache-Control header.
96 * @param value sets the intial
97 */
98 public void setMaxAge(long value) {
99 maxAge = value;
100 }
101
102 /**
103 * Get an output stream. This is used by the {@link SplitServletOutputStream}
104 * to capture the original (uncached) response into a byte array.
105 * @return the original (uncached) response, returns null if response is already committed.
106 */
107 public OutputStream getOutputStream() {
108 return bout;
109 }
110
111 /**
112 * Gets the size of this cached content.
113 *
114 * @return The size of the content, in bytes. If no content
115 * exists, this method returns <code>-1</code>.
116 */
117 public int getSize() {
118 return (content != null) ? content.length : (-1);
119 }
120
121 /**
122 * Called once the response has been written in its entirety. This
123 * method commits the response output stream by converting the output
124 * stream into a byte array.
125 */
126 public void commit() {
127 if (bout != null) {
128 content = bout.toByteArray();
129 bout = null;
130 }
131 }
132
133 /**
134 * Writes this cached data out to the supplied <code>ServletResponse</code>.
135 *
136 * @param response The servlet response to output the cached content to.
137 * @throws IOException
138 */
139 public void writeTo(ServletResponse response) throws IOException {
140 writeTo(response, false, false);
141 }
142
143 /**
144 * Writes this cached data out to the supplied <code>ServletResponse</code>.
145 *
146 * @param response The servlet response to output the cached content to.
147 * @param fragment is true if this content a fragment or part of a page
148 * @param acceptsGZip is true if client browser supports gzip compression
149 * @throws IOException
150 */
151 public void writeTo(ServletResponse response, boolean fragment, boolean acceptsGZip) throws IOException {
152 //Send the content type and data to this response
153 if (contentType != null) {
154 response.setContentType(contentType);
155 }
156
157 if (fragment) {
158 // Don't support gzip compression if the content is a fragment of a page
159 acceptsGZip = false;
160 } else {
161 // add special headers for a complete page
162 if (response instanceof HttpServletResponse) {
163 HttpServletResponse httpResponse = (HttpServletResponse) response;
164
165 // add the last modified header
166 if (lastModified != -1) {
167 httpResponse.setDateHeader(CacheFilter.HEADER_LAST_MODIFIED, lastModified);
168 }
169
170 // add the expires header
171 if (expires != Long.MAX_VALUE) {
172 httpResponse.setDateHeader(CacheFilter.HEADER_EXPIRES, expires);
173 }
174
175 // add the cache-control header for max-age
176 if (maxAge == CacheFilter.MAX_AGE_NO_INIT || maxAge == CacheFilter.MAX_AGE_TIME) {
177 // do nothing
178 } else if (maxAge > 0) { // set max-age based on life time
179 long currentMaxAge = maxAge / 1000 - System.currentTimeMillis() / 1000;
180 if (currentMaxAge < 0) {
181 currentMaxAge = 0;
182 }
183 httpResponse.addHeader(CacheFilter.HEADER_CACHE_CONTROL, "max-age=" + currentMaxAge);
184 } else {
185 httpResponse.addHeader(CacheFilter.HEADER_CACHE_CONTROL, "max-age=" + (-maxAge));
186 }
187
188 }
189 }
190
191 if (locale != null) {
192 response.setLocale(locale);
193 }
194
195 OutputStream out = new BufferedOutputStream(response.getOutputStream());
196
197 if (isContentGZiped()) {
198 if (acceptsGZip) {
199 ((HttpServletResponse) response).addHeader(CacheFilter.HEADER_CONTENT_ENCODING, "gzip");
200 response.setContentLength(content.length);
201 out.write(content);
202 } else {
203 // client doesn't support, so we have to uncompress it
204 ByteArrayInputStream bais = new ByteArrayInputStream(content);
205 GZIPInputStream zis = new GZIPInputStream(bais);
206
207 ByteArrayOutputStream baos = new ByteArrayOutputStream();
208 int numBytesRead = 0;
209 byte[] tempBytes = new byte[4196];
210
211 while ((numBytesRead = zis.read(tempBytes, 0, tempBytes.length)) != -1) {
212 baos.write(tempBytes, 0, numBytesRead);
213 }
214
215 byte[] result = baos.toByteArray();
216
217 response.setContentLength(result.length);
218 out.write(result);
219 }
220 } else {
221 // the content isn't compressed
222 // regardless if the client browser supports gzip we will just return the content
223 response.setContentLength(content.length);
224 out.write(content);
225 }
226 out.flush();
227 }
228
229
230 /**
231 * @return true if the content is GZIP compressed
232 */
233 public boolean isContentGZiped() {
234 return "gzip".equals(contentEncoding);
235 }
236
237 }