1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.module.cache.filter;
35
36 import info.magnolia.cms.core.Path;
37 import info.magnolia.cms.util.RequestHeaderUtil;
38
39 import java.io.ByteArrayInputStream;
40 import java.io.ByteArrayOutputStream;
41 import java.io.File;
42 import java.io.FileInputStream;
43 import java.io.FileOutputStream;
44 import java.io.IOException;
45 import java.io.OutputStream;
46 import java.io.OutputStreamWriter;
47 import java.io.PrintWriter;
48 import java.util.Collection;
49 import java.util.Iterator;
50
51 import javax.servlet.ServletOutputStream;
52 import javax.servlet.http.HttpServletResponse;
53 import javax.servlet.http.HttpServletResponseWrapper;
54
55 import org.apache.commons.collections.MultiMap;
56 import org.apache.commons.collections.map.MultiValueMap;
57 import org.apache.commons.httpclient.util.DateParseException;
58 import org.apache.commons.httpclient.util.DateUtil;
59 import org.apache.commons.io.FileUtils;
60 import org.apache.commons.io.IOUtils;
61 import org.apache.commons.io.output.ThresholdingOutputStream;
62
63
64
65
66
67
68
69
70
71
72 public class CacheResponseWrapper extends HttpServletResponseWrapper {
73
74 public static final int DEFAULT_THRESHOLD = 500 * 1024;
75
76 private final ServletOutputStream wrappedStream;
77 private PrintWriter wrappedWriter = null;
78 private final MultiMap headers = new MultiValueMap();
79 private int status = SC_OK;
80 private boolean isError;
81 private String redirectionLocation;
82 private HttpServletResponse originalResponse;
83 private ByteArrayOutputStream inMemoryBuffer;
84 private File contentFile;
85 private long contentLength = -1;
86 private ResponseExpirationCalculator responseExpirationCalculator = new ResponseExpirationCalculator();
87
88 private ThresholdingOutputStream thresholdingOutputStream;
89 private boolean serveIfThresholdReached;
90
91 private String errorMsg;
92
93 public CacheResponseWrapper(final HttpServletResponse response, int threshold, boolean serveIfThresholdReached) {
94 super(response);
95 this.serveIfThresholdReached = serveIfThresholdReached;
96 this.originalResponse = response;
97 this.inMemoryBuffer = new ByteArrayOutputStream();
98 this.thresholdingOutputStream = new ThresholdingCacheOutputStream(threshold);
99 this.wrappedStream = new SimpleServletOutputStream(thresholdingOutputStream);
100 }
101
102 public boolean isThresholdExceeded() {
103 return thresholdingOutputStream.isThresholdExceeded();
104 }
105
106 public byte[] getBufferedContent(){
107 return inMemoryBuffer.toByteArray();
108 }
109
110 public File getContentFile() {
111 return contentFile;
112 }
113
114
115 public ServletOutputStream getOutputStream() throws IOException {
116 return wrappedStream;
117 }
118
119 public ThresholdingOutputStream getThresholdingOutputStream() throws IOException {
120 return thresholdingOutputStream;
121 }
122
123 public PrintWriter getWriter() throws IOException {
124 if (wrappedWriter == null) {
125 String encoding = getCharacterEncoding();
126 wrappedWriter = encoding != null
127 ? new PrintWriter(new OutputStreamWriter(getOutputStream(), encoding))
128 : new PrintWriter(new OutputStreamWriter(getOutputStream()));
129 }
130
131 return wrappedWriter;
132 }
133
134 public void flushBuffer() throws IOException {
135 flush();
136 }
137
138 public void flush() throws IOException {
139 wrappedStream.flush();
140
141 if (wrappedWriter != null) {
142 wrappedWriter.flush();
143 }
144 }
145
146
147 public void reset() {
148 super.reset();
149
150
151
152 wrappedWriter = null;
153 status = SC_OK;
154
155
156 headers.clear();
157
158
159 }
160
161
162 public void resetBuffer() {
163 super.resetBuffer();
164
165
166
167 wrappedWriter = null;
168 }
169
170 public int getStatus() {
171 return status;
172 }
173
174 public boolean isError() {
175 return isError;
176 }
177
178 public MultiMap getHeaders() {
179 return headers;
180 }
181
182 public long getLastModified() {
183
184
185 final Collection values = (Collection) headers.get("Last-Modified");
186 if (values == null || values.size() != 1) {
187 throw new IllegalStateException("Can't get Last-Modified header : no or multiple values : " + values);
188 }
189 final Object value = values.iterator().next();
190 if (value instanceof String) {
191 return parseStringDate((String) value);
192 } else if (value instanceof Long) {
193 return ((Long)value).longValue();
194 } else {
195 throw new IllegalStateException("Can't get Last-Modified header : " + value);
196 }
197 }
198
199 private long parseStringDate(String value) {
200 try {
201 return DateUtil.parseDate(value).getTime();
202 } catch (DateParseException e) {
203 throw new IllegalStateException("Could not parse Last-Modified header with value " + value + " : " + e.getMessage());
204 }
205 }
206
207
208
209
210
211 public int getTimeToLiveInSeconds() {
212 return responseExpirationCalculator.getMaxAgeInSeconds();
213 }
214
215 public String getRedirectionLocation() {
216 return redirectionLocation;
217 }
218
219 public void setDateHeader(String name, long date) {
220 replaceHeader(name, new Long(date));
221 }
222
223 public void addDateHeader(String name, long date) {
224 appendHeader(name, new Long(date));
225 }
226
227 public void setHeader(String name, String value) {
228 replaceHeader(name, value);
229 }
230
231 public void addHeader(String name, String value) {
232 appendHeader(name, value);
233 }
234
235 public void setIntHeader(String name, int value) {
236 replaceHeader(name, new Integer(value));
237 }
238
239 public void addIntHeader(String name, int value) {
240 appendHeader(name, new Integer(value));
241 }
242
243 @Override
244 public boolean containsHeader(String name) {
245 return headers.containsKey(name);
246 }
247
248 private void replaceHeader(String name, Object value) {
249 if (!responseExpirationCalculator.addHeader(name, value)) {
250 headers.remove(name);
251 headers.put(name, value);
252 }
253 }
254
255 private void appendHeader(String name, Object value) {
256 if (!responseExpirationCalculator.addHeader(name, value)) {
257 headers.put(name, value);
258 }
259 }
260
261 public void setStatus(int status) {
262 this.status = status;
263 }
264
265 public void setStatus(int status, String string) {
266 this.status = status;
267 }
268
269 public void sendRedirect(String location) throws IOException {
270 this.status = SC_MOVED_TEMPORARILY;
271 this.redirectionLocation = location;
272 }
273
274 public void sendError(int status, String errorMsg) throws IOException {
275 this.errorMsg = errorMsg;
276 this.status = status;
277 this.isError = true;
278 }
279
280 public void sendError(int status) throws IOException {
281 this.status = status;
282 this.isError = true;
283 }
284
285 public void setContentLength(int len) {
286 this.contentLength = len;
287 }
288
289 public int getContentLength() {
290 return (int)(contentLength >=0 ? contentLength : thresholdingOutputStream.getByteCount());
291 }
292
293 public void replay(HttpServletResponse target) throws IOException {
294 replayHeadersAndStatus(target);
295 replayContent(target, true);
296 }
297
298 public void replayHeadersAndStatus(HttpServletResponse target) throws IOException {
299 if(isError){
300 if(errorMsg != null){
301 target.sendError(status, errorMsg);
302 }
303 else{
304 target.sendError(status);
305 }
306 }
307 else if(redirectionLocation != null){
308 target.sendRedirect(redirectionLocation);
309 }
310 else{
311 target.setStatus(status);
312 }
313
314 target.setStatus(getStatus());
315
316 final Iterator it = headers.keySet().iterator();
317 while (it.hasNext()) {
318 final String header = (String) it.next();
319
320 final Collection values = (Collection) headers.get(header);
321 final Iterator valIt = values.iterator();
322 while (valIt.hasNext()) {
323 final Object val = valIt.next();
324 RequestHeaderUtil.setHeader(target, header, val);
325 }
326 }
327
328
329 target.setContentType(getContentType());
330 target.setCharacterEncoding(getCharacterEncoding());
331 }
332
333 public void replayContent(HttpServletResponse target, boolean setContentLength) throws IOException {
334 if(setContentLength){
335 target.setContentLength(getContentLength());
336 }
337 if(getContentLength()>0){
338 if(isThresholdExceeded()){
339 FileInputStream in = FileUtils.openInputStream(getContentFile());
340 IOUtils.copy(in, target.getOutputStream());
341 IOUtils.closeQuietly(in);
342 }
343 else{
344 IOUtils.copy(new ByteArrayInputStream(inMemoryBuffer.toByteArray()), target.getOutputStream());
345 }
346 target.flushBuffer();
347 }
348 }
349
350 private final class ThresholdingCacheOutputStream extends ThresholdingOutputStream {
351 OutputStream out = inMemoryBuffer;
352
353 private ThresholdingCacheOutputStream(int threshold) {
354 super(threshold);
355 }
356
357 @Override
358 protected OutputStream getStream() throws IOException {
359 return out;
360 }
361
362 @Override
363 protected void thresholdReached() throws IOException {
364 if(serveIfThresholdReached){
365 replayHeadersAndStatus(originalResponse);
366 out = originalResponse.getOutputStream();
367 }
368 else{
369 contentFile = File.createTempFile("cacheStream", null, Path.getTempDirectory());
370 contentFile.deleteOnExit();
371 out = new FileOutputStream(contentFile);
372 }
373 out.write(getBufferedContent());
374 }
375 }
376
377 }