001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.jetspeed.resource;
018:
019: import java.io.ByteArrayOutputStream;
020: import java.io.CharArrayWriter;
021: import java.io.IOException;
022: import java.io.PrintWriter;
023: import java.util.ArrayList;
024: import java.util.HashMap;
025: import java.util.Iterator;
026: import java.util.Locale;
027: import java.util.Map.Entry;
028:
029: import javax.servlet.ServletOutputStream;
030: import javax.servlet.http.Cookie;
031: import javax.servlet.http.HttpServletResponse;
032: import javax.servlet.http.HttpServletResponseWrapper;
033:
034: /**
035: * <p>
036: * BufferedHttpServletResponse fully captures all HttpServletResponse interactions to be flushed out later.
037: * This wrapper is specifically written to allow included servlets to set headers, cookies, encoding etc. which isn't allowed by
038: * the servlet specification on included responses.
039: * </p>
040: * <p>
041: * Call flush(HttpServletResponse) after the include has returned to flush out the buffered data, headers and state.
042: * </p>
043: * <p>
044: * Note: the only method not fully supported by this buffered version is getCharacterEncoding(). Setting characterEncoding through
045: * setContentType or setLocale on this class won't be reflected in the return value from getCharacterEncoding(), and calling getWriter()
046: * won't set it either although calling setLocale, setContentType or setCharacterEncoding (servlet api 2.4+) after that will be ignored.
047: * But, when this object is flused to a (real) response, the contentType, locale and/or characterEncoding recorded will be set on the
048: * target response then.
049: * </p>
050: *
051: * @author <a href="mailto:ate@douma.nu">Ate Douma</a>
052: * @version $Id: BufferedHttpServletResponse.java 544024 2007-06-04 00:59:09Z ate $
053: */
054: public class BufferedHttpServletResponse extends
055: HttpServletResponseWrapper {
056: private static class CharArrayWriterBuffer extends CharArrayWriter {
057: public char[] getBuffer() {
058: return buf;
059: }
060:
061: public int getCount() {
062: return count;
063: }
064: }
065:
066: private ByteArrayOutputStream byteOutputBuffer;
067: private CharArrayWriterBuffer charOutputBuffer;
068: private ServletOutputStream outputStream;
069: private PrintWriter printWriter;
070: private HashMap headers;
071: private ArrayList cookies;
072: private int errorCode;
073: private int statusCode;
074: private String errorMessage;
075: private String redirectLocation;
076: private boolean committed;
077: private boolean hasStatus;
078: private boolean hasError;
079: private Locale locale;
080: private boolean closed;
081: private String characterEncoding;
082: private int contentLength = -1;
083: private String contentType;
084: private boolean flushed;
085:
086: public BufferedHttpServletResponse(HttpServletResponse response) {
087: super (response);
088: }
089:
090: public void flush(HttpServletResponse response) throws IOException {
091: if (flushed) {
092: throw new IllegalStateException("Already flushed");
093: }
094: flushed = true;
095:
096: if (locale != null) {
097: response.setLocale(locale);
098: }
099: if (contentType != null) {
100: response.setContentType(contentType);
101: }
102: if (characterEncoding != null) {
103: // setCharacterEncoding only available on Servlet Spec 2.4+
104: try {
105: response.getClass().getMethod("setCharacterEncoding",
106: new Class[] { String.class }).invoke(response,
107: new Object[] { characterEncoding });
108: } catch (NoSuchMethodException nsme) {
109: // servlet spec 2.3
110: } catch (Exception e) {
111: throw new RuntimeException(e);
112: }
113: }
114: if (cookies != null) {
115: for (int i = 0, size = cookies.size(); i < size; i++) {
116: response.addCookie((Cookie) cookies.get(i));
117: }
118: cookies = null;
119: }
120: if (headers != null) {
121: Iterator iter = headers.entrySet().iterator();
122: while (iter.hasNext()) {
123: Entry e = (Entry) iter.next();
124: String name = (String) e.getKey();
125: ArrayList values = (ArrayList) e.getValue();
126: for (int i = 0, size = values.size(); i < size; i++) {
127: Object value = values.get(i);
128: if (value instanceof Integer) {
129: response.addIntHeader(name, ((Integer) value)
130: .intValue());
131: } else if (value instanceof Long) {
132: response.addDateHeader(name, ((Long) value)
133: .longValue());
134: } else {
135: response.addHeader(name, (String) value);
136: }
137: }
138: }
139: headers = null;
140: }
141: if (contentLength > -1) {
142: response.setContentLength(contentLength);
143: }
144: if (hasStatus) {
145: response.setStatus(statusCode);
146: }
147: if (hasError) {
148: response.sendError(errorCode, errorMessage);
149: } else if (redirectLocation != null) {
150: response.sendRedirect(redirectLocation);
151: } else {
152: if (outputStream != null) {
153: if (!closed) {
154: outputStream.flush();
155: }
156: ServletOutputStream realOutputStream = response
157: .getOutputStream();
158: int len = byteOutputBuffer.size();
159: if (contentLength > -1 && contentLength < len) {
160: len = contentLength;
161: }
162: if (len > 0) {
163: realOutputStream.write(byteOutputBuffer
164: .toByteArray(), 0, len);
165: }
166: outputStream.close();
167: outputStream = null;
168: byteOutputBuffer = null;
169: } else if (printWriter != null) {
170: if (!closed) {
171: printWriter.flush();
172: if (charOutputBuffer.getCount() > 0) {
173: response.getWriter().write(
174: charOutputBuffer.getBuffer(), 0,
175: charOutputBuffer.getCount());
176: }
177: printWriter.close();
178:
179: printWriter = null;
180: charOutputBuffer = null;
181: }
182: }
183:
184: }
185: }
186:
187: private ArrayList getHeaderList(String name, boolean create) {
188: if (headers == null) {
189: headers = new HashMap();
190: }
191: ArrayList headerList = (ArrayList) headers.get(name);
192: if (headerList == null && create) {
193: headerList = new ArrayList();
194: headers.put(name, headerList);
195: }
196: return headerList;
197: }
198:
199: private void failIfCommitted() {
200: if (committed) {
201: throw new IllegalStateException(
202: "Response is already committed");
203: }
204: }
205:
206: /* (non-Javadoc)
207: * @see javax.servlet.http.HttpServletResponseWrapper#addCookie(javax.servlet.http.Cookie)
208: */
209: public void addCookie(Cookie cookie) {
210: if (!committed) {
211: if (cookies == null) {
212: cookies = new ArrayList();
213: }
214: cookies.add(cookie);
215: }
216: }
217:
218: /* (non-Javadoc)
219: * @see javax.servlet.http.HttpServletResponseWrapper#addDateHeader(java.lang.String, long)
220: */
221: public void addDateHeader(String name, long date) {
222: if (!committed) {
223: ArrayList headerList = getHeaderList(name, true);
224: headerList.add(new Long(date));
225: }
226: }
227:
228: /* (non-Javadoc)
229: * @see javax.servlet.http.HttpServletResponseWrapper#addHeader(java.lang.String, java.lang.String)
230: */
231: public void addHeader(String name, String value) {
232: if (!committed) {
233: ArrayList headerList = getHeaderList(name, true);
234: headerList.add(value);
235: }
236: }
237:
238: /* (non-Javadoc)
239: * @see javax.servlet.http.HttpServletResponseWrapper#addIntHeader(java.lang.String, int)
240: */
241: public void addIntHeader(String name, int value) {
242: if (!committed) {
243: ArrayList headerList = getHeaderList(name, true);
244: headerList.add(new Integer(value));
245: }
246: }
247:
248: /* (non-Javadoc)
249: * @see javax.servlet.http.HttpServletResponseWrapper#containsHeader(java.lang.String)
250: */
251: public boolean containsHeader(String name) {
252: return getHeaderList(name, false) != null;
253: }
254:
255: /* (non-Javadoc)
256: * @see javax.servlet.http.HttpServletResponseWrapper#sendError(int, java.lang.String)
257: */
258: public void sendError(int errorCode, String errorMessage)
259: throws IOException {
260: failIfCommitted();
261: committed = true;
262: closed = true;
263: hasError = true;
264: this .errorCode = errorCode;
265: this .errorMessage = errorMessage;
266: }
267:
268: /* (non-Javadoc)
269: * @see javax.servlet.http.HttpServletResponseWrapper#sendError(int)
270: */
271: public void sendError(int errorCode) throws IOException {
272: sendError(errorCode, null);
273: }
274:
275: /* (non-Javadoc)
276: * @see javax.servlet.http.HttpServletResponseWrapper#sendRedirect(java.lang.String)
277: */
278: public void sendRedirect(String redirectLocation)
279: throws IOException {
280: failIfCommitted();
281: closed = true;
282: committed = true;
283: this .redirectLocation = redirectLocation;
284: }
285:
286: /* (non-Javadoc)
287: * @see javax.servlet.http.HttpServletResponseWrapper#setDateHeader(java.lang.String, long)
288: */
289: public void setDateHeader(String name, long date) {
290: if (!committed) {
291: ArrayList headerList = getHeaderList(name, true);
292: headerList.clear();
293: headerList.add(new Long(date));
294: }
295: }
296:
297: /* (non-Javadoc)
298: * @see javax.servlet.http.HttpServletResponseWrapper#setHeader(java.lang.String, java.lang.String)
299: */
300: public void setHeader(String name, String value) {
301: if (!committed) {
302: ArrayList headerList = getHeaderList(name, true);
303: headerList.clear();
304: headerList.add(value);
305: }
306: }
307:
308: /* (non-Javadoc)
309: * @see javax.servlet.http.HttpServletResponseWrapper#setIntHeader(java.lang.String, int)
310: */
311: public void setIntHeader(String name, int value) {
312: if (!committed) {
313: ArrayList headerList = getHeaderList(name, true);
314: headerList.clear();
315: headerList.add(new Integer(value));
316: }
317: }
318:
319: /* (non-Javadoc)
320: * @see javax.servlet.http.HttpServletResponseWrapper#setStatus(int, java.lang.String)
321: */
322: public void setStatus(int statusCode, String message) {
323: throw new UnsupportedOperationException(
324: "This method is deprecated and no longer available");
325: }
326:
327: /* (non-Javadoc)
328: * @see javax.servlet.http.HttpServletResponseWrapper#setStatus(int)
329: */
330: public void setStatus(int statusCode) {
331: if (!committed) {
332: this .statusCode = statusCode;
333: this .hasStatus = true;
334: resetBuffer();
335: }
336: }
337:
338: /* (non-Javadoc)
339: * @see javax.servlet.ServletResponseWrapper#flushBuffer()
340: */
341: public void flushBuffer() throws IOException {
342: if (!closed) {
343: committed = true;
344: }
345: }
346:
347: /* (non-Javadoc)
348: * @see javax.servlet.ServletResponseWrapper#getBufferSize()
349: */
350: public int getBufferSize() {
351: return Integer.MAX_VALUE;
352: }
353:
354: /* (non-Javadoc)
355: * @see javax.servlet.ServletResponseWrapper#getCharacterEncoding()
356: */
357: public String getCharacterEncoding() {
358: return characterEncoding != null ? characterEncoding
359: : "ISO-8859-1";
360: }
361:
362: /* (non-Javadoc)
363: * @see javax.servlet.ServletResponseWrapper#getLocale()
364: */
365: public Locale getLocale() {
366: return locale != null ? locale : super .getLocale();
367: }
368:
369: /* (non-Javadoc)
370: * @see javax.servlet.ServletResponseWrapper#getOutputStream()
371: */
372: public ServletOutputStream getOutputStream() throws IOException {
373: if (outputStream == null) {
374: if (printWriter != null) {
375: throw new IllegalStateException(
376: "getWriter() has already been called on this response");
377: }
378: byteOutputBuffer = new ByteArrayOutputStream();
379: outputStream = new ServletOutputStream() {
380: public void write(int b) throws IOException {
381: if (!closed) {
382: byteOutputBuffer.write(b);
383: if (contentLength > -1
384: && byteOutputBuffer.size() >= contentLength) {
385: committed = true;
386: closed = true;
387: }
388: }
389: }
390: };
391: }
392: return outputStream;
393: }
394:
395: /* (non-Javadoc)
396: * @see javax.servlet.ServletResponseWrapper#getWriter()
397: */
398: public PrintWriter getWriter() throws IOException {
399: if (printWriter == null) {
400: if (outputStream != null) {
401: throw new IllegalStateException(
402: "getOutputStream() has already been called on this response");
403: }
404: charOutputBuffer = new CharArrayWriterBuffer();
405: printWriter = new PrintWriter(charOutputBuffer);
406: }
407: return printWriter;
408: }
409:
410: /* (non-Javadoc)
411: * @see javax.servlet.ServletResponseWrapper#isCommitted()
412: */
413: public boolean isCommitted() {
414: return committed;
415: }
416:
417: /* (non-Javadoc)
418: * @see javax.servlet.ServletResponseWrapper#reset()
419: */
420: public void reset() {
421: resetBuffer(); // fails if committed
422: headers = null;
423: cookies = null;
424: hasStatus = false;
425: contentLength = -1;
426: if (printWriter == null) {
427: contentType = null;
428: characterEncoding = null;
429: locale = null;
430: }
431: }
432:
433: /* (non-Javadoc)
434: * @see javax.servlet.ServletResponseWrapper#resetBuffer()
435: */
436: public void resetBuffer() {
437: failIfCommitted();
438: if (outputStream != null) {
439: try {
440: outputStream.flush();
441: } catch (Exception e) {
442: }
443: byteOutputBuffer.reset();
444: } else if (printWriter != null) {
445: printWriter.flush();
446: charOutputBuffer.reset();
447: }
448: }
449:
450: /* (non-Javadoc)
451: * @see javax.servlet.ServletResponseWrapper#setBufferSize(int)
452: */
453: public void setBufferSize(int size) {
454: failIfCommitted();
455: if ((charOutputBuffer != null && charOutputBuffer.size() > 0)
456: || (byteOutputBuffer != null && byteOutputBuffer.size() > 0)) {
457: throw new IllegalStateException(
458: "Content has already been written");
459: }
460: }
461:
462: /* (non-Javadoc)
463: * @see javax.servlet.ServletResponseWrapper#setCharacterEncoding(java.lang.String)
464: */
465: public void setCharacterEncoding(String charset) {
466: if (charset != null && !committed && printWriter == null) {
467: characterEncoding = charset;
468: }
469: }
470:
471: /* (non-Javadoc)
472: * @see javax.servlet.ServletResponseWrapper#setContentLength(int)
473: */
474: public void setContentLength(int len) {
475: if (!committed && printWriter == null && len > 0) {
476: contentLength = len;
477: if (outputStream != null) {
478: try {
479: outputStream.flush();
480: } catch (Exception e) {
481: }
482: }
483: if (!closed && byteOutputBuffer != null
484: && byteOutputBuffer.size() >= len) {
485: committed = true;
486: closed = true;
487: }
488: }
489: }
490:
491: /* (non-Javadoc)
492: * @see javax.servlet.ServletResponseWrapper#setContentType(java.lang.String)
493: */
494: public void setContentType(String type) {
495: if (!committed) {
496: contentType = type;
497: if (printWriter == null) {
498: // TODO: parse possible encoding for better return value from getCharacterEncoding()
499: }
500: }
501: }
502:
503: /* (non-Javadoc)
504: * @see javax.servlet.ServletResponseWrapper#setLocale(java.util.Locale)
505: */
506: public void setLocale(Locale locale) {
507: if (!committed) {
508: this .locale = locale;
509: /* NON-FIXABLE ISSUE: defaulting the characterEncoding from the Locale
510: This feature cannot be implemented/wrapped as it might depend on web.xml locale settings
511: */
512: }
513: }
514: }
|