/* * 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.compiler; import org.kie.api.io.Resource; import org.kie.dmn.api.core.DMNCompiler; import org.kie.dmn.api.core.DMNMessage; import org.kie.dmn.api.core.DMNModel; import org.kie.dmn.api.core.DMNType; import org.kie.dmn.api.core.*; import org.kie.dmn.api.core.ast.BusinessKnowledgeModelNode; import org.kie.dmn.api.marshalling.v1_1.DMNExtensionRegister; import org.kie.dmn.core.api.DMNExpressionEvaluator; import org.kie.dmn.api.core.ast.DMNNode; import org.kie.dmn.api.core.ast.DecisionNode; import org.kie.dmn.api.core.ast.InputDataNode; import org.kie.dmn.backend.marshalling.v1_1.DMNMarshallerFactory; import org.kie.dmn.core.ast.*; import org.kie.dmn.core.impl.BaseDMNTypeImpl; import org.kie.dmn.core.impl.CompositeTypeImpl; import org.kie.dmn.core.impl.DMNModelImpl; import org.kie.dmn.core.impl.SimpleTypeImpl; import org.kie.dmn.feel.lang.types.BuiltInType; import org.kie.dmn.feel.runtime.UnaryTest; import org.kie.dmn.core.util.Msg; import org.kie.dmn.core.util.MsgUtil; import org.kie.dmn.model.v1_1.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.xml.XMLConstants; import javax.xml.namespace.QName; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.List; public class DMNCompilerImpl implements DMNCompiler { private static final Logger logger = LoggerFactory.getLogger( DMNCompilerImpl.class ); private final DMNEvaluatorCompiler evaluatorCompiler; private final DMNFEELHelper feel; private DMNCompilerConfiguration dmnCompilerConfig; public DMNCompilerImpl() { this.feel = new DMNFEELHelper(); this.evaluatorCompiler = new DMNEvaluatorCompiler( this, feel ); } public DMNCompilerImpl(DMNCompilerConfiguration dmnCompilerConfig) { this.feel = new DMNFEELHelper(); this.evaluatorCompiler = new DMNEvaluatorCompiler( this, feel ); this.dmnCompilerConfig = dmnCompilerConfig; } @Override public DMNModel compile(Resource resource) { try { return compile( resource.getReader() ); } catch ( IOException e ) { logger.error( "Error retrieving reader for resource: " + resource.getSourcePath(), e ); } return null; } @Override public DMNModel compile(Reader source) { try { Definitions dmndefs = null; if(dmnCompilerConfig != null && !dmnCompilerConfig.getRegisteredExtensions().isEmpty()) { dmndefs = DMNMarshallerFactory.newMarshallerWithExtensions(getDmnCompilerConfig().getRegisteredExtensions()).unmarshal( source ); } else { dmndefs = DMNMarshallerFactory.newDefaultMarshaller().unmarshal(source); } DMNModel model = compile( dmndefs ); return model; } catch ( Exception e ) { logger.error( "Error compiling model from source.", e ); } return null; } @Override public DMNModel compile(Definitions dmndefs) { DMNModelImpl model = null; if ( dmndefs != null ) { model = new DMNModelImpl( dmndefs ); DMNCompilerContext ctx = new DMNCompilerContext(); processItemDefinitions( ctx, feel, model, dmndefs ); processDrgElements( ctx, feel, model, dmndefs ); return model; } return model; } private void processItemDefinitions(DMNCompilerContext ctx, DMNFEELHelper feel, DMNModelImpl model, Definitions dmndefs) { dmndefs.getItemDefinition().stream().forEach(x->processItemDefQNameURIs(x)); List<ItemDefinition> ordered = new ItemDefinitionDependenciesSorter(model.getNamespace()).sort(dmndefs.getItemDefinition()); for ( ItemDefinition id : ordered ) { ItemDefNodeImpl idn = new ItemDefNodeImpl( id ); DMNType type = buildTypeDef( ctx, feel, model, idn, id, true ); idn.setType( type ); model.addItemDefinition( idn ); } } /** * Utility method to ensure any QName references contained inside the ItemDefinition have the namespace correctly valorized, also accordingly to the prefix. * (Even in the case of {@link XMLConstants.DEFAULT_NS_PREFIX} it will take the DMN model namespace for the no-prefix accordingly.) * @param id the ItemDefinition for which to ensure the QName references are valorized correctly. */ private void processItemDefQNameURIs( ItemDefinition id ) { QName typeRef = id.getTypeRef(); if ( typeRef != null && XMLConstants.NULL_NS_URI.equals( typeRef.getNamespaceURI() ) ) { String actualNS = id.getNamespaceURI( typeRef.getPrefix() ); id.setTypeRef(new QName(actualNS, typeRef.getLocalPart(), typeRef.getPrefix())); } for ( ItemDefinition ic : id.getItemComponent() ) { processItemDefQNameURIs( ic ); } } private void processDrgElements(DMNCompilerContext ctx, DMNFEELHelper feel, DMNModelImpl model, Definitions dmndefs) { for ( DRGElement e : dmndefs.getDrgElement() ) { if ( e instanceof InputData ) { InputData input = (InputData) e; InputDataNodeImpl idn = new InputDataNodeImpl( input ); if ( input.getVariable() != null ) { DMNCompilerHelper.checkVariableName( model, input, input.getName() ); DMNType type = resolveTypeRef( model, idn, e, input.getVariable(), input.getVariable().getTypeRef() ); idn.setType( type ); } else { idn.setType( DMNTypeRegistry.UNKNOWN ); reportMissingVariable( model, e, input, Msg.MISSING_VARIABLE_FOR_INPUT ); } model.addInput( idn ); } else if ( e instanceof Decision ) { Decision decision = (Decision) e; DecisionNodeImpl dn = new DecisionNodeImpl( decision ); DMNType type = null; if ( decision.getVariable() == null ) { reportMissingVariable( model, e, decision, Msg.MISSING_VARIABLE_FOR_DECISION ); continue; } DMNCompilerHelper.checkVariableName( model, decision, decision.getName() ); if ( decision.getVariable() != null && decision.getVariable().getTypeRef() != null ) { type = resolveTypeRef( model, dn, decision, decision.getVariable(), decision.getVariable().getTypeRef() ); } else { type = resolveTypeRef( model, dn, decision, decision, null ); } dn.setResultType( type ); model.addDecision( dn ); } else if ( e instanceof BusinessKnowledgeModel ) { BusinessKnowledgeModel bkm = (BusinessKnowledgeModel) e; BusinessKnowledgeModelNodeImpl bkmn = new BusinessKnowledgeModelNodeImpl( bkm ); DMNType type = null; if ( bkm.getVariable() == null ) { reportMissingVariable( model, e, bkm, Msg.MISSING_VARIABLE_FOR_BKM ); continue; } DMNCompilerHelper.checkVariableName( model, bkm, bkm.getName() ); if ( bkm.getVariable() != null && bkm.getVariable().getTypeRef() != null ) { type = resolveTypeRef( model, bkmn, bkm, bkm.getVariable(), bkm.getVariable().getTypeRef() ); } else { // for now the call bellow will return type UNKNOWN type = resolveTypeRef( model, bkmn, bkm, bkm, null ); } bkmn.setResultType( type ); model.addBusinessKnowledgeModel( bkmn ); } else if ( e instanceof KnowledgeSource ) { // don't do anything as KnowledgeSource is a documentation element // without runtime semantics } else { MsgUtil.reportMessage( logger, DMNMessage.Severity.ERROR, e, model, null, null, Msg.UNSUPPORTED_ELEMENT, e.getClass().getSimpleName(), e.getId() ); } } for ( BusinessKnowledgeModelNode bkm : model.getBusinessKnowledgeModels() ) { BusinessKnowledgeModelNodeImpl bkmi = (BusinessKnowledgeModelNodeImpl) bkm; linkRequirements( model, bkmi ); ctx.enterFrame(); try { for( DMNNode dep : bkmi.getDependencies().values() ) { if( dep instanceof BusinessKnowledgeModelNode ) { // might need to create a DMNType for "functions" and replace the type here by that ctx.setVariable( dep.getName(), ((BusinessKnowledgeModelNode)dep).getResultType() ); } } // to allow recursive call from inside a BKM node, a variable for self must be available for the compiler context: ctx.setVariable(bkm.getName(), bkm.getResultType()); FunctionDefinition funcDef = bkm.getBusinessKnowledModel().getEncapsulatedLogic(); DMNExpressionEvaluator exprEvaluator = evaluatorCompiler.compileExpression( ctx, model, bkmi, bkm.getName(), funcDef ); bkmi.setEvaluator( exprEvaluator ); } finally { ctx.exitFrame(); } } for ( DecisionNode d : model.getDecisions() ) { DecisionNodeImpl di = (DecisionNodeImpl) d; linkRequirements( model, di ); ctx.enterFrame(); try { for( DMNNode dep : di.getDependencies().values() ) { if( dep instanceof DecisionNode ) { ctx.setVariable( dep.getName(), ((DecisionNode) dep).getResultType() ); } else if( dep instanceof InputDataNode ) { ctx.setVariable( dep.getName(), ((InputDataNode) dep).getType() ); } else if( dep instanceof BusinessKnowledgeModelNode ) { // might need to create a DMNType for "functions" and replace the type here by that ctx.setVariable( dep.getName(), ((BusinessKnowledgeModelNode)dep).getResultType() ); } } DMNExpressionEvaluator evaluator = evaluatorCompiler.compileExpression( ctx, model, di, d.getName(), d.getDecision().getExpression() ); di.setEvaluator( evaluator ); } finally { ctx.exitFrame(); } } } private void reportMissingVariable(DMNModelImpl model, DRGElement node, DMNModelInstrumentedBase source, Msg.Message1 message ) { MsgUtil.reportMessage( logger, DMNMessage.Severity.ERROR, source, model, null, null, message, node.getIdentifierString() ); } private void linkRequirements(DMNModelImpl model, DMNBaseNode node) { for ( InformationRequirement ir : node.getInformationRequirement() ) { if ( ir.getRequiredInput() != null ) { String id = getId( ir.getRequiredInput() ); InputDataNode input = model.getInputById( id ); if ( input != null ) { node.addDependency( input.getName(), input ); } else { MsgUtil.reportMessage( logger, DMNMessage.Severity.ERROR, ir.getRequiredInput(), model, null, null, Msg.REQ_INPUT_NOT_FOUND_FOR_NODE, id, node.getName() ); } } else if ( ir.getRequiredDecision() != null ) { String id = getId( ir.getRequiredDecision() ); DecisionNode dn = model.getDecisionById( id ); if ( dn != null ) { node.addDependency( dn.getName(), dn ); } else { MsgUtil.reportMessage( logger, DMNMessage.Severity.ERROR, ir.getRequiredDecision(), model, null, null, Msg.REQ_DECISION_NOT_FOUND_FOR_NODE, id, node.getName() ); } } } for ( KnowledgeRequirement kr : node.getKnowledgeRequirement() ) { if ( kr.getRequiredKnowledge() != null ) { String id = getId( kr.getRequiredKnowledge() ); BusinessKnowledgeModelNode bkmn = model.getBusinessKnowledgeModelById( id ); if ( bkmn != null ) { node.addDependency( bkmn.getName(), bkmn ); } else { MsgUtil.reportMessage( logger, DMNMessage.Severity.ERROR, kr.getRequiredKnowledge(), model, null, null, Msg.REQ_BKM_NOT_FOUND_FOR_NODE, id, node.getName() ); } } } } private String getId(DMNElementReference er) { String href = er.getHref(); return href.contains( "#" ) ? href.substring( href.indexOf( '#' ) + 1 ) : href; } private DMNType buildTypeDef(DMNCompilerContext ctx, DMNFEELHelper feel, DMNModelImpl dmnModel, DMNNode node, ItemDefinition itemDef, boolean topLevel) { BaseDMNTypeImpl type = null; if ( itemDef.getTypeRef() != null ) { // this is a reference to an existing type, so resolve the reference type = (BaseDMNTypeImpl) resolveTypeRef( dmnModel, node, itemDef, itemDef, itemDef.getTypeRef() ); if ( type != null ) { UnaryTests allowedValuesStr = itemDef.getAllowedValues(); // we only want to clone the type definition if it is a top level type (not a field in a composite type) // or if it changes the metadata for the base type if( topLevel || allowedValuesStr != null || itemDef.isIsCollection() != type.isCollection() ) { // we have to clone this type definition into a new one BaseDMNTypeImpl baseType = type; type = type.clone(); type.setBaseType( baseType ); type.setNamespace( dmnModel.getNamespace() ); type.setName( itemDef.getName() ); type.setAllowedValues(null); if ( allowedValuesStr != null ) { List<UnaryTest> av = feel.evaluateUnaryTests( ctx, allowedValuesStr.getText(), dmnModel, itemDef, Msg.ERR_COMPILING_ALLOWED_VALUES_LIST_ON_ITEM_DEF, allowedValuesStr.getText(), node.getName() ); type.setAllowedValues( av ); } if ( itemDef.isIsCollection() ) { type.setCollection( itemDef.isIsCollection() ); } } if( topLevel ) { DMNType registered = dmnModel.getTypeRegistry().registerType( type ); if( registered != type ) { MsgUtil.reportMessage( logger, DMNMessage.Severity.ERROR, itemDef, dmnModel, null, null, Msg.DUPLICATED_ITEM_DEFINITION, itemDef.getName() ); } } } } else { // this is a composite type DMNCompilerHelper.checkVariableName( dmnModel, itemDef, itemDef.getName() ); CompositeTypeImpl compType = new CompositeTypeImpl( dmnModel.getNamespace(), itemDef.getName(), itemDef.getId(), itemDef.isIsCollection() ); for ( ItemDefinition fieldDef : itemDef.getItemComponent() ) { DMNCompilerHelper.checkVariableName( dmnModel, fieldDef, fieldDef.getName() ); DMNType fieldType = buildTypeDef( ctx, feel, dmnModel, node, fieldDef, false ); fieldType = fieldType != null ? fieldType : DMNTypeRegistry.UNKNOWN; compType.addField( fieldDef.getName(), fieldType ); } type = compType; if( topLevel ) { DMNType registered = dmnModel.getTypeRegistry().registerType( type ); if( registered != type ) { MsgUtil.reportMessage( logger, DMNMessage.Severity.ERROR, itemDef, dmnModel, null, null, Msg.DUPLICATED_ITEM_DEFINITION, itemDef.getName() ); } } } return type; } public DMNType resolveTypeRef(DMNModelImpl dmnModel, DMNNode node, NamedElement model, DMNModelInstrumentedBase localElement, QName typeRef) { if ( typeRef != null ) { String namespace = getNamespace( localElement, typeRef ); DMNType type = dmnModel.getTypeRegistry().resolveType( namespace, typeRef.getLocalPart() ); if( type == null && DMNModelInstrumentedBase.URI_FEEL.equals( namespace ) ) { if ( model instanceof Decision && ((Decision) model).getExpression() instanceof DecisionTable ) { DecisionTable dt = (DecisionTable) ((Decision) model).getExpression(); if ( dt.getOutput().size() > 1 ) { // implicitly define a type for the decision table result CompositeTypeImpl compType = new CompositeTypeImpl( dmnModel.getNamespace(), model.getName()+"_Type", model.getId(), dt.getHitPolicy().isMultiHit() ); for ( OutputClause oc : dt.getOutput() ) { DMNType fieldType = resolveTypeRef( dmnModel, node, model, oc, oc.getTypeRef() ); compType.addField( oc.getName(), fieldType ); } dmnModel.getTypeRegistry().registerType( compType ); return compType; } else if ( dt.getOutput().size() == 1 ) { return resolveTypeRef( dmnModel, node, model, dt.getOutput().get( 0 ), dt.getOutput().get( 0 ).getTypeRef() ); } } } else if( type == null ) { MsgUtil.reportMessage( logger, DMNMessage.Severity.ERROR, localElement, dmnModel, null, null, Msg.UNKNOWN_TYPE_REF_ON_NODE, typeRef.toString(), localElement.getParentDRDElement().getIdentifierString() ); } return type; } return dmnModel.getTypeRegistry().resolveType( DMNModelInstrumentedBase.URI_FEEL, BuiltInType.UNKNOWN.getName() ); } private String getNamespace(DMNModelInstrumentedBase localElement, QName typeRef) { String prefix = typeRef.getPrefix(); return localElement.getNamespaceURI( prefix ); } private DMNCompilerConfiguration getDmnCompilerConfig() { return this.dmnCompilerConfig; } }