/**
* Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright ownership. Apereo
* 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 the
* following location:
*
* <p>http://www.apache.org/licenses/LICENSE-2.0
*
* <p>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.apereo.portal.portlet.container.cache;
import com.google.common.base.Function;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import javax.portlet.CacheControl;
import org.apache.commons.io.output.StringBuilderWriter;
import org.apereo.portal.portlet.rendering.PortletOutputHandler;
/**
* Captures the output of a portlet for later re-use. The maximumSize field allows for setting a max
* number of bytes/chars to cache before giving up on the caching.
*
*/
public class CachingPortletOutputHandler implements PortletOutputHandler {
private final PortletOutputHandler portletOutputHandler;
private final int maximumSize;
private LimitingTeeWriter teeWriter;
private PrintWriter printWriter;
private LimitingTeeOutputStream teeStream;
private StringBuilderWriter cachingWriter;
private ByteArrayOutputStream cachingOutputStream;
private String contentType;
public CachingPortletOutputHandler(PortletOutputHandler portletOutputHandler, int maximumSize) {
this.portletOutputHandler = portletOutputHandler;
this.maximumSize = maximumSize;
}
public <T extends Serializable> CachedPortletData<T> getCachedPortletData(
T portletResult, CacheControl cacheControl) {
if ((this.teeWriter != null && this.teeWriter.isLimitReached())
|| (this.teeStream != null && this.teeStream.isLimitReached())) {
//Hit the caching limit, nothing to return
return null;
}
return new CachedPortletData<T>(
portletResult,
this.cachingWriter != null ? this.cachingWriter.toString() : null,
this.cachingOutputStream != null ? this.cachingOutputStream.toByteArray() : null,
contentType,
cacheControl.isPublicScope(),
cacheControl.getETag(),
cacheControl.getExpirationTime());
}
public String getCachedWriterOutput() {
if (cachingWriter == null) {
return null;
}
return cachingWriter.toString();
}
public byte[] getCachedStreamOutput() {
if (cachingOutputStream == null) {
return null;
}
return cachingOutputStream.toByteArray();
}
@Override
public String getContentType() {
return contentType;
}
@Override
public PrintWriter getPrintWriter() throws IOException {
if (teeStream != null) {
throw new IllegalStateException("getOutputStream() has already been called");
}
if (this.printWriter == null) {
final PrintWriter delegateWriter = this.portletOutputHandler.getPrintWriter();
this.cachingWriter = new StringBuilderWriter();
//Create the limiting tee writer to write to the actual PrintWriter and the cachingWriter
this.teeWriter =
new LimitingTeeWriter(
this.maximumSize,
delegateWriter,
this.cachingWriter,
new Function<LimitingTeeWriter, Object>() {
@Override
public Object apply(LimitingTeeWriter input) {
//Limit hit, clear the cache
clearCachedWriter();
return null;
}
});
//Wrap the limiting tee writer in a PrintWriter to return to the caller
this.printWriter = new PrintWriter(this.teeWriter);
}
return this.printWriter;
}
@Override
public OutputStream getOutputStream() throws IOException {
if (this.printWriter != null) {
throw new IllegalStateException("getPrintWriter() has already been called");
}
if (this.teeStream == null) {
final OutputStream delegateOutputStream = this.portletOutputHandler.getOutputStream();
this.cachingOutputStream = new ByteArrayOutputStream();
//Create the limiting tee output stream to the actual OutputStream and the cachingOutputStream
this.teeStream =
new LimitingTeeOutputStream(
this.maximumSize,
delegateOutputStream,
this.cachingOutputStream,
new Function<LimitingTeeOutputStream, Object>() {
@Override
public Object apply(LimitingTeeOutputStream input) {
//Limit hit, clear the cache
clearCachedStream();
return null;
}
});
}
return teeStream;
}
@Override
public void flushBuffer() throws IOException {
this.portletOutputHandler.flushBuffer();
}
@Override
public int getBufferSize() {
return this.portletOutputHandler.getBufferSize();
}
@Override
public boolean isCommitted() {
return this.portletOutputHandler.isCommitted();
}
@Override
public void reset() {
this.portletOutputHandler.reset();
//Parent reset worked, clear the caches
resetCached();
}
@Override
public void resetBuffer() {
this.portletOutputHandler.resetBuffer();
//Parent reset worked, clear the caches
resetCached();
}
private void resetCached() {
resetCachedWriter();
resetCachedStream();
}
private void resetCachedWriter() {
if (this.cachingWriter != null) {
this.teeWriter.resetByteCount();
this.clearCachedWriter();
}
}
private void clearCachedWriter() {
if (this.cachingWriter != null) {
final StringBuilder builder = this.cachingWriter.getBuilder();
if (builder.length() > 0) {
builder.delete(0, builder.length());
}
}
}
private void resetCachedStream() {
if (this.cachingOutputStream != null) {
this.teeStream.resetByteCount();
this.clearCachedStream();
}
}
private void clearCachedStream() {
if (this.cachingOutputStream != null) {
this.cachingOutputStream.reset();
}
}
@Override
public void setBufferSize(int size) {
this.portletOutputHandler.setBufferSize(size);
}
@Override
public void setContentType(String contentType) {
this.portletOutputHandler.setContentType(contentType);
this.contentType = contentType;
}
}