/*
* =============================================================================
*
* 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.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import org.thymeleaf.exceptions.TemplateProcessingException;
/**
* <p>
* Throttled implementation of {@link Iterator}, meant to be queried in scenarios when an iterated
* context variable is allowed to be in control of the engine's throttling (i.e. the engine's execution
* is <em>data-driven</em>).
* </p>
* <p>
* A common scenario for this would be reactive systems executing the template engine as a part of a
* flow obtaining data from a data source, so that as the data is obtained, a part of the template is output
* containing that part of the data.
* </p>
* <p>
* This class is meant for <strong>internal use only</strong> from the diverse integrations of Thymeleaf in
* reactive architectures. There is normally no reason why a user would have to use this class directly.
* </p>
*
* @author Daniel Fernández
*
* @since 3.0.0
*
*/
public final class DataDrivenTemplateIterator implements Iterator<Object> {
private static final String SSE_HEAD_EVENT_TYPE = "head";
private static final String SSE_MESSAGE_EVENT_TYPE = "message";
private static final String SSE_TAIL_EVENT_TYPE = "tail";
private final List<Object> values;
private IThrottledTemplateWriterControl writerControl;
private ISSEThrottledTemplateWriterControl sseControl;
private long sseEventID;
private boolean inStep;
private boolean feedingComplete;
private boolean queried;
public DataDrivenTemplateIterator() {
super();
this.values = new ArrayList<Object>(10);
this.writerControl = null;
this.sseControl = null;
this.sseEventID = 0L;
this.inStep = false;
this.feedingComplete = false;
this.queried = false;
}
public void setWriterControl(final IThrottledTemplateWriterControl writerControl) {
this.writerControl = writerControl;
if (writerControl instanceof ISSEThrottledTemplateWriterControl) {
this.sseControl = (ISSEThrottledTemplateWriterControl) this.writerControl;
} else {
this.sseControl = null;
}
}
public void setFirstSSEEventID(final long firstSSEEventID) {
this.sseEventID = firstSSEEventID;
}
public void takeBackLastEventID() {
if (this.sseEventID > 0L) {
this.sseEventID--;
}
}
@Override
public boolean hasNext() {
this.queried = true;
return !this.values.isEmpty();
}
@Override
public Object next() {
this.queried = true;
if (this.values.isEmpty()) {
throw new NoSuchElementException();
}
final Object value = this.values.get(0);
this.values.remove(0);
return value;
}
public void startIteration() {
this.inStep = true;
if (this.sseControl != null) {
this.sseControl.startEvent(Long.toString(this.sseEventID), SSE_MESSAGE_EVENT_TYPE);
this.sseEventID++;
}
}
public void finishIteration() {
finishStep();
}
/**
* <p>
* Returns whether this data driven iterator has been actually queried, i.e., whether its {@link #hasNext()} or
* {@link #next()} methods have been called at least once.
* </p>
* <p>
* This indicates if the template has actually reached a point at which this iterator has been already
* needed or not. The typical use of this is to be able to switch between the "head" and the "data/buffer" phase.
* </p>
*
* @return <tt>true</tt> if this iterator has been queried, <tt>false</tt> if not.
*
* @since 3.0.3
*/
public boolean hasBeenQueried() {
return this.queried;
}
@Override
public void remove() {
throw new UnsupportedOperationException("remove() is not supported in Throttled Iterator");
}
boolean isPaused() {
this.queried = true;
return this.values.isEmpty() && !this.feedingComplete;
}
public boolean continueBufferExecution() {
return !this.values.isEmpty();
}
public void feedBuffer(final List<Object> newElements) {
this.values.addAll(newElements);
}
public void startHead() {
this.inStep = true;
if (this.sseControl != null) {
this.sseControl.startEvent(Long.toString(this.sseEventID), SSE_HEAD_EVENT_TYPE);
this.sseEventID++;
}
}
public void feedingComplete() {
this.feedingComplete = true;
}
public void startTail() {
this.inStep = true;
if (this.sseControl != null) {
this.sseControl.startEvent(Long.toString(this.sseEventID), SSE_TAIL_EVENT_TYPE);
this.sseEventID++;
}
}
public void finishStep() {
if (!this.inStep) {
return;
}
this.inStep = false;
if (this.sseControl != null) {
try {
this.sseControl.endEvent();
} catch (final IOException e) {
throw new TemplateProcessingException("Cannot signal end of SSE event", e);
}
}
}
public boolean isStepOutputFinished() {
if (this.inStep) {
return false;
}
if (this.writerControl != null) {
try {
return !this.writerControl.isOverflown();
} catch (final IOException e) {
throw new TemplateProcessingException("Cannot signal end of SSE event", e);
}
}
// We just don't know, so we will not worry about overflow
return true;
}
}