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