Skip to content

Commit

Permalink
[incubator-kie-issues#852] DMN - Allow importing into the default nam…
Browse files Browse the repository at this point in the history
…espace (#5684)

* [private-bamoe-issues#253] WIP - working only ItemDefNode import

* [incubator-kie-issues#852] WIP

* [incubator-kie-issues#852] WIP - experimenting different approach

* [incubator-kie-issues#852] WIP - Experimenting model merge.

* [incubator-kie-issues#852] WIP - Working Imported model test

* [incubator-kie-issues#852] Working state

* [incubator-kie-issues#852] Add tests. Minor fixes

* [incubator-kie-issues#852] Fixing UnnamedImportUtilsTest - using a unique name for imported model

---------

Co-authored-by: BAMOE CI <bamoe.ci@ibm.com>
Co-authored-by: Gabriele-Cardosi <gabriele.cardosi@ibm.com>
  • Loading branch information
3 people authored Feb 14, 2024
1 parent eb2f4f6 commit d55ce2d
Show file tree
Hide file tree
Showing 19 changed files with 672 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import javax.xml.XMLConstants;
import javax.xml.namespace.QName;

Expand Down Expand Up @@ -106,6 +107,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.kie.dmn.core.compiler.UnnamedImportUtils.processMergedModel;

public class DMNCompilerImpl implements DMNCompiler {

private static final Logger logger = LoggerFactory.getLogger( DMNCompilerImpl.class );
Expand Down Expand Up @@ -211,7 +214,7 @@ public DMNModel compile(Definitions dmndefs, Collection<DMNModel> dmnModels, Res
DMNFEELHelper feel = new DMNFEELHelper(cc.getRootClassLoader(), helperFEELProfiles);
DMNCompilerContext ctx = new DMNCompilerContext(feel);
ctx.setRelativeResolver(relativeResolver);

List<DMNModel> toMerge = new ArrayList<>();
if (!dmndefs.getImport().isEmpty()) {
for (Import i : dmndefs.getImport()) {
if (ImportDMNResolverUtil.whichImportType(i) == ImportType.DMN) {
Expand All @@ -230,8 +233,15 @@ public DMNModel compile(Definitions dmndefs, Collection<DMNModel> dmnModels, Res
}, Function.identity());
if (located != null) {
String iAlias = Optional.ofNullable(i.getName()).orElse(located.getName());
model.setImportAliasForNS(iAlias, located.getNamespace(), located.getName());
importFromModel(model, located, iAlias);
// incubator-kie-issues#852: The idea is to not treat the anonymous models as import, but to "merge" them
// with original one,
// because otherwise we would have to deal with clashing name aliases, or similar issues
if (iAlias != null && !iAlias.isEmpty()) {
model.setImportAliasForNS(iAlias, located.getNamespace(), located.getName());
importFromModel(model, located, iAlias);
} else {
toMerge.add(located);
}
}
} else if (ImportDMNResolverUtil.whichImportType(i) == ImportType.PMML) {
processPMMLImport(model, i, relativeResolver);
Expand All @@ -249,6 +259,7 @@ public DMNModel compile(Definitions dmndefs, Collection<DMNModel> dmnModels, Res
}
}

toMerge.forEach(mergedModel -> processMergedModel(model, (DMNModelImpl) mergedModel));
processItemDefinitions(ctx, model, dmndefs);
processDrgElements(ctx, model, dmndefs);
return model;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,21 @@
*/
package org.kie.dmn.core.compiler;

import java.util.Map;

import org.kie.dmn.api.core.DMNType;
import org.kie.dmn.feel.lang.types.FEELTypeRegistry;

public interface DMNTypeRegistry extends FEELTypeRegistry {

DMNType unknown();

Map<String, Map<String, DMNType>> getTypes();

DMNType registerType(DMNType type);

DMNType resolveType(String namespace, String name);

String feelNS();

}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ public abstract class DMNTypeRegistryAbstract implements DMNTypeRegistry, FEELTy
protected ScopeImpl feelTypesScope = new ScopeImpl(); // no parent scope, intentional.
protected Map<String, ScopeImpl> feelTypesScopeChildLU = new HashMap<>();

protected abstract String feelNS();

public DMNTypeRegistryAbstract(Map<String, QName> aliases) {
this.aliases = aliases;
Expand Down Expand Up @@ -93,6 +92,11 @@ public Type resolveFEELType(List<String> qns) {
}
}

@Override
public Map<String, Map<String, DMNType>> getTypes() {
return types;
}

protected void registerAsFEELType(DMNType dmnType) {
Optional<String> optAliasKey = keyfromNS(dmnType.getNamespace());
Type feelType = ((BaseDMNTypeImpl) dmnType).getFeelType();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public DMNTypeRegistryV11(Map<String, QName> aliases) {
}

@Override
protected String feelNS() {
public String feelNS() {
return KieDMNModelInstrumentedBase.URI_FEEL;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public DMNType unknown() {
BuiltInType.CONTEXT));

@Override
protected String feelNS() {
public String feelNS() {
return KieDMNModelInstrumentedBase.URI_FEEL;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public DMNType unknown() {
}

@Override
protected String feelNS() {
public String feelNS() {
return KieDMNModelInstrumentedBase.URI_FEEL;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public DMNType unknown() {
}

@Override
protected String feelNS() {
public String feelNS() {
return KieDMNModelInstrumentedBase.URI_FEEL;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public DMNType unknown() {
}

@Override
protected String feelNS() {
public String feelNS() {
return KieDMNModelInstrumentedBase.URI_FEEL;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
import org.kie.dmn.model.api.DRGElement;
import org.kie.dmn.model.api.Decision;

import static org.kie.dmn.core.compiler.UnnamedImportUtils.isInUnnamedImport;

public class DecisionCompiler implements DRGElementCompiler {
@Override
public boolean accept(DRGElement de) {
Expand Down Expand Up @@ -96,6 +98,9 @@ public static void loadInCtx(DMNBaseNode node, DMNCompilerContext ctx, DMNModelI
if (dep.getModelNamespace().equals(model.getNamespace())) {
// for BKMs might need to create a DMNType for "functions" and replace the type here by that
ctx.setVariable(dep.getName(), depType);
} else if (isInUnnamedImport(dep, model)) {
// the dependency is an unnamed import
ctx.setVariable(dep.getName(), depType);
} else {
// then the dependency is an imported dependency.
Optional<String> alias = model.getImportAliasFor(dep.getModelNamespace(), dep.getModelName());
Expand All @@ -109,4 +114,5 @@ public static void loadInCtx(DMNBaseNode node, DMNCompilerContext ctx, DMNModelI
ctx.setVariable(importedType.getKey(), importedType.getValue());
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 java.util.Collection;
import java.util.Objects;

import org.kie.dmn.api.core.ast.DMNNode;
import org.kie.dmn.core.impl.DMNModelImpl;
import org.kie.dmn.model.api.Definitions;
import org.kie.dmn.model.api.Import;
import org.kie.dmn.model.api.NamedElement;

/**
* Class meant to provide helper methods to deal with unnamed model imports
*/
public class UnnamedImportUtils {

private UnnamedImportUtils() {
}

public static boolean isInUnnamedImport(DMNNode node, DMNModelImpl model) {
for (Import imported : model.getDefinitions().getImport()) {
String importedName = imported.getName();
if ((node.getModelNamespace().equals(imported.getNamespace()) &&
(importedName != null && importedName.isEmpty()))) {
return true;
}
}
return false;
}

public static void processMergedModel(DMNModelImpl parentModel, DMNModelImpl mergedModel) {
// incubator-kie-issues#852: The idea is to not treat the anonymous models as import, but to "merge" them with original opne,
// Here we try to put all the definitions from the "imported" model inside the parent one
Definitions parentDefinitions = parentModel.getDefinitions();
Definitions mergedDefinitions = mergedModel.getDefinitions();
parentDefinitions.getArtifact().addAll(mergedDefinitions.getArtifact());

addIfNotPresent(parentDefinitions.getDecisionService(), mergedDefinitions.getDecisionService());
addIfNotPresent(parentDefinitions.getBusinessContextElement(), mergedDefinitions.getBusinessContextElement());
addIfNotPresent(parentDefinitions.getDrgElement(), mergedDefinitions.getDrgElement());
addIfNotPresent(parentDefinitions.getImport(), mergedDefinitions.getImport());
addIfNotPresent(parentDefinitions.getItemDefinition(), mergedDefinitions.getItemDefinition());
mergedDefinitions.getChildren().forEach(parentDefinitions::addChildren);
mergedModel.getTypeRegistry().getTypes().forEach((s, stringDMNTypeMap) ->
stringDMNTypeMap.values().
forEach(dmnType -> parentModel.getTypeRegistry().registerType(dmnType)));
}

static <T extends NamedElement> void addIfNotPresent(Collection<T> target, Collection<T> source) {
source.forEach(sourceElement -> addIfNotPresent(target, sourceElement));
}

static <T extends NamedElement> void addIfNotPresent(Collection<T> target, T source) {
if (target.stream().noneMatch(namedElement -> Objects.equals(namedElement.getName(), source.getName()))) {
target.add(source);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@
import org.kie.dmn.model.api.DMNModelInstrumentedBase;
import org.kie.dmn.model.api.Definitions;

import static org.kie.dmn.core.compiler.UnnamedImportUtils.isInUnnamedImport;

public class DMNModelImpl
implements DMNModel, DMNMessageManager, Externalizable {

Expand Down Expand Up @@ -205,13 +207,20 @@ public void addDecision(DecisionNode dn) {
}

private String computeDRGElementModelLocalId(DMNNode node) {
// incubator-kie-issues#852: The idea is to not treat the anonymous models as import, but to "merge" them with original opne,
// Here, if the node comes from an unnamed imported model, then it is stored only with its id, to be looked for
// as if defined in the model itself
if (node.getModelNamespace().equals(definitions.getNamespace())) {
return node.getId();
} else if (isInUnnamedImport(node, this)) {
// the node is an unnamed import
return node.getId();
} else {
return node.getModelNamespace() + "#" + node.getId();
}
}


@Override
public DecisionNode getDecisionById(String id) {
return this.decisions.get(id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
import static org.kie.dmn.api.core.DMNDecisionResult.DecisionEvaluationStatus.EVALUATING;
import static org.kie.dmn.api.core.DMNDecisionResult.DecisionEvaluationStatus.FAILED;
import static org.kie.dmn.api.core.DMNDecisionResult.DecisionEvaluationStatus.SKIPPED;
import static org.kie.dmn.core.compiler.UnnamedImportUtils.isInUnnamedImport;
import static org.kie.dmn.core.util.CoerceUtil.coerceValue;

public class DMNRuntimeImpl
Expand Down Expand Up @@ -501,6 +502,9 @@ private boolean walkIntoImportScope(DMNResultImpl result, DMNNode callerNode, DM
if (result.getContext().scopeNamespace().isEmpty()) {
if (destinationNode.getModelNamespace().equals(result.getModel().getNamespace())) {
return false;
} else if (isInUnnamedImport(destinationNode, (DMNModelImpl) result.getModel())) {
// the destinationNode is an unnamed import
return false;
} else {
Optional<String> importAlias = callerNode.getModelImportAliasFor(destinationNode.getModelNamespace(), destinationNode.getModelName());
if (importAlias.isPresent()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,98 @@ public void testImport() {
}
}

@Test
public void testEmptyNamedModelImport() {
final DMNRuntime runtime = createRuntimeWithAdditionalResources("Importing_EmptyNamed_Model.dmn",
this.getClass(),
"Imported_Model_Unamed.dmn");

final DMNModel importedModel = runtime.getModel("http://www.trisotech.com/dmn/definitions/_f27bb64b-6fc7-4e1f-9848-11ba35e0df36",
"Imported Model");
assertThat(importedModel).isNotNull();
for (final DMNMessage message : importedModel.getMessages()) {
LOG.debug("{}", message);
}

final DMNModel importingModel = runtime.getModel("http://www.trisotech.com/dmn/definitions/_f79aa7a4-f9a3-410a-ac95-bea496edabgc",
"Importing empty-named Model");
assertThat(importingModel).isNotNull();
for (final DMNMessage message : importingModel.getMessages()) {
LOG.debug("{}", message);
}

final DMNContext context = runtime.newContext();
context.set("A Person", mapOf(entry("name", "John"), entry("age", 47)));

final DMNResult evaluateAll = evaluateModel(runtime, importingModel, context);
for (final DMNMessage message : evaluateAll.getMessages()) {
LOG.debug("{}", message);
}
LOG.debug("{}", evaluateAll);
// Verify locally-defined BusinessKnowledgeModel
assertThat(evaluateAll.getDecisionResultByName("Local Greeting").getResult()).isEqualTo("Local Hello John!");

if (isTypeSafe()) {
FEELPropertyAccessible outputSet = ((DMNContextFPAImpl)evaluateAll.getContext()).getFpa();
Map<String, Object> allProperties = outputSet.allFEELProperties();
assertThat(allProperties).containsEntry("Local Greeting", "Local Hello John!");
}
// Verify unnamed-imported BusinessKnowledgeModel
assertThat(evaluateAll.getDecisionResultByName("Imported Greeting").getResult()).isEqualTo("Hello John!");

if (isTypeSafe()) {
FEELPropertyAccessible outputSet = ((DMNContextFPAImpl)evaluateAll.getContext()).getFpa();
Map<String, Object> allProperties = outputSet.allFEELProperties();
assertThat(allProperties).containsEntry("Imported Greeting", "Hello John!");
}
}

@Test
public void testOverridingEmptyNamedModelImport() {
final DMNRuntime runtime = createRuntimeWithAdditionalResources("Importing_OverridingEmptyNamed_Model.dmn",
this.getClass(),
"Imported_Model_Unamed.dmn");

final DMNModel importedModel = runtime.getModel("http://www.trisotech.com/dmn/definitions/_f27bb64b-6fc7-4e1f-9848-11ba35e0df36",
"Imported Model");
assertThat(importedModel).isNotNull();
for (final DMNMessage message : importedModel.getMessages()) {
LOG.debug("{}", message);
}

final DMNModel importingModel = runtime.getModel("http://www.trisotech.com/dmn/definitions/_f79aa7a4-f9a3-410a-ac95-bea496edabgc",
"Importing empty-named Model");
assertThat(importingModel).isNotNull();
for (final DMNMessage message : importingModel.getMessages()) {
LOG.debug("{}", message);
}

final DMNContext context = runtime.newContext();
context.set("A Person", mapOf(entry("name", "John"), entry("age", 47)));

final DMNResult evaluateAll = evaluateModel(runtime, importingModel, context);
for (final DMNMessage message : evaluateAll.getMessages()) {
LOG.debug("{}", message);
}
LOG.debug("{}", evaluateAll);
// Verify locally-defined BusinessKnowledgeModel
assertThat(evaluateAll.getDecisionResultByName("Local Greeting").getResult()).isEqualTo("Local Hello John!");

if (isTypeSafe()) {
FEELPropertyAccessible outputSet = ((DMNContextFPAImpl)evaluateAll.getContext()).getFpa();
Map<String, Object> allProperties = outputSet.allFEELProperties();
assertThat(allProperties).containsEntry("Local Greeting", "Local Hello John!");
}
// Verify unnamed-imported BusinessKnowledgeModel
assertThat(evaluateAll.getDecisionResultByName("Imported Greeting").getResult()).isEqualTo("Hello John!");

if (isTypeSafe()) {
FEELPropertyAccessible outputSet = ((DMNContextFPAImpl)evaluateAll.getContext()).getFpa();
Map<String, Object> allProperties = outputSet.allFEELProperties();
assertThat(allProperties).containsEntry("Imported Greeting", "Hello John!");
}
}

@Test
public void testWrongComparisonOps() {
final DMNRuntime runtime = createRuntime("WrongComparisonOps.dmn", this.getClass());
Expand Down
Loading

0 comments on commit d55ce2d

Please sign in to comment.