/*
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* 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.wso2.siddhi.core.window;
import org.wso2.siddhi.core.config.ExecutionPlanContext;
import org.wso2.siddhi.core.event.ComplexEvent;
import org.wso2.siddhi.core.event.ComplexEventChunk;
import org.wso2.siddhi.core.event.state.StateEvent;
import org.wso2.siddhi.core.event.stream.MetaStreamEvent;
import org.wso2.siddhi.core.event.stream.StreamEvent;
import org.wso2.siddhi.core.event.stream.StreamEventCloner;
import org.wso2.siddhi.core.event.stream.StreamEventPool;
import org.wso2.siddhi.core.event.stream.converter.ZeroStreamEventConverter;
import org.wso2.siddhi.core.exception.OperationNotSupportedException;
import org.wso2.siddhi.core.executor.VariableExpressionExecutor;
import org.wso2.siddhi.core.query.input.stream.single.EntryValveProcessor;
import org.wso2.siddhi.core.query.processor.Processor;
import org.wso2.siddhi.core.query.processor.SchedulingProcessor;
import org.wso2.siddhi.core.query.processor.stream.window.FindableProcessor;
import org.wso2.siddhi.core.query.processor.stream.window.WindowProcessor;
import org.wso2.siddhi.core.stream.StreamJunction;
import org.wso2.siddhi.core.table.Table;
import org.wso2.siddhi.core.util.Scheduler;
import org.wso2.siddhi.core.util.collection.operator.CompiledCondition;
import org.wso2.siddhi.core.util.collection.operator.MatchingMetaInfoHolder;
import org.wso2.siddhi.core.util.lock.LockWrapper;
import org.wso2.siddhi.core.util.parser.SchedulerParser;
import org.wso2.siddhi.core.util.parser.SingleInputStreamParser;
import org.wso2.siddhi.core.util.snapshot.Snapshotable;
import org.wso2.siddhi.core.util.statistics.LatencyTracker;
import org.wso2.siddhi.query.api.definition.Attribute;
import org.wso2.siddhi.query.api.definition.WindowDefinition;
import org.wso2.siddhi.query.api.execution.query.output.stream.OutputStream;
import org.wso2.siddhi.query.api.expression.Expression;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
/**
* Window implementation of SiddhiQL.
* It can be seen as a global Window which can be accessed from multiple queries.
*/
public class Window implements FindableProcessor, Snapshotable {
/**
* Element id of this snapshot.
*/
private final String elementId;
/**
* WindowDefinition used to construct this window.
*/
private final WindowDefinition windowDefinition;
/**
* ExecutionPlanContext is used to create the elementId and WindowProcessor.
*/
private final ExecutionPlanContext executionPlanContext;
/**
* LockWrapper to coordinate asynchronous events.
*/
private final LockWrapper lockWrapper;
/**
* TemplateBuilder to convert {@link StateEvent}s to {@link StreamEvent}s
*/
private final ZeroStreamEventConverter eventConverter = new ZeroStreamEventConverter();
/**
* Publisher to which the output events from internal window have to be sent.
*/
private StreamJunction.Publisher outputPublisher;
/**
* Processor for the internal window.
* It will contain the PublisherProcessor as the last windowProcessor in the chain.
*/
private Processor windowProcessor;
/**
* WindowProcessor reference to the actual processor which is holding the events.
* If this.windowProcessor refers to EntryValveProcessor (if the Window is a scheduler based, it may be),
* internalWindowProcessor refers to the this.windowProcessor.getNextProcessor()
*/
private WindowProcessor internalWindowProcessor;
/**
* StreamEventPool to create new empty StreamEvent.
*/
private StreamEventPool streamEventPool;
/**
* Construct a Window object.
*
* @param windowDefinition definition of the window
* @param executionPlanContext execution plan context of Siddhi
*/
public Window(WindowDefinition windowDefinition, ExecutionPlanContext executionPlanContext) {
this.windowDefinition = windowDefinition;
this.executionPlanContext = executionPlanContext;
this.elementId = executionPlanContext.getElementIdGenerator().createNewId();
this.lockWrapper = new LockWrapper(windowDefinition.getId());
this.lockWrapper.setLock(new ReentrantLock());
}
/**
* Initialize the WindowEvent table by creating {@link WindowProcessor} to handle the events.
*
* @param tableMap map of {@link Table}s
* @param eventWindowMap map of EventWindows
* @param latencyTracker to rack the latency if statistic of underlying {@link WindowProcessor} is required
*/
public void init(Map<String, Table> tableMap, Map<String, Window> eventWindowMap, LatencyTracker
latencyTracker, String queryName) {
if (this.windowProcessor != null) {
return;
}
// Create and initialize MetaStreamEvent
MetaStreamEvent metaStreamEvent = new MetaStreamEvent();
metaStreamEvent.addInputDefinition(windowDefinition);
metaStreamEvent.setWindowEvent(true);
metaStreamEvent.initializeAfterWindowData();
for (Attribute attribute : windowDefinition.getAttributeList()) {
metaStreamEvent.addOutputData(attribute);
}
this.streamEventPool = new StreamEventPool(metaStreamEvent, 5);
StreamEventCloner streamEventCloner = new StreamEventCloner(metaStreamEvent, this.streamEventPool);
OutputStream.OutputEventType outputEventType = windowDefinition.getOutputEventType();
boolean outputExpectsExpiredEvents = outputEventType != OutputStream.OutputEventType.CURRENT_EVENTS;
WindowProcessor internalWindowProcessor = (WindowProcessor) SingleInputStreamParser.generateProcessor
(windowDefinition.getWindow(), metaStreamEvent, new ArrayList<VariableExpressionExecutor>(), this
.executionPlanContext, tableMap, false, outputExpectsExpiredEvents, queryName);
internalWindowProcessor.setStreamEventCloner(streamEventCloner);
internalWindowProcessor.constructStreamEventPopulater(metaStreamEvent, 0);
EntryValveProcessor entryValveProcessor = null;
if (internalWindowProcessor instanceof SchedulingProcessor) {
entryValveProcessor = new EntryValveProcessor(this.executionPlanContext);
Scheduler scheduler = SchedulerParser.parse(this.executionPlanContext.getScheduledExecutorService(),
entryValveProcessor, this.executionPlanContext);
scheduler.init(this.lockWrapper, queryName);
scheduler.setStreamEventPool(streamEventPool);
((SchedulingProcessor) internalWindowProcessor).setScheduler(scheduler);
}
if (entryValveProcessor != null) {
entryValveProcessor.setToLast(internalWindowProcessor);
this.windowProcessor = entryValveProcessor;
} else {
this.windowProcessor = internalWindowProcessor;
}
// StreamPublishProcessor must be the last in chain so that it can publish the events to StreamJunction
this.windowProcessor.setToLast(new StreamPublishProcessor(outputEventType));
this.internalWindowProcessor = internalWindowProcessor;
}
/**
* Set Publisher to which the the output events from internal window have to be sent.
*
* @param publisher output publisher
*/
public void setPublisher(StreamJunction.Publisher publisher) {
this.outputPublisher = publisher;
}
/**
* Return the {@link WindowDefinition} used to construct this Window.
*
* @return the window definition
*/
public WindowDefinition getWindowDefinition() {
return this.windowDefinition;
}
/**
* Add the given ComplexEventChunk to the Window.
*
* @param complexEventChunk the event chunk to be added
*/
public void add(ComplexEventChunk complexEventChunk) {
try {
this.lockWrapper.lock();
complexEventChunk.reset();
// Convert all events to StreamEvent because StateEvents can be passed if directly received from a join
ComplexEvent complexEvents = complexEventChunk.getFirst();
StreamEvent firstEvent = streamEventPool.borrowEvent();
eventConverter.convertComplexEvent(complexEvents, firstEvent);
StreamEvent currentEvent = firstEvent;
complexEvents = complexEvents.getNext();
while (complexEvents != null) {
StreamEvent nextEvent = streamEventPool.borrowEvent();
eventConverter.convertComplexEvent(complexEvents, nextEvent);
currentEvent.setNext(nextEvent);
currentEvent = nextEvent;
complexEvents = complexEvents.getNext();
}
// Send to the window windowProcessor
windowProcessor.process(new ComplexEventChunk<StreamEvent>(firstEvent, currentEvent, complexEventChunk
.isBatch()));
} finally {
this.lockWrapper.unlock();
}
}
/**
* {@inheritDoc}
*/
@Override
public StreamEvent find(StateEvent matchingEvent, CompiledCondition compiledCondition) {
return ((FindableProcessor) this.internalWindowProcessor).find(matchingEvent, compiledCondition);
}
/**
* {@inheritDoc}
*/
@Override
public CompiledCondition compileCondition(Expression expression, MatchingMetaInfoHolder matchingMetaInfoHolder,
ExecutionPlanContext executionPlanContext,
List<VariableExpressionExecutor> variableExpressionExecutors,
Map<String, Table> tableMap, String queryName) {
if (this.internalWindowProcessor instanceof FindableProcessor) {
return ((FindableProcessor) this.internalWindowProcessor).compileCondition(expression,
matchingMetaInfoHolder, executionPlanContext, variableExpressionExecutors, tableMap,
queryName);
} else {
throw new OperationNotSupportedException("Cannot construct finder for the window " + this
.windowDefinition.getWindow());
}
}
public LockWrapper getLock() {
return lockWrapper;
}
/**
* Return an object array containing the internal state of the internalWindowProcessor.
*
* @return current state of the Window
*/
@Override
public Map<String, Object> currentState() {
return this.internalWindowProcessor.currentState();
}
/**
* Restore the internalWindowProcessor using given state.
*
* @param state the stateful objects of the element as an array on
*/
@Override
public void restoreState(Map<String, Object> state) {
this.internalWindowProcessor.restoreState(state);
}
/**
* Return the elementId which may be used for snapshot creation.
*
* @return the element id of this {@link Snapshotable} object
*/
@Override
public String getElementId() {
return this.elementId;
}
/**
* PublisherProcessor receives events from the last window processor of Window,
* filter them depending on user defined output type and publish them to the stream junction.
*/
private class StreamPublishProcessor implements Processor {
/**
* Allow current events.
*/
private final boolean allowCurrentEvents;
/**
* Allow expired events.
*/
private final boolean allowExpiredEvents;
/**
* User preference of output event type. Stored for cloning purpose.
*/
private final OutputStream.OutputEventType outputEventType;
public StreamPublishProcessor(OutputStream.OutputEventType outputEventType) {
this.outputEventType = outputEventType;
this.allowCurrentEvents = (outputEventType == OutputStream.OutputEventType.CURRENT_EVENTS ||
outputEventType == OutputStream.OutputEventType.ALL_EVENTS);
this.allowExpiredEvents = (outputEventType == OutputStream.OutputEventType.EXPIRED_EVENTS ||
outputEventType == OutputStream.OutputEventType.ALL_EVENTS);
}
public void process(ComplexEventChunk complexEventChunk) {
// Filter the events depending on user defined output type.
// if(allowCurrentEvents && allowExpiredEvents)
complexEventChunk.reset();
while (complexEventChunk.hasNext()) {
ComplexEvent event = complexEventChunk.next();
if (((event.getType() != StreamEvent.Type.CURRENT || !allowCurrentEvents) && (event.getType() !=
StreamEvent.Type.EXPIRED || !allowExpiredEvents))) {
complexEventChunk.remove();
}
}
complexEventChunk.reset();
if (complexEventChunk.hasNext()) {
// Publish the events
outputPublisher.send(complexEventChunk.getFirst());
}
}
public Processor getNextProcessor() {
return null;
}
public void setNextProcessor(Processor processor) {
// Do nothing
}
public void setToLast(Processor processor) {
// Do nothing
}
public Processor cloneProcessor(String key) {
return new StreamPublishProcessor(this.outputEventType);
}
}
}