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/DmnEngine.scala b/src/main/scala/org/camunda/dmn/DmnEngine.scala index 0d49af3d..e9c47226 100644 --- a/src/main/scala/org/camunda/dmn/DmnEngine.scala +++ b/src/main/scala/org/camunda/dmn/DmnEngine.scala @@ -156,19 +156,20 @@ class DmnEngine(configuration: DmnEngine.Configuration = val parser = new DmnParser( configuration = configuration, feelParser = feelEngine.parseExpression(_).left.map(_.message), - feelUnaryTestsParser = feelEngine.parseUnaryTests(_).left.map(_.message), - dmnRepository = dmnRepository - ) + feelUnaryTestsParser = feelEngine.parseUnaryTests(_).left.map(_.message)) - val decisionEval = new DecisionEvaluator(eval = this.evalExpression, - evalBkm = bkmEval.createFunction) + val decisionEval = new DecisionEvaluator( + eval = this.evalExpression, + evalBkm = bkmEval.createFunction, + repository = dmnRepository + ) val literalExpressionEval = new LiteralExpressionEvaluator(feelEngine) val decisionTableEval = new DecisionTableEvaluator( literalExpressionEval.evalExpression) - val bkmEval = new BusinessKnowledgeEvaluator(this.evalExpression, valueMapper) + val bkmEval = new BusinessKnowledgeEvaluator(this.evalExpression, valueMapper, dmnRepository) val contextEval = new ContextEvaluator(this.evalExpression) @@ -178,7 +179,9 @@ class DmnEngine(configuration: DmnEngine.Configuration = val invocationEval = new InvocationEvaluator( eval = literalExpressionEval.evalExpression, - evalBkm = bkmEval.eval) + evalBkm = bkmEval.eval, + repository = dmnRepository + ) val functionDefinitionEval = new FunctionDefinitionEvaluator( literalExpressionEval.evalExpression) diff --git a/src/main/scala/org/camunda/dmn/evaluation/BusinessKnowledgeEvaluator.scala b/src/main/scala/org/camunda/dmn/evaluation/BusinessKnowledgeEvaluator.scala index 97514ede..80353ace 100644 --- a/src/main/scala/org/camunda/dmn/evaluation/BusinessKnowledgeEvaluator.scala +++ b/src/main/scala/org/camunda/dmn/evaluation/BusinessKnowledgeEvaluator.scala @@ -17,21 +17,20 @@ package org.camunda.dmn.evaluation import org.camunda.dmn.DmnEngine._ import org.camunda.dmn.FunctionalHelper._ -import org.camunda.dmn.parser.{ - ParsedBusinessKnowledgeModel, - ParsedDecisionLogic -} +import org.camunda.dmn.parser.{DmnRepository, EmbeddedBusinessKnowledgeModel, ExpressionFailure, ImportedBusinessKnowledgeModel, ParsedBusinessKnowledgeModel, ParsedBusinessKnowledgeModelFailure, ParsedBusinessKnowledgeModelReference, ParsedDecisionLogic} import org.camunda.feel.syntaxtree.{Val, ValError, ValFunction} import org.camunda.feel.valuemapper.ValueMapper class BusinessKnowledgeEvaluator( eval: (ParsedDecisionLogic, EvalContext) => Either[Failure, Val], - valueMapper: ValueMapper) { + valueMapper: ValueMapper, + repository: DmnRepository) { def eval(bkm: ParsedBusinessKnowledgeModel, context: EvalContext): Either[Failure, Val] = { - evalRequiredKnowledge(bkm.requiredBkms, context) + resolveRequiredBkms(bkm) + .flatMap(evalRequiredKnowledge(_, context)) .flatMap(functions => { val evalContext = @@ -43,11 +42,21 @@ class BusinessKnowledgeEvaluator( }) } + private def resolveRequiredBkms(bkm: ParsedBusinessKnowledgeModel): Either[Failure, Iterable[ParsedBusinessKnowledgeModel]] = { + mapEither[ParsedBusinessKnowledgeModelReference, ParsedBusinessKnowledgeModel](bkm.requiredBkms, { + case ImportedBusinessKnowledgeModel(namespace, id, _) => repository.getBusinessKnowledgeModel(namespace = namespace, bkmId = id) + case ParsedBusinessKnowledgeModelFailure(_, _, failureMessage) => Left(Failure(failureMessage)) + case bkm: EmbeddedBusinessKnowledgeModel => Right(bkm) + }) + } + def createFunction( bkm: ParsedBusinessKnowledgeModel, context: EvalContext): Either[Failure, (String, ValFunction)] = { - evalRequiredKnowledge(bkm.requiredBkms, context).map(functions => { + resolveRequiredBkms(bkm) + .flatMap(evalRequiredKnowledge(_, 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..b47a3870 100644 --- a/src/main/scala/org/camunda/dmn/evaluation/DecisionEvaluator.scala +++ b/src/main/scala/org/camunda/dmn/evaluation/DecisionEvaluator.scala @@ -18,13 +18,14 @@ 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.{DmnRepository, EmbeddedBusinessKnowledgeModel, EmbeddedDecision, ImportedBusinessKnowledgeModel, ImportedDecision, ParsedBusinessKnowledgeModel, ParsedBusinessKnowledgeModelFailure, ParsedBusinessKnowledgeModelReference, ParsedDecision, ParsedDecisionFailure, ParsedDecisionLogic, ParsedDecisionReference} import org.camunda.feel.context.Context.StaticContext class DecisionEvaluator( eval: (ParsedDecisionLogic, EvalContext) => Either[Failure, Val], evalBkm: (ParsedBusinessKnowledgeModel, - EvalContext) => Either[Failure, (String, ValFunction)]) { + EvalContext) => Either[Failure, (String, ValFunction)], + repository: DmnRepository) { def eval(decision: ParsedDecision, context: EvalContext): Either[Failure, Val] = { @@ -42,38 +43,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 +60,45 @@ class DecisionEvaluator( } private def evalRequiredDecisions( - requiredDecisions: Iterable[ParsedDecision], - context: EvalContext): Either[Failure, List[(String, Val)]] = { - mapEither(requiredDecisions, - (d: ParsedDecision) => evalDecision(d, context)) + requiredDecisions: Iterable[ParsedDecisionReference], + context: EvalContext): Either[Failure, List[(String, Val)]] = { + mapEither[ParsedDecisionReference, (String, Val)](requiredDecisions, { + case ImportedDecision(namespace, decisionId, importName) => + repository.getDecision(namespace = namespace, decisionId = decisionId) + .flatMap(evalDecision(_, context)) + .map { case (name, result) => + importName -> ValContext(StaticContext( + variables = Map(name -> result), + functions = Map.empty + )) + } + + case ParsedDecisionFailure(_, _, failureMessage) => Left(Failure(failureMessage)) + case decision: EmbeddedDecision => evalDecision(decision, context) + } + ) } private def evalRequiredKnowledge( - requiredBkms: Iterable[ParsedBusinessKnowledgeModel], - context: EvalContext): Either[Failure, List[(String, ValFunction)]] = { - mapEither(requiredBkms, - (bkm: ParsedBusinessKnowledgeModel) => evalBkm(bkm, context)) + requiredBkms: Iterable[ParsedBusinessKnowledgeModelReference], + context: EvalContext): Either[Failure, List[(String, Val)]] = { + mapEither[ParsedBusinessKnowledgeModelReference, (String, Val)](requiredBkms, { + case ImportedBusinessKnowledgeModel(namespace, id, importName) => + repository.getBusinessKnowledgeModel(namespace = namespace, bkmId = id) + .flatMap(evalBkm(_, context)) + .map { case (name, resultFunction) => + importName -> ValContext( + StaticContext( + variables = Map.empty, + functions = Map(name -> List(resultFunction)) + ) + ) + } + + case ParsedBusinessKnowledgeModelFailure(_, _, failureMessage) => Left(Failure(failureMessage)) + case bkm: EmbeddedBusinessKnowledgeModel => evalBkm(bkm, context) + } + ) } } diff --git a/src/main/scala/org/camunda/dmn/evaluation/InvocationEvaluator.scala b/src/main/scala/org/camunda/dmn/evaluation/InvocationEvaluator.scala index c8f1a298..959107fd 100644 --- a/src/main/scala/org/camunda/dmn/evaluation/InvocationEvaluator.scala +++ b/src/main/scala/org/camunda/dmn/evaluation/InvocationEvaluator.scala @@ -17,29 +17,35 @@ package org.camunda.dmn.evaluation import org.camunda.dmn.DmnEngine._ import org.camunda.dmn.FunctionalHelper._ -import org.camunda.dmn.parser.{ - ParsedBusinessKnowledgeModel, - ParsedExpression, - ParsedInvocation -} +import org.camunda.dmn.parser.{DmnRepository, EmbeddedBusinessKnowledgeModel, ImportedBusinessKnowledgeModel, ParsedBusinessKnowledgeModel, ParsedBusinessKnowledgeModelFailure, ParsedBusinessKnowledgeModelReference, ParsedExpression, ParsedInvocation} import org.camunda.feel.syntaxtree.Val class InvocationEvaluator( eval: (ParsedExpression, EvalContext) => Either[Failure, Val], - evalBkm: (ParsedBusinessKnowledgeModel, EvalContext) => Either[Failure, Val]) { + evalBkm: (ParsedBusinessKnowledgeModel, EvalContext) => Either[Failure, Val], + repository: DmnRepository) { def eval(invocation: ParsedInvocation, context: EvalContext): Either[Failure, Val] = { val result = evalParameters(invocation.bindings, context).flatMap { p => val ctx = context.copy(variables = context.variables ++ p.toMap) - evalBkm(invocation.invocation, ctx) + + resolveBkm(invocation.invocation).flatMap(evalBkm(_, ctx)) } context.audit(invocation, result) result } + private def resolveBkm(bkmRef: ParsedBusinessKnowledgeModelReference): Either[Failure, ParsedBusinessKnowledgeModel] = { + bkmRef match { + case ImportedBusinessKnowledgeModel(namespace, id, _) => repository.getBusinessKnowledgeModel(namespace = namespace, bkmId = id) + case ParsedBusinessKnowledgeModelFailure(_, _, failureMessage) => Left(Failure(failureMessage)) + case bkm: EmbeddedBusinessKnowledgeModel => Right(bkm) + } + } + private def evalParameters( bindings: Iterable[(String, ParsedExpression)], context: EvalContext): Either[Failure, List[(String, Any)]] = { diff --git a/src/main/scala/org/camunda/dmn/parser/DmnParser.scala b/src/main/scala/org/camunda/dmn/parser/DmnParser.scala index b5fa6ac3..3172f131 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,10 @@ 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]) { import DmnParser._ @@ -61,6 +78,7 @@ class DmnParser( case class ModelReference(namespace: String, id: String) { def isEmbedded: Boolean = namespace.isEmpty + def isImported: Boolean = !isEmbedded } @@ -71,8 +89,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 +120,7 @@ class DmnParser( } private def parseModel( - model: DmnModelInstance): Either[Iterable[Failure], ParsedDmn] = { + model: DmnModelInstance): Either[Iterable[Failure], ParsedDmn] = { val ctx = ParsingContext(model) @@ -126,8 +144,8 @@ class DmnParser( val parsedDmn = ParsedDmn( model = model, - decisions = ctx.decisions.values, - bkms = ctx.bkms.values, + decisions = ctx.decisions.values.collect{ case decision: ParsedDecision => decision }, + bkms = ctx.bkms.values.collect { case bkm: ParsedBusinessKnowledgeModel => bkm }, namespace = definitions.getNamespace) if (ctx.failures.isEmpty) { @@ -227,7 +245,7 @@ class DmnParser( private def parseDecision(decision: Decision)( implicit - ctx: ParsingContext): ParsedDecision = { + ctx: ParsingContext): ParsedDecisionReference = { val requiredDecisions = decision.getInformationRequirements.asScala .flatMap(requirement => @@ -287,92 +305,46 @@ 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(reference.namespace, reference.id, 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, 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(reference.namespace, reference.id, 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, 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 +532,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 +549,8 @@ class DmnParser( ctx.bkms .get(expression) - .map(bkm => { - ParsedInvocation(bindings, bkm) + .map(bkmRef => { + ParsedInvocation(bindings, bkmRef) }) .getOrElse { ctx.failures += Failure(s"no BKM found with name '$expression'") @@ -635,8 +605,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 +652,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 +674,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..6650cbeb 100644 --- a/src/main/scala/org/camunda/dmn/parser/ParsedDmn.scala +++ b/src/main/scala/org/camunda/dmn/parser/ParsedDmn.scala @@ -54,56 +54,77 @@ 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 ParsedDecisionReference { + def isEmbedded: Boolean + + def isImported: Boolean = !isEmbedded +} + +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 isEmbedded: Boolean = true } -trait ParsedBusinessKnowledgeModel extends ParsedDecisionLogicContainer { +case class ImportedDecision(namespace: String, id: String, importedModelName: String) extends ParsedDecisionReference { + + 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 { + def isEmbedded: Boolean + + def isImported: Boolean = !isEmbedded } + 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 isEmbedded: Boolean = true +} + +case class ImportedBusinessKnowledgeModel(namespace: String, id: String, importedModelName: String) extends ParsedBusinessKnowledgeModelReference { + + override def isEmbedded: Boolean = false +} + +case class ParsedBusinessKnowledgeModelFailure(id: String, namespace: String, failureMessage: String) + extends ParsedBusinessKnowledgeModelReference { + override def isEmbedded: Boolean = false +} + +case class ParsedDecisionFailure(id: String, namespace: String, failureMessage: String) + extends ParsedDecisionReference { + override def isEmbedded: Boolean = false } sealed trait ParsedDecisionLogic case class ParsedInvocation(bindings: Iterable[(String, ParsedExpression)], - invocation: ParsedBusinessKnowledgeModel) + invocation: ParsedBusinessKnowledgeModelReference) extends ParsedDecisionLogic case class ParsedContext(entries: Iterable[(String, ParsedDecisionLogic)], 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",