org.thymeleaf.engine.ThrottledTemplateWriterOutputStreamAdapter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of thymeleaf Show documentation
Show all versions of thymeleaf Show documentation
Modern server-side Java template engine for both web and standalone environments
/*
* =============================================================================
*
* Copyright (c) 2011-2016, The THYMELEAF team (http://www.thymeleaf.org)
*
* Licensed 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.thymeleaf.engine;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import org.thymeleaf.exceptions.TemplateOutputException;
/**
*
* @author Daniel Fernández
*
* @since 3.0.0
*
*/
final class ThrottledTemplateWriterOutputStreamAdapter
extends OutputStream
implements ThrottledTemplateWriter.IThrottledTemplateWriterAdapter {
private final String templateName;
private final TemplateFlowController flowController;
// We will use a different increment depending on the size of the chunks asked by the throttled template
// client. This is a complex setup because this adapter will be fed by a byte array channel acting as a
// Writer -> OutputStream bridge that will in fact have its own buffer, and due to this we will need at least
// the same size as the buffer in this channel (in fact, a bit more) if we don't want to be continuously growing
// our overflow buffer. So if chunks are x in size, the channel's buffer will be x/4, and we will have an overflow
// of (x/8)*3, growing in chunks of x/8.
// See the implementation of this mechanism at ThrottledTemplateWriter for more info.
private final int overflowIncrementInBytes;
private OutputStream os;
private byte[] overflow;
private int overflowSize;
private int maxOverflowSize;
private int overflowGrowCount;
private boolean unlimited;
private int limit;
private int writtenCount;
ThrottledTemplateWriterOutputStreamAdapter(
final String templateName, final TemplateFlowController flowController, final int overflowIncrementInBytes) {
super();
this.templateName = templateName;
this.flowController = flowController;
this.overflowIncrementInBytes = overflowIncrementInBytes;
this.overflow = null;
this.overflowSize = 0;
this.maxOverflowSize = 0;
this.overflowGrowCount = 0;
this.unlimited = false;
this.limit = 0;
this.writtenCount = 0;
this.flowController.stopProcessing = true;
}
void setOutputStream(final OutputStream os) {
this.os = os;
this.writtenCount = 0;
}
public boolean isOverflown() {
return this.overflowSize > 0;
}
public boolean isStopped() {
return this.limit == 0;
}
public int getWrittenCount() {
return this.writtenCount;
}
public int getMaxOverflowSize() {
return this.maxOverflowSize;
}
public int getOverflowGrowCount() {
return this.overflowGrowCount;
}
public void allow(final int limit) {
if (limit == Integer.MAX_VALUE || limit < 0) {
this.unlimited = true;
this.limit = -1;
} else {
this.unlimited = false;
this.limit = limit;
}
this.flowController.stopProcessing = (this.limit == 0);
if (this.overflowSize == 0 || this.limit == 0) {
return;
}
try {
if (this.unlimited || this.limit > this.overflowSize) {
this.os.write(this.overflow, 0, this.overflowSize);
if (!this.unlimited) {
this.limit -= this.overflowSize;
}
this.writtenCount += this.overflowSize;
this.overflowSize = 0;
return;
}
this.os.write(this.overflow, 0, this.limit);
if (this.limit < this.overflowSize) {
System.arraycopy(this.overflow, this.limit, this.overflow, 0, this.overflowSize - this.limit);
}
this.overflowSize -= this.limit;
this.writtenCount += this.limit;
this.limit = 0;
this.flowController.stopProcessing = true;
} catch (final IOException e) {
throw new TemplateOutputException(
"Exception while trying to write overflowed buffer in throttled template", this.templateName, -1, -1, e);
}
}
@Override
public void write(final int b) throws IOException {
if (this.limit == 0) {
overflow(b);
return;
}
this.os.write(b);
if (!this.unlimited) {
this.limit--;
}
this.writtenCount++;
if (this.limit == 0) {
this.flowController.stopProcessing = true;
}
}
@Override
public void write(final byte[] bytes, final int off, final int len) throws IOException {
if (this.limit == 0) {
overflow(bytes, off, len);
return;
}
if (this.unlimited || this.limit > len) {
this.os.write(bytes, off, len);
if (!this.unlimited) {
this.limit -= len;
}
this.writtenCount += len;
return;
}
this.os.write(bytes, off, this.limit);
if (this.limit < len) {
overflow(bytes, off + this.limit, (len - this.limit));
}
this.writtenCount += this.limit;
this.limit = 0;
this.flowController.stopProcessing = true;
}
@Override
public void write(final byte[] bytes) throws IOException {
final int len = bytes.length;
if (this.limit == 0) {
overflow(bytes, 0, len);
return;
}
if (this.unlimited || this.limit > len) {
this.os.write(bytes, 0, len);
if (!this.unlimited) {
this.limit -= len;
}
this.writtenCount += len;
return;
}
this.os.write(bytes, 0, this.limit);
if (this.limit < len) {
overflow(bytes, this.limit, (len - this.limit));
}
this.writtenCount += this.limit;
this.limit = 0;
this.flowController.stopProcessing = true;
}
private void overflow(final int c) {
ensureOverflowCapacity(1);
this.overflow[this.overflowSize] = (byte)c;
this.overflowSize++;
if (this.overflowSize > this.maxOverflowSize) {
this.maxOverflowSize = this.overflowSize;
}
}
private void overflow(final byte[] bytes, final int off, final int len) {
ensureOverflowCapacity(len);
System.arraycopy(bytes, off, this.overflow, this.overflowSize, len);
this.overflowSize += len;
if (this.overflowSize > this.maxOverflowSize) {
this.maxOverflowSize = this.overflowSize;
}
}
private void ensureOverflowCapacity(final int len) {
if (this.overflow == null) {
int bufferInitialSize = this.overflowIncrementInBytes * 3;
while (bufferInitialSize < len) {
bufferInitialSize += this.overflowIncrementInBytes;
}
this.overflow = new byte[bufferInitialSize];
return;
}
final int targetLen = this.overflowSize + len;
if (this.overflow.length < targetLen) {
int newLen = this.overflow.length;
do {
newLen += this.overflowIncrementInBytes;
} while (newLen < targetLen);
this.overflow = Arrays.copyOf(this.overflow, newLen);
this.overflowGrowCount++;
}
}
@Override
public void flush() throws IOException {
// No need to control overflow here. The fact that this has overflow will be used as a flag to determine
// that further write operations are actually needed by means of the isOverflown() method.
this.os.flush();
}
@Override
public void close() throws IOException {
// This will normally be NEVER called, as Thymeleaf will not call close() on its Writers/OutputStreams
// (only flush() is guaranteed to be called at the end).
this.os.close();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy