/* * Copyright 2005 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. * 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.drools.compiler.rule.builder.dialect.mvel; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.antlr.runtime.RecognitionException; import org.drools.compiler.compiler.BoundIdentifiers; import org.drools.compiler.compiler.DescrBuildError; import org.drools.compiler.lang.descr.BaseDescr; import org.drools.compiler.rule.builder.PackageBuildContext; import org.drools.compiler.rule.builder.RuleBuildContext; import org.drools.compiler.rule.builder.dialect.DialectUtil; import org.drools.core.base.EvaluatorWrapper; import org.drools.core.rule.Declaration; import org.drools.core.rule.MVELDialectRuntimeData; import org.drools.core.rule.RuleConditionElement; import org.kie.api.definition.rule.Rule; import org.mvel2.CompileException; import org.mvel2.MVEL; import org.mvel2.ParserConfiguration; import org.mvel2.ParserContext; import org.mvel2.optimizers.OptimizerFactory; import org.mvel2.util.PropertyTools; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Expression analyzer. */ public class MVELExprAnalyzer { private static final Logger log = LoggerFactory.getLogger( MVELExprAnalyzer.class ); static { // always use mvel reflective optimizer OptimizerFactory.setDefaultOptimizer(OptimizerFactory.SAFE_REFLECTIVE); } public MVELExprAnalyzer() { // intentionally left blank. } // ------------------------------------------------------------ // Instance methods // ------------------------------------------------------------ /** * Analyze an expression. * * @param expr * The expression to analyze. * @param availableIdentifiers * Total set of declarations available. * * @return The <code>Set</code> of declarations used by the expression. * @throws RecognitionException * If an error occurs in the parser. */ @SuppressWarnings("unchecked") public static MVELAnalysisResult analyzeExpression(final PackageBuildContext context, final String expr, final BoundIdentifiers availableIdentifiers, final Map<String, Class< ? >> localTypes, String contextIdentifier, Class kcontextClass) { if ( expr.trim().length() <= 0 ) { MVELAnalysisResult result = analyze( (Set<String>) Collections.EMPTY_SET, availableIdentifiers ); result.setMvelVariables( new HashMap<String, Class< ? >>() ); result.setTypesafe( true ); return result; } MVEL.COMPILER_OPT_ALLOW_NAKED_METH_CALL = true; MVEL.COMPILER_OPT_ALLOW_OVERRIDE_ALL_PROPHANDLING = true; MVEL.COMPILER_OPT_ALLOW_RESOLVE_INNERCLASSES_WITH_DOTNOTATION = true; MVEL.COMPILER_OPT_SUPPORT_JAVA_STYLE_CLASS_LITERALS = true; MVELDialect dialect = (MVELDialect) context.getDialect( "mvel" ); ParserConfiguration conf = context.getMVELDialectRuntimeData().getParserConfiguration(); conf.setClassLoader( context.getKnowledgeBuilder().getRootClassLoader() ); // first compilation is for verification only // @todo proper source file name final ParserContext parserContext1 = new ParserContext( conf ); if ( localTypes != null ) { for ( Entry entry : localTypes.entrySet() ) { parserContext1.addInput( (String) entry.getKey(), (Class) entry.getValue() ); } } if ( availableIdentifiers.getThisClass() != null ) { parserContext1.addInput( "this", availableIdentifiers.getThisClass() ); } if ( availableIdentifiers.getOperators() != null ) { for ( Entry<String, EvaluatorWrapper> opEntry : availableIdentifiers.getOperators().entrySet() ) { parserContext1.addInput( opEntry.getKey(), opEntry.getValue().getClass() ); } } parserContext1.setStrictTypeEnforcement( false ); parserContext1.setStrongTyping( false ); parserContext1.setInterceptors( dialect.getInterceptors() ); Class< ? > returnType; try { returnType = MVEL.analyze( expr, parserContext1 ); } catch ( Exception e ) { BaseDescr base = (context instanceof RuleBuildContext) ? ((RuleBuildContext)context).getRuleDescr() : context.getParentDescr(); if ( e instanceof CompileException && e.getCause() != null && e.getMessage().startsWith( "[Error: null]" )) { // rewrite error message in cause original message is null e = new CompileException(e.getCause().toString(), ( (CompileException) e ).getExpr(), ( (CompileException) e ).getCursor(), e.getCause()); } DialectUtil.copyErrorLocation(e, context.getParentDescr()); context.addError( new DescrBuildError( base, context.getParentDescr(), null, "Unable to Analyse Expression " + expr + ":\n" + e.getMessage() ) ); return null; } Set<String> requiredInputs = new HashSet<String>(); requiredInputs.addAll( parserContext1.getInputs().keySet() ); HashMap<String, Class< ? >> variables = (HashMap<String, Class< ? >>) ((Map) parserContext1.getVariables()); if ( localTypes != null ) { for ( String str : localTypes.keySet() ) { // we have to do this due to mvel regressions on detecting true local vars variables.remove( str ); } } // MVEL includes direct fields of context object in non-strict mode. so we need to strip those if ( availableIdentifiers.getThisClass() != null ) { requiredInputs.removeIf( s -> PropertyTools.getFieldOrAccessor( availableIdentifiers.getThisClass(), s ) != null ); } // now, set the required input types and compile again final ParserContext parserContext2 = new ParserContext( conf ); parserContext2.setStrictTypeEnforcement( true ); parserContext2.setStrongTyping( true ); parserContext2.setInterceptors( dialect.getInterceptors() ); for ( String input : requiredInputs ) { if ("this".equals( input )) { continue; } Class< ? > cls = availableIdentifiers.resolveType( input ); if ( cls == null ) { if ( input.equals( contextIdentifier ) || input.equals( "kcontext" ) ) { cls = kcontextClass; } else if ( input.equals( "rule" ) ) { cls = Rule.class; } else if ( localTypes != null ) { cls = localTypes.get( input ); } } if ( cls != null ) { parserContext2.addInput( input, cls ); } } if ( availableIdentifiers.getThisClass() != null ) { parserContext2.addInput( "this", availableIdentifiers.getThisClass() ); } boolean typesafe = context.isTypesafe(); try { returnType = MVEL.analyze( expr, parserContext2 ); typesafe = true; } catch ( Exception e ) { // is this an error, or can we fall back to non-typesafe mode? if ( typesafe ) { BaseDescr base = (context instanceof RuleBuildContext) ? ((RuleBuildContext)context).getRuleDescr() : context.getParentDescr(); DialectUtil.copyErrorLocation(e, context.getParentDescr()); context.addError( new DescrBuildError( base, context.getParentDescr(), null, "Unable to Analyse Expression " + expr + ":\n" + e.getMessage() ) ); return null; } } if ( typesafe ) { requiredInputs = new HashSet<String>(); requiredInputs.addAll( parserContext2.getInputs().keySet() ); requiredInputs.addAll( variables.keySet() ); variables = (HashMap<String, Class< ? >>) ((Map) parserContext2.getVariables()); if ( localTypes != null ) { for ( String str : localTypes.keySet() ) { // we have to do this due to mvel regressions on detecting true local vars variables.remove( str ); } } } MVELAnalysisResult result = analyze( requiredInputs, availableIdentifiers ); result.setReturnType( returnType ); result.setMvelVariables( variables ); result.setTypesafe( typesafe ); return result; } /** * Analyse an expression. * @throws RecognitionException * If an error occurs in the parser. */ private static MVELAnalysisResult analyze(final Set<String> identifiers, final BoundIdentifiers availableIdentifiers) { MVELAnalysisResult result = new MVELAnalysisResult(); result.setIdentifiers( identifiers ); final Set<String> notBound = new HashSet<String>( identifiers ); notBound.remove( "this" ); Map<String, Class< ? >> usedDecls = new HashMap<String, Class< ? >>(); Map<String, Class< ? >> usedGlobals = new HashMap<String, Class< ? >>(); Map<String, EvaluatorWrapper> usedOperators = new HashMap<String, EvaluatorWrapper>(); for ( Entry<String, Class< ? >> entry : availableIdentifiers.getDeclrClasses().entrySet() ) { if ( identifiers.contains( entry.getKey() ) ) { usedDecls.put( entry.getKey(), entry.getValue() ); notBound.remove( entry.getKey() ); } } for ( String identifier : identifiers ) { Class<?> type = availableIdentifiers.resolveVarType( identifier ); if (type != null) { usedGlobals.put( identifier, type ); notBound.remove( identifier ); } } for ( Map.Entry<String, EvaluatorWrapper> op : availableIdentifiers.getOperators().entrySet() ) { if ( identifiers.contains( op.getKey() ) ) { usedOperators.put( op.getKey(), op.getValue() ); notBound.remove( op.getKey() ); } } BoundIdentifiers boundIdentifiers = new BoundIdentifiers( usedDecls, availableIdentifiers.getContext(), usedOperators, availableIdentifiers.getThisClass() ); boundIdentifiers.setGlobals( usedGlobals ); result.setBoundIdentifiers( boundIdentifiers ); result.setNotBoundedIdentifiers( notBound ); return result; } public static Class<?> getExpressionType(PackageBuildContext context, Map<String, Class< ? >> declCls, RuleConditionElement source, String expression) { MVELDialectRuntimeData data = ( MVELDialectRuntimeData) context.getPkg().getDialectRuntimeRegistry().getDialectData( "mvel" ); ParserConfiguration conf = data.getParserConfiguration(); conf.setClassLoader( context.getKnowledgeBuilder().getRootClassLoader() ); ParserContext pctx = new ParserContext( conf ); pctx.setStrongTyping(true); pctx.setStrictTypeEnforcement(true); for (Map.Entry<String, Class< ? >> entry : declCls.entrySet()) { pctx.addInput(entry.getKey(), entry.getValue()); } for (Declaration decl : source.getOuterDeclarations().values()) { pctx.addInput(decl.getBindingName(), decl.getDeclarationClass()); } try { return MVEL.analyze( expression, pctx ); } catch (Exception e) { log.warn( "Unable to parse expression: " + expression, e ); } return null; } }