/* * 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(); } }