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",