/*
***************************************************************************************
* Copyright (C) 2006 EsperTech, Inc. All rights reserved. *
* http://www.espertech.com/esper *
* http://www.espertech.com *
* ---------------------------------------------------------------------------------- *
* The software in this package is published under the terms of the GPL license *
* a copy of which has been included with this distribution in the license.txt file. *
***************************************************************************************
*/
package com.espertech.esper.view;
import com.espertech.esper.client.annotation.Audit;
import com.espertech.esper.client.annotation.AuditEnum;
import com.espertech.esper.client.annotation.HintEnum;
import com.espertech.esper.collection.Pair;
import com.espertech.esper.core.context.util.AgentInstanceContext;
import com.espertech.esper.core.context.util.AgentInstanceViewFactoryChainContext;
import com.espertech.esper.core.service.StatementContext;
import com.espertech.esper.epl.expression.core.ExprNode;
import com.espertech.esper.epl.expression.core.ExprNodeUtility;
import com.espertech.esper.epl.spec.ViewSpec;
import com.espertech.esper.epl.virtualdw.VirtualDWViewFactory;
import com.espertech.esper.view.std.GroupByViewFactoryMarker;
import com.espertech.esper.view.std.MergeViewFactoryMarker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.annotation.Annotation;
import java.util.*;
/**
* Utility methods to deal with chains of views, and for merge/group-by views.
*/
public class ViewServiceHelper {
public static Set<String> getUniqueCandidateProperties(List<ViewFactory> viewFactory, Annotation[] annotations) {
boolean disableUniqueImplicit = HintEnum.DISABLE_UNIQUE_IMPLICIT_IDX.getHint(annotations) != null;
if (viewFactory == null || viewFactory.isEmpty()) {
return null;
}
if (viewFactory.get(0) instanceof GroupByViewFactoryMarker) {
ExprNode[] criteria = ((GroupByViewFactoryMarker) viewFactory.get(0)).getCriteriaExpressions();
Set<String> groupedCriteria = ExprNodeUtility.getPropertyNamesIfAllProps(criteria);
if (groupedCriteria == null) {
return null;
}
if (viewFactory.get(1) instanceof DataWindowViewFactoryUniqueCandidate && !disableUniqueImplicit) {
DataWindowViewFactoryUniqueCandidate uniqueFactory = (DataWindowViewFactoryUniqueCandidate) viewFactory.get(1);
Set<String> uniqueCandidates = uniqueFactory.getUniquenessCandidatePropertyNames();
if (uniqueCandidates != null) {
uniqueCandidates.addAll(groupedCriteria);
}
return uniqueCandidates;
}
return null;
} else if (viewFactory.get(0) instanceof DataWindowViewFactoryUniqueCandidate && !disableUniqueImplicit) {
DataWindowViewFactoryUniqueCandidate uniqueFactory = (DataWindowViewFactoryUniqueCandidate) viewFactory.get(0);
return uniqueFactory.getUniquenessCandidatePropertyNames();
} else if (viewFactory.get(0) instanceof VirtualDWViewFactory) {
VirtualDWViewFactory vdw = (VirtualDWViewFactory) viewFactory.get(0);
return vdw.getUniqueKeys();
}
return null;
}
/**
* Add merge views for any views in the chain requiring a merge (group view).
* Appends to the list of view specifications passed in one ore more
* new view specifications that represent merge views.
* Merge views have the same parameter list as the (group) view they merge data for.
*
* @param specifications is a list of view definitions defining the chain of views.
* @throws ViewProcessingException indicating that the view chain configuration is invalid
*/
protected static void addMergeViews(List<ViewSpec> specifications)
throws ViewProcessingException {
if (log.isDebugEnabled()) {
log.debug(".addMergeViews Incoming specifications=" + Arrays.toString(specifications.toArray()));
}
// A grouping view requires a merge view and cannot be last since it would not group sub-views
if (specifications.size() > 0) {
ViewSpec lastView = specifications.get(specifications.size() - 1);
ViewEnum viewEnum = ViewEnum.forName(lastView.getObjectNamespace(), lastView.getObjectName());
if ((viewEnum != null) && (viewEnum.getMergeView() != null)) {
throw new ViewProcessingException("Invalid use of the '" +
lastView.getObjectName() + "' view, the view requires one or more child views to group, or consider using the group-by clause");
}
}
LinkedList<ViewSpec> mergeViewSpecs = new LinkedList<ViewSpec>();
for (ViewSpec spec : specifications) {
ViewEnum viewEnum = ViewEnum.forName(spec.getObjectNamespace(), spec.getObjectName());
if (viewEnum == null) {
continue;
}
if (viewEnum.getMergeView() == null) {
continue;
}
// The merge view gets the same parameters as the view that requires the merge
ViewSpec mergeViewSpec = new ViewSpec(viewEnum.getMergeView().getNamespace(), viewEnum.getMergeView().getName(),
spec.getObjectParameters());
// The merge views are added to the beginning of the list.
// This enables group views to stagger ie. marketdata.group("symbol").group("feed").xxx.merge(...).merge(...)
mergeViewSpecs.addFirst(mergeViewSpec);
}
specifications.addAll(mergeViewSpecs);
if (log.isDebugEnabled()) {
log.debug(".addMergeViews Outgoing specifications=" + Arrays.toString(specifications.toArray()));
}
}
/**
* Instantiate a chain of views.
*
* @param parentViewable - parent view to add the chain to
* @param viewFactories - is the view factories to use to make each view, or reuse and existing view
* @param viewFactoryChainContext context
* @return chain of views instantiated
*/
public static List<View> instantiateChain(Viewable parentViewable,
List<ViewFactory> viewFactories,
AgentInstanceViewFactoryChainContext viewFactoryChainContext) {
List<View> newViews = new LinkedList<View>();
Viewable parent = parentViewable;
for (int i = 0; i < viewFactories.size(); i++) {
ViewFactory viewFactory = viewFactories.get(i);
// Create the new view object
View currentView = viewFactory.makeView(viewFactoryChainContext);
newViews.add(currentView);
parent.addView(currentView);
// Next parent is the new view
parent = currentView;
}
return newViews;
}
public static void removeFirstUnsharedView(List<View> childViews) {
for (int i = childViews.size() - 1; i >= 0; i--) {
View child = childViews.get(i);
Viewable parent = child.getParent();
if (parent == null) {
return;
}
parent.removeView(child);
if (parent.hasViews()) {
return;
}
}
}
/**
* Removes a view from a parent view returning the orphaned parent views in a list.
*
* @param parentViewable - parent to remove view from
* @param viewToRemove - view to remove
* @return chain of orphaned views
*/
protected static List<View> removeChainLeafView(Viewable parentViewable,
Viewable viewToRemove) {
List<View> removedViews = new LinkedList<View>();
// The view to remove must be a leaf node - non-leaf views are just not removed
if (viewToRemove.hasViews()) {
return removedViews;
}
// Find child viewToRemove among descendent views
List<View> viewPath = ViewSupport.findDescendent(parentViewable, viewToRemove);
if (viewPath == null) {
String message = "Viewable not found when removing view " + viewToRemove;
throw new IllegalArgumentException(message);
}
// The viewToRemove is a direct child view of the stream
if (viewPath.isEmpty()) {
boolean isViewRemoved = parentViewable.removeView((View) viewToRemove);
if (!isViewRemoved) {
String message = "Failed to remove immediate child view " + viewToRemove;
log.error(".remove " + message);
throw new IllegalStateException(message);
}
removedViews.add((View) viewToRemove);
return removedViews;
}
View[] viewPathArray = viewPath.toArray(new View[viewPath.size()]);
View currentView = (View) viewToRemove;
// Remove child from parent views until a parent view has more children,
// or there are no more parents (index=0).
for (int index = viewPathArray.length - 1; index >= 0; index--) {
boolean isViewRemoved = viewPathArray[index].removeView(currentView);
removedViews.add(currentView);
if (!isViewRemoved) {
String message = "Failed to remove view " + currentView;
log.error(".remove " + message);
throw new IllegalStateException(message);
}
// If the parent views has more child views, we are done
if (viewPathArray[index].hasViews()) {
break;
}
// The parent of the top parent is the stream, remove from stream
if (index == 0) {
parentViewable.removeView(viewPathArray[0]);
removedViews.add(viewPathArray[0]);
} else {
currentView = viewPathArray[index];
}
}
return removedViews;
}
/**
* Match the views under the stream to the list of view specications passed in.
* The method changes the view specifications list passed in and removes those
* specifications for which matcing views have been found.
* If none of the views under the stream matches the first view specification passed in,
* the method returns the stream itself and leaves the view specification list unchanged.
* If one view under the stream matches, the view's specification is removed from the list.
* The method will then attempt to determine if any child views of that view also match
* specifications.
*
* @param rootViewable is the top rootViewable event stream to which all views are attached as child views
* This parameter is changed by this method, ie. specifications are removed if they match existing views.
* @param viewFactories is the view specifications for making views
* @param agentInstanceContext agent instance context
* @return a pair of (A) the stream if no views matched, or the last child view that matched (B) the full list
* of parent views
*/
protected static Pair<Viewable, List<View>> matchExistingViews(Viewable rootViewable,
List<ViewFactory> viewFactories,
AgentInstanceContext agentInstanceContext) {
Viewable currentParent = rootViewable;
List<View> matchedViewList = new LinkedList<View>();
boolean foundMatch;
if (viewFactories.isEmpty()) {
return new Pair<Viewable, List<View>>(rootViewable, Collections.<View>emptyList());
}
do {
foundMatch = false;
for (View childView : currentParent.getViews()) {
ViewFactory currentFactory = viewFactories.get(0);
if (!(currentFactory.canReuse(childView, agentInstanceContext))) {
continue;
}
// The specifications match, check current data window size
viewFactories.remove(0);
currentParent = childView;
foundMatch = true;
matchedViewList.add(childView);
break;
}
}
while (foundMatch && (!viewFactories.isEmpty()));
return new Pair<Viewable, List<View>>(currentParent, matchedViewList);
}
/**
* Given a list of view specifications obtained from by parsing this method instantiates a list of view factories.
* The view factories are not yet aware of each other after leaving this method (so not yet chained logically).
* They are simply instantiated and assigned view parameters.
*
* @param streamNum is the stream number
* @param viewSpecList is the view definition
* @param statementContext is statement service context and statement info
* @param isSubquery subquery indicator
* @param subqueryNumber for subqueries
* @return list of view factories
* @throws ViewProcessingException if the factory cannot be creates such as for invalid view spec
*/
public static List<ViewFactory> instantiateFactories(int streamNum,
List<ViewSpec> viewSpecList,
StatementContext statementContext,
boolean isSubquery,
int subqueryNumber)
throws ViewProcessingException {
List<ViewFactory> factoryChain = new ArrayList<ViewFactory>();
boolean grouped = false;
for (ViewSpec spec : viewSpecList) {
// Create the new view factory
ViewFactory viewFactory = statementContext.getViewResolutionService().create(spec.getObjectNamespace(), spec.getObjectName());
Audit audit = AuditEnum.VIEW.getAudit(statementContext.getAnnotations());
if (audit != null) {
viewFactory = (ViewFactory) ViewFactoryProxy.newInstance(statementContext.getEngineURI(), statementContext.getStatementName(), viewFactory, spec.getObjectName());
}
factoryChain.add(viewFactory);
// Set view factory parameters
try {
ViewFactoryContext context = new ViewFactoryContext(statementContext, streamNum, spec.getObjectNamespace(), spec.getObjectName(), isSubquery, subqueryNumber, grouped);
viewFactory.setViewParameters(context, spec.getObjectParameters());
} catch (ViewParameterException e) {
throw new ViewProcessingException("Error in view '" + spec.getObjectName() +
"', " + e.getMessage(), e);
}
if (viewFactory instanceof GroupByViewFactoryMarker) {
grouped = true;
}
if (viewFactory instanceof MergeViewFactoryMarker) {
grouped = false;
}
}
return factoryChain;
}
private static final Logger log = LoggerFactory.getLogger(ViewServiceHelper.class);
}