001: /***************************************************************
002: * This file is part of the [fleXive](R) project.
003: *
004: * Copyright (c) 1999-2007
005: * UCS - unique computing solutions gmbh (http://www.ucs.at)
006: * All rights reserved
007: *
008: * The [fleXive](R) project is free software; you can redistribute
009: * it and/or modify it under the terms of the GNU General Public
010: * License as published by the Free Software Foundation;
011: * either version 2 of the License, or (at your option) any
012: * later version.
013: *
014: * The GNU General Public License can be found at
015: * http://www.gnu.org/copyleft/gpl.html.
016: * A copy is found in the textfile GPL.txt and important notices to the
017: * license from the author are found in LICENSE.txt distributed with
018: * these libraries.
019: *
020: * This library is distributed in the hope that it will be useful,
021: * but WITHOUT ANY WARRANTY; without even the implied warranty of
022: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
023: * GNU General Public License for more details.
024: *
025: * For further information about UCS - unique computing solutions gmbh,
026: * please see the company website: http://www.ucs.at
027: *
028: * For further information about [fleXive](R), please see the
029: * project website: http://www.flexive.org
030: *
031: *
032: * This copyright notice MUST APPEAR in all copies of the file!
033: ***************************************************************/package com.flexive.war.filter;
034:
035: import com.flexive.shared.FxSharedUtils;
036: import com.flexive.shared.exceptions.FxApplicationException;
037:
038: import javax.servlet.ServletOutputStream;
039: import javax.servlet.ServletResponse;
040: import javax.servlet.http.Cookie;
041: import javax.servlet.http.HttpServletResponse;
042: import javax.servlet.http.HttpServletResponseWrapper;
043: import java.io.IOException;
044: import java.io.OutputStream;
045: import java.io.PrintWriter;
046: import java.io.StringWriter;
047: import java.text.SimpleDateFormat;
048: import java.util.Date;
049:
050: /**
051: * Response Wrapper to provide access to the content length and status.
052: *
053: * @author Gregor Schober (gregor.schober@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
054: * @version $Rev: 211 $
055: */
056: public class FxResponseWrapper extends HttpServletResponseWrapper {
057:
058: private static final String HTTP_MODIFIED_AT_FORMATER_TXT = "EEE, dd MMM yyyy HH:mm:ss z";
059: private static final String HTTP_HEADER_EXPIRES = "Expires";
060: private static final String HTTP_HEADER_LAST_MODIFIED = "Last-Modified";
061: private static final String HTTP_HEADER_X_POWERED_BY = "X-Powered-By";
062: private static final String HTTP_HEADER_CACHE_CONTROL = "Cache-Control";
063: private static final String HTTP_HEADER_PRAGMA = "Pragma";
064:
065: FxOutputStream fos = null;
066: HttpServletResponse resp = null;
067: PrintWriter pw = null;
068: FxWriter fxWriter = null;
069: int length = -1;
070: int status = HttpServletResponse.SC_OK;
071: String status_msg = null;
072: public int level = 0;
073: boolean catchContent = false;
074: private long createdAt = System.currentTimeMillis();
075: private static long PAGE_ID_GEN = 0;
076: private long pageId = getPageId();
077: private String contentType = null;
078:
079: private static synchronized long getPageId() {
080: if (PAGE_ID_GEN == Long.MIN_VALUE) {
081: PAGE_ID_GEN = 0;
082: }
083: return PAGE_ID_GEN++;
084:
085: }
086:
087: public static enum CacheControl {
088: NO_CACHE, PUBLIC, PRIVATE
089: }
090:
091: private static SimpleDateFormat getHttpDateFormat() {
092: return new SimpleDateFormat(HTTP_MODIFIED_AT_FORMATER_TXT);
093: }
094:
095: /**
096: * Returns the modified-at date for a http response with
097: * the given time.
098: * <p/>
099: * This function uses the HTTP standard time format ("Sat, 07 Apr 2001 00:58:08 GMT")
100: *
101: * @param date the date to use
102: * @return the modified-at date with the current time
103: */
104: protected static String buildModifiedAtDate(Date date) {
105: // HTTP standard time format: "Sat, 07 Apr 2001 00:58:08 GMT"
106: // Apache server SSI format: Saturday, 08-Sep-2001 21:46:40 EDT
107: return getHttpDateFormat().format(date);
108: }
109:
110: /**
111: * Returns the setting of the ClientWriteThrough option.
112: *
113: * @return the setting of the ClientWriteThrough option.
114: */
115: public boolean isClientWriteThrough() {
116: return !catchContent;
117: }
118:
119: /**
120: * Sets the modified at date in the response to the given date.
121: *
122: * @param date the date to use
123: */
124: public void setModifiedAtDate(Date date) {
125: setHeader(HTTP_HEADER_LAST_MODIFIED, buildModifiedAtDate(date));
126: }
127:
128: /**
129: * Sets the x powered by header.
130: *
131: * @param value the value
132: */
133: public void setXPoweredBy(String value) {
134: setHeader(HTTP_HEADER_X_POWERED_BY, value);
135: }
136:
137: /**
138: * Force the browser to disable its cache.
139: * <p/>
140: * Ths functions sets the expires, cache control and pragma="no cache" headers
141: */
142: public void disableBrowserCache() {
143: setModifiedAtDate(new Date());
144: setHeader(HTTP_HEADER_EXPIRES, getHttpDateFormat().format(
145: new Date(0)));
146: setHeader(HTTP_HEADER_CACHE_CONTROL,
147: "no-cache, must-revalidate");
148: setHeader(HTTP_HEADER_PRAGMA, "no-cache");
149: }
150:
151: /**
152: * Enables the browser cache.
153: *
154: * @param ct the type, CacheControl.PUBLIC or CacheControl.PRIVATE
155: * @param maxAge in seconds, use null if you dont want this to be set - specifies the maximum amount of
156: * time that an object will be considered fresh.
157: * Similar to Expires, this directive allows more flexibility.
158: * [seconds] is the number of seconds from the time of the request you wish the object to be fresh for.
159: * @param mustRevalidate tells caches that they must obey any freshness information you give them about an object.
160: * The HTTP allows caches to take liberties with the freshness of objects; by specifying this header, you're
161: * telling the cache that you want it to strictly follow your rules.
162: */
163: public void enableBrowserCache(CacheControl ct, Integer maxAge,
164: boolean mustRevalidate) {
165: String sCacheControl = ct == CacheControl.PUBLIC ? "public"
166: : "private";
167: if (maxAge != null)
168: sCacheControl += ", max-age==" + maxAge;
169: if (mustRevalidate)
170: sCacheControl += ", must-revalidate";
171: setHeader(HTTP_HEADER_CACHE_CONTROL, sCacheControl);
172: setHeader(HTTP_HEADER_PRAGMA, "cache");
173: setDateHeader(HTTP_HEADER_EXPIRES,
174: System.currentTimeMillis() + 24 * 3600 * 1000);
175: setModifiedAtDate(new Date());
176: }
177:
178: @Override
179: public void setDateHeader(String s, long l) {
180: resp.setDateHeader(s, l);
181: }
182:
183: @Override
184: public void setHeader(String s, String s1) {
185: resp.setHeader(s, s1);
186: }
187:
188: /**
189: * Returns the wrapped response.
190: *
191: * @return the wrapped response
192: */
193: public HttpServletResponse getWrappedResponse() {
194: return resp;
195: }
196:
197: /**
198: * Returns true if generating the response has at least one error.
199: *
200: * @return true if generating the response has at least one error
201: */
202: public boolean hadError() {
203: return (this .getStatus() != HttpServletResponse.SC_OK);
204: }
205:
206: /**
207: * The status message delivered to the client, may be null.
208: *
209: * @return the status message delivered to the client
210: */
211: public String getStatusMsg() {
212: return this .status_msg == null ? "" : this .status_msg;
213: }
214:
215: /**
216: * The http response status delivered to the client.
217: *
218: * @return The http response status delivered to the client
219: */
220: public int getStatus() {
221: return this .status;
222: }
223:
224: @Override
225: public void reset() {
226: super .reset();
227: resetBuffer();
228: }
229:
230: @Override
231: public void setContentType(String s) {
232: if (s.equals("text/plain") && contentType != null) {
233: return;
234: }
235: super .setContentType(s);
236: this .resp.setContentType(s);
237: this .contentType = s;
238: }
239:
240: @Override
241: public void resetBuffer() {
242: super .resetBuffer();
243: pw = null; // reset our buffered response
244: fos = null;
245: }
246:
247: @Override
248: public int getBufferSize() {
249: return super .getBufferSize();
250: }
251:
252: @Override
253: public void setBufferSize(int i) {
254: super .setBufferSize(i);
255: }
256:
257: @Override
258: public void sendRedirect(String s) throws IOException {
259: super .sendRedirect(s);
260: }
261:
262: /**
263: * Constructor.
264: *
265: * @param resp the original response object
266: * @param catchData Has to be set to true if getData() is called later on.
267: */
268: public FxResponseWrapper(ServletResponse resp, boolean catchData) {
269: super ((HttpServletResponse) resp);
270: this .resp = (HttpServletResponse) resp;
271: this .catchContent = catchData;
272: }
273:
274: /**
275: * Returns a PrintWriter object that can send character text to the client.
276: *
277: * @return the writer
278: * @throws IOException
279: */
280: @Override
281: public PrintWriter getWriter() throws IOException {
282: if (pw == null) {
283: // Content type HAS to be set before the print writer is used
284: forceContentType(contentType);
285: // obtain and store the printwriter
286: fxWriter = new FxWriter(catchContent ? null : resp
287: .getWriter(), catchContent);
288: pw = new PrintWriter(fxWriter);
289: }
290: return pw;
291: }
292:
293: /**
294: * Forces the contentype directly at the wrapped catalina response, since the
295: * wrapper is not always allowing the type to change.
296: *
297: * @param type the content type
298: */
299: private void forceContentType(String type) {
300: if (type == null || type.length() == 0)
301: return;
302: this .resp.setContentType(type);
303: }
304:
305: /**
306: * Returns a ServletOutputStream suitable for writing binary data in the response.
307: *
308: * @return the output stream
309: * @throws IOException
310: */
311: @Override
312: public ServletOutputStream getOutputStream() throws IOException {
313: if (fos == null) {
314: fos = new FxOutputStream(catchContent ? null : resp
315: .getOutputStream(), catchContent, !catchContent);
316: }
317: return fos;
318: }
319:
320: /**
321: * Manual setter for the content length.
322: *
323: * @param i the length
324: */
325: @Override
326: public void setContentLength(int i) {
327: super .setContentLength(i);
328: this .resp.setContentLength(i);
329: length = i;
330: }
331:
332: /**
333: * Get the content length delivered to the client (without headers).
334: *
335: * @return the content length delivered to the client
336: */
337: public long getContentLength() {
338: return _getContentLength()
339: + ((status_msg == null) ? 0 : status_msg.length());
340: }
341:
342: /**
343: * Get the content length delivered to the client (without headers and error messages).
344: *
345: * @return the content length delivered to the client (without headers and error messages).
346: */
347: private long _getContentLength() {
348: // overrides
349: if (length > -1) {
350: return length;
351: }
352: //
353: if (fos == null && fxWriter == null)
354: return 0;
355: if (fos != null)
356: return fos.getContentLength();
357: return fxWriter.getContentLength();
358: }
359:
360: /**
361: * Returns the data sent to the client.
362: * <p/>
363: * The option catchContent has to be set to true in the constructor, or a empty byte array is returned.
364: *
365: * @return the data sent to the client.
366: */
367: public byte[] getData() {
368: if (!catchContent || (fos == null && fxWriter == null))
369: return new byte[0];
370:
371: if (fos != null)
372: return fos.getData();
373: return fxWriter.getData();
374: }
375:
376: /**
377: * Writes the cached data to the underlying response, or the given string if the
378: * override parameter is set.
379: * <p/>
380: * This function may only be called if ClientWriteThrough was not set to true
381: * at creation time.
382: *
383: * @param override overrides the data if set
384: * @throws IOException if an io exception occured
385: * @throws FxApplicationException if an exception occured
386: */
387: public void writeToUnderlyingResponse(String override)
388: throws IOException, FxApplicationException {
389: if (!catchContent) {
390: throw new FxApplicationException(
391: "Cannot write to underlying response, ClientWriteThrough option is set");
392: }
393: byte _data[] = override != null ? FxSharedUtils
394: .getBytes(override) : this .getData();
395: final OutputStream out = this .resp.getOutputStream();
396: this .resp.setStatus(this .getStatus());
397: this .resp.setContentLength(_data.length);
398: if (contentType != null)
399: this .resp.setContentType(contentType);
400: this .resp.setContentType(contentType);
401: out.write(_data);
402: }
403:
404: /**
405: * Return the wrapped ServletResponse object.
406: *
407: * @return the wrapped response object
408: */
409: @Override
410: public ServletResponse getResponse() {
411: return this .resp;
412: }
413:
414: /**
415: * Sets the wrapped ServletResponse object.
416: *
417: * @param servletResponse the wrapped response
418: */
419: @Override
420: public void setResponse(ServletResponse servletResponse) {
421: this .resp = (HttpServletResponse) servletResponse;
422: }
423:
424: /**
425: * Sets the status code for this response
426: *
427: * @param i the status
428: */
429: @Override
430: public void setStatus(int i) {
431: if (!catchContent)
432: resp.setStatus(i);
433: }
434:
435: /**
436: * Sets the status code for this response
437: *
438: * @param i the status
439: * @param s the status message
440: * @deprecated
441: */
442: @Override
443: public void setStatus(int i, String s) {
444: if (!catchContent) //noinspection deprecation
445: resp.setStatus(i, s);
446: this .status = i;
447: this .status_msg = s;
448: }
449:
450: /**
451: * Sends a error to the client.
452: *
453: * @param i the error code
454: * @param s the error message
455: * @throws IOException if a io exception occured
456: */
457: @Override
458: public void sendError(int i, String s) throws IOException {
459: if (!catchContent)
460: resp.sendError(i, s);
461: this .status = i;
462: this .status_msg = s;
463: }
464:
465: /**
466: * Sends a error to the client.
467: *
468: * @param i the error code
469: * @param t the error
470: * @throws IOException if a io exception occured
471: */
472: public void sendError(int i, Throwable t) throws IOException {
473: String message = t != null ? t.getMessage() : null;
474: if (message == null) {
475: message = "Exception occured: "
476: + (t != null ? t.getClass().getName() : "null");
477: }
478: message += "\n\n" + getStackTrace(t);
479: if (!catchContent)
480: resp.sendError(i, message);
481: this .status = i;
482: this .status_msg = message;
483: }
484:
485: /**
486: * Gets the stacktrace as a string from a throwable.
487: *
488: * @param th the throwable
489: * @return the stacktrace as string
490: */
491: private String getStackTrace(Throwable th) {
492: StringWriter sw = null;
493: PrintWriter pw = null;
494: try {
495: sw = new StringWriter();
496: pw = new PrintWriter(sw);
497: th.printStackTrace(pw);
498: return sw.toString();
499: } catch (Throwable t) {
500: return "n/a";
501: } finally {
502: try {
503: if (sw != null)
504: sw.close();
505: } catch (Throwable t) {/*ignore*/
506: }
507: try {
508: if (pw != null)
509: pw.close();
510: } catch (Throwable t) {/*ignore*/
511: }
512: }
513: }
514:
515: /**
516: * Sends a error to the client.
517: *
518: * @param i the error code
519: * @throws IOException if a exception occurs
520: */
521: @Override
522: public void sendError(int i) throws IOException {
523: if (!catchContent)
524: resp.sendError(i);
525: this .status = i;
526: }
527:
528: /**
529: * Flushes the buffers.
530: *
531: * @throws IOException
532: */
533: @Override
534: public void flushBuffer() throws IOException {
535: super .flushBuffer();
536: if (fos != null)
537: fos.flush();
538: if (pw != null)
539: pw.flush();
540: }
541:
542: /**
543: * Returns true if the reponse was commited.
544: *
545: * @return true if the reponse was commited
546: */
547: @Override
548: public boolean isCommitted() {
549: if (fos != null)
550: return fos.isCommited();
551: if (fxWriter != null)
552: return fxWriter.isCommited();
553: return resp.isCommitted();
554: }
555:
556: /**
557: * Adds the specified cookie to the response. This method can be called multiple times to set more than one cookie.
558: *
559: * @param cookie the cookie to return to the client
560: */
561: @Override
562: public void addCookie(Cookie cookie) {
563: resp.addCookie(cookie);
564: }
565:
566: /**
567: * Returns a boolean indicating whether the named response header has already been set.
568: *
569: * @param s the header name
570: * @return a boolean indicating whether the named response header has already been set
571: */
572: @Override
573: public boolean containsHeader(String s) {
574: return resp.containsHeader(s);
575: }
576:
577: /**
578: * Returns the time that this response object was created at.
579: *
580: * @return the time that this response object was created at
581: */
582: public long getCreatedAt() {
583: return createdAt;
584: }
585:
586: /**
587: * Returns the unique responseWrapper id.
588: * <p/>
589: * IDs are reused when MAX_LONGVALUE is reached.
590: *
591: * @return the unique responseWrapper id
592: */
593: public long getId() {
594: return pageId;
595: }
596: }
|