All Downloads are FREE. Search and download functionalities are using the official Maven repository.

camundala.worker.WorkerExecutor.scala Maven / Gradle / Ivy

The newest version!
package camundala
package worker

import camundala.domain.*
import camundala.worker.CamundalaWorkerError.*
import camundala.bpmn.WithConfig
import io.circe.syntax.*

case class WorkerExecutor[
    In <: Product: InOutCodec,
    Out <: Product: InOutCodec,
    T <: Worker[In, Out, ?]
](
    worker: T
)(using context: EngineRunContext):

  def execute(
      processVariables: Seq[Either[BadVariableError, (String, Option[Json])]]
  ) =
    for
      validatedInput <- InputValidator.validate(processVariables)
      initializedOutput <- Initializer.initVariables(validatedInput)
      mockedOutput <- OutMocker.mockedOutput(validatedInput)
      // only run the work if it is not mocked
      output <- if mockedOutput.isEmpty then WorkRunner.run(validatedInput) else Right(mockedOutput.get)
      allOutputs = camundaOutputs(validatedInput, initializedOutput, output)
      filteredOut = filteredOutput(allOutputs)
      // make MockedOutput as error if mocked
      _ <- if mockedOutput.isDefined then Left(MockedOutput(filteredOut)) else Right(())
    yield filteredOut

  object InputValidator:
    lazy val prototype = worker.in
    lazy val validationHandler = worker.validationHandler

    def validate(
        inputParamsAsJson: Seq[Either[Any, (String, Option[Json])]]
    ): Either[ValidatorError, In] =
      val jsonResult: Either[ValidatorError, Seq[(String, Option[Json])]] =
        inputParamsAsJson
          .partition(_.isRight) match
          case (successes, failures) if failures.isEmpty =>
            Right(
              successes.collect { case Right(value) => value }
            )
          case (_, failures) =>
            Left(
              ValidatorError(
                failures
                  .collect { case Left(value) => value }
                  .mkString("Validator Error(s):\n - ", " - ", "\n")
              )
            )
      val json: Either[ValidatorError, JsonObject] = jsonResult
        .map(_.foldLeft(JsonObject()) { case (jsonObj, jsonKey -> jsonValue) =>
          if jsonValue.isDefined
          then jsonObj.add(jsonKey, jsonValue.get)
          else jsonObj
        })
      def toIn(posJsonObj: Either[ValidatorError, JsonObject]): Either[ValidatorError, In] =
        posJsonObj
          .flatMap(jsonObj =>
            decodeTo[In](jsonObj.asJson.deepDropNullValues.toString).left
              .map(ex => ValidatorError(errorMsg = ex.errorMsg))
              .flatMap(in => validationHandler.map(h => h.validate(in)).getOrElse(Right(in)))
          )
      val in = toIn(json)
      val result = in.flatMap:
        case i: WithConfig[?] =>
          val newIn =
            for
              jsonObj: JsonObject <- json
              inputVariables = jsonObj.toMap
              configJson: JsonObject =
                inputVariables.get("inConfig").getOrElse(i.defaultConfigAsJson).asObject.get
              newJsonConfig = worker.inConfigVariableNames
                .foldLeft(configJson):
                  case (configJson, n) =>
                    if jsonObj.contains(n)
                    then configJson.add(n, jsonObj(n).get)
                    else configJson
            yield jsonObj.add("inConfig", newJsonConfig.asJson)
          toIn(newIn)
        case x =>
          in
      result
    end validate

  end InputValidator

  object Initializer:
    private val defaultVariables = Map(
      "serviceName" -> "NOT-USED" // serviceName is not needed anymore
    )

    def initVariables(
        validatedInput: In
    ): Either[InitProcessError, Map[String, Any]] =
      worker.initProcessHandler
        .map { vi =>
          vi.init(validatedInput).map(_ ++ defaultVariables)
        }
        .getOrElse(Right(defaultVariables))
  end Initializer

  object OutMocker:

    def mockedOutput(in: In): Either[MockerError, Option[Out]] =
      (
        context.generalVariables.isMockedWorker(worker.topic),
        context.generalVariables.outputMock,
        context.generalVariables.outputServiceMock
      ) match
        // if the outputMock is set than we mock
        case (_, Some(outputMock), _) =>
          decodeMock(outputMock)
        // if your worker is mocked we use the default mock
        case (true, None, None) =>
          worker.defaultMock(in).map(Some(_))
        // otherwise it is not mocked or it is a service mock which is handled in service Worker during running
        case (_, None, _) =>
          Right(None)
    end mockedOutput

    private def decodeMock(
        json: Json
    ) =
      json.as[Out]
        .map:
          Some(_)
        .left.map: error =>
          MockerError(errorMsg = s"$error:\n- $json")
    end decodeMock

  end OutMocker

  object WorkRunner:
    def run(inputObject: In): Either[RunWorkError, Out | NoOutput] =
      worker.runWorkHandler
        .map(_.runWork(inputObject))
        .getOrElse(Right(NoOutput()))
  end WorkRunner

  private def camundaOutputs(
      initializedInput: In,
      internalVariables: Map[String, Any],
      output: Out | NoOutput
  ): Map[String, Any] =
    context.toEngineObject(initializedInput) ++ internalVariables ++
      (output match
        case o: NoOutput =>
          context.toEngineObject(o)
        case _ =>
          context.toEngineObject(output.asInstanceOf[Out])
      )
  private def filteredOutput(
      allOutputs: Map[String, Any]
  ): Map[String, Any] =
    val filter = context.generalVariables.outputVariables
    if filter.isEmpty then
      allOutputs
    else
      allOutputs
        .filter { case k -> _ => filter.contains(k) }
    end if
  end filteredOutput

end WorkerExecutor




© 2015 - 2024 Weber Informatics LLC | Privacy Policy