diff --git a/api-check-ignore.xml b/api-check-ignore.xml
index f62f6eef..e4c1af26 100644
--- a/api-check-ignore.xml
+++ b/api-check-ignore.xml
@@ -11,7 +11,6 @@
7004
*$anonfun*
- *
*
@@ -19,8 +18,6 @@
7005
*$anonfun*
-
- *
*
@@ -43,4 +40,39 @@
7002
*
+
+
+ org/camunda/dmn/DmnEngine
+ 7004
+ DmnEngine(*
+
+
+ org/camunda/dmn/parser/DmnParser
+ 7004
+ DmnParser(*
+
+
+ org/camunda/dmn/parser/ParsedDmn
+ 7004
+ *
+
+
+ org/camunda/dmn/parser/ParsedBusinessKnowledgeModel
+ 2000
+
+
+ org/camunda/dmn/parser/ParsedBusinessKnowledgeModel
+ 4001
+ **
+
+
+ org/camunda/dmn/parser/ParsedDecision
+ 2000
+
+
+ org/camunda/dmn/parser/ParsedDecision
+ 4001
+ **
+
+
diff --git a/pom.xml b/pom.xml
index d061e8d7..04474e14 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.camunda.bpm.extension.dmn.scala
dmn-engine
DMN Scala Engine
- 1.8.2-SNAPSHOT
+ 1.9.0-SNAPSHOT
org.camunda
@@ -15,7 +15,7 @@
- 1.16.0
+ 1.16.1
7.19.0
11
2.13.11
diff --git a/src/main/scala/org/camunda/dmn/evaluation/BusinessKnowledgeEvaluator.scala b/src/main/scala/org/camunda/dmn/evaluation/BusinessKnowledgeEvaluator.scala
index 97514ede..a8672728 100644
--- a/src/main/scala/org/camunda/dmn/evaluation/BusinessKnowledgeEvaluator.scala
+++ b/src/main/scala/org/camunda/dmn/evaluation/BusinessKnowledgeEvaluator.scala
@@ -31,7 +31,7 @@ class BusinessKnowledgeEvaluator(
def eval(bkm: ParsedBusinessKnowledgeModel,
context: EvalContext): Either[Failure, Val] = {
- evalRequiredKnowledge(bkm.requiredBkms, context)
+ evalRequiredKnowledge(bkm.requiredBkms.map(_.resolve()), context)
.flatMap(functions => {
val evalContext =
@@ -47,7 +47,7 @@ class BusinessKnowledgeEvaluator(
bkm: ParsedBusinessKnowledgeModel,
context: EvalContext): Either[Failure, (String, ValFunction)] = {
- evalRequiredKnowledge(bkm.requiredBkms, context).map(functions => {
+ evalRequiredKnowledge(bkm.requiredBkms.map(_.resolve()), context).map(functions => {
val evalContext = context.copy(variables = context.variables ++ functions,
currentElement = bkm)
diff --git a/src/main/scala/org/camunda/dmn/evaluation/DecisionEvaluator.scala b/src/main/scala/org/camunda/dmn/evaluation/DecisionEvaluator.scala
index fe32cf35..8722affd 100644
--- a/src/main/scala/org/camunda/dmn/evaluation/DecisionEvaluator.scala
+++ b/src/main/scala/org/camunda/dmn/evaluation/DecisionEvaluator.scala
@@ -18,7 +18,13 @@ package org.camunda.dmn.evaluation
import org.camunda.dmn.DmnEngine._
import org.camunda.dmn.FunctionalHelper._
import org.camunda.feel.syntaxtree.{Val, ValContext, ValFunction}
-import org.camunda.dmn.parser.{ParsedBusinessKnowledgeModel, ParsedDecision, ParsedDecisionLogic}
+import org.camunda.dmn.parser.{
+ ParsedBusinessKnowledgeModel,
+ ParsedBusinessKnowledgeModelReference,
+ ParsedDecision,
+ ParsedDecisionReference,
+ ParsedDecisionLogic,
+ ParsedDecisionLogicContainerReference}
import org.camunda.feel.context.Context.StaticContext
class DecisionEvaluator(
@@ -42,38 +48,10 @@ class DecisionEvaluator(
evalRequiredKnowledge(decision.requiredBkms, context)
.flatMap(functions => {
- val isImported: ((String, Val)) => Boolean = {
- case (name, _) => name.contains(".")
- }
-
- // todo: replace the hack to wrap the imported BKMs and decisions into a context, maybe move to the BKM evaluation logic
- val importedFunctions = functions
- .filter(isImported)
- .map { case (name, function) =>
- val Array(prefix: String, functionName: String) = name.split('.')
- prefix -> ValContext(StaticContext(
- variables = Map.empty,
- functions = Map(functionName -> List(function))
- ))
- }
- val embeddedFunctions = functions.filterNot(isImported)
-
- val importedDecisions = decisionResults
- .filter(isImported)
- .map { case (name, decisionResult) =>
- val Array(prefix: String, decisionName: String) = name.split('.')
- prefix -> ValContext(StaticContext(
- variables = Map(decisionName -> decisionResult),
- functions = Map.empty
- ))
- }
- val embeddedDecisions = decisionResults.filterNot(isImported)
-
val decisionEvaluationContext = context.copy(
variables = context.variables
- ++ embeddedDecisions ++ importedDecisions
- ++ embeddedFunctions ++ importedFunctions,
- currentElement = decision)
+ ++ decisionResults ++ functions,
+ currentElement = decision)
eval(decision.logic, decisionEvaluationContext)
.flatMap(
@@ -87,17 +65,36 @@ class DecisionEvaluator(
}
private def evalRequiredDecisions(
- requiredDecisions: Iterable[ParsedDecision],
- context: EvalContext): Either[Failure, List[(String, Val)]] = {
+ requiredDecisions: Iterable[ParsedDecisionReference],
+ context: EvalContext): Either[Failure, List[(String, Val)]] = {
mapEither(requiredDecisions,
- (d: ParsedDecision) => evalDecision(d, context))
+ (decisionRef: ParsedDecisionReference) => evalDecision(decisionRef.resolve(), context)
+ .map(maybeWrapResult(decisionRef, _)))
}
private def evalRequiredKnowledge(
- requiredBkms: Iterable[ParsedBusinessKnowledgeModel],
- context: EvalContext): Either[Failure, List[(String, ValFunction)]] = {
+ requiredBkms: Iterable[ParsedBusinessKnowledgeModelReference],
+ context: EvalContext): Either[Failure, List[(String, Val)]] = {
mapEither(requiredBkms,
- (bkm: ParsedBusinessKnowledgeModel) => evalBkm(bkm, context))
+ (bkmRef: ParsedBusinessKnowledgeModelReference) => evalBkm(bkmRef.resolve(), context)
+ .map(maybeWrapResult(bkmRef, _)))
}
+ private def maybeWrapResult(
+ reference: ParsedDecisionLogicContainerReference[_], result: (String, Val)) =
+ reference.importedModelName match {
+ case Some(importName) =>
+ val ctx = result._2 match {
+ case func: ValFunction => StaticContext(
+ variables = Map.empty,
+ functions = Map(result._1 -> List(func))
+ )
+ case _ => StaticContext(
+ variables = Map(result._1 -> result._2),
+ functions = Map.empty
+ )
+ }
+ importName -> ValContext(ctx)
+ case _ => result
+ }
}
diff --git a/src/main/scala/org/camunda/dmn/parser/DmnParser.scala b/src/main/scala/org/camunda/dmn/parser/DmnParser.scala
index b5fa6ac3..3cac8558 100644
--- a/src/main/scala/org/camunda/dmn/parser/DmnParser.scala
+++ b/src/main/scala/org/camunda/dmn/parser/DmnParser.scala
@@ -15,19 +15,37 @@
*/
package org.camunda.dmn.parser
-import java.io.InputStream
-import org.camunda.dmn.logger
import org.camunda.bpm.model.dmn._
import org.camunda.bpm.model.dmn.impl.DmnModelConstants
-import org.camunda.bpm.model.dmn.impl.instance.DrgElementImpl
-import org.camunda.bpm.model.dmn.instance.{BusinessKnowledgeModel, Column, Context, Decision, DecisionTable, Definitions, DmnElementReference, DrgElement, Expression, FunctionDefinition, InformationItem, InformationRequirement, Invocation, ItemDefinition, KnowledgeRequirement, LiteralExpression, Relation, RequiredDecisionReference, RequiredKnowledgeReference, UnaryTests, List => DmnList}
+import org.camunda.bpm.model.dmn.instance.BusinessKnowledgeModel
+import org.camunda.bpm.model.dmn.instance.Column
+import org.camunda.bpm.model.dmn.instance.Context
+import org.camunda.bpm.model.dmn.instance.Decision
+import org.camunda.bpm.model.dmn.instance.DecisionTable
+import org.camunda.bpm.model.dmn.instance.Definitions
+import org.camunda.bpm.model.dmn.instance.DrgElement
+import org.camunda.bpm.model.dmn.instance.Expression
+import org.camunda.bpm.model.dmn.instance.FunctionDefinition
+import org.camunda.bpm.model.dmn.instance.InformationItem
+import org.camunda.bpm.model.dmn.instance.InformationRequirement
+import org.camunda.bpm.model.dmn.instance.Invocation
+import org.camunda.bpm.model.dmn.instance.ItemDefinition
+import org.camunda.bpm.model.dmn.instance.KnowledgeRequirement
+import org.camunda.bpm.model.dmn.instance.LiteralExpression
+import org.camunda.bpm.model.dmn.instance.Relation
+import org.camunda.bpm.model.dmn.instance.RequiredDecisionReference
+import org.camunda.bpm.model.dmn.instance.RequiredKnowledgeReference
+import org.camunda.bpm.model.dmn.instance.UnaryTests
+import org.camunda.bpm.model.dmn.instance.{List => DmnList}
import org.camunda.bpm.model.xml.instance.ModelElementInstance
-import org.camunda.dmn.DmnEngine.{Configuration, Failure}
+import org.camunda.dmn.DmnEngine.Configuration
+import org.camunda.dmn.DmnEngine.Failure
+import org.camunda.dmn.logger
import org.camunda.feel
-import scala.annotation.tailrec
-import scala.collection.JavaConverters._
+import java.io.InputStream
import scala.collection.mutable
+import scala.jdk.CollectionConverters._
import scala.util.Try
object DmnParser {
@@ -49,11 +67,11 @@ object DmnParser {
}
class DmnParser(
- configuration: Configuration,
- feelParser: String => Either[String, feel.syntaxtree.ParsedExpression],
- feelUnaryTestsParser: String => Either[String,
- feel.syntaxtree.ParsedExpression],
- dmnRepository: DmnRepository) {
+ configuration: Configuration,
+ feelParser: String => Either[String, feel.syntaxtree.ParsedExpression],
+ feelUnaryTestsParser: String => Either[String,
+ feel.syntaxtree.ParsedExpression],
+ dmnRepository: DmnRepository) {
import DmnParser._
@@ -61,6 +79,7 @@ class DmnParser(
case class ModelReference(namespace: String, id: String) {
def isEmbedded: Boolean = namespace.isEmpty
+
def isImported: Boolean = !isEmbedded
}
@@ -71,8 +90,8 @@ class DmnParser(
val parsedFeelExpressions = mutable.Map[String, ParsedExpression]()
val parsedFeelUnaryTest = mutable.Map[String, ParsedExpression]()
- val decisions = mutable.Map[String, ParsedDecision]()
- val bkms = mutable.Map[String, ParsedBusinessKnowledgeModel]()
+ val decisions = mutable.Map[String, ParsedDecisionReference]()
+ val bkms = mutable.Map[String, ParsedBusinessKnowledgeModelReference]()
val importedModels = mutable.ListBuffer[ImportedModel]()
@@ -102,7 +121,7 @@ class DmnParser(
}
private def parseModel(
- model: DmnModelInstance): Either[Iterable[Failure], ParsedDmn] = {
+ model: DmnModelInstance): Either[Iterable[Failure], ParsedDmn] = {
val ctx = ParsingContext(model)
@@ -126,8 +145,8 @@ class DmnParser(
val parsedDmn = ParsedDmn(
model = model,
- decisions = ctx.decisions.values,
- bkms = ctx.bkms.values,
+ decisions = ctx.decisions.values.filter(_.isEmbedded).map(_.resolve()),
+ bkms = ctx.bkms.values.filter(_.isEmbedded).map(_.resolve()),
namespace = definitions.getNamespace)
if (ctx.failures.isEmpty) {
@@ -227,7 +246,7 @@ class DmnParser(
private def parseDecision(decision: Decision)(
implicit
- ctx: ParsingContext): ParsedDecision = {
+ ctx: ParsingContext): ParsedDecisionReference = {
val requiredDecisions = decision.getInformationRequirements.asScala
.flatMap(requirement =>
@@ -287,92 +306,47 @@ class DmnParser(
)
}
- private def parseRequiredDecision(informationRequirement: InformationRequirement, reference: ModelReference)(implicit ctx: ParsingContext): ParsedDecision = {
+ private def parseRequiredDecision(informationRequirement: InformationRequirement, reference: ModelReference)(implicit ctx: ParsingContext): ParsedDecisionReference = {
if (reference.isEmbedded) {
val requiredDecision = informationRequirement.getRequiredDecision
ctx.decisions.getOrElseUpdate(requiredDecision.getId, parseDecision(decision = requiredDecision))
} else {
ctx.importedModels
.find(importedModel => reference.namespace == importedModel.namespace)
- .map(importedModel => createReferenceForImportedDecision(importedModel, reference))
+ .map(importedModel => ImportedDecision(dmnRepository, reference.namespace, reference.id, Some(importedModel.name)))
.getOrElse {
- ctx.failures += Failure(s"No import found for namespace '${reference.namespace}'.")
- ImportedDecision(() =>
- throw new RuntimeException(s"Failed to invoke decision. No import found for namespace '${reference.namespace}'.")
- )
+ val failure = Failure(s"No import found for namespace '${reference.namespace}'.")
+ ctx.failures += failure
+ ParsedDecisionFailure(reference.id, reference.namespace, ExpressionFailure(failure.message))
}
}
}
- private def createReferenceForImportedDecision(importedModel: ImportedModel, reference: ModelReference): ImportedDecision = {
- ImportedDecision(() => {
- // todo: extract loading, try to move to evaluation phase
- dmnRepository.getDecision(
- namespace = reference.namespace,
- decisionId = reference.id
- ) match {
- case Right(decision) => EmbeddedDecision(
- id = reference.id,
- // todo: replace the hack to add the namespace to the name
- name = s"${importedModel.name}.${decision.name}",
- resultName = s"${importedModel.name}.${decision.resultName}",
- logic = decision.logic,
- resultType = decision.resultType,
- requiredDecisions = decision.requiredDecisions,
- requiredBkms = decision.requiredBkms
- )
- // todo: don't throw an exception if a decision was not found, but return a failure
- case Left(failure) => throw new RuntimeException(failure.message)
- }
- })
- }
-
private def getBkmReference(knowledgeRequirement: KnowledgeRequirement): Option[ModelReference] = {
Option(knowledgeRequirement.getUniqueChildElementByType(classOf[RequiredKnowledgeReference]))
.map(createModelReference)
}
- private def parseRequiredBkm(knowledgeRequirement: KnowledgeRequirement, reference: ModelReference)(implicit ctx: ParsingContext): ParsedBusinessKnowledgeModel = {
+ private def parseRequiredBkm(knowledgeRequirement: KnowledgeRequirement, reference: ModelReference)(implicit ctx: ParsingContext): ParsedBusinessKnowledgeModelReference = {
if (reference.isEmbedded) {
val requiredKnowledge = knowledgeRequirement.getRequiredKnowledge
ctx.bkms.getOrElseUpdate(requiredKnowledge.getName, parseBusinessKnowledgeModel(requiredKnowledge))
} else {
ctx.importedModels
.find(importedModel => reference.namespace == importedModel.namespace)
- .map(importedModel => createReferenceForImportedBkm(importedModel, reference))
+ .map(importedModel => ImportedBusinessKnowledgeModel(
+ dmnRepository, reference.namespace, reference.id, Some(importedModel.name)))
.getOrElse {
- ctx.failures += Failure(s"No import found for namespace '${reference.namespace}'.")
- ImportedBusinessKnowledgeModel(() =>
- throw new RuntimeException(s"Failed to invoke BKM. No import found for namespace '${reference.namespace}'.")
- )
+ val failure = Failure(s"No import found for namespace '${reference.namespace}'.")
+ ctx.failures += failure
+ ParsedBusinessKnowledgeModelFailure(reference.id, reference.namespace, ExpressionFailure(failure.message))
}
}
}
- private def createReferenceForImportedBkm(importedModel: ImportedModel, reference: ModelReference): ImportedBusinessKnowledgeModel = {
- ImportedBusinessKnowledgeModel(() => {
- // todo: extract loading, try to move to evaluation phase
- dmnRepository.getBusinessKnowledgeModel(
- namespace = reference.namespace,
- bkmId = reference.id
- ) match {
- case Right(bkm) => EmbeddedBusinessKnowledgeModel(
- id = reference.id,
- // todo: replace the hack to add the namespace to the name
- name = s"${importedModel.name}.${bkm.name}",
- logic = bkm.logic,
- parameters = bkm.parameters,
- requiredBkms = bkm.requiredBkms
- )
- // todo: don't throw an exception if a BKM was not found, but return a failure
- case Left(failure) => throw new RuntimeException(failure.message)
- }
- })
- }
-
private def parseBusinessKnowledgeModel(bkm: BusinessKnowledgeModel)(
implicit
- ctx: ParsingContext): ParsedBusinessKnowledgeModel = {
+ ctx: ParsingContext): ParsedBusinessKnowledgeModelReference = {
// TODO be aware of loops
val knowledgeRequirements = bkm.getKnowledgeRequirement.asScala
@@ -560,18 +534,16 @@ class DmnParser(
ctx: ParsingContext): ParsedDecisionLogic = {
val bindings = invocation.getBindings.asScala
- .map(b =>
+ .flatMap(b =>
b.getExpression match {
case lt: LiteralExpression =>
Some(b.getParameter.getName -> parseFeelExpression(lt))
case other => {
ctx.failures += Failure(
s"expected binding with literal expression but found '$other'")
-
None
}
})
- .flatten
invocation.getExpression match {
case lt: LiteralExpression => {
@@ -579,8 +551,8 @@ class DmnParser(
ctx.bkms
.get(expression)
- .map(bkm => {
- ParsedInvocation(bindings, bkm)
+ .map(bkmRef => {
+ ParsedInvocation(bindings, bkmRef.resolve())
})
.getOrElse {
ctx.failures += Failure(s"no BKM found with name '$expression'")
@@ -635,8 +607,7 @@ class DmnParser(
.map(_.getTextContent)
.toRight(Failure(s"The expression '${lt.getId}' must not be empty."))
- private def validateExpressionLanguage(
- lt: LiteralExpression): Either[Failure, Unit] = {
+ private def validateExpressionLanguage(lt: LiteralExpression): Either[Failure, Unit] = {
val language =
Option(lt.getExpressionLanguage).map(_.toLowerCase).getOrElse("feel")
if (feelNameSpaces.contains(language)) {
@@ -683,11 +654,11 @@ class DmnParser(
ctx.parsedFeelUnaryTest.getOrElseUpdate(
expression, {
- if (expression.isEmpty()) {
+ if (expression.isEmpty) {
EmptyExpression
} else {
- var escapedExpression =
+ val escapedExpression =
escapeNamesInExpression(expression, ctx.namesToEscape)
feelUnaryTestsParser(escapedExpression) match {
@@ -705,8 +676,8 @@ class DmnParser(
}
private def escapeNamesInExpression(
- expression: String,
- namesWithSpaces: Iterable[String]): String = {
+ expression: String,
+ namesWithSpaces: Iterable[String]): String = {
(expression /: namesWithSpaces)(
(e, name) =>
diff --git a/src/main/scala/org/camunda/dmn/parser/DmnRepository.scala b/src/main/scala/org/camunda/dmn/parser/DmnRepository.scala
index 8d026e9b..3d7280ed 100644
--- a/src/main/scala/org/camunda/dmn/parser/DmnRepository.scala
+++ b/src/main/scala/org/camunda/dmn/parser/DmnRepository.scala
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2022 Camunda Services GmbH (info@camunda.com)
+ *
+ * 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.camunda.dmn.parser
import org.camunda.dmn.DmnEngine.Failure
diff --git a/src/main/scala/org/camunda/dmn/parser/InMemoryDmnRepository.scala b/src/main/scala/org/camunda/dmn/parser/InMemoryDmnRepository.scala
index d846c324..1987f1b7 100644
--- a/src/main/scala/org/camunda/dmn/parser/InMemoryDmnRepository.scala
+++ b/src/main/scala/org/camunda/dmn/parser/InMemoryDmnRepository.scala
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2022 Camunda Services GmbH (info@camunda.com)
+ *
+ * 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.camunda.dmn.parser
import org.camunda.dmn.DmnEngine.Failure
diff --git a/src/main/scala/org/camunda/dmn/parser/ParsedDmn.scala b/src/main/scala/org/camunda/dmn/parser/ParsedDmn.scala
index afd2da06..47556693 100644
--- a/src/main/scala/org/camunda/dmn/parser/ParsedDmn.scala
+++ b/src/main/scala/org/camunda/dmn/parser/ParsedDmn.scala
@@ -54,52 +54,96 @@ sealed trait ParsedDecisionLogicContainer {
trait ParsedDecision extends ParsedDecisionLogicContainer {
val resultName: String
val resultType: Option[String]
- val requiredDecisions: Iterable[ParsedDecision]
- val requiredBkms: Iterable[ParsedBusinessKnowledgeModel]
+ val requiredDecisions: Iterable[ParsedDecisionReference]
+ val requiredBkms: Iterable[ParsedBusinessKnowledgeModelReference]
}
-case class EmbeddedDecision(id: String,
- name: String,
- logic: ParsedDecisionLogic,
- resultName: String,
- resultType: Option[String],
- requiredDecisions: Iterable[ParsedDecision],
- requiredBkms: Iterable[ParsedBusinessKnowledgeModel])
- extends ParsedDecision
-
-case class ImportedDecision(importer: () => ParsedDecision) extends ParsedDecision {
- private lazy val model = importer()
- override lazy val id: String = model.id
- override lazy val name: String = model.name
- override lazy val logic: ParsedDecisionLogic = model.logic
- override lazy val resultName: String = model.resultName
- override lazy val resultType: Option[String] = model.resultType
- override lazy val requiredDecisions: Iterable[ParsedDecision] = model.requiredDecisions
- override lazy val requiredBkms: Iterable[ParsedBusinessKnowledgeModel] = model.requiredBkms
+trait ParsedDecisionLogicContainerReference[T <: ParsedDecisionLogicContainer] {
+ val importedModelName: Option[String] = None
+ def resolve(): T
+ def isEmbedded: Boolean
+ def isImported: Boolean = !isEmbedded
}
-trait ParsedBusinessKnowledgeModel extends ParsedDecisionLogicContainer {
+trait ParsedDecisionReference extends ParsedDecisionLogicContainerReference[ParsedDecision] {
+}
+
+case class EmbeddedDecision(
+ id: String,
+ name: String,
+ logic: ParsedDecisionLogic,
+ resultName: String,
+ resultType: Option[String],
+ requiredDecisions: Iterable[ParsedDecisionReference],
+ requiredBkms: Iterable[ParsedBusinessKnowledgeModelReference]
+) extends ParsedDecision with ParsedDecisionReference {
+ override def resolve(): ParsedDecision = this
+
+ override def isEmbedded: Boolean = true
+}
+
+trait ImportedParsedDecisionLogicFailure[T <: ParsedDecisionLogicContainer]
+ extends ParsedDecisionLogicContainerReference[T] {
+ val id: String
+ val namespace: String
+ val expressionFailure: ExpressionFailure
+ override def resolve(): T = throw new RuntimeException(expressionFailure.failure)
+
+ override def isEmbedded: Boolean = false
+}
+
+case class ImportedDecision(repository: DmnRepository, namespace: String, id: String, override val importedModelName: Option[String]) extends ParsedDecisionReference {
+ override def resolve(): ParsedDecision = repository.getDecision(namespace, id) match {
+ case Right(found) => found
+ case Left(failure) => ParsedDecisionFailure(id, namespace, ExpressionFailure(failure.message)).resolve()
+ }
+
+ override def isEmbedded: Boolean = false
+
+}
+
+sealed trait ParsedBusinessKnowledgeModel extends ParsedDecisionLogicContainer {
val parameters: Iterable[(String, String)]
- val requiredBkms: Iterable[ParsedBusinessKnowledgeModel]
+ val requiredBkms: Iterable[ParsedBusinessKnowledgeModelReference]
}
+trait ParsedBusinessKnowledgeModelReference extends ParsedDecisionLogicContainerReference[ParsedBusinessKnowledgeModel]
+
+
case class EmbeddedBusinessKnowledgeModel(
- id: String,
- name: String,
- logic: ParsedDecisionLogic,
- parameters: Iterable[(String, String)],
- requiredBkms: Iterable[ParsedBusinessKnowledgeModel])
- extends ParsedBusinessKnowledgeModel
-
-case class ImportedBusinessKnowledgeModel(importer: () => ParsedBusinessKnowledgeModel) extends ParsedBusinessKnowledgeModel {
- private lazy val model = importer()
- override lazy val id: String = model.id
- override lazy val name: String = model.name
- override lazy val logic: ParsedDecisionLogic = model.logic
- override lazy val parameters: Iterable[(String, String)] = model.parameters
- override lazy val requiredBkms: Iterable[ParsedBusinessKnowledgeModel] = model.requiredBkms
+ id: String,
+ name: String,
+ logic: ParsedDecisionLogic,
+ parameters: Iterable[(String, String)],
+ requiredBkms: Iterable[ParsedBusinessKnowledgeModelReference]) extends
+ParsedBusinessKnowledgeModel with ParsedBusinessKnowledgeModelReference {
+
+ override def resolve(): ParsedBusinessKnowledgeModel = this
+
+ override def isEmbedded: Boolean = true
+}
+
+case class ImportedBusinessKnowledgeModel(
+ repository: DmnRepository,
+ namespace: String, id: String,
+ override val importedModelName: Option[String]) extends ParsedBusinessKnowledgeModelReference {
+ override def resolve(): ParsedBusinessKnowledgeModel = repository.getBusinessKnowledgeModel(namespace, id) match {
+ case Right(found) => found
+ case Left(failure) =>
+ ParsedBusinessKnowledgeModelFailure(id, namespace, ExpressionFailure(failure.message)).resolve()
+ }
+
+ override def isEmbedded: Boolean = false
}
+case class ParsedBusinessKnowledgeModelFailure(id: String, namespace: String, expressionFailure: ExpressionFailure)
+ extends ImportedParsedDecisionLogicFailure[ParsedBusinessKnowledgeModel]
+ with ParsedBusinessKnowledgeModelReference
+
+case class ParsedDecisionFailure(id: String, namespace: String, expressionFailure: ExpressionFailure)
+ extends ImportedParsedDecisionLogicFailure[ParsedDecision]
+ with ParsedDecisionReference
+
sealed trait ParsedDecisionLogic
case class ParsedInvocation(bindings: Iterable[(String, ParsedExpression)],
diff --git a/src/main/scala/org/camunda/dmn/parser/StatelessDmnRepository.scala b/src/main/scala/org/camunda/dmn/parser/StatelessDmnRepository.scala
index 5668292e..c366caf0 100644
--- a/src/main/scala/org/camunda/dmn/parser/StatelessDmnRepository.scala
+++ b/src/main/scala/org/camunda/dmn/parser/StatelessDmnRepository.scala
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2022 Camunda Services GmbH (info@camunda.com)
+ *
+ * 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.camunda.dmn.parser
import org.camunda.dmn.DmnEngine.Failure
diff --git a/src/test/scala/org/camunda/dmn/DmnImportTest.scala b/src/test/scala/org/camunda/dmn/DmnImportTest.scala
index cb5860aa..17076f45 100644
--- a/src/test/scala/org/camunda/dmn/DmnImportTest.scala
+++ b/src/test/scala/org/camunda/dmn/DmnImportTest.scala
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2022 Camunda Services GmbH (info@camunda.com)
+ *
+ * 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.camunda.dmn
import org.camunda.dmn.parser.InMemoryDmnRepository
@@ -11,15 +26,15 @@ class DmnImportTest extends AnyFlatSpec with Matchers with DecisionTest{
dmnRepository = new InMemoryDmnRepository()
)
+ private val decisionWithBkmImport = parse("/tck/0086-import/0086-import.dmn")
+ private val decisionWithDecisionImport = parse("/tck/0089-nested-inputdata-imports/0089-nested-inputdata-imports.dmn")
+
// parse required DMNs
parse("/tck/0086-import/Imported_Model.dmn")
parse("/tck/0089-nested-inputdata-imports/Say_hello_1ID1D.dmn")
parse("/tck/0089-nested-inputdata-imports/Model_B.dmn")
parse("/tck/0089-nested-inputdata-imports/Model_B2.dmn")
- private val decisionWithBkmImport = parse("/tck/0086-import/0086-import.dmn")
- private val decisionWithDecisionImport = parse("/tck/0089-nested-inputdata-imports/0089-nested-inputdata-imports.dmn")
-
"A decision with an imported BKM" should "invoke the BKM from the imported DMN" in {
eval(decisionWithBkmImport,
"decision_with_imported_bkm",