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

pl.touk.nussknacker.engine.definition.component.DynamicComponentStaticDefinitionDeterminer.scala Maven / Gradle / Ivy

The newest version!
package pl.touk.nussknacker.engine.definition.component

import com.typesafe.scalalogging.LazyLogging
import pl.touk.nussknacker.engine.ModelData
import pl.touk.nussknacker.engine.api.component.{ComponentId, ParameterConfig}
import pl.touk.nussknacker.engine.api.context.ValidationContext
import pl.touk.nussknacker.engine.api.context.transformation.{
  DynamicComponent,
  JoinDynamicComponent,
  SingleInputDynamicComponent,
  WithStaticParameters
}
import pl.touk.nussknacker.engine.api.definition.{OutputVariableNameDependency, Parameter}
import pl.touk.nussknacker.engine.api.parameter.ParameterName
import pl.touk.nussknacker.engine.api.process.ProcessName
import pl.touk.nussknacker.engine.api.typed.typing.{TypingResult, Unknown}
import pl.touk.nussknacker.engine.api.{JobData, MetaData, NodeId, ProcessVersion}
import pl.touk.nussknacker.engine.compile.nodecompilation.DynamicNodeValidator
import pl.touk.nussknacker.engine.definition.component.DynamicComponentStaticDefinitionDeterminer.staticReturnType
import pl.touk.nussknacker.engine.definition.component.dynamic.DynamicComponentDefinitionWithImplementation
import pl.touk.nussknacker.engine.definition.component.parameter.StandardParameterEnrichment

// This class purpose is to provide initial set of parameters that will be presented after first usage of a component.
// It is necessary to provide them, because:
// - We want to avoid flickering of parameters after first entering into the node
// - Sometimes user want to just use the component without filling parameters with own data - in this case we want to make sure
//   that parameters will be available in the scenario, even with a default values
class DynamicComponentStaticDefinitionDeterminer(
    nodeValidator: DynamicNodeValidator,
    createMetaData: ProcessName => MetaData
) extends LazyLogging {

  private def determineStaticDefinition(
      dynamic: DynamicComponentDefinitionWithImplementation
  ): ComponentStaticDefinition = {
    val parameters = determineInitialParameters(dynamic)
    ComponentStaticDefinition(
      parameters,
      staticReturnType(dynamic.component)
    )
  }

  private def determineInitialParameters(dynamic: DynamicComponentDefinitionWithImplementation): List[Parameter] = {
    def inferParameters(transformer: DynamicComponent[_])(inputContext: transformer.InputContext) = {
      // TODO: We could determine initial parameters when component is firstly used in scenario instead of during loading model data
      //       Thanks to that, instead of passing fake nodeId/metaData and empty additionalFields, we could pass the real once
      val scenarioName              = ProcessName("fakeScenarioName")
      val metaData                  = createMetaData(scenarioName)
      implicit val jobData: JobData = JobData(metaData, ProcessVersion.empty.copy(processName = scenarioName))
      implicit val nodeId: NodeId   = NodeId("fakeNodeId")
      nodeValidator
        .validateNode(
          transformer,
          Nil,
          Nil,
          if (dynamic.component.nodeDependencies.contains(OutputVariableNameDependency)) Some("fakeOutputVariable")
          else None,
          dynamic.parametersConfig
        )(inputContext)
        .map(_.parameters)
        .valueOr { err =>
          logger.warn(
            s"Errors during inferring of initial parameters for component: $transformer: ${err.toList.mkString(", ")}. Will be used empty list of parameters as a fallback"
          )
          // It is better to return empty list than throw an exception. User will have an option to open the node, validate node again
          // and replace those parameters by the correct once
          List.empty
        }
    }

    dynamic.component match {
      case withStatic: WithStaticParameters =>
        StandardParameterEnrichment.enrichParameterDefinitions(withStatic.staticParameters, dynamic.parametersConfig)
      case single: SingleInputDynamicComponent[_] =>
        inferParameters(single)(ValidationContext())
      case join: JoinDynamicComponent[_] =>
        inferParameters(join)(Map.empty)
    }
  }

}

object DynamicComponentStaticDefinitionDeterminer {

  def collectStaticDefinitionsForDynamicComponents(
      modelDataForType: ModelData,
      createMetaData: ProcessName => MetaData,
      extractComponentsDefinitions: Components => List[ComponentDefinitionWithImplementation]
  ): Map[ComponentId, ComponentStaticDefinition] = {
    val nodeValidator = DynamicNodeValidator(modelDataForType)
    val toStaticComponentDefinitionTransformer =
      new DynamicComponentStaticDefinitionDeterminer(nodeValidator, createMetaData)

    // We have to wrap this block with model's class loader because it invokes node compilation under the hood
    modelDataForType.withThisAsContextClassLoader {
      extractComponentsDefinitions(modelDataForType.modelDefinition.components).collect {
        case dynamic: DynamicComponentDefinitionWithImplementation =>
          dynamic.id -> toStaticComponentDefinitionTransformer.determineStaticDefinition(dynamic)
      }.toMap
    }
  }

  def staticReturnType(component: DynamicComponent[_]): Option[TypingResult] = {
    if (component.nodeDependencies.contains(OutputVariableNameDependency)) Some(Unknown) else None
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy