/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.wicket.protocol.http;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.Cookie;
import org.apache.wicket.Application;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.request.Response;
import org.apache.wicket.request.http.WebResponse;
import org.apache.wicket.response.filter.IResponseFilter;
import org.apache.wicket.util.lang.Args;
import org.apache.wicket.util.string.AppendingStringBuffer;
import org.apache.wicket.util.time.Time;
/**
* Subclass of {@link WebResponse} that buffers the actions and performs those on another response.
*
* @see #writeTo(WebResponse)
*
* @author Matej Knopp
*/
public class BufferedWebResponse extends WebResponse implements IMetaDataBufferingWebResponse
{
private final WebResponse originalResponse;
/**
* Construct.
*
* @param originalResponse
*/
public BufferedWebResponse(WebResponse originalResponse)
{
// if original response had some metadata set
// we should transfer it to the current response
if (originalResponse instanceof IMetaDataBufferingWebResponse)
{
((IMetaDataBufferingWebResponse)originalResponse).writeMetaData(this);
}
this.originalResponse = originalResponse;
}
/**
* transfer cookie operations (add, clear) to given web response
*
* @param response
* web response that should receive the current cookie operation
*/
@Override
public void writeMetaData(WebResponse response)
{
for (Action action : actions)
{
if (action instanceof MetaDataAction)
action.invoke(response);
}
}
@Override
public String encodeURL(CharSequence url)
{
if (originalResponse != null)
{
return originalResponse.encodeURL(url);
}
else
{
return url != null ? url.toString() : null;
}
}
@Override
public String encodeRedirectURL(CharSequence url)
{
if (originalResponse != null)
{
return originalResponse.encodeRedirectURL(url);
}
else
{
return url != null ? url.toString() : null;
}
}
private enum ActionType {
HEADER, NORMAL, DATA;
}
private static abstract class Action implements Comparable<Action>
{
protected abstract void invoke(WebResponse response);
protected ActionType getType()
{
return ActionType.NORMAL;
}
@Override
public final int compareTo(Action o)
{
return getType().ordinal() - o.getType().ordinal();
}
}
/**
* Actions not related directly to the content of the response, eg setting cookies, headers.
*
* @author igor
*/
private static abstract class MetaDataAction extends Action
{
@Override
protected ActionType getType()
{
return ActionType.HEADER;
}
}
private static class WriteCharSequenceAction extends Action
{
private final StringBuilder builder = new StringBuilder(4096);
public WriteCharSequenceAction()
{
}
public void append(CharSequence sequence)
{
builder.append(sequence);
}
@Override
protected void invoke(WebResponse response)
{
AppendingStringBuffer responseBuffer = new AppendingStringBuffer(builder);
List<IResponseFilter> responseFilters = Application.get()
.getRequestCycleSettings()
.getResponseFilters();
if (responseFilters != null)
{
for (IResponseFilter filter : responseFilters)
{
responseBuffer = filter.filter(responseBuffer);
}
}
response.write(responseBuffer);
}
@Override
protected ActionType getType()
{
return ActionType.DATA;
}
}
private static class WriteDataAction extends Action
{
private final ByteArrayOutputStream stream = new ByteArrayOutputStream();
public WriteDataAction()
{
}
public void append(byte data[])
{
try
{
stream.write(data);
}
catch (IOException e)
{
throw new WicketRuntimeException(e);
}
}
public void append(byte data[], int offset, int length)
{
try
{
stream.write(data, offset, length);
}
catch (Exception e)
{
throw new WicketRuntimeException(e);
}
}
@Override
protected void invoke(WebResponse response)
{
writeStream(response, stream);
}
@Override
protected ActionType getType()
{
return ActionType.DATA;
}
}
private static class CloseAction extends Action
{
@Override
protected void invoke(WebResponse response)
{
response.close();
}
}
private static class AddCookieAction extends MetaDataAction
{
private final Cookie cookie;
public AddCookieAction(Cookie cookie)
{
this.cookie = cookie;
}
@Override
protected void invoke(WebResponse response)
{
response.addCookie(cookie);
}
}
private static class ClearCookieAction extends MetaDataAction
{
private final Cookie cookie;
public ClearCookieAction(Cookie cookie)
{
this.cookie = cookie;
}
@Override
protected void invoke(WebResponse response)
{
response.clearCookie(cookie);
}
}
private static class SetHeaderAction extends MetaDataAction
{
private final String name;
private final String value;
public SetHeaderAction(String name, String value)
{
this.name = name;
this.value = value;
}
@Override
protected void invoke(WebResponse response)
{
response.setHeader(name, value);
}
}
private static class AddHeaderAction extends MetaDataAction
{
private final String name;
private final String value;
public AddHeaderAction(String name, String value)
{
this.name = name;
this.value = value;
}
@Override
protected void invoke(WebResponse response)
{
response.addHeader(name, value);
}
}
private static class SetDateHeaderAction extends MetaDataAction
{
private final String name;
private final Time value;
public SetDateHeaderAction(String name, Time value)
{
this.name = name;
this.value = Args.notNull(value, "value");
}
@Override
protected void invoke(WebResponse response)
{
response.setDateHeader(name, value);
}
}
private static class SetContentLengthAction extends MetaDataAction
{
private final long contentLength;
public SetContentLengthAction(long contentLength)
{
this.contentLength = contentLength;
}
@Override
protected void invoke(WebResponse response)
{
response.setContentLength(contentLength);
}
}
private static class SetContentTypeAction extends MetaDataAction
{
private final String contentType;
public SetContentTypeAction(String contentType)
{
this.contentType = contentType;
}
@Override
protected void invoke(WebResponse response)
{
response.setContentType(contentType);
}
}
private static class SetStatusAction extends MetaDataAction
{
private final int sc;
public SetStatusAction(int sc)
{
this.sc = sc;
}
@Override
protected void invoke(WebResponse response)
{
response.setStatus(sc);
}
}
private static class DisableCachingAction extends MetaDataAction
{
@Override
protected void invoke(WebResponse response)
{
response.disableCaching();;
}
}
private static class SendErrorAction extends Action
{
private final int sc;
private final String msg;
public SendErrorAction(int sc, String msg)
{
this.sc = sc;
this.msg = msg;
}
@Override
protected void invoke(WebResponse response)
{
response.sendError(sc, msg);
}
}
private static class SendRedirectAction extends Action
{
private final String url;
public SendRedirectAction(String url)
{
this.url = url;
}
@Override
protected void invoke(WebResponse response)
{
response.sendRedirect(url);
}
}
private static class FlushAction extends Action
{
@Override
protected void invoke(WebResponse response)
{
response.flush();
}
}
private final List<Action> actions = new ArrayList<Action>();
private WriteCharSequenceAction charSequenceAction;
private WriteDataAction dataAction;
@Override
public void reset()
{
super.reset();
actions.clear();
charSequenceAction = null;
dataAction = null;
}
@Override
public void addCookie(Cookie cookie)
{
actions.add(new AddCookieAction(cookie));
}
@Override
public void clearCookie(Cookie cookie)
{
actions.add(new ClearCookieAction(cookie));
}
@Override
public void setContentLength(long length)
{
actions.add(new SetContentLengthAction(length));
}
@Override
public void setContentType(String mimeType)
{
actions.add(new SetContentTypeAction(mimeType));
}
@Override
public void setDateHeader(String name, Time date)
{
actions.add(new SetDateHeaderAction(name, date));
}
@Override
public void setHeader(String name, String value)
{
actions.add(new SetHeaderAction(name, value));
}
@Override
public void addHeader(String name, String value)
{
actions.add(new AddHeaderAction(name, value));
}
@Override
public void disableCaching() {
actions.add(new DisableCachingAction());
}
@Override
public void write(CharSequence sequence)
{
if (dataAction != null)
{
throw new IllegalStateException(
"Can't call write(CharSequence) after write(byte[]) has been called.");
}
if (charSequenceAction == null)
{
charSequenceAction = new WriteCharSequenceAction();
actions.add(charSequenceAction);
}
charSequenceAction.append(sequence);
}
/**
* Returns the text already written to this response.
*
* @return text
*/
public CharSequence getText()
{
if (dataAction != null)
{
throw new IllegalStateException("write(byte[]) has already been called.");
}
if (charSequenceAction != null)
{
return charSequenceAction.builder;
}
else
{
return null;
}
}
/**
* Replaces the text in this response
*
* @param text
*/
public void setText(CharSequence text)
{
if (dataAction != null)
{
throw new IllegalStateException("write(byte[]) has already been called.");
}
if (charSequenceAction != null)
{
charSequenceAction.builder.setLength(0);
}
write(text);
}
@Override
public void write(byte[] array)
{
if (charSequenceAction != null)
{
throw new IllegalStateException(
"Can't call write(byte[]) after write(CharSequence) has been called.");
}
if (dataAction == null)
{
dataAction = new WriteDataAction();
actions.add(dataAction);
}
dataAction.append(array);
}
@Override
public void write(byte[] array, int offset, int length)
{
if (charSequenceAction != null)
{
throw new IllegalStateException(
"Can't call write(byte[]) after write(CharSequence) has been called.");
}
if (dataAction == null)
{
dataAction = new WriteDataAction();
actions.add(dataAction);
}
dataAction.append(array, offset, length);
}
@Override
public void sendRedirect(String url)
{
actions.add(new SendRedirectAction(url));
}
@Override
public void setStatus(int sc)
{
actions.add(new SetStatusAction(sc));
}
@Override
public void sendError(int sc, String msg)
{
actions.add(new SendErrorAction(sc, msg));
}
/**
* Writes the content of the buffer to the specified response. Also sets the properties and and
* headers.
*
* @param response
*/
public void writeTo(final WebResponse response)
{
Args.notNull(response, "response");
Collections.sort(actions);
for (Action action : actions)
{
action.invoke(response);
}
}
@Override
public boolean isRedirect()
{
for (Action action : actions)
{
if (action instanceof SendRedirectAction)
{
return true;
}
}
return false;
}
@Override
public void flush()
{
actions.add(new FlushAction());
}
private static void writeStream(final Response response, ByteArrayOutputStream stream)
{
final boolean copied[] = { false };
try
{
// try to avoid copying the array
stream.writeTo(new OutputStream()
{
@Override
public void write(int b) throws IOException
{
}
@Override
public void write(byte[] b, int off, int len) throws IOException
{
if (off == 0 && len == b.length)
{
response.write(b);
copied[0] = true;
}
}
});
}
catch (IOException e1)
{
throw new WicketRuntimeException(e1);
}
if (copied[0] == false)
{
response.write(stream.toByteArray());
}
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
final String toString;
if (charSequenceAction != null)
{
toString = charSequenceAction.builder.toString();
}
else
{
toString = super.toString();
}
return toString;
}
@Override
public Object getContainerResponse()
{
return originalResponse.getContainerResponse();
}
}