org.thymeleaf.engine.ThrottledTemplateProcessor 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-2018, 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.io.Writer;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.Charset;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.thymeleaf.IThrottledTemplateProcessor;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.TemplateSpec;
import org.thymeleaf.context.IEngineContext;
import org.thymeleaf.exceptions.TemplateEngineException;
import org.thymeleaf.exceptions.TemplateOutputException;
import org.thymeleaf.exceptions.TemplateProcessingException;
import org.thymeleaf.util.LoggingUtils;
/**
*
* Standard implementation of {@link IThrottledTemplateProcessor}.
*
*
* This class is for internal use only. There is usually no reason why user's code should directly
* reference it.
*
*
* @author Daniel Fernández
*
* @since 3.0.0
*
*/
public final class ThrottledTemplateProcessor implements IThrottledTemplateProcessor {
private static final Logger logger = LoggerFactory.getLogger(TemplateEngine.class);
private static final Logger timerLogger = LoggerFactory.getLogger(TemplateEngine.TIMER_LOGGER_NAME);
private static final int NANOS_IN_SECOND = 1000000;
private static final String OUTPUT_TYPE_CHARS = "chars";
private static final String OUTPUT_TYPE_BYTES = "bytes";
// We will use an AtomicLong in order to generate identifiers. Even if 100 throttled template processors were
// generated each millisecond, this would still give us enough unique identifiers for almost 6M years.
private static final AtomicLong identifierGenerator = new AtomicLong(0L);
private final String identifier;
private final TemplateSpec templateSpec;
private final IEngineContext context;
private final TemplateModel templateModel;
private final ITemplateHandler templateHandler;
private final ProcessorTemplateHandler processorTemplateHandler;
private final TemplateFlowController flowController;
private final ThrottledTemplateWriter writer;
private int offset;
private boolean eventProcessingFinished;
// This is signaled as volatile so that several threads can ask whether the processor has finished
// avoiding visibility issues (concurrency should not be an issue because we should NEVER have more than
// one thread executing the processor at the same time, but thread visibility could still be an issue).
private volatile boolean allProcessingFinished;
ThrottledTemplateProcessor(
final TemplateSpec templateSpec,
final IEngineContext context,
final TemplateModel templateModel, final ITemplateHandler templateHandler,
final ProcessorTemplateHandler processorTemplateHandler,
final TemplateFlowController flowController,
final ThrottledTemplateWriter writer) {
super();
this.identifier = Long.toString(identifierGenerator.getAndIncrement());
this.templateSpec = templateSpec;
this.context = context;
this.templateModel = templateModel;
this.templateHandler = templateHandler;
this.processorTemplateHandler = processorTemplateHandler;
this.flowController = flowController;
this.writer = writer;
this.offset = 0;
this.eventProcessingFinished = false;
this.allProcessingFinished = false;
}
public IThrottledTemplateWriterControl getThrottledTemplateWriterControl() {
return this.writer;
}
public boolean isFinished() {
return this.allProcessingFinished;
}
private boolean computeFinish() throws IOException {
if (this.allProcessingFinished) {
return true;
}
final boolean finished =
this.eventProcessingFinished && !this.flowController.processorTemplateHandlerPending && !this.writer.isOverflown();
if (finished) {
// updating the volatile variable (which would apply a memory barrier) is avoided if unneeded
this.allProcessingFinished = finished;
}
return finished;
}
private void reportFinish(final String outputType) {
if (this.allProcessingFinished) {
if (logger.isTraceEnabled()) {
logger.trace(
"[THYMELEAF][{}] Finished throttled processing of template \"{}\" with locale {}. Maximum overflow was {} {} (overflow buffer grown {} times).",
new Object[]{TemplateEngine.threadIndex(), this.templateSpec, this.context.getLocale(), Integer.valueOf(this.writer.getMaxOverflowSize()), outputType, this.writer.getOverflowGrowCount() });
}
}
}
@Override
public String getProcessorIdentifier() {
return this.identifier;
}
@Override
public TemplateSpec getTemplateSpec() {
return this.templateSpec;
}
@Override
public int processAll(final Writer writer) {
this.writer.setOutput(writer);
return process(Integer.MAX_VALUE, OUTPUT_TYPE_CHARS);
}
@Override
public int processAll(final OutputStream outputStream, final Charset charset) {
this.writer.setOutput(outputStream, charset, Integer.MAX_VALUE);
return process(Integer.MAX_VALUE, OUTPUT_TYPE_BYTES);
}
@Override
public int process(final int maxOutputInChars, final Writer writer) {
this.writer.setOutput(writer);
return process(maxOutputInChars, OUTPUT_TYPE_CHARS);
}
@Override
public int process(final int maxOutputInBytes, final OutputStream outputStream, final Charset charset) {
this.writer.setOutput(outputStream, charset, maxOutputInBytes);
return process(maxOutputInBytes, OUTPUT_TYPE_BYTES);
}
private int process(final int maxOutput, final String outputType) {
int writtenCount = 0;
try {
if (this.allProcessingFinished || maxOutput == 0) {
return 0; // No bytes written
}
if (logger.isTraceEnabled()) {
logger.trace("[THYMELEAF][{}] Starting throttled process (limit:{} {}) of template \"{}\" with locale {}",
new Object[]{TemplateEngine.threadIndex(), Integer.valueOf(maxOutput), outputType, this.templateSpec, this.context.getLocale()});
}
final long startNanos = System.nanoTime();
// Save the initial written count so that we can know at the end how many bytes were written
final int initialWrittenCount = this.writer.getWrittenCount();
// Set the new limit for the writer (might provoke overflow being processed)
this.writer.allow(maxOutput);
// Maybe by processing all overflow we just finished
if (!computeFinish() && !this.writer.isStopped()) {
if (this.flowController.processorTemplateHandlerPending) {
this.processorTemplateHandler.handlePending();
}
if (!computeFinish() && !this.writer.isStopped()) {
this.offset += this.templateModel.process(this.templateHandler, this.offset, this.flowController);
if (this.offset == this.templateModel.size()) {
EngineContextManager.disposeEngineContext(this.context);
this.eventProcessingFinished = true;
computeFinish();
}
}
}
final long endNanos = System.nanoTime();
/*
* Finally, flush the writer in order to make sure that everything has been written to output
*/
try {
this.writer.flush();
} catch (final IOException e) {
throw new TemplateOutputException("An error happened while flushing output writer", templateSpec.getTemplate(), -1, -1, e);
}
writtenCount = this.writer.getWrittenCount() - initialWrittenCount;
if (logger.isTraceEnabled()) {
logger.trace("[THYMELEAF][{}] Finished throttled process (limit:{} {}, output: {} {}) of template \"{}\" with locale {}",
new Object[]{TemplateEngine.threadIndex(), Integer.valueOf(maxOutput), outputType, Integer.valueOf(writtenCount), outputType, this.templateSpec, this.context.getLocale()});
}
if (timerLogger.isTraceEnabled()) {
final BigDecimal elapsed = BigDecimal.valueOf(endNanos - startNanos);
final BigDecimal elapsedMs = elapsed.divide(BigDecimal.valueOf(NANOS_IN_SECOND), RoundingMode.HALF_UP);
timerLogger.trace(
"[THYMELEAF][{}][{}][{}][{}][{}] TEMPLATE \"{}\" WITH LOCALE {} PROCESSED (THROTTLED, LIMIT:{} {}, OUTPUT: {} {}) IN {} nanoseconds (approx. {}ms)",
new Object[]{
TemplateEngine.threadIndex(),
LoggingUtils.loggifyTemplateName(this.templateSpec.getTemplate()), this.context.getLocale(), elapsed, elapsedMs,
this.templateSpec, this.context.getLocale(), Integer.valueOf(maxOutput), outputType, Integer.valueOf(writtenCount), outputType, elapsed, elapsedMs});
}
} catch (final TemplateOutputException e) {
this.eventProcessingFinished = true;
this.allProcessingFinished = true;
// We log the exception just in case higher levels do not end up logging it (e.g. they could simply display traces in the browser
logger.error(String.format("[THYMELEAF][%s] Exception processing throttled template \"%s\": %s", new Object[] {TemplateEngine.threadIndex(), this.templateSpec, e.getMessage()}), e);
throw e;
} catch (final TemplateEngineException e) {
this.eventProcessingFinished = true;
this.allProcessingFinished = true;
// We log the exception just in case higher levels do not end up logging it (e.g. they could simply display traces in the browser
logger.error(String.format("[THYMELEAF][%s] Exception processing throttled template \"%s\": %s", new Object[] {TemplateEngine.threadIndex(), this.templateSpec, e.getMessage()}), e);
throw e;
} catch (final Exception e) {
this.eventProcessingFinished = true;
this.allProcessingFinished = true;
// We log the exception just in case higher levels do not end up logging it (e.g. they could simply display traces in the browser
logger.error(String.format("[THYMELEAF][%s] Exception processing throttled template \"%s\": %s", new Object[] {TemplateEngine.threadIndex(), this.templateSpec, e.getMessage()}), e);
throw new TemplateProcessingException("Exception processing throttled template", this.templateSpec.toString(), e);
}
reportFinish(outputType);
return writtenCount;
}
}