org.drools.compiler.rule.builder.MVELConstraintBuilder Maven / Gradle / Ivy
/*
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* 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.drools.compiler.rule.builder;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.drools.compiler.compiler.AnalysisResult;
import org.drools.compiler.compiler.DescrBuildError;
import org.drools.compiler.compiler.Dialect;
import org.drools.compiler.lang.descr.BaseDescr;
import org.drools.compiler.lang.descr.LiteralRestrictionDescr;
import org.drools.compiler.lang.descr.OperatorDescr;
import org.drools.compiler.lang.descr.PredicateDescr;
import org.drools.compiler.lang.descr.RelationalExprDescr;
import org.drools.compiler.rule.builder.dialect.mvel.MVELAnalysisResult;
import org.drools.compiler.rule.builder.dialect.mvel.MVELDialect;
import org.drools.core.base.ClassObjectType;
import org.drools.core.base.DroolsQuery;
import org.drools.core.base.EvaluatorWrapper;
import org.drools.core.base.SimpleValueType;
import org.drools.core.base.ValueType;
import org.drools.core.base.evaluators.EvaluatorDefinition;
import org.drools.core.base.evaluators.Operator;
import org.drools.core.base.mvel.MVELCompilationUnit;
import org.drools.core.rule.Declaration;
import org.drools.core.rule.Pattern;
import org.drools.core.rule.constraint.EvaluatorConstraint;
import org.drools.core.rule.constraint.MvelConstraint;
import org.drools.core.spi.Constraint;
import org.drools.core.spi.Evaluator;
import org.drools.core.spi.FieldValue;
import org.drools.core.spi.InternalReadAccessor;
import org.drools.core.spi.KnowledgeHelper;
import org.drools.core.util.index.IndexUtil;
import org.mvel2.ConversionHandler;
import org.mvel2.DataConversion;
import org.mvel2.util.CompatibilityStrategy;
import org.mvel2.util.NullType;
import static org.drools.compiler.rule.builder.PatternBuilder.buildAnalysis;
import static org.drools.compiler.rule.builder.PatternBuilder.buildOperators;
import static org.drools.compiler.rule.builder.PatternBuilder.getOperators;
import static org.drools.compiler.rule.builder.PatternBuilder.getUsedDeclarations;
import static org.drools.compiler.rule.builder.dialect.DialectUtil.copyErrorLocation;
import static org.drools.core.base.evaluators.PointInTimeEvaluator.getTimestampFromDate;
import static org.drools.core.util.ClassUtils.convertFromPrimitiveType;
public class MVELConstraintBuilder implements ConstraintBuilder {
public static final boolean USE_MVEL_EXPRESSION = true;
protected static final Set MVEL_OPERATORS;
static {
if (USE_MVEL_EXPRESSION) {
CompatibilityStrategy.setCompatibilityEvaluator(StringCoercionCompatibilityEvaluator.INSTANCE);
DataConversion.addConversionHandler(Boolean.class, BooleanConversionHandler.INSTANCE);
DataConversion.addConversionHandler(boolean.class, BooleanConversionHandler.INSTANCE);
MVEL_OPERATORS = new HashSet() {{
add("==");
add("!=");
add(">");
add(">=");
add("<");
add("<=");
add("~=");
add("str");
add("contains");
add("matches");
add("excludes");
add("memberOf");
add("instanceof");
}};
}
}
public boolean isMvelOperator(String operator) {
return MVEL_OPERATORS.contains(operator);
}
public boolean useMvelExpression() {
return USE_MVEL_EXPRESSION;
}
public Constraint buildVariableConstraint(RuleBuildContext context,
Pattern pattern,
String expression,
Declaration[] declarations,
String leftValue,
OperatorDescr operatorDescr,
String rightValue,
InternalReadAccessor extractor,
Declaration requiredDeclaration,
RelationalExprDescr relDescr,
Map aliases) {
if (!isMvelOperator(operatorDescr.getOperator())) {
if (requiredDeclaration == null) {
return null;
}
EvaluatorDefinition.Target right = getRightTarget( extractor );
EvaluatorDefinition.Target left = (requiredDeclaration.isPatternDeclaration() &&
!(Date.class.isAssignableFrom( requiredDeclaration.getDeclarationClass() )
|| Number.class.isAssignableFrom( requiredDeclaration.getDeclarationClass() ))) ?
EvaluatorDefinition.Target.HANDLE :
EvaluatorDefinition.Target.FACT;
final Evaluator evaluator = getEvaluator( context,
relDescr,
extractor.getValueType(),
operatorDescr.getOperator(),
relDescr.isNegated(),
relDescr.getParametersText(),
left,
right );
return new EvaluatorConstraint(new Declaration[] { requiredDeclaration }, evaluator, extractor);
}
boolean isUnification = requiredDeclaration != null &&
requiredDeclaration.getPattern().getObjectType().equals( new ClassObjectType( DroolsQuery.class ) ) &&
Operator.EQUAL.getOperatorString().equals( operatorDescr.getOperator() );
if (isUnification && leftValue.equals(rightValue)) {
expression = resolveUnificationAmbiguity(expression, declarations, leftValue, rightValue);
}
expression = normalizeMVELVariableExpression(expression, leftValue, rightValue, relDescr);
IndexUtil.ConstraintType constraintType = IndexUtil.ConstraintType.decode(operatorDescr.getOperator(), operatorDescr.isNegated());
MVELCompilationUnit compilationUnit = isUnification ? null : buildCompilationUnit(context, pattern, expression, aliases);
EvaluatorWrapper[] operators = getOperators(buildOperators(context, pattern, relDescr, aliases));
return new MvelConstraint( Collections.singletonList( context.getPkg().getName() ), expression, declarations, operators, compilationUnit, constraintType, requiredDeclaration, extractor, isUnification);
}
public Constraint buildMvelConstraint(String packageName,
String expression,
Declaration[] declarations,
EvaluatorWrapper[] operators,
MVELCompilationUnit compilationUnit,
boolean isDynamic) {
return new MvelConstraint( packageName, expression, declarations, operators, compilationUnit, isDynamic );
}
public Constraint buildMvelConstraint(Collection packageNames,
String expression,
Declaration[] declarations,
EvaluatorWrapper[] operators,
MVELCompilationUnit compilationUnit,
IndexUtil.ConstraintType constraintType,
Declaration indexingDeclaration,
InternalReadAccessor extractor,
boolean isUnification) {
return new MvelConstraint( packageNames, expression, declarations, operators, compilationUnit, constraintType, indexingDeclaration, extractor, isUnification );
}
public Constraint buildLiteralConstraint(RuleBuildContext context,
Pattern pattern,
ValueType vtype,
FieldValue field,
String expression,
String leftValue,
String operator,
boolean negated,
String rightValue,
InternalReadAccessor extractor,
LiteralRestrictionDescr restrictionDescr,
Map aliases) {
if (!isMvelOperator(operator)) {
Evaluator evaluator = buildLiteralEvaluator(context, extractor, restrictionDescr, vtype);
if (evaluator != null && evaluator.isTemporal()) {
try {
field = context.getCompilerFactory().getFieldFactory().getFieldValue(field.getValue(),
ValueType.DATE_TYPE);
} catch (Exception e) {
context.addError( new DescrBuildError( context.getParentDescr(),
restrictionDescr,
null,
e.getMessage() ) );
}
}
return new EvaluatorConstraint(field, evaluator, extractor);
}
String mvelExpr = normalizeMVELLiteralExpression(vtype, field, expression, leftValue, operator, rightValue, negated, restrictionDescr);
IndexUtil.ConstraintType constraintType = IndexUtil.ConstraintType.decode(operator, negated);
if (constraintType == IndexUtil.ConstraintType.EQUAL && negated) {
mvelExpr = normalizeDoubleNegation(mvelExpr);
}
MVELCompilationUnit compilationUnit = buildCompilationUnit(context, pattern, mvelExpr, aliases);
EvaluatorWrapper[] operators = getOperators(buildOperators(context, pattern, restrictionDescr, aliases));
return new MvelConstraint(context.getPkg().getName(), mvelExpr, compilationUnit, constraintType, field, extractor, operators);
}
private static String normalizeDoubleNegation(String expr) {
if (expr.charAt( 0 ) == '!') {
expr = expr.substring( 1 ).trim();
if (expr.charAt( 0 ) == '(') {
expr = expr.substring( 1, expr.lastIndexOf( ')' ) ).trim();
}
expr = expr.replace( "!=", "==" );
}
return expr;
}
protected static String resolveUnificationAmbiguity(String expr, Declaration[] declrations, String leftValue, String rightValue) {
// resolve ambiguity between variable and bound value with the same name in unifications
rightValue = rightValue + "__";
for (Declaration declaration : declrations) {
if (declaration.getIdentifier().equals(leftValue)) {
declaration.setBindingName(rightValue);
}
}
return leftValue + " == " + rightValue;
}
protected static String normalizeMVELLiteralExpression(ValueType vtype,
FieldValue field,
String expr,
String leftValue,
String operator,
String rightValue,
boolean negated,
LiteralRestrictionDescr restrictionDescr) {
if (vtype.getSimpleType() == SimpleValueType.DATE) {
String normalized = leftValue + " " + operator + getNormalizeDate( vtype, field );
if (!negated) {
return normalized;
}
IndexUtil.ConstraintType constraintType = IndexUtil.ConstraintType.decode(operator);
return constraintType.getOperator() != null ?
leftValue + " " + constraintType.negate().getOperator() + getNormalizeDate( vtype, field ) :
"!(" + normalized + ")";
}
if (operator.equals("str")) {
return normalizeStringOperator( leftValue, rightValue, restrictionDescr );
}
// resolve ambiguity between mvel's "empty" keyword and constraints like: List(empty == ...)
return normalizeEmptyKeyword( expr, operator );
}
protected static String getNormalizeDate( ValueType vtype, FieldValue field ) {
Object value = field.getValue();
if (value == null) {
return "null";
}
long lvalue = getTimestampFromDate( value );
if (vtype == ValueType.DATE_TYPE) {
return " new java.util.Date(" + lvalue + ")";
}
if (vtype == ValueType.LOCAL_DATE_TYPE) {
return " java.time.Instant.ofEpochMilli(" + lvalue + ").atZone(java.time.ZoneId.systemDefault()).toLocalDate()";
}
if (vtype == ValueType.LOCAL_TIME_TYPE) {
return " java.time.Instant.ofEpochMilli(" + lvalue + ").atZone(java.time.ZoneId.systemDefault()).toLocalDateTime()";
}
throw new IllegalArgumentException( "Unsupported type " + vtype );
}
protected static String normalizeStringOperator( String leftValue, String rightValue, LiteralRestrictionDescr restrictionDescr ) {
String method = restrictionDescr.getParameterText();
if (method.equals("length")) {
return leftValue + ".length()" + (restrictionDescr.isNegated() ? " != " : " == ") + rightValue;
}
return (restrictionDescr.isNegated() ? "!" : "") + leftValue + "." + method + "(" + rightValue + ")";
}
protected static String normalizeEmptyKeyword( String expr, String operator ) {
return expr.startsWith("empty") && (operator.equals("==") || operator.equals("!=")) && !Character.isJavaIdentifierPart(expr.charAt(5)) ?
"isEmpty()" + expr.substring(5) :
expr;
}
protected static String normalizeMVELVariableExpression(String expr,
String leftValue,
String rightValue,
RelationalExprDescr relDescr) {
if (relDescr.getOperator().equals("str")) {
String method = relDescr.getParametersText();
if (method.equals("length")) {
return leftValue + ".length()" + (relDescr.isNegated() ? " != " : " == ") + rightValue;
}
return (relDescr.isNegated() ? "!" : "") + leftValue + "." + method + "(" + rightValue + ")";
}
return expr;
}
public Evaluator buildLiteralEvaluator( RuleBuildContext context,
InternalReadAccessor extractor,
LiteralRestrictionDescr literalRestrictionDescr,
ValueType vtype) {
EvaluatorDefinition.Target right = getRightTarget( extractor );
EvaluatorDefinition.Target left = EvaluatorDefinition.Target.FACT;
return getEvaluator( context,
literalRestrictionDescr,
vtype,
literalRestrictionDescr.getEvaluator(),
literalRestrictionDescr.isNegated(),
literalRestrictionDescr.getParameterText(),
left,
right );
}
public EvaluatorDefinition.Target getRightTarget( final InternalReadAccessor extractor ) {
return ( extractor.isSelfReference() &&
!(Date.class.isAssignableFrom( extractor.getExtractToClass() ) ||
Number.class.isAssignableFrom( extractor.getExtractToClass() ))) ? EvaluatorDefinition.Target.HANDLE : EvaluatorDefinition.Target.FACT;
}
public Evaluator getEvaluator( final RuleBuildContext context,
final BaseDescr descr,
final ValueType valueType,
final String evaluatorString,
final boolean isNegated,
final String parameters,
final EvaluatorDefinition.Target left,
final EvaluatorDefinition.Target right ) {
final EvaluatorDefinition def = context.getConfiguration().getEvaluatorRegistry().getEvaluatorDefinition( evaluatorString );
if ( def == null ) {
context.addError( new DescrBuildError( context.getParentDescr(),
descr,
null,
"Unable to determine the Evaluator for ID '" + evaluatorString + "'" ) );
return null;
}
final Evaluator evaluator = def.getEvaluator( valueType,
evaluatorString,
isNegated,
parameters,
left,
right );
if ( evaluator == null ) {
context.addError( new DescrBuildError( context.getParentDescr(),
descr,
null,
"Evaluator '" + (isNegated ? "not " : "") + evaluatorString + "' does not support type '" + valueType ) );
}
return evaluator;
}
public EvaluatorWrapper wrapEvaluator( Evaluator evaluator, Declaration left, Declaration right ) {
return new EvaluatorWrapper( evaluator, left, right );
}
public MVELCompilationUnit buildCompilationUnit(RuleBuildContext context, Pattern pattern, String expression, Map aliases) {
Dialect dialect = context.getDialect();
context.setDialect( context.getDialect( "mvel" ) );
try {
PredicateDescr predicateDescr = new PredicateDescr( context.getRuleDescr().getResource(), expression );
AnalysisResult analysis = buildAnalysis( context, pattern, predicateDescr, aliases );
if ( analysis == null ) {
// something bad happened
return null;
}
Declaration[][] usedDeclarations = getUsedDeclarations( context, pattern, analysis );
return buildCompilationUnit( context, usedDeclarations[0], usedDeclarations[1], predicateDescr, analysis );
} finally {
context.setDialect( dialect );
}
}
public MVELCompilationUnit buildCompilationUnit( final RuleBuildContext context,
final Declaration[] previousDeclarations,
final Declaration[] localDeclarations,
final PredicateDescr predicateDescr,
final AnalysisResult analysis ) {
if (context.isTypesafe() && analysis instanceof MVELAnalysisResult) {
Class> returnClass = ((MVELAnalysisResult)analysis).getReturnType();
if (returnClass != Boolean.class && returnClass != Boolean.TYPE) {
context.addError( new DescrBuildError( context.getParentDescr(),
predicateDescr,
null,
"Predicate '" + predicateDescr.getContent() + "' must be a Boolean expression\n" + predicateDescr.positionAsString() ) );
}
}
MVELDialect dialect = (MVELDialect) context.getDialect( "mvel" );
MVELCompilationUnit unit = null;
try {
Map> declIds = context.getDeclarationResolver().getDeclarationClasses( context.getRule() );
Pattern p = (Pattern) context.getDeclarationResolver().peekBuildStack();
if ( p.getObjectType() instanceof ClassObjectType) {
declIds.put( "this",
((ClassObjectType) p.getObjectType()).getClassType() );
}
unit = dialect.getMVELCompilationUnit( (String) predicateDescr.getContent(),
analysis,
previousDeclarations,
localDeclarations,
null,
context,
"drools",
KnowledgeHelper.class,
context.isInXpath(),
MVELCompilationUnit.Scope.CONSTRAINT );
} catch ( final Exception e ) {
copyErrorLocation(e, predicateDescr);
context.addError( new DescrBuildError( context.getParentDescr(),
predicateDescr,
e,
"Unable to build expression for 'inline-eval' : " + e.getMessage() + "'" + predicateDescr.getContent() + "'\n" + e.getMessage() ) );
}
return unit;
}
public static class BooleanConversionHandler implements ConversionHandler {
private static final BooleanConversionHandler INSTANCE = new BooleanConversionHandler();
private BooleanConversionHandler() { }
public Object convertFrom(Object in) {
if (in.getClass() == Boolean.class || in.getClass() == boolean.class) {
return in;
}
return in instanceof String && ((String)in).equalsIgnoreCase("true");
}
public boolean canConvertFrom(Class cls) {
return cls == Boolean.class || cls == boolean.class || cls == String.class;
}
}
public static class StringCoercionCompatibilityEvaluator extends CompatibilityStrategy.DefaultCompatibilityEvaluator {
private static final CompatibilityStrategy.CompatibilityEvaluator INSTANCE = new StringCoercionCompatibilityEvaluator();
private StringCoercionCompatibilityEvaluator() { }
@Override
public boolean areEqualityCompatible(Class> c1, Class> c2) {
if (c1 == NullType.class || c2 == NullType.class) {
return true;
}
if (c1 == String.class || c2 == String.class) {
return true;
}
Class> boxed1 = convertFromPrimitiveType(c1);
Class> boxed2 = convertFromPrimitiveType(c2);
if (boxed1.isAssignableFrom(boxed2) || boxed2.isAssignableFrom(boxed1)) {
return true;
}
if (Number.class.isAssignableFrom(boxed1) && Number.class.isAssignableFrom(boxed2)) {
return true;
}
return !Modifier.isFinal(c1.getModifiers()) && !Modifier.isFinal(c2.getModifiers());
}
@Override
public boolean areComparisonCompatible(Class> c1, Class> c2) {
return super.areEqualityCompatible(c1, c2);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy