package org.kie.dmn.core.compiler; import org.antlr.v4.runtime.CommonToken; import org.kie.dmn.api.core.DMNContext; import org.kie.dmn.api.core.DMNMessage; import org.kie.dmn.api.core.DMNType; import org.kie.dmn.api.feel.runtime.events.FEELEvent; import org.kie.dmn.api.feel.runtime.events.FEELEventListener; import org.kie.dmn.core.impl.BaseDMNTypeImpl; import org.kie.dmn.core.impl.DMNModelImpl; import org.kie.dmn.core.util.Msg; import org.kie.dmn.core.util.MsgUtil; import org.kie.dmn.feel.FEEL; import org.kie.dmn.feel.lang.CompiledExpression; import org.kie.dmn.feel.lang.CompilerContext; import org.kie.dmn.feel.lang.Type; import org.kie.dmn.feel.lang.impl.EvaluationContextImpl; import org.kie.dmn.feel.lang.impl.FEELImpl; import org.kie.dmn.feel.lang.types.BuiltInType; import org.kie.dmn.feel.runtime.FEELFunction; import org.kie.dmn.feel.runtime.UnaryTest; import org.kie.dmn.feel.runtime.events.SyntaxErrorEvent; import org.kie.dmn.feel.runtime.events.UnknownVariableErrorEvent; import org.kie.dmn.model.v1_1.DMNElement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; public class DMNFEELHelper implements FEELEventListener { private static final Logger logger = LoggerFactory.getLogger( DMNFEELHelper.class ); private final FEEL feel; private final Queue<FEELEvent> feelEvents; public DMNFEELHelper() { this.feelEvents = new LinkedList<>(); this.feel = createFEELInstance(); } private FEEL createFEELInstance() { FEEL feel = FEEL.newInstance(); feel.addListener( this ); return feel; } @Override public void onEvent(FEELEvent event) { feelEvents.add( event ); } public static boolean valueMatchesInUnaryTests(List<UnaryTest> unaryTests, Object value, DMNContext dmnContext) { EvaluationContextImpl ctx = new EvaluationContextImpl( null ); try { ctx.enterFrame(); if ( dmnContext != null ) { // need to set the values for in context variables... for ( Map.Entry<String,Object> entry : dmnContext.getAll().entrySet() ) { ctx.setValue( entry.getKey(), entry.getValue() ); } } for ( UnaryTest t : unaryTests ) { try { Boolean applyT = t.apply(ctx, value); // the unary test above can actually return null, so we have to handle it here if ( applyT == null ) { return false; } else if ( applyT ) { return true; } } catch ( Throwable e ) { // if an unaryTest fail for any treason, is simply considered FALSE. logger.warn("A non-critical error happened while evaluating a unary test. Execution will continue.", e); } } } finally { ctx.exitFrame(); } return false; } public CompiledExpression compileFeelExpression(DMNCompilerContext ctx, String expression, DMNModelImpl model, DMNElement element, Msg.Message errorMsg, Object... msgParams) { CompilerContext feelctx = feel.newCompilerContext(); for ( Map.Entry<String, DMNType> entry : ctx.getVariables().entrySet() ) { feelctx.addInputVariableType( entry.getKey(), ((BaseDMNTypeImpl) entry.getValue()).getFeelType() ); } CompiledExpression ce = feel.compile( expression, feelctx ); processEvents( model, element, errorMsg, msgParams ); return ce; } public FEELFunction evaluateFunctionDef(DMNCompilerContext ctx, String expression, DMNModelImpl model, DMNElement element, Msg.Message errorMsg, Object... msgParams) { FEELFunction function = null; try { function = (FEELFunction) feel.evaluate( expression ); } catch( Throwable t ) { logger.error( "Error evaluating function definition. Error will be reported in the model.", t ); } processEvents( model, element, errorMsg, msgParams ); return function; } public FEELFunction evaluateFunctionDef(DMNCompilerContext ctx, CompiledExpression expression, DMNModelImpl model, DMNElement element, Msg.Message errorMsg, Object... msgParams) { FEELFunction function = null; try { function = (FEELFunction) feel.evaluate( expression, Collections.emptyMap() ); } catch( Throwable t ) { logger.error( "Error evaluating function definition. Error will be reported in the model.", t ); } processEvents( model, element, errorMsg, msgParams ); return function; } public List<UnaryTest> evaluateUnaryTests(DMNCompilerContext ctx, String unaryTests, DMNModelImpl model, DMNElement element, Msg.Message errorMsg, Object... msgParams) { List<UnaryTest> result = Collections.emptyList(); try { Map<String, Type> variableTypes = new HashMap<>(); for ( Map.Entry<String, DMNType> entry : ctx.getVariables().entrySet() ) { variableTypes.put( entry.getKey(), ((BaseDMNTypeImpl) entry.getValue()).getFeelType() ); } result = feel.evaluateUnaryTests( unaryTests, variableTypes ); } catch( Throwable t ) { logger.error( "Error evaluating unary tests. Error will be reported in the model.", t ); } processEvents( model, element, errorMsg, msgParams ); return result; } public void processEvents(DMNModelImpl model, DMNElement element, Msg.Message msg, Object... msgParams) { while ( !feelEvents.isEmpty() ) { FEELEvent event = feelEvents.remove(); if ( !isDuplicateEvent( model, msg, element ) ) { if ( event instanceof SyntaxErrorEvent || event.getSeverity() == FEELEvent.Severity.ERROR ) { if ( msg instanceof Msg.Message2 ) { MsgUtil.reportMessage( logger, DMNMessage.Severity.ERROR, element, model, null, event, (Msg.Message2) msg, msgParams[0], msgParams[1] ); } else if ( msg instanceof Msg.Message3 ) { MsgUtil.reportMessage( logger, DMNMessage.Severity.ERROR, element, model, null, event, (Msg.Message3) msg, msgParams[0], msgParams[1], msgParams[2] ); } else if ( msg instanceof Msg.Message4 ) { String message = null; if( event.getOffendingSymbol() == null ) { message = ""; } else if( event instanceof UnknownVariableErrorEvent ) { message = event.getMessage(); } else if( event.getOffendingSymbol() instanceof CommonToken ) { message = "syntax error near '" + ((CommonToken)event.getOffendingSymbol()).getText() + "'"; } else { message = "syntax error near '" + event.getOffendingSymbol() + "'"; } MsgUtil.reportMessage( logger, DMNMessage.Severity.ERROR, element, model, null, event, (Msg.Message4) msg, msgParams[0], msgParams[1], msgParams[2], message ); } } } } } private boolean isDuplicateEvent(DMNModelImpl model, Msg.Message error, DMNElement element) { return model.getMessages().stream().anyMatch( msg -> msg.getMessageType() == error.getType() && (msg.getSourceId() == element.getId() || (msg.getSourceId() != null && element.getId() != null && msg.getSourceId().equals( element.getId() ))) ); } }