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 @Override
116 public ServletOutputStream getOutputStream() throws IOException {
117 return wrappedStream;
118 }
119
120 public ThresholdingOutputStream getThresholdingOutputStream() throws IOException {
121 return thresholdingOutputStream;
122 }
123
124 @Override
125 public PrintWriter getWriter() throws IOException {
126 if (wrappedWriter == null) {
127 String encoding = getCharacterEncoding();
128 wrappedWriter = encoding != null
129 ? new PrintWriter(new OutputStreamWriter(getOutputStream(), encoding))
130 : new PrintWriter(new OutputStreamWriter(getOutputStream()));
131 }
132
133 return wrappedWriter;
134 }
135
136 @Override
137 public void flushBuffer() throws IOException {
138 flush();
139 }
140
141 public void flush() throws IOException {
142 wrappedStream.flush();
143
144 if (wrappedWriter != null) {
145 wrappedWriter.flush();
146 }
147 }
148
149
150 @Override
151 public void reset() {
152 super.reset();
153
154
155
156 wrappedWriter = null;
157 status = SC_OK;
158
159
160 headers.clear();
161
162
163 }
164
165
166 @Override
167 public void resetBuffer() {
168 super.resetBuffer();
169
170
171
172 wrappedWriter = null;
173 }
174
175 public int getStatus() {
176 return status;
177 }
178
179 public boolean isError() {
180 return isError;
181 }
182
183 public MultiMap getHeaders() {
184 return headers;
185 }
186
187 public long getLastModified() {
188
189
190 final Collection values = (Collection) headers.get("Last-Modified");
191 if (values == null || values.size() != 1) {
192 throw new IllegalStateException("Can't get Last-Modified header : no or multiple values : " + values);
193 }
194 final Object value = values.iterator().next();
195 if (value instanceof String) {
196 return parseStringDate((String) value);
197 } else if (value instanceof Long) {
198 return ((Long)value).longValue();
199 } else {
200 throw new IllegalStateException("Can't get Last-Modified header : " + value);
201 }
202 }
203
204 private long parseStringDate(String value) {
205 try {
206 return DateUtil.parseDate(value).getTime();
207 } catch (DateParseException e) {
208 throw new IllegalStateException("Could not parse Last-Modified header with value " + value + " : " + e.getMessage());
209 }
210 }
211
212
213
214
215
216 public int getTimeToLiveInSeconds() {
217 return responseExpirationCalculator.getMaxAgeInSeconds();
218 }
219
220 public String getRedirectionLocation() {
221 return redirectionLocation;
222 }
223
224 @Override
225 public void setDateHeader(String name, long date) {
226 replaceHeader(name, Long.valueOf(date));
227 }
228
229 @Override
230 public void addDateHeader(String name, long date) {
231 appendHeader(name, Long.valueOf(date));
232 }
233
234 @Override
235 public void setHeader(String name, String value) {
236 replaceHeader(name, value);
237 }
238
239 @Override
240 public void addHeader(String name, String value) {
241 appendHeader(name, value);
242 }
243
244 @Override
245 public void setIntHeader(String name, int value) {
246 replaceHeader(name, Integer.valueOf(value));
247 }
248
249 @Override
250 public void addIntHeader(String name, int value) {
251 appendHeader(name, Integer.valueOf(value));
252 }
253
254 @Override
255 public boolean containsHeader(String name) {
256 return headers.containsKey(name);
257 }
258
259 private void replaceHeader(String name, Object value) {
260 if (!responseExpirationCalculator.addHeader(name, value)) {
261 headers.remove(name);
262 headers.put(name, value);
263 }
264 }
265
266 private void appendHeader(String name, Object value) {
267 if (!responseExpirationCalculator.addHeader(name, value)) {
268 headers.put(name, value);
269 }
270 }
271
272 @Override
273 public void setStatus(int status) {
274 this.status = status;
275 }
276
277 @Override
278 public void setStatus(int status, String string) {
279 this.status = status;
280 }
281
282 @Override
283 public void sendRedirect(String location) throws IOException {
284 this.status = SC_MOVED_TEMPORARILY;
285 this.redirectionLocation = location;
286 }
287
288 @Override
289 public void sendError(int status, String errorMsg) throws IOException {
290 this.errorMsg = errorMsg;
291 this.status = status;
292 this.isError = true;
293 }
294
295 @Override
296 public void sendError(int status) throws IOException {
297 this.status = status;
298 this.isError = true;
299 }
300
301 @Override
302 public void setContentLength(int len) {
303 this.contentLength = len;
304 }
305
306 public int getContentLength() {
307 return (int)(contentLength >=0 ? contentLength : thresholdingOutputStream.getByteCount());
308 }
309
310 public void replay(HttpServletResponse target) throws IOException {
311 replayHeadersAndStatus(target);
312 replayContent(target, true);
313 }
314
315 public void replayHeadersAndStatus(HttpServletResponse target) throws IOException {
316 if(isError){
317 if(errorMsg != null){
318 target.sendError(status, errorMsg);
319 }
320 else{
321 target.sendError(status);
322 }
323 }
324 else if(redirectionLocation != null){
325 target.sendRedirect(redirectionLocation);
326 }
327 else{
328 target.setStatus(status);
329 }
330
331 target.setStatus(getStatus());
332
333 final Iterator it = headers.keySet().iterator();
334 while (it.hasNext()) {
335 final String header = (String) it.next();
336
337 final Collection values = (Collection) headers.get(header);
338 final Iterator valIt = values.iterator();
339 while (valIt.hasNext()) {
340 final Object val = valIt.next();
341 RequestHeaderUtil.setHeader(target, header, val);
342 }
343 }
344
345
346 target.setContentType(getContentType());
347 target.setCharacterEncoding(getCharacterEncoding());
348 }
349
350 public void replayContent(HttpServletResponse target, boolean setContentLength) throws IOException {
351 if(setContentLength){
352 target.setContentLength(getContentLength());
353 }
354 if(getContentLength()>0){
355 if(isThresholdExceeded()){
356 FileInputStream in = FileUtils.openInputStream(getContentFile());
357 IOUtils.copy(in, target.getOutputStream());
358 IOUtils.closeQuietly(in);
359 }
360 else{
361 IOUtils.copy(new ByteArrayInputStream(inMemoryBuffer.toByteArray()), target.getOutputStream());
362 }
363 target.flushBuffer();
364 }
365 }
366
367 private final class ThresholdingCacheOutputStream extends ThresholdingOutputStream {
368 OutputStream out = inMemoryBuffer;
369
370 private ThresholdingCacheOutputStream(int threshold) {
371 super(threshold);
372 }
373
374 @Override
375 protected OutputStream getStream() throws IOException {
376 return out;
377 }
378
379 @Override
380 protected void thresholdReached() throws IOException {
381 if(serveIfThresholdReached){
382 replayHeadersAndStatus(originalResponse);
383 out = originalResponse.getOutputStream();
384 }
385 else{
386 contentFile = File.createTempFile("cacheStream", null, Path.getTempDirectory());
387 contentFile.deleteOnExit();
388 out = new FileOutputStream(contentFile);
389 }
390 out.write(getBufferedContent());
391 }
392 }
393
394 }