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

com.ing.baker.il.RecipeValidations.scala Maven / Gradle / Ivy

The newest version!
package com.ing.baker.il

import com.ing.baker.il.petrinet.InteractionTransition
import com.ing.baker.petrinet.api._
import com.ing.baker.types
import scala.collection.immutable.Seq

object RecipeValidations {

  def validateInteraction(compiledRecipe: CompiledRecipe)(interactionTransition: InteractionTransition): Seq[String] = {
    val interactionWithNoRequirementsValidation : Seq[String] =
      Some(s"Interaction $interactionTransition does not have any requirements (ingredients or preconditions)! This will result in an infinite execution loop.")
        .filter(_ => compiledRecipe.petriNet.inMarking(interactionTransition).isEmpty).toIndexedSeq

    // check if the process id argument type is correct
    val processIdArgumentTypeValidation : Seq[String] =
        interactionTransition.requiredIngredients.filter(id =>
          id.name.equals(recipeInstanceIdName)
        ).flatMap {
          case IngredientDescriptor(_, types.CharArray) => None
          case IngredientDescriptor(_, incompatibleType) => Some(s"Non supported process id type: ${incompatibleType} on interaction: '${interactionTransition.interactionName}'")
        }

    //Check if MetaData is correct type
    val bakerMetaDataTypeValidation: Seq[String] =
      interactionTransition.requiredIngredients.filter(id =>
        id.name.equals(recipeInstanceMetadataName)
      ).flatMap {
        case IngredientDescriptor(_, types.MapType(types.CharArray)) => None
        case IngredientDescriptor(_, incompatibleType) => Some(s"Non supported MetaData type: ${incompatibleType} on interaction: '${interactionTransition.interactionName}'")
      }

    //Check if BakerEventList is correct type
    val bakerEventListTypeValidation: Seq[String] =
      interactionTransition.requiredIngredients.filter(id =>
        id.name.equals(recipeInstanceEventListName)
      ).flatMap {
        case IngredientDescriptor(_, types.ListType(types.CharArray)) => None
        case IngredientDescriptor(_, incompatibleType) => Some(s"Non supported EventList type: ${incompatibleType} on interaction: '${interactionTransition.interactionName}'")
      }

    // check if the predefined ingredient is of the expected type
    val predefinedIngredientOfExpectedTypeValidation : Iterable[String] =
      interactionTransition.predefinedParameters.flatMap {
        case (name, value) =>
          interactionTransition.requiredIngredients.find(_.name == name) match {
            case None =>
              Some(s"Predefined argument '$name' is not defined on interaction: '${interactionTransition.interactionName}'")
            case Some(ingredientDescriptor) if !value.isInstanceOf(ingredientDescriptor.`type`) =>
              Some(s"Predefined argument '$name' is not of type: ${ingredientDescriptor.`type`} on interaction: '${interactionTransition.interactionName}'")
            case _ =>
              None
          }
      }

    interactionWithNoRequirementsValidation ++
      processIdArgumentTypeValidation ++
      bakerMetaDataTypeValidation ++
      bakerEventListTypeValidation ++
      predefinedIngredientOfExpectedTypeValidation
  }

  def validateInteractions(compiledRecipe: CompiledRecipe): Seq[String] = {
    compiledRecipe.interactionTransitions.toIndexedSeq.flatMap(validateInteraction(compiledRecipe))
  }


  /**
    * Validates that all required ingredients for interactions are provided for and of the correct type.
    */
  def validateInteractionIngredients(compiledRecipe: CompiledRecipe): Seq[String] = {
    compiledRecipe.interactionTransitions.toIndexedSeq.flatMap { t =>
      t.nonProvidedIngredients.flatMap {
        case (IngredientDescriptor(name, expectedType)) =>
          compiledRecipe.allIngredients.find(_.name == name) match {
            case None =>
              Some(
                s"Ingredient '$name' for interaction '${t.interactionName}' is not provided by any event or interaction")
            case Some(IngredientDescriptor(name, ingredientType)) if !expectedType.isAssignableFrom(ingredientType) =>
              Some(s"Interaction '${t.interactionName}' expects ingredient '$name:$expectedType', however incompatible type: '$ingredientType' was provided")
            case _ =>
              None
          }
      }
    }
  }

  /**
    * Validates that provided ingredients do not contain reserved names for Baker
    */
  def validateSpecialIngredientsNotProvided(compiledRecipe: CompiledRecipe): Seq[String] = {
    compiledRecipe.allIngredients.filter(i =>
      i.name == recipeInstanceIdName || i.name == recipeInstanceMetadataName || i.name == recipeInstanceEventListName
    ).map(i => s"Ingredient '${i.name}' is provided and this is a reserved name for internal use in Baker")
  }.toSeq

  def validateNoCycles(compiledRecipe: CompiledRecipe): Seq[String] = {
    val cycle: Option[compiledRecipe.petriNet.innerGraph.Cycle] = compiledRecipe.petriNet.innerGraph.findCycle
    cycle.map(c => s"The petrinet topology contains a cycle: $c").toList
  }

  def validateAllInteractionsExecutable(compiledRecipe: CompiledRecipe): Seq[String] = {
    val rootNode = PetriNetAnalysis.calculateCoverabilityTree(compiledRecipe.petriNet, compiledRecipe.initialMarking.multiplicities)

    compiledRecipe.interactionTransitions filterNot { interaction =>
      rootNode.isCoverable(compiledRecipe.petriNet.inMarking(interaction))
    } map (interaction => s"${interaction.interactionName} is not executable") toIndexedSeq

  }

  /**
    * Validates the compiled recipe.
    *
    * The validations are:
    *   1. Validate that an implementation is provided for the interaction
    *   2. Validate that the return type the interaction matches the expectations from the annotation
    *   3. Check if all the required events are provided
    *   4. Check if all ingredients are provided
    *   5. Check if the provided ingredients are of the same type as the expected ingredients
    */
  def postCompileValidations(compiledRecipe: CompiledRecipe,
                             validationSettings: ValidationSettings): CompiledRecipe = {

    val postCompileValidationErrors : Seq[String] = Seq(
      validateInteractionIngredients(compiledRecipe),
      validateSpecialIngredientsNotProvided(compiledRecipe),
      validateInteractions(compiledRecipe),
      if (!validationSettings.allowCycles) validateNoCycles(compiledRecipe) else Seq(),
      if (!validationSettings.allowDisconnectedness && !compiledRecipe.petriNet.innerGraph.isConnected) Seq("The petrinet topology is not completely connected") else Seq(),
      if (!validationSettings.allowNonExecutableInteractions) validateAllInteractionsExecutable(compiledRecipe) else Seq(),
    ).flatten

    compiledRecipe.copy(
      validationErrors = compiledRecipe.validationErrors ++ postCompileValidationErrors)
  }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy