/*
* Copyright 2001-2008 Geert Bevin <gbevin[remove] at uwyn dot com>
* Licensed under the Apache License, Version 2.0 (the "License")
* $Id: AbstractResponse.java 3918 2008-04-14 17:35:35Z gbevin $
*/
package com.uwyn.rife.engine;
import com.uwyn.rife.config.RifeConfig;
import com.uwyn.rife.engine.exceptions.EmbeddedElementCantSetContentLengthException;
import com.uwyn.rife.engine.exceptions.EngineException;
import com.uwyn.rife.engine.exceptions.ResponseOutputStreamRetrievalErrorException;
import com.uwyn.rife.template.InternalString;
import com.uwyn.rife.template.Template;
import com.uwyn.rife.tools.HttpUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.zip.GZIPOutputStream;
/**
* This abstract class implements parts of the {@link Response} interface to
* provide behaviour that is specific to RIFE.
* <p>Additional abstract methods have been provided to integrate with the
* concrete back-end classes that extend <code>AbstractResponse</code>.
*
* @author Geert Bevin (gbevin[remove] at uwyn dot com)
* @version $Revision: 3918 $
* @since 1.1
*/
public abstract class AbstractResponse implements Response
{
private Request mRequest = null;
private boolean mEmbedded = false;
private ElementSupport mLastElement = null;
private String mContentType = null;
private boolean mTextBufferEnabled = true;
private ArrayList<CharSequence> mTextBuffer = null;
private OutputStream mResponseOutputStream = null;
private ByteArrayOutputStream mGzipByteOutputStream = null;
private GZIPOutputStream mGzipOutputStream = null;
private OutputStream mOutputStream = null;
/**
* This method needs to be implemented by the extending back-end class and
* will be called by <code>AbstractResponse</code> during the
* RIFE-specific additional behaviour. It behaves exactly like its {@link
* Response#setContentType(String) counter-part in the Response interface}.
*
* @see Response#setContentType(String)
* @since 1.1
*/
protected abstract void _setContentType(String contentType);
/**
* This method needs to be implemented by the extending back-end class and
* will be called by <code>AbstractResponse</code> during the
* RIFE-specific additional behaviour. It behaves exactly like its {@link
* Response#getCharacterEncoding() counter-part in the Response interface}.
*
* @see Response#getCharacterEncoding()
* @since 1.1
*/
protected abstract String _getCharacterEncoding();
/**
* This method needs to be implemented by the extending back-end class and
* will be called by <code>AbstractResponse</code> during the
* RIFE-specific additional behaviour. It behaves exactly like its {@link
* Response#setContentLength(int) counter-part in the Response interface}.
*
* @see Response#setContentLength(int)
* @since 1.1
*/
protected abstract void _setContentLength(int length);
/**
* This method needs to be implemented by the extending back-end class and
* will be called by <code>AbstractResponse</code> during the
* RIFE-specific additional behaviour. It behaves exactly like its {@link
* Response#sendRedirect(String) counter-part in the Response interface}.
*
* @see Response#sendRedirect(String)
* @since 1.1
*/
protected abstract void _sendRedirect(String location);
/**
* This method needs to be implemented by the extending back-end class and
* will be called by <code>AbstractResponse</code> during the
* RIFE-specific additional behaviour. It behaves exactly like its {@link
* Response#getOutputStream() counter-part in the Request interface}.
*
* @see Response#getOutputStream()
* @since 1.1
*/
protected abstract OutputStream _getOutputStream() throws IOException;
/**
* Constructor that needs to be called by all the constructors of the
* extending classes.
*
* @param request the {@link Request} that is associated with this
* response
* @param embedded <code>true</code> if the response is embedded; or
* <p><code>false</code> otherwise
* @since 1.1
*/
protected AbstractResponse(Request request, boolean embedded)
{
mRequest = request;
mEmbedded = embedded;
}
/**
* Retrieves the request that is associated with this response.
*
* @return the associated request
* @since 1.1
*/
public Request getRequest()
{
return mRequest;
}
/**
* Retrieves the last element that has been processed with this response.
*
* @return the last processed element
* @since 1.1
*/
public ElementSupport getLastElement()
{
return mLastElement;
}
public void setLastElement(ElementSupport element)
{
mLastElement = element;
}
public ArrayList<CharSequence> getEmbeddedContent()
{
if (!mEmbedded)
{
return null;
}
if (null == mOutputStream)
{
return mTextBuffer;
}
flush();
return ((EmbeddedStream)mOutputStream).getEmbeddedContent();
}
public boolean isEmbedded()
{
return mEmbedded;
}
public boolean isContentTypeSet()
{
return mContentType != null;
}
public String getContentType()
{
return mContentType;
}
public void setContentType(String contentType)
{
if (mEmbedded)
{
return;
}
if (null == contentType)
{
return;
}
if (-1 == contentType.indexOf(HttpUtils.CHARSET))
{
contentType = contentType+"; charset=UTF-8";
}
mContentType = contentType;
_setContentType(contentType);
}
public void enableTextBuffer(boolean enabled)
{
if (mTextBufferEnabled != enabled)
{
flush();
}
mTextBufferEnabled = enabled;
}
public boolean isTextBufferEnabled()
{
return mTextBufferEnabled;
}
public void print(Template template)
throws EngineException
{
if (null == template) return;
print(template.getDeferredContent());
}
public void print(Collection<CharSequence> deferredContent)
throws EngineException
{
if (!isContentTypeSet())
{
setContentType(RifeConfig.Engine.getDefaultContentType());
}
if (null == deferredContent ||
0 == deferredContent.size())
{
return;
}
if (mTextBufferEnabled)
{
if (mOutputStream != null)
{
try
{
mOutputStream.flush();
}
catch (IOException e)
{
throw new EngineException(e);
}
}
if (null == mTextBuffer)
{
mTextBuffer = new ArrayList<CharSequence>();
}
mTextBuffer.addAll(deferredContent);
}
else
{
writeDeferredContent(deferredContent);
}
}
public void print(Object value)
throws EngineException
{
if (!isContentTypeSet())
{
setContentType(RifeConfig.Engine.getDefaultContentType());
}
if (null == value)
{
return;
}
String text = String.valueOf(value);
if (mTextBufferEnabled)
{
if (mOutputStream != null)
{
try
{
mOutputStream.flush();
}
catch (IOException e)
{
throw new EngineException(e);
}
}
if (null == mTextBuffer)
{
mTextBuffer = new ArrayList<CharSequence>();
}
mTextBuffer.add(text);
}
else
{
ensureOutputStream();
try
{
mOutputStream.write(text.getBytes(getCharacterEncoding()));
mOutputStream.flush();
}
catch (IOException e)
{
throw new EngineException(e);
}
}
}
public String getCharacterEncoding()
{
if (mEmbedded)
{
return RifeConfig.Engine.getResponseEncoding();
}
String encoding = _getCharacterEncoding();
if (encoding == null)
{
encoding = RifeConfig.Engine.getResponseEncoding();
}
return encoding;
}
private void writeDeferredContent(Collection<CharSequence> deferredContent)
throws EngineException
{
if (!mEmbedded)
{
// create a string version of each char sequence so that any state operation happens
// before any content is actually being written
for (CharSequence charsequence : deferredContent)
{
charsequence.toString();
}
}
ensureOutputStream();
String encoding = getCharacterEncoding();
try
{
mOutputStream.flush();
if (mEmbedded)
{
EmbeddedStream embedded_stream = (EmbeddedStream)mOutputStream;
for (CharSequence charsequence : deferredContent)
{
embedded_stream.write(charsequence);
}
}
else
{
// write the content to the output stream
for (CharSequence charsequence : deferredContent)
{
if (charsequence instanceof com.uwyn.rife.template.InternalString)
{
mOutputStream.write(((InternalString)charsequence).getBytes(encoding));
}
else if (charsequence instanceof java.lang.String)
{
mOutputStream.write(((String)charsequence).getBytes(encoding));
}
}
}
mOutputStream.flush();
}
catch (IOException e)
{
// don't do anything since this exception is merely caused by someone that
// stopped or closed his browsing request
}
}
public void clearBuffer()
{
if (mTextBuffer != null &&
mTextBuffer.size() > 0)
{
mTextBuffer.clear();
}
}
public void flush()
throws EngineException
{
if (mTextBuffer != null &&
mTextBuffer.size() > 0)
{
writeDeferredContent(mTextBuffer);
mTextBuffer.clear();
}
if (mOutputStream != null)
{
try
{
mOutputStream.flush();
}
catch (IOException e)
{
// don't do anything, the response stream has probably been
// closed or reset
}
}
}
public void close()
throws EngineException
{
flush();
if (!mEmbedded &&
mOutputStream != null)
{
try
{
if (mGzipOutputStream != null)
{
mGzipOutputStream.flush();
mGzipOutputStream.finish();
byte[] bytes = mGzipByteOutputStream.toByteArray();
mGzipOutputStream = null;
mGzipByteOutputStream = null;
setContentLength(bytes.length);
addHeader("Content-Encoding", "gzip");
mResponseOutputStream.write(bytes);
mOutputStream = mResponseOutputStream;
}
try
{
mOutputStream.flush();
mOutputStream.close();
}
catch (IOException e)
{
// don't do anything, the response stream has probably been
// closed or reset
}
mOutputStream = null;
}
catch (IOException e)
{
// don't do anything, the response stream has probably been
// closed or reset
}
}
}
public OutputStream getOutputStream()
throws EngineException
{
ensureOutputStream();
return mOutputStream;
}
public void setContentLength(int length)
throws EngineException
{
assert length >= 0;
if (mEmbedded)
{
throw new EmbeddedElementCantSetContentLengthException();
}
_setContentLength(length);
}
public void sendRedirect(String location)
throws EngineException
{
String user_agent = mRequest.getHeader("User-Agent");
// dirty hack around an IE bug that incorrectly handles anchors in redirect headers
if (user_agent != null &&
location.indexOf("#") != -1 &&
user_agent.indexOf("MSIE") != -1)
{
setHeader("Refresh", "0; URL="+location);
}
else
{
_sendRedirect(location);
}
}
private void ensureOutputStream()
throws EngineException
{
if (null == mOutputStream)
{
if (mEmbedded)
{
mOutputStream = new EmbeddedStream();
}
else
{
if (null == mResponseOutputStream)
{
try
{
mResponseOutputStream = _getOutputStream();
if (mContentType != null)
{
String content_type = HttpUtils.extractMimeTypeFromContentType(mContentType);
// check if the content type should be gzip encoded
if (RifeConfig.Engine.getGzipCompression() &&
RifeConfig.Engine.getGzipCompressionTypes().contains(content_type))
{
String accept_encoding = mRequest.getHeader("Accept-Encoding");
if (accept_encoding != null &&
accept_encoding.indexOf("gzip") != -1)
{
mGzipByteOutputStream = new ByteArrayOutputStream();
mGzipOutputStream = new GZIPOutputStream(mGzipByteOutputStream);
}
}
}
}
catch (IOException e)
{
throw new ResponseOutputStreamRetrievalErrorException(e);
}
}
if (mGzipOutputStream != null)
{
mOutputStream = mGzipOutputStream;
}
else
{
mOutputStream = mResponseOutputStream;
}
}
}
}
}