/*
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. 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
*
* 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.query.selector;
import org.apache.log4j.Logger;
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.GroupedComplexEvent;
import org.wso2.siddhi.core.event.state.populater.StateEventPopulator;
import org.wso2.siddhi.core.event.stream.StreamEvent;
import org.wso2.siddhi.core.exception.ExecutionPlanCreationException;
import org.wso2.siddhi.core.executor.condition.ConditionExpressionExecutor;
import org.wso2.siddhi.core.query.output.ratelimit.OutputRateLimiter;
import org.wso2.siddhi.core.query.processor.Processor;
import org.wso2.siddhi.core.query.selector.attribute.processor.AttributeProcessor;
import org.wso2.siddhi.query.api.execution.query.selection.Selector;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Processor implementation representing selector portion of the Siddhi query.
*/
public class QuerySelector implements Processor {
private static final Logger log = Logger.getLogger(QuerySelector.class);
private static final ThreadLocal<String> keyThreadLocal = new ThreadLocal<String>();
private Selector selector;
private ExecutionPlanContext executionPlanContext;
private boolean currentOn = false;
private boolean expiredOn = false;
private boolean containsAggregator = false;
private OutputRateLimiter outputRateLimiter;
private List<AttributeProcessor> attributeProcessorList;
private ConditionExpressionExecutor havingConditionExecutor = null;
private boolean isGroupBy = false;
private GroupByKeyGenerator groupByKeyGenerator;
private String id;
private StateEventPopulator eventPopulator;
private boolean batchingEnabled = true;
public QuerySelector(String id, Selector selector, boolean currentOn, boolean expiredOn, ExecutionPlanContext
executionPlanContext) {
this.id = id;
this.currentOn = currentOn;
this.expiredOn = expiredOn;
this.selector = selector;
this.executionPlanContext = executionPlanContext;
}
public static String getThreadLocalGroupByKey() {
return keyThreadLocal.get();
}
@Override
public void process(ComplexEventChunk complexEventChunk) {
if (log.isTraceEnabled()) {
log.trace("event is processed by selector " + id + this);
}
if (containsAggregator && complexEventChunk.isBatch() && batchingEnabled) {
if (isGroupBy) {
processInBatchGroupBy(complexEventChunk);
} else {
processInBatchNoGroupBy(complexEventChunk);
}
} else {
if (isGroupBy) {
processGroupBy(complexEventChunk);
} else {
processNoGroupBy(complexEventChunk);
}
}
}
private void processNoGroupBy(ComplexEventChunk complexEventChunk) {
complexEventChunk.reset();
synchronized (this) {
while (complexEventChunk.hasNext()) {
ComplexEvent event = complexEventChunk.next();
switch (event.getType()) {
case CURRENT:
case EXPIRED:
eventPopulator.populateStateEvent(event);
for (AttributeProcessor attributeProcessor : attributeProcessorList) {
attributeProcessor.process(event);
}
if (((event.getType() != StreamEvent.Type.CURRENT || !currentOn) && (event.getType() !=
StreamEvent.Type.EXPIRED || !expiredOn)) || ((havingConditionExecutor != null &&
!havingConditionExecutor.execute(event)))) {
complexEventChunk.remove();
}
break;
case RESET:
for (AttributeProcessor attributeProcessor : attributeProcessorList) {
attributeProcessor.process(event);
}
break;
case TIMER:
complexEventChunk.remove();
break;
}
}
}
complexEventChunk.reset();
if (complexEventChunk.hasNext()) {
outputRateLimiter.process(complexEventChunk);
}
}
private void processGroupBy(ComplexEventChunk complexEventChunk) {
complexEventChunk.reset();
ComplexEventChunk<ComplexEvent> currentComplexEventChunk = new ComplexEventChunk<ComplexEvent>
(complexEventChunk.isBatch());
synchronized (this) {
while (complexEventChunk.hasNext()) {
ComplexEvent event = complexEventChunk.next();
switch (event.getType()) {
case CURRENT:
case EXPIRED:
eventPopulator.populateStateEvent(event);
String groupedByKey = groupByKeyGenerator.constructEventKey(event);
keyThreadLocal.set(groupedByKey);
for (AttributeProcessor attributeProcessor : attributeProcessorList) {
attributeProcessor.process(event);
}
if ((event.getType() == StreamEvent.Type.CURRENT && currentOn) || (event.getType() ==
StreamEvent.Type.EXPIRED && expiredOn)) {
if (!(havingConditionExecutor != null && !havingConditionExecutor.execute(event))) {
complexEventChunk.remove();
currentComplexEventChunk.add(new GroupedComplexEvent(groupedByKey, event));
}
}
keyThreadLocal.remove();
break;
case TIMER:
break;
case RESET:
for (AttributeProcessor attributeProcessor : attributeProcessorList) {
attributeProcessor.process(event);
}
break;
}
}
}
currentComplexEventChunk.reset();
if (currentComplexEventChunk.hasNext()) {
outputRateLimiter.process(currentComplexEventChunk);
}
}
private void processInBatchNoGroupBy(ComplexEventChunk complexEventChunk) {
complexEventChunk.reset();
ComplexEvent lastEvent = null;
synchronized (this) {
while (complexEventChunk.hasNext()) {
ComplexEvent event = complexEventChunk.next();
switch (event.getType()) {
case CURRENT:
case EXPIRED:
eventPopulator.populateStateEvent(event);
for (AttributeProcessor attributeProcessor : attributeProcessorList) {
attributeProcessor.process(event);
}
if (!(havingConditionExecutor != null && !havingConditionExecutor.execute(event))) {
if ((event.getType() == StreamEvent.Type.CURRENT && currentOn) || (event.getType() ==
StreamEvent.Type.EXPIRED && expiredOn)) {
complexEventChunk.remove();
lastEvent = event;
}
}
break;
case TIMER:
break;
case RESET:
for (AttributeProcessor attributeProcessor : attributeProcessorList) {
attributeProcessor.process(event);
}
break;
}
}
}
if (lastEvent != null) {
complexEventChunk.clear();
complexEventChunk.add(lastEvent);
outputRateLimiter.process(complexEventChunk);
}
}
private void processInBatchGroupBy(ComplexEventChunk complexEventChunk) {
Map<String, ComplexEvent> groupedEvents = new LinkedHashMap<String, ComplexEvent>();
complexEventChunk.reset();
synchronized (this) {
while (complexEventChunk.hasNext()) {
ComplexEvent event = complexEventChunk.next();
switch (event.getType()) {
case CURRENT:
case EXPIRED:
eventPopulator.populateStateEvent(event);
String groupByKey = groupByKeyGenerator.constructEventKey(event);
keyThreadLocal.set(groupByKey);
for (AttributeProcessor attributeProcessor : attributeProcessorList) {
attributeProcessor.process(event);
}
if (!(havingConditionExecutor != null && !havingConditionExecutor.execute(event))) {
if ((event.getType() == StreamEvent.Type.CURRENT && currentOn) || (event.getType() ==
StreamEvent.Type.EXPIRED && expiredOn)) {
complexEventChunk.remove();
groupedEvents.put(groupByKey, event);
}
}
keyThreadLocal.remove();
break;
case TIMER:
break;
case RESET:
for (AttributeProcessor attributeProcessor : attributeProcessorList) {
attributeProcessor.process(event);
}
break;
}
}
}
if (groupedEvents.size() != 0) {
complexEventChunk.clear();
for (Map.Entry<String, ComplexEvent> groupedEventEntry : groupedEvents.entrySet()) {
complexEventChunk.add(new GroupedComplexEvent(groupedEventEntry.getKey(), groupedEventEntry.getValue
()));
}
complexEventChunk.reset();
outputRateLimiter.process(complexEventChunk);
}
}
@Override
public Processor getNextProcessor() {
return null; //since there is no processors after a query selector
}
@Override
public void setNextProcessor(Processor processor) {
//this method will not be used as there is no processors after a query selector
}
public void setNextProcessor(OutputRateLimiter outputRateLimiter) {
if (this.outputRateLimiter == null) {
this.outputRateLimiter = outputRateLimiter;
} else {
throw new ExecutionPlanCreationException("outputRateLimiter is already assigned");
}
}
@Override
public void setToLast(Processor processor) {
if (getNextProcessor() == null) {
this.setNextProcessor(processor);
} else {
getNextProcessor().setToLast(processor);
}
}
@Override
public Processor cloneProcessor(String key) {
return null;
}
public List<AttributeProcessor> getAttributeProcessorList() {
return attributeProcessorList;
}
public void setAttributeProcessorList(List<AttributeProcessor> attributeProcessorList, boolean containsAggregator) {
this.attributeProcessorList = attributeProcessorList;
this.containsAggregator = this.containsAggregator || containsAggregator;
}
public void setGroupByKeyGenerator(GroupByKeyGenerator groupByKeyGenerator) {
isGroupBy = true;
this.groupByKeyGenerator = groupByKeyGenerator;
}
public void setHavingConditionExecutor(ConditionExpressionExecutor havingConditionExecutor, boolean
containsAggregator) {
this.havingConditionExecutor = havingConditionExecutor;
this.containsAggregator = this.containsAggregator || containsAggregator;
}
public QuerySelector clone(String key) {
QuerySelector clonedQuerySelector = new QuerySelector(id + key, selector, currentOn, expiredOn,
executionPlanContext);
List<AttributeProcessor> clonedAttributeProcessorList = new ArrayList<AttributeProcessor>();
for (AttributeProcessor attributeProcessor : attributeProcessorList) {
clonedAttributeProcessorList.add(attributeProcessor.cloneProcessor(key));
}
clonedQuerySelector.attributeProcessorList = clonedAttributeProcessorList;
clonedQuerySelector.isGroupBy = isGroupBy;
clonedQuerySelector.containsAggregator = containsAggregator;
clonedQuerySelector.groupByKeyGenerator = groupByKeyGenerator;
clonedQuerySelector.havingConditionExecutor = havingConditionExecutor;
clonedQuerySelector.eventPopulator = eventPopulator;
clonedQuerySelector.batchingEnabled = batchingEnabled;
return clonedQuerySelector;
}
public void setBatchingEnabled(boolean batchingEnabled) {
this.batchingEnabled = batchingEnabled;
}
public void setEventPopulator(StateEventPopulator eventPopulator) {
this.eventPopulator = eventPopulator;
}
}