/**************************************************************************************
* Copyright (C) 2008 EsperTech, Inc. All rights reserved. *
* http://esper.codehaus.org *
* 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.collection.Pair;
import com.espertech.esper.core.context.util.AgentInstanceViewFactoryChainContext;
import com.espertech.esper.core.service.StatementContext;
import com.espertech.esper.epl.expression.ExprNode;
import com.espertech.esper.epl.expression.ExprNodeUtility;
import com.espertech.esper.epl.spec.ViewSpec;
import com.espertech.esper.epl.virtualdw.VirtualDWViewFactory;
import com.espertech.esper.view.ext.IStreamSortRankRandomAccess;
import com.espertech.esper.view.std.GroupByViewFactoryMarker;
import com.espertech.esper.view.window.IStreamRandomAccess;
import com.espertech.esper.view.window.IStreamRelativeAccess;
import com.espertech.esper.view.window.RandomAccessByIndexGetter;
import com.espertech.esper.view.window.RelativeAccessByEventNIndexMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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) {
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) {
DataWindowViewFactoryUniqueCandidate uniqueFactory = (DataWindowViewFactoryUniqueCandidate) viewFactory.get(1);
Set<String> uniqueCandidates = uniqueFactory.getUniquenessCandidatePropertyNames();
uniqueCandidates.addAll(groupedCriteria);
return uniqueCandidates;
}
return null;
}
else if (viewFactory.get(0) instanceof DataWindowViewFactoryUniqueCandidate) {
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;
}
public static IStreamRandomAccess getOptPreviousExprRandomAccess(AgentInstanceViewFactoryChainContext agentInstanceViewFactoryContext) {
IStreamRandomAccess randomAccess = null;
if (agentInstanceViewFactoryContext.getPreviousNodeGetter() != null)
{
RandomAccessByIndexGetter getter = (RandomAccessByIndexGetter) agentInstanceViewFactoryContext.getPreviousNodeGetter();
randomAccess = new IStreamRandomAccess(getter);
getter.updated(randomAccess);
}
return randomAccess;
}
public static IStreamRelativeAccess getOptPreviousExprRelativeAccess(AgentInstanceViewFactoryChainContext agentInstanceViewFactoryContext) {
IStreamRelativeAccess relativeAccessByEvent = null;
if (agentInstanceViewFactoryContext.getPreviousNodeGetter() != null)
{
RelativeAccessByEventNIndexMap getter = (RelativeAccessByEventNIndexMap) agentInstanceViewFactoryContext.getPreviousNodeGetter();
relativeAccessByEvent = new IStreamRelativeAccess(getter);
getter.updated(relativeAccessByEvent, null);
}
return relativeAccessByEvent;
}
public static IStreamSortRankRandomAccess getOptPreviousExprSortedRankedAccess(AgentInstanceViewFactoryChainContext agentInstanceViewFactoryContext) {
IStreamSortRankRandomAccess rankedRandomAccess = null;
if (agentInstanceViewFactoryContext.getPreviousNodeGetter() != null)
{
RandomAccessByIndexGetter getter = (RandomAccessByIndexGetter) agentInstanceViewFactoryContext.getPreviousNodeGetter();
rankedRandomAccess = new IStreamSortRankRandomAccess(getter);
getter.updated(rankedRandomAccess);
}
return rankedRandomAccess;
}
/**
* 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.getObjectNamespace() + ":" +
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
* @return chain of views instantiated
*/
protected 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;
}
/**
* 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.fatal(".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.fatal(".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
* @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)
{
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 // while ((foundMatch) && (specifications.size() > 0));
{
foundMatch = false;
for (View childView : currentParent.getViews())
{
ViewFactory currentFactory = viewFactories.get(0);
if (!(currentFactory.canReuse(childView)))
{
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
* @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)
throws ViewProcessingException
{
List<ViewFactory> factoryChain = new ArrayList<ViewFactory>();
int viewNum = 0;
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, viewNum, spec.getObjectNamespace(), spec.getObjectName());
viewFactory.setViewParameters(context, spec.getObjectParameters());
}
catch (ViewParameterException e)
{
throw new ViewProcessingException("Error in view '" + spec.getObjectNamespace() + ':' + spec.getObjectName() +
"', " + e.getMessage());
}
viewNum++;
}
return factoryChain;
}
private static final Log log = LogFactory.getLog(ViewServiceHelper.class);
}