001: /*
002: * Copyright 2001-2007 Geert Bevin <gbevin[remove] at uwyn dot com>
003: * Distributed under the terms of either:
004: * - the common development and distribution license (CDDL), v1.0; or
005: * - the GNU Lesser General Public License, v2.1 or later
006: * $Id: AbstractResponse.java 3634 2007-01-08 21:42:24Z gbevin $
007: */
008: package com.uwyn.rife.engine;
009:
010: import com.uwyn.rife.config.RifeConfig;
011: import com.uwyn.rife.engine.exceptions.EmbeddedElementCantSetContentLengthException;
012: import com.uwyn.rife.engine.exceptions.EngineException;
013: import com.uwyn.rife.engine.exceptions.ResponseOutputStreamRetrievalErrorException;
014: import com.uwyn.rife.template.InternalString;
015: import com.uwyn.rife.template.Template;
016: import com.uwyn.rife.tools.HttpUtils;
017: import java.io.ByteArrayOutputStream;
018: import java.io.IOException;
019: import java.io.OutputStream;
020: import java.util.ArrayList;
021: import java.util.Collection;
022: import java.util.zip.GZIPOutputStream;
023:
024: /**
025: * This abstract class implements parts of the {@link Response} interface to
026: * provide behaviour that is specific to RIFE.
027: * <p>Additional abstract methods have been provided to integrate with the
028: * concrete back-end classes that extend <code>AbstractResponse</code>.
029: *
030: * @author Geert Bevin (gbevin[remove] at uwyn dot com)
031: * @version $Revision: 3634 $
032: * @since 1.1
033: */
034: public abstract class AbstractResponse implements Response {
035: private Request mRequest = null;
036: private boolean mEmbedded = false;
037: private ElementSupport mLastElement = null;
038: private String mContentType = null;
039: private boolean mTextBufferEnabled = true;
040: private ArrayList<CharSequence> mTextBuffer = null;
041: private OutputStream mResponseOutputStream = null;
042: private ByteArrayOutputStream mGzipByteOutputStream = null;
043: private GZIPOutputStream mGzipOutputStream = null;
044: private OutputStream mOutputStream = null;
045:
046: /**
047: * This method needs to be implemented by the extending back-end class and
048: * will be called by <code>AbstractResponse</code> during the
049: * RIFE-specific additional behaviour. It behaves exactly like its {@link
050: * Response#setContentType(String) counter-part in the Response interface}.
051: *
052: * @see Response#setContentType(String)
053: * @since 1.1
054: */
055: protected abstract void _setContentType(String contentType);
056:
057: /**
058: * This method needs to be implemented by the extending back-end class and
059: * will be called by <code>AbstractResponse</code> during the
060: * RIFE-specific additional behaviour. It behaves exactly like its {@link
061: * Response#getCharacterEncoding() counter-part in the Response interface}.
062: *
063: * @see Response#getCharacterEncoding()
064: * @since 1.1
065: */
066: protected abstract String _getCharacterEncoding();
067:
068: /**
069: * This method needs to be implemented by the extending back-end class and
070: * will be called by <code>AbstractResponse</code> during the
071: * RIFE-specific additional behaviour. It behaves exactly like its {@link
072: * Response#setContentLength(int) counter-part in the Response interface}.
073: *
074: * @see Response#setContentLength(int)
075: * @since 1.1
076: */
077: protected abstract void _setContentLength(int length);
078:
079: /**
080: * This method needs to be implemented by the extending back-end class and
081: * will be called by <code>AbstractResponse</code> during the
082: * RIFE-specific additional behaviour. It behaves exactly like its {@link
083: * Response#sendRedirect(String) counter-part in the Response interface}.
084: *
085: * @see Response#sendRedirect(String)
086: * @since 1.1
087: */
088: protected abstract void _sendRedirect(String location);
089:
090: /**
091: * This method needs to be implemented by the extending back-end class and
092: * will be called by <code>AbstractResponse</code> during the
093: * RIFE-specific additional behaviour. It behaves exactly like its {@link
094: * Response#getOutputStream() counter-part in the Request interface}.
095: *
096: * @see Response#getOutputStream()
097: * @since 1.1
098: */
099: protected abstract OutputStream _getOutputStream()
100: throws IOException;
101:
102: /**
103: * Constructor that needs to be called by all the constructors of the
104: * extending classes.
105: *
106: * @param request the {@link Request} that is associated with this
107: * response
108: * @param embedded <code>true</code> if the response is embedded; or
109: * <p><code>false</code> otherwise
110: * @since 1.1
111: */
112: protected AbstractResponse(Request request, boolean embedded) {
113: mRequest = request;
114: mEmbedded = embedded;
115: }
116:
117: /**
118: * Retrieves the request that is associated with this response.
119: *
120: * @return the associated request
121: * @since 1.1
122: */
123: public Request getRequest() {
124: return mRequest;
125: }
126:
127: /**
128: * Retrieves the last element that has been processed with this response.
129: *
130: * @return the last processed element
131: * @since 1.1
132: */
133: public ElementSupport getLastElement() {
134: return mLastElement;
135: }
136:
137: public void setLastElement(ElementSupport element) {
138: mLastElement = element;
139: }
140:
141: public ArrayList<CharSequence> getEmbeddedContent() {
142: if (!mEmbedded) {
143: return null;
144: }
145:
146: if (null == mOutputStream) {
147: return mTextBuffer;
148: }
149:
150: flush();
151:
152: return ((EmbeddedStream) mOutputStream).getEmbeddedContent();
153: }
154:
155: public boolean isEmbedded() {
156: return mEmbedded;
157: }
158:
159: public boolean isContentTypeSet() {
160: return mContentType != null;
161: }
162:
163: public String getContentType() {
164: return mContentType;
165: }
166:
167: public void setContentType(String contentType) {
168: if (mEmbedded) {
169: return;
170: }
171:
172: if (null == contentType) {
173: return;
174: }
175:
176: if (-1 == contentType.indexOf(HttpUtils.CHARSET)) {
177: contentType = contentType + "; charset=UTF-8";
178: }
179:
180: mContentType = contentType;
181: _setContentType(contentType);
182: }
183:
184: public void enableTextBuffer(boolean enabled) {
185: if (mTextBufferEnabled != enabled) {
186: flush();
187: }
188:
189: mTextBufferEnabled = enabled;
190: }
191:
192: public boolean isTextBufferEnabled() {
193: return mTextBufferEnabled;
194: }
195:
196: public void print(Template template) throws EngineException {
197: if (null == template)
198: return;
199:
200: print(template.getDeferredContent());
201: }
202:
203: public void print(Collection<CharSequence> deferredContent)
204: throws EngineException {
205: if (!isContentTypeSet()) {
206: setContentType(RifeConfig.Engine.getDefaultContentType());
207: }
208:
209: if (null == deferredContent || 0 == deferredContent.size()) {
210: return;
211: }
212:
213: if (mTextBufferEnabled) {
214: if (mOutputStream != null) {
215: try {
216: mOutputStream.flush();
217: } catch (IOException e) {
218: throw new EngineException(e);
219: }
220: }
221:
222: if (null == mTextBuffer) {
223: mTextBuffer = new ArrayList<CharSequence>();
224: }
225: mTextBuffer.addAll(deferredContent);
226: } else {
227: writeDeferredContent(deferredContent);
228: }
229: }
230:
231: public void print(Object value) throws EngineException {
232: if (!isContentTypeSet()) {
233: setContentType(RifeConfig.Engine.getDefaultContentType());
234: }
235:
236: if (null == value) {
237: return;
238: }
239:
240: String text = String.valueOf(value);
241:
242: if (mTextBufferEnabled) {
243: if (mOutputStream != null) {
244: try {
245: mOutputStream.flush();
246: } catch (IOException e) {
247: throw new EngineException(e);
248: }
249: }
250:
251: if (null == mTextBuffer) {
252: mTextBuffer = new ArrayList<CharSequence>();
253: }
254: mTextBuffer.add(text);
255: } else {
256: ensureOutputStream();
257:
258: try {
259: mOutputStream.write(text
260: .getBytes(getCharacterEncoding()));
261: mOutputStream.flush();
262: } catch (IOException e) {
263: throw new EngineException(e);
264: }
265: }
266: }
267:
268: public String getCharacterEncoding() {
269: if (mEmbedded) {
270: return RifeConfig.Engine.getResponseEncoding();
271: }
272:
273: String encoding = _getCharacterEncoding();
274: if (encoding == null) {
275: encoding = RifeConfig.Engine.getResponseEncoding();
276: }
277:
278: return encoding;
279: }
280:
281: private void writeDeferredContent(
282: Collection<CharSequence> deferredContent)
283: throws EngineException {
284: if (!mEmbedded) {
285: // create a string version of each char sequence so that any state operation happens
286: // before any content is actually being written
287: for (CharSequence charsequence : deferredContent) {
288: charsequence.toString();
289: }
290: }
291:
292: ensureOutputStream();
293:
294: String encoding = getCharacterEncoding();
295: try {
296: mOutputStream.flush();
297:
298: if (mEmbedded) {
299: EmbeddedStream embedded_stream = (EmbeddedStream) mOutputStream;
300: for (CharSequence charsequence : deferredContent) {
301: embedded_stream.write(charsequence);
302: }
303: } else {
304: // write the content to the output stream
305: for (CharSequence charsequence : deferredContent) {
306: if (charsequence instanceof com.uwyn.rife.template.InternalString) {
307: mOutputStream
308: .write(((InternalString) charsequence)
309: .getBytes(encoding));
310: } else if (charsequence instanceof java.lang.String) {
311: mOutputStream.write(((String) charsequence)
312: .getBytes(encoding));
313: }
314: }
315: }
316:
317: mOutputStream.flush();
318: } catch (IOException e) {
319: // don't do anything since this exception is merely caused by someone that
320: // stopped or closed his browsing request
321: }
322: }
323:
324: public void clearBuffer() {
325: if (mTextBuffer != null && mTextBuffer.size() > 0) {
326: mTextBuffer.clear();
327: }
328: }
329:
330: public void flush() throws EngineException {
331: if (mTextBuffer != null && mTextBuffer.size() > 0) {
332: writeDeferredContent(mTextBuffer);
333:
334: mTextBuffer.clear();
335: }
336:
337: if (mOutputStream != null) {
338: try {
339: mOutputStream.flush();
340: } catch (IOException e) {
341: // don't do anything, the response stream has probably been
342: // closed or reset
343: }
344: }
345: }
346:
347: public void close() throws EngineException {
348: flush();
349:
350: if (!mEmbedded && mOutputStream != null) {
351: try {
352: if (mGzipOutputStream != null) {
353: mGzipOutputStream.flush();
354: mGzipOutputStream.finish();
355:
356: byte[] bytes = mGzipByteOutputStream.toByteArray();
357:
358: mGzipOutputStream = null;
359: mGzipByteOutputStream = null;
360:
361: setContentLength(bytes.length);
362: addHeader("Content-Encoding", "gzip");
363: mResponseOutputStream.write(bytes);
364: mOutputStream = mResponseOutputStream;
365: }
366:
367: try {
368: mOutputStream.flush();
369: mOutputStream.close();
370: } catch (IOException e) {
371: // don't do anything, the response stream has probably been
372: // closed or reset
373: }
374:
375: mOutputStream = null;
376: } catch (IOException e) {
377: // don't do anything, the response stream has probably been
378: // closed or reset
379: }
380: }
381: }
382:
383: public OutputStream getOutputStream() throws EngineException {
384: ensureOutputStream();
385:
386: return mOutputStream;
387: }
388:
389: public void setContentLength(int length) throws EngineException {
390: assert length >= 0;
391:
392: if (mEmbedded) {
393: throw new EmbeddedElementCantSetContentLengthException();
394: }
395:
396: _setContentLength(length);
397: }
398:
399: public void sendRedirect(String location) throws EngineException {
400: String user_agent = mRequest.getHeader("User-Agent");
401: // dirty hack around an IE bug that incorrectly handles anchors in redirect headers
402: if (user_agent != null && location.indexOf("#") != -1
403: && user_agent.indexOf("MSIE") != -1) {
404: setHeader("Refresh", "0; URL=" + location);
405: } else {
406: _sendRedirect(location);
407: }
408: }
409:
410: private void ensureOutputStream() throws EngineException {
411: if (null == mOutputStream) {
412: if (mEmbedded) {
413: mOutputStream = new EmbeddedStream();
414: } else {
415: if (null == mResponseOutputStream) {
416: try {
417: mResponseOutputStream = _getOutputStream();
418:
419: if (mContentType != null) {
420: String content_type = HttpUtils
421: .extractMimeTypeFromContentType(mContentType);
422:
423: // check if the content type should be gzip encoded
424: if (RifeConfig.Engine.getGzipCompression()
425: && RifeConfig.Engine
426: .getGzipCompressionTypes()
427: .contains(content_type)) {
428: String accept_encoding = mRequest
429: .getHeader("Accept-Encoding");
430: if (accept_encoding != null
431: && accept_encoding
432: .indexOf("gzip") != -1) {
433: mGzipByteOutputStream = new ByteArrayOutputStream();
434: mGzipOutputStream = new GZIPOutputStream(
435: mGzipByteOutputStream);
436: }
437: }
438: }
439: } catch (IOException e) {
440: throw new ResponseOutputStreamRetrievalErrorException(
441: e);
442: }
443: }
444:
445: if (mGzipOutputStream != null) {
446: mOutputStream = mGzipOutputStream;
447: } else {
448: mOutputStream = mResponseOutputStream;
449: }
450: }
451: }
452: }
453: }
|