/*
* Copyright 2016 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.kie.dmn.core.ast;
import org.kie.dmn.api.core.DMNMessage;
import org.kie.dmn.api.core.DMNResult;
import org.kie.dmn.core.api.DMNExpressionEvaluator;
import org.kie.dmn.api.core.ast.DMNNode;
import org.kie.dmn.core.api.EvaluatorResult;
import org.kie.dmn.core.api.EvaluatorResult.ResultType;
import org.kie.dmn.api.core.event.DMNRuntimeEventManager;
import org.kie.dmn.api.feel.runtime.events.FEELEvent;
import org.kie.dmn.api.feel.runtime.events.FEELEventListener;
import org.kie.dmn.core.impl.DMNResultImpl;
import org.kie.dmn.core.impl.DMNRuntimeEventManagerImpl;
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.impl.EvaluationContextImpl;
import org.kie.dmn.feel.lang.impl.FEELImpl;
import org.kie.dmn.feel.runtime.events.DecisionTableRulesSelectedEvent;
import org.kie.dmn.feel.runtime.events.DecisionTableRulesMatchedEvent;
import org.kie.dmn.feel.runtime.functions.DTInvokerFunction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
/**
* An evaluator for DMN Decision Table Expressions
*/
public class DMNDTExpressionEvaluator
implements DMNExpressionEvaluator, FEELEventListener {
private static Logger logger = LoggerFactory.getLogger( DMNDTExpressionEvaluator.class );
private final DMNNode node;
private DTInvokerFunction dt;
private FEELImpl feel;
private List<FEELEvent> events = new ArrayList<>();
public DMNDTExpressionEvaluator(DMNNode node, DTInvokerFunction dt) {
this.node = node;
this.dt = dt;
feel = (FEELImpl) FEEL.newInstance();
feel.addListener( this );
}
@Override
public EvaluatorResult evaluate(DMNRuntimeEventManager dmrem, DMNResult dmnr) {
DMNRuntimeEventManagerImpl eventManager = (DMNRuntimeEventManagerImpl) dmrem;
DMNResultImpl result = (DMNResultImpl) dmnr;
EventResults r = null;
try {
eventManager.fireBeforeEvaluateDecisionTable( node.getName(), dt.getName(), result );
List<String> paramNames = dt.getParameterNames().get( 0 );
Object[] params = new Object[paramNames.size()];
EvaluationContextImpl ctx = new EvaluationContextImpl( feel.getEventsManager() );
ctx.enterFrame();
// need to set the values for in context variables...
for ( Map.Entry<String,Object> entry : result.getContext().getAll().entrySet() ) {
ctx.setValue( entry.getKey(), entry.getValue() );
}
for ( int i = 0; i < params.length; i++ ) {
params[i] = feel.evaluate( paramNames.get( i ), result.getContext().getAll() );
ctx.setValue( paramNames.get( i ), params[i] );
}
Object dtr = dt.invoke( ctx, params ).cata( e -> { events.add( e); return null; }, Function.identity());
// since ctx is a local variable that will be discarded, no need for a try/finally,
// but still wanted to match the enter/exit frame for future maintainability purposes
ctx.exitFrame();
r = processEvents( events, eventManager, result );
return new EvaluatorResultImpl( dtr, r.hasErrors ? ResultType.FAILURE : ResultType.SUCCESS );
} finally {
eventManager.fireAfterEvaluateDecisionTable( node.getName(), dt.getName(), result, (r != null ? r.matchedRules : null), (r != null ? r.fired : null) );
}
}
private EventResults processEvents(List<FEELEvent> events, DMNRuntimeEventManager eventManager, DMNResultImpl result) {
EventResults r = new EventResults();
for ( FEELEvent e : events ) {
if ( e instanceof DecisionTableRulesMatchedEvent ) {
r.matchedRules = ((DecisionTableRulesMatchedEvent) e).getMatches();
} else if ( e instanceof DecisionTableRulesSelectedEvent ) {
r.fired = ((DecisionTableRulesSelectedEvent) e).getFired();
} else if ( e.getSeverity() == FEELEvent.Severity.ERROR ) {
MsgUtil.reportMessage( logger,
DMNMessage.Severity.ERROR,
((DMNBaseNode)node).getSource(),
result,
null,
e,
Msg.FEEL_ERROR,
e.getMessage() );
r.hasErrors = true;
} else if ( e.getSeverity() == FEELEvent.Severity.WARN ) {
MsgUtil.reportMessage( logger,
DMNMessage.Severity.WARN,
((DMNBaseNode)node).getSource(),
result,
null,
e,
Msg.FEEL_WARN,
e.getMessage() );
}
}
events.clear();
return r;
}
private static class EventResults {
public boolean hasErrors = false;
public List<Integer> matchedRules;
public List<Integer> fired;
}
@Override
public void onEvent(FEELEvent event) {
this.events.add( event );
}
}