Skip to content

Commit d9dc857

Browse files
committed
refactor: move decision/BKM loading to evaluation
Remove the resolve() function of the references. Instead, load imported models during the evaluation. Handle missing models during the evaluation by returning a failure. Refine and simplify the parsed types.
1 parent 0adecd2 commit d9dc857

File tree

6 files changed

+100
-102
lines changed

6 files changed

+100
-102
lines changed

src/main/scala/org/camunda/dmn/DmnEngine.scala

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -156,19 +156,21 @@ class DmnEngine(configuration: DmnEngine.Configuration =
156156
val parser = new DmnParser(
157157
configuration = configuration,
158158
feelParser = feelEngine.parseExpression(_).toEither.left.map(_.message),
159-
feelUnaryTestsParser = feelEngine.parseUnaryTests(_).toEither.left.map(_.message),
160-
dmnRepository = dmnRepository
159+
feelUnaryTestsParser = feelEngine.parseUnaryTests(_).toEither.left.map(_.message)
161160
)
162161

163-
val decisionEval = new DecisionEvaluator(eval = this.evalExpression,
164-
evalBkm = bkmEval.createFunction)
162+
val decisionEval = new DecisionEvaluator(
163+
eval = this.evalExpression,
164+
evalBkm = bkmEval.createFunction,
165+
repository = dmnRepository
166+
)
165167

166168
val literalExpressionEval = new LiteralExpressionEvaluator(feelEngine)
167169

168170
val decisionTableEval = new DecisionTableEvaluator(
169171
literalExpressionEval.evalExpression)
170172

171-
val bkmEval = new BusinessKnowledgeEvaluator(this.evalExpression, valueMapper)
173+
val bkmEval = new BusinessKnowledgeEvaluator(this.evalExpression, valueMapper, dmnRepository)
172174

173175
val contextEval = new ContextEvaluator(this.evalExpression)
174176

@@ -178,7 +180,9 @@ class DmnEngine(configuration: DmnEngine.Configuration =
178180

179181
val invocationEval = new InvocationEvaluator(
180182
eval = literalExpressionEval.evalExpression,
181-
evalBkm = bkmEval.eval)
183+
evalBkm = bkmEval.eval,
184+
repository = dmnRepository
185+
)
182186

183187
val functionDefinitionEval = new FunctionDefinitionEvaluator(
184188
literalExpressionEval.evalExpression)

src/main/scala/org/camunda/dmn/evaluation/BusinessKnowledgeEvaluator.scala

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,20 @@ package org.camunda.dmn.evaluation
1717

1818
import org.camunda.dmn.DmnEngine._
1919
import org.camunda.dmn.FunctionalHelper._
20-
import org.camunda.dmn.parser.{
21-
ParsedBusinessKnowledgeModel,
22-
ParsedDecisionLogic
23-
}
20+
import org.camunda.dmn.parser.{DmnRepository, EmbeddedBusinessKnowledgeModel, ExpressionFailure, ImportedBusinessKnowledgeModel, ParsedBusinessKnowledgeModel, ParsedBusinessKnowledgeModelFailure, ParsedBusinessKnowledgeModelReference, ParsedDecisionLogic}
2421
import org.camunda.feel.syntaxtree.{Val, ValError, ValFunction}
2522
import org.camunda.feel.valuemapper.ValueMapper
2623

2724
class BusinessKnowledgeEvaluator(
2825
eval: (ParsedDecisionLogic, EvalContext) => Either[Failure, Val],
29-
valueMapper: ValueMapper) {
26+
valueMapper: ValueMapper,
27+
repository: DmnRepository) {
3028

3129
def eval(bkm: ParsedBusinessKnowledgeModel,
3230
context: EvalContext): Either[Failure, Val] = {
3331

34-
evalRequiredKnowledge(bkm.requiredBkms.map(_.resolve()), context)
32+
resolveRequiredBkms(bkm)
33+
.flatMap(evalRequiredKnowledge(_, context))
3534
.flatMap(functions => {
3635

3736
val evalContext =
@@ -43,11 +42,21 @@ class BusinessKnowledgeEvaluator(
4342
})
4443
}
4544

45+
private def resolveRequiredBkms(bkm: ParsedBusinessKnowledgeModel): Either[Failure, Iterable[ParsedBusinessKnowledgeModel]] = {
46+
mapEither[ParsedBusinessKnowledgeModelReference, ParsedBusinessKnowledgeModel](bkm.requiredBkms, {
47+
case ImportedBusinessKnowledgeModel(namespace, id, _) => repository.getBusinessKnowledgeModel(namespace = namespace, bkmId = id)
48+
case ParsedBusinessKnowledgeModelFailure(_, _, failureMessage) => Left(Failure(failureMessage))
49+
case bkm: EmbeddedBusinessKnowledgeModel => Right(bkm)
50+
})
51+
}
52+
4653
def createFunction(
4754
bkm: ParsedBusinessKnowledgeModel,
4855
context: EvalContext): Either[Failure, (String, ValFunction)] = {
4956

50-
evalRequiredKnowledge(bkm.requiredBkms.map(_.resolve()), context).map(functions => {
57+
resolveRequiredBkms(bkm)
58+
.flatMap(evalRequiredKnowledge(_, context))
59+
.map(functions => {
5160

5261
val evalContext = context.copy(variables = context.variables ++ functions,
5362
currentElement = bkm)

src/main/scala/org/camunda/dmn/evaluation/DecisionEvaluator.scala

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,14 @@ package org.camunda.dmn.evaluation
1818
import org.camunda.dmn.DmnEngine._
1919
import org.camunda.dmn.FunctionalHelper._
2020
import org.camunda.feel.syntaxtree.{Val, ValContext, ValFunction}
21-
import org.camunda.dmn.parser.{
22-
ParsedBusinessKnowledgeModel,
23-
ParsedBusinessKnowledgeModelReference,
24-
ParsedDecision,
25-
ParsedDecisionReference,
26-
ParsedDecisionLogic,
27-
ParsedDecisionLogicContainerReference}
21+
import org.camunda.dmn.parser.{DmnRepository, EmbeddedBusinessKnowledgeModel, EmbeddedDecision, ImportedBusinessKnowledgeModel, ImportedDecision, ParsedBusinessKnowledgeModel, ParsedBusinessKnowledgeModelFailure, ParsedBusinessKnowledgeModelReference, ParsedDecision, ParsedDecisionFailure, ParsedDecisionLogic, ParsedDecisionReference}
2822
import org.camunda.feel.context.Context.StaticContext
2923

3024
class DecisionEvaluator(
3125
eval: (ParsedDecisionLogic, EvalContext) => Either[Failure, Val],
3226
evalBkm: (ParsedBusinessKnowledgeModel,
33-
EvalContext) => Either[Failure, (String, ValFunction)]) {
27+
EvalContext) => Either[Failure, (String, ValFunction)],
28+
repository: DmnRepository) {
3429

3530
def eval(decision: ParsedDecision,
3631
context: EvalContext): Either[Failure, Val] = {
@@ -67,34 +62,43 @@ class DecisionEvaluator(
6762
private def evalRequiredDecisions(
6863
requiredDecisions: Iterable[ParsedDecisionReference],
6964
context: EvalContext): Either[Failure, List[(String, Val)]] = {
70-
mapEither(requiredDecisions,
71-
(decisionRef: ParsedDecisionReference) => evalDecision(decisionRef.resolve(), context)
72-
.map(maybeWrapResult(decisionRef, _)))
65+
mapEither[ParsedDecisionReference, (String, Val)](requiredDecisions, {
66+
case ImportedDecision(namespace, decisionId, importName) =>
67+
repository.getDecision(namespace = namespace, decisionId = decisionId)
68+
.flatMap(evalDecision(_, context))
69+
.map { case (name, result) =>
70+
importName -> ValContext(StaticContext(
71+
variables = Map(name -> result),
72+
functions = Map.empty
73+
))
74+
}
75+
76+
case ParsedDecisionFailure(_, _, failureMessage) => Left(Failure(failureMessage))
77+
case decision: EmbeddedDecision => evalDecision(decision, context)
78+
}
79+
)
7380
}
7481

7582
private def evalRequiredKnowledge(
7683
requiredBkms: Iterable[ParsedBusinessKnowledgeModelReference],
7784
context: EvalContext): Either[Failure, List[(String, Val)]] = {
78-
mapEither(requiredBkms,
79-
(bkmRef: ParsedBusinessKnowledgeModelReference) => evalBkm(bkmRef.resolve(), context)
80-
.map(maybeWrapResult(bkmRef, _)))
81-
}
85+
mapEither[ParsedBusinessKnowledgeModelReference, (String, Val)](requiredBkms, {
86+
case ImportedBusinessKnowledgeModel(namespace, id, importName) =>
87+
repository.getBusinessKnowledgeModel(namespace = namespace, bkmId = id)
88+
.flatMap(evalBkm(_, context))
89+
.map { case (name, resultFunction) =>
90+
importName -> ValContext(
91+
StaticContext(
92+
variables = Map.empty,
93+
functions = Map(name -> List(resultFunction))
94+
)
95+
)
96+
}
8297

83-
private def maybeWrapResult(
84-
reference: ParsedDecisionLogicContainerReference[_], result: (String, Val)) =
85-
reference.importedModelName match {
86-
case Some(importName) =>
87-
val ctx = result._2 match {
88-
case func: ValFunction => StaticContext(
89-
variables = Map.empty,
90-
functions = Map(result._1 -> List(func))
91-
)
92-
case _ => StaticContext(
93-
variables = Map(result._1 -> result._2),
94-
functions = Map.empty
95-
)
98+
case ParsedBusinessKnowledgeModelFailure(_, _, failureMessage) => Left(Failure(failureMessage))
99+
case bkm: EmbeddedBusinessKnowledgeModel => evalBkm(bkm, context)
96100
}
97-
importName -> ValContext(ctx)
98-
case _ => result
99-
}
101+
)
102+
}
103+
100104
}

src/main/scala/org/camunda/dmn/evaluation/InvocationEvaluator.scala

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,35 @@ package org.camunda.dmn.evaluation
1717

1818
import org.camunda.dmn.DmnEngine._
1919
import org.camunda.dmn.FunctionalHelper._
20-
import org.camunda.dmn.parser.{
21-
ParsedBusinessKnowledgeModel,
22-
ParsedExpression,
23-
ParsedInvocation
24-
}
20+
import org.camunda.dmn.parser.{DmnRepository, EmbeddedBusinessKnowledgeModel, ImportedBusinessKnowledgeModel, ParsedBusinessKnowledgeModel, ParsedBusinessKnowledgeModelFailure, ParsedBusinessKnowledgeModelReference, ParsedExpression, ParsedInvocation}
2521
import org.camunda.feel.syntaxtree.Val
2622

2723
class InvocationEvaluator(
2824
eval: (ParsedExpression, EvalContext) => Either[Failure, Val],
29-
evalBkm: (ParsedBusinessKnowledgeModel, EvalContext) => Either[Failure, Val]) {
25+
evalBkm: (ParsedBusinessKnowledgeModel, EvalContext) => Either[Failure, Val],
26+
repository: DmnRepository) {
3027

3128
def eval(invocation: ParsedInvocation,
3229
context: EvalContext): Either[Failure, Val] = {
3330

3431
val result = evalParameters(invocation.bindings, context).flatMap { p =>
3532
val ctx = context.copy(variables = context.variables ++ p.toMap)
36-
evalBkm(invocation.invocation, ctx)
33+
34+
resolveBkm(invocation.invocation).flatMap(evalBkm(_, ctx))
3735
}
3836

3937
context.audit(invocation, result)
4038
result
4139
}
4240

41+
private def resolveBkm(bkmRef: ParsedBusinessKnowledgeModelReference): Either[Failure, ParsedBusinessKnowledgeModel] = {
42+
bkmRef match {
43+
case ImportedBusinessKnowledgeModel(namespace, id, _) => repository.getBusinessKnowledgeModel(namespace = namespace, bkmId = id)
44+
case ParsedBusinessKnowledgeModelFailure(_, _, failureMessage) => Left(Failure(failureMessage))
45+
case bkm: EmbeddedBusinessKnowledgeModel => Right(bkm)
46+
}
47+
}
48+
4349
private def evalParameters(
4450
bindings: Iterable[(String, ParsedExpression)],
4551
context: EvalContext): Either[Failure, List[(String, Any)]] = {

src/main/scala/org/camunda/dmn/parser/DmnParser.scala

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,7 @@ class DmnParser(
7272
configuration: Configuration,
7373
feelParser: String => Either[String, feel.syntaxtree.ParsedExpression],
7474
feelUnaryTestsParser: String => Either[String,
75-
feel.syntaxtree.ParsedExpression],
76-
dmnRepository: DmnRepository) {
75+
feel.syntaxtree.ParsedExpression]) {
7776

7877
import DmnParser._
7978

@@ -147,8 +146,8 @@ class DmnParser(
147146

148147
val parsedDmn = ParsedDmn(
149148
model = model,
150-
decisions = ctx.decisions.values.filter(_.isEmbedded).map(_.resolve()),
151-
bkms = ctx.bkms.values.filter(_.isEmbedded).map(_.resolve()),
149+
decisions = ctx.decisions.values.collect{ case decision: ParsedDecision => decision },
150+
bkms = ctx.bkms.values.collect { case bkm: ParsedBusinessKnowledgeModel => bkm },
152151
namespace = definitions.getNamespace)
153152

154153
if (ctx.failures.isEmpty) {
@@ -315,11 +314,11 @@ class DmnParser(
315314
} else {
316315
ctx.importedModels
317316
.find(importedModel => reference.namespace == importedModel.namespace)
318-
.map(importedModel => ImportedDecision(dmnRepository, reference.namespace, reference.id, Some(importedModel.name)))
317+
.map(importedModel => ImportedDecision(reference.namespace, reference.id, importedModel.name))
319318
.getOrElse {
320319
val failure = Failure(s"No import found for namespace '${reference.namespace}'.")
321320
ctx.failures += failure
322-
ParsedDecisionFailure(reference.id, reference.namespace, ExpressionFailure(failure.message))
321+
ParsedDecisionFailure(reference.id, reference.namespace, failure.message)
323322
}
324323
}
325324
}
@@ -336,12 +335,11 @@ class DmnParser(
336335
} else {
337336
ctx.importedModels
338337
.find(importedModel => reference.namespace == importedModel.namespace)
339-
.map(importedModel => ImportedBusinessKnowledgeModel(
340-
dmnRepository, reference.namespace, reference.id, Some(importedModel.name)))
338+
.map(importedModel => ImportedBusinessKnowledgeModel(reference.namespace, reference.id, importedModel.name))
341339
.getOrElse {
342340
val failure = Failure(s"No import found for namespace '${reference.namespace}'.")
343341
ctx.failures += failure
344-
ParsedBusinessKnowledgeModelFailure(reference.id, reference.namespace, ExpressionFailure(failure.message))
342+
ParsedBusinessKnowledgeModelFailure(reference.id, reference.namespace, failure.message)
345343
}
346344
}
347345
}
@@ -554,7 +552,7 @@ class DmnParser(
554552
ctx.bkms
555553
.get(expression)
556554
.map(bkmRef => {
557-
ParsedInvocation(bindings, bkmRef.resolve())
555+
ParsedInvocation(bindings, bkmRef)
558556
})
559557
.getOrElse {
560558
ctx.failures += Failure(s"no BKM found with name '$expression'")

src/main/scala/org/camunda/dmn/parser/ParsedDmn.scala

Lines changed: 18 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,10 @@ trait ParsedDecision extends ParsedDecisionLogicContainer {
5858
val requiredBkms: Iterable[ParsedBusinessKnowledgeModelReference]
5959
}
6060

61-
trait ParsedDecisionLogicContainerReference[T <: ParsedDecisionLogicContainer] {
62-
val importedModelName: Option[String] = None
63-
def resolve(): T
61+
trait ParsedDecisionReference {
6462
def isEmbedded: Boolean
65-
def isImported: Boolean = !isEmbedded
66-
}
6763

68-
trait ParsedDecisionReference extends ParsedDecisionLogicContainerReference[ParsedDecision] {
64+
def isImported: Boolean = !isEmbedded
6965
}
7066

7167
case class EmbeddedDecision(
@@ -77,26 +73,11 @@ case class EmbeddedDecision(
7773
requiredDecisions: Iterable[ParsedDecisionReference],
7874
requiredBkms: Iterable[ParsedBusinessKnowledgeModelReference]
7975
) extends ParsedDecision with ParsedDecisionReference {
80-
override def resolve(): ParsedDecision = this
8176

8277
override def isEmbedded: Boolean = true
8378
}
8479

85-
trait ImportedParsedDecisionLogicFailure[T <: ParsedDecisionLogicContainer]
86-
extends ParsedDecisionLogicContainerReference[T] {
87-
val id: String
88-
val namespace: String
89-
val expressionFailure: ExpressionFailure
90-
override def resolve(): T = throw new RuntimeException(expressionFailure.failure)
91-
92-
override def isEmbedded: Boolean = false
93-
}
94-
95-
case class ImportedDecision(repository: DmnRepository, namespace: String, id: String, override val importedModelName: Option[String]) extends ParsedDecisionReference {
96-
override def resolve(): ParsedDecision = repository.getDecision(namespace, id) match {
97-
case Right(found) => found
98-
case Left(failure) => ParsedDecisionFailure(id, namespace, ExpressionFailure(failure.message)).resolve()
99-
}
80+
case class ImportedDecision(namespace: String, id: String, importedModelName: String) extends ParsedDecisionReference {
10081

10182
override def isEmbedded: Boolean = false
10283

@@ -107,7 +88,11 @@ sealed trait ParsedBusinessKnowledgeModel extends ParsedDecisionLogicContainer {
10788
val requiredBkms: Iterable[ParsedBusinessKnowledgeModelReference]
10889
}
10990

110-
trait ParsedBusinessKnowledgeModelReference extends ParsedDecisionLogicContainerReference[ParsedBusinessKnowledgeModel]
91+
trait ParsedBusinessKnowledgeModelReference {
92+
def isEmbedded: Boolean
93+
94+
def isImported: Boolean = !isEmbedded
95+
}
11196

11297

11398
case class EmbeddedBusinessKnowledgeModel(
@@ -118,36 +103,28 @@ case class EmbeddedBusinessKnowledgeModel(
118103
requiredBkms: Iterable[ParsedBusinessKnowledgeModelReference]) extends
119104
ParsedBusinessKnowledgeModel with ParsedBusinessKnowledgeModelReference {
120105

121-
override def resolve(): ParsedBusinessKnowledgeModel = this
122-
123106
override def isEmbedded: Boolean = true
124107
}
125108

126-
case class ImportedBusinessKnowledgeModel(
127-
repository: DmnRepository,
128-
namespace: String, id: String,
129-
override val importedModelName: Option[String]) extends ParsedBusinessKnowledgeModelReference {
130-
override def resolve(): ParsedBusinessKnowledgeModel = repository.getBusinessKnowledgeModel(namespace, id) match {
131-
case Right(found) => found
132-
case Left(failure) =>
133-
ParsedBusinessKnowledgeModelFailure(id, namespace, ExpressionFailure(failure.message)).resolve()
134-
}
109+
case class ImportedBusinessKnowledgeModel(namespace: String, id: String, importedModelName: String) extends ParsedBusinessKnowledgeModelReference {
135110

136111
override def isEmbedded: Boolean = false
137112
}
138113

139-
case class ParsedBusinessKnowledgeModelFailure(id: String, namespace: String, expressionFailure: ExpressionFailure)
140-
extends ImportedParsedDecisionLogicFailure[ParsedBusinessKnowledgeModel]
141-
with ParsedBusinessKnowledgeModelReference
114+
case class ParsedBusinessKnowledgeModelFailure(id: String, namespace: String, failureMessage: String)
115+
extends ParsedBusinessKnowledgeModelReference {
116+
override def isEmbedded: Boolean = false
117+
}
142118

143-
case class ParsedDecisionFailure(id: String, namespace: String, expressionFailure: ExpressionFailure)
144-
extends ImportedParsedDecisionLogicFailure[ParsedDecision]
145-
with ParsedDecisionReference
119+
case class ParsedDecisionFailure(id: String, namespace: String, failureMessage: String)
120+
extends ParsedDecisionReference {
121+
override def isEmbedded: Boolean = false
122+
}
146123

147124
sealed trait ParsedDecisionLogic
148125

149126
case class ParsedInvocation(bindings: Iterable[(String, ParsedExpression)],
150-
invocation: ParsedBusinessKnowledgeModel)
127+
invocation: ParsedBusinessKnowledgeModelReference)
151128
extends ParsedDecisionLogic
152129

153130
case class ParsedContext(entries: Iterable[(String, ParsedDecisionLogic)],

0 commit comments

Comments
 (0)