/* *************************************************************************************** * 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.epl.declexpr; import com.espertech.esper.client.EventBean; import com.espertech.esper.client.EventPropertyGetter; import com.espertech.esper.client.EventType; import com.espertech.esper.client.PropertyAccessException; 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.ContextDescriptor; import com.espertech.esper.epl.core.StreamTypeService; import com.espertech.esper.epl.core.StreamTypeServiceImpl; import com.espertech.esper.epl.enummethod.dot.ExprDeclaredOrLambdaNode; import com.espertech.esper.epl.expression.core.*; import com.espertech.esper.epl.expression.visitor.ExprNodeIdentVisitorWParent; import com.espertech.esper.epl.expression.visitor.ExprNodeSummaryVisitor; import com.espertech.esper.epl.expression.visitor.ExprNodeVisitor; import com.espertech.esper.epl.expression.visitor.ExprNodeVisitorWithParent; import com.espertech.esper.epl.spec.ExpressionDeclItem; import com.espertech.esper.filter.FilterSpecLookupable; import com.espertech.esper.util.SerializableObjectCopier; import java.io.StringWriter; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * Expression instance as declared elsewhere. */ public class ExprDeclaredNodeImpl extends ExprNodeBase implements ExprDeclaredNode, ExprDeclaredOrLambdaNode, ExprFilterOptimizableNode, ExprNodeInnerNodeProvider, ExprConstantNode { private static final long serialVersionUID = 9140100131374697808L; private final ExpressionDeclItem prototype; private List<ExprNode> chainParameters; private transient ExprEvaluator exprEvaluator; private ExprNode expressionBodyCopy; public ExprDeclaredNodeImpl(ExpressionDeclItem prototype, List<ExprNode> chainParameters, ContextDescriptor contextDescriptor) { this.prototype = prototype; this.chainParameters = chainParameters; // copy expression - we do it at this time and not later try { expressionBodyCopy = (ExprNode) SerializableObjectCopier.copy(prototype.getInner()); } catch (Exception e) { throw new RuntimeException("Internal error providing expression tree: " + e.getMessage(), e); } // replace context-properties where they are currently identifiers if (contextDescriptor == null) { return; } ExprNodeIdentVisitorWParent visitorWParent = new ExprNodeIdentVisitorWParent(); expressionBodyCopy.accept(visitorWParent); for (Pair<ExprNode, ExprIdentNode> pair : visitorWParent.getIdentNodes()) { String streamOrProp = pair.getSecond().getStreamOrPropertyName(); if (streamOrProp != null && contextDescriptor.getContextPropertyRegistry().isContextPropertyPrefix(streamOrProp)) { ExprContextPropertyNode context = new ExprContextPropertyNode(pair.getSecond().getUnresolvedPropertyName()); if (pair.getFirst() == null) { expressionBodyCopy = context; } else { ExprNodeUtility.replaceChildNode(pair.getFirst(), pair.getSecond(), context); } } } } public ExprNode getBody() { return expressionBodyCopy; } public List<ExprNode> getAdditionalNodes() { return chainParameters; } public boolean validated() { return exprEvaluator != null; } public Class getConstantType() { return exprEvaluator.getType(); } public Object getConstantValue(ExprEvaluatorContext context) { return exprEvaluator.evaluate(null, true, context); } public boolean isConstantValue() { return expressionBodyCopy.isConstantResult(); } public LinkedHashMap<String, Integer> getOuterStreamNames(Map<String, Integer> outerStreamNames) throws ExprValidationException { checkParameterCount(); // determine stream ids for each parameter LinkedHashMap<String, Integer> streamParameters = new LinkedHashMap<String, Integer>(); for (int param = 0; param < chainParameters.size(); param++) { if (!(chainParameters.get(param) instanceof ExprIdentNode)) { throw new ExprValidationException("Sub-selects in an expression declaration require passing only stream names as parameters"); } String parameterName = ((ExprIdentNode) chainParameters.get(param)).getUnresolvedPropertyName(); Integer streamIdFound = outerStreamNames.get(parameterName); if (streamIdFound == null) { throw new ExprValidationException("Failed validation of expression declaration '" + prototype.getName() + "': Invalid parameter to expression declaration, parameter " + param + " is not the name of a stream in the query"); } String prototypeName = prototype.getParametersNames().get(param); streamParameters.put(prototypeName, streamIdFound); } return streamParameters; } public ExprNode validate(ExprValidationContext validationContext) throws ExprValidationException { if (prototype.isAlias()) { try { expressionBodyCopy = ExprNodeUtility.getValidatedSubtree(ExprNodeOrigin.ALIASEXPRBODY, expressionBodyCopy, validationContext); } catch (ExprValidationException ex) { String message = "Error validating expression alias '" + prototype.getName() + "': " + ex.getMessage(); throw new ExprValidationException(message, ex); } exprEvaluator = expressionBodyCopy.getExprEvaluator(); return null; } if (exprEvaluator != null) { return null; // already evaluated } if (this.getChildNodes().length > 0) { throw new IllegalStateException("Execution node has its own child nodes"); } // validate chain List<ExprNode> validated = new ArrayList<ExprNode>(); for (ExprNode expr : chainParameters) { validated.add(ExprNodeUtility.getValidatedSubtree(ExprNodeOrigin.DECLAREDEXPRPARAM, expr, validationContext)); } chainParameters = validated; // validate parameter count checkParameterCount(); // create context for expression body EventType[] eventTypes = new EventType[prototype.getParametersNames().size()]; String[] streamNames = new String[prototype.getParametersNames().size()]; boolean[] isIStreamOnly = new boolean[prototype.getParametersNames().size()]; int[] streamsIdsPerStream = new int[prototype.getParametersNames().size()]; boolean allStreamIdsMatch = true; for (int i = 0; i < prototype.getParametersNames().size(); i++) { ExprNode parameter = chainParameters.get(i); streamNames[i] = prototype.getParametersNames().get(i); if (parameter instanceof ExprStreamUnderlyingNode) { ExprStreamUnderlyingNode und = (ExprStreamUnderlyingNode) parameter; eventTypes[i] = validationContext.getStreamTypeService().getEventTypes()[und.getStreamId()]; isIStreamOnly[i] = validationContext.getStreamTypeService().getIStreamOnly()[und.getStreamId()]; streamsIdsPerStream[i] = und.getStreamId(); } else if (parameter instanceof ExprWildcard) { if (validationContext.getStreamTypeService().getEventTypes().length != 1) { throw new ExprValidationException("Expression '" + prototype.getName() + "' only allows a wildcard parameter if there is a single stream available, please use a stream or tag name instead"); } eventTypes[i] = validationContext.getStreamTypeService().getEventTypes()[0]; isIStreamOnly[i] = validationContext.getStreamTypeService().getIStreamOnly()[0]; streamsIdsPerStream[i] = 0; } else { throw new ExprValidationException("Expression '" + prototype.getName() + "' requires a stream name as a parameter"); } if (streamsIdsPerStream[i] != i) { allStreamIdsMatch = false; } } StreamTypeService streamTypeService = validationContext.getStreamTypeService(); StreamTypeServiceImpl copyTypes = new StreamTypeServiceImpl(eventTypes, streamNames, isIStreamOnly, streamTypeService.getEngineURIQualifier(), streamTypeService.isOnDemandStreams()); copyTypes.setRequireStreamNames(true); // validate expression body in this context try { ExprValidationContext expressionBodyContext = new ExprValidationContext(copyTypes, validationContext); expressionBodyCopy = ExprNodeUtility.getValidatedSubtree(ExprNodeOrigin.DECLAREDEXPRBODY, expressionBodyCopy, expressionBodyContext); } catch (ExprValidationException ex) { String message = "Error validating expression declaration '" + prototype.getName() + "': " + ex.getMessage(); throw new ExprValidationException(message, ex); } // analyze child node ExprNodeSummaryVisitor summaryVisitor = new ExprNodeSummaryVisitor(); expressionBodyCopy.accept(summaryVisitor); boolean isCache = !(summaryVisitor.isHasAggregation() || summaryVisitor.isHasPreviousPrior()); isCache &= validationContext.getExprEvaluatorContext().getExpressionResultCacheService().isDeclaredExprCacheEnabled(); // determine a suitable evaluation if (expressionBodyCopy.isConstantResult()) { // pre-evaluated exprEvaluator = new ExprDeclaredEvalConstant(expressionBodyCopy.getExprEvaluator().getType(), prototype, expressionBodyCopy.getExprEvaluator().evaluate(null, true, null)); } else if (prototype.getParametersNames().isEmpty() || (allStreamIdsMatch && prototype.getParametersNames().size() == streamTypeService.getEventTypes().length)) { exprEvaluator = new ExprDeclaredEvalNoRewrite(expressionBodyCopy.getExprEvaluator(), prototype, isCache); } else { exprEvaluator = new ExprDeclaredEvalRewrite(expressionBodyCopy.getExprEvaluator(), prototype, isCache, streamsIdsPerStream); } Audit audit = AuditEnum.EXPRDEF.getAudit(validationContext.getAnnotations()); if (audit != null) { exprEvaluator = (ExprEvaluator) ExprEvaluatorProxy.newInstance(validationContext.getStreamTypeService().getEngineURIQualifier(), validationContext.getStatementName(), prototype.getName(), exprEvaluator); } return null; } public boolean getFilterLookupEligible() { return true; } public FilterSpecLookupable getFilterLookupable() { return new FilterSpecLookupable(ExprNodeUtility.toExpressionStringMinPrecedenceSafe(this), new DeclaredNodeEventPropertyGetter(exprEvaluator), exprEvaluator.getType(), true); } public boolean isConstantResult() { return false; } public boolean equalsNode(ExprNode node, boolean ignoreStreamPrefix) { if (!(node instanceof ExprDeclaredNodeImpl)) { return false; } ExprDeclaredNodeImpl otherExprCaseNode = (ExprDeclaredNodeImpl) node; return ExprNodeUtility.deepEquals(expressionBodyCopy, otherExprCaseNode, false); } public void accept(ExprNodeVisitor visitor) { super.accept(visitor); if (this.getChildNodes().length == 0) { expressionBodyCopy.accept(visitor); } } public void accept(ExprNodeVisitorWithParent visitor) { super.accept(visitor); if (this.getChildNodes().length == 0) { expressionBodyCopy.accept(visitor); } } public void acceptChildnodes(ExprNodeVisitorWithParent visitor, ExprNode parent) { super.acceptChildnodes(visitor, parent); if (visitor.isVisit(this) && this.getChildNodes().length == 0) { expressionBodyCopy.accept(visitor); } } public ExprNode getExpressionBodyCopy() { return expressionBodyCopy; } public ExpressionDeclItem getPrototype() { return prototype; } public List<ExprNode> getChainParameters() { return chainParameters; } public ExprEvaluator getExprEvaluator() { return exprEvaluator; } private void checkParameterCount() throws ExprValidationException { if (chainParameters.size() != prototype.getParametersNames().size()) { throw new ExprValidationException("Parameter count mismatches for declared expression '" + prototype.getName() + "', expected " + prototype.getParametersNames().size() + " parameters but received " + chainParameters.size() + " parameters"); } } public void toPrecedenceFreeEPL(StringWriter writer) { writer.append(prototype.getName()); if (prototype.isAlias()) { return; } writer.append("("); String delimiter = ""; for (ExprNode parameter : chainParameters) { writer.append(delimiter); parameter.toEPL(writer, ExprPrecedenceEnum.MINIMUM); delimiter = ","; } writer.append(")"); } public ExprPrecedenceEnum getPrecedence() { return ExprPrecedenceEnum.UNARY; } private final static class DeclaredNodeEventPropertyGetter implements EventPropertyGetter { private final ExprEvaluator exprEvaluator; private DeclaredNodeEventPropertyGetter(ExprEvaluator exprEvaluator) { this.exprEvaluator = ((ExprDeclaredEvalBase) exprEvaluator).getInnerEvaluator(); } public Object get(EventBean eventBean) throws PropertyAccessException { EventBean[] events = new EventBean[1]; events[0] = eventBean; return exprEvaluator.evaluate(events, true, null); } public boolean isExistsProperty(EventBean eventBean) { return false; } public Object getFragment(EventBean eventBean) throws PropertyAccessException { return null; } } }