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

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

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

import cats.data.NonEmptyList
import com.typesafe.config.Config
import net.ceedubs.ficus.Ficus._
import pl.touk.nussknacker.engine.api.component._
import pl.touk.nussknacker.engine.api.process._
import pl.touk.nussknacker.engine.definition.component.Components.ComponentDefinitionExtractionMode
import pl.touk.nussknacker.engine.definition.component.ComponentsFromProvidersExtractor.componentConfigPath
import pl.touk.nussknacker.engine.modelconfig.ComponentsUiConfig
import pl.touk.nussknacker.engine.util.loader.ScalaServiceLoader

object ComponentsFromProvidersExtractor {

  val componentConfigPath = "components"

  def apply(
      classLoader: ClassLoader,
      shouldIncludeComponentProvider: ComponentProvider => Boolean
  ): ComponentsFromProvidersExtractor = {
    new ComponentsFromProvidersExtractor(
      classLoader,
      shouldIncludeComponentProvider,
      NussknackerVersion.current
    )
  }

}

class ComponentsFromProvidersExtractor(
    classLoader: ClassLoader,
    shouldIncludeComponentProvider: ComponentProvider => Boolean,
    nussknackerVersion: NussknackerVersion
) {

  private lazy val providers: Map[String, List[ComponentProvider]] = {
    ScalaServiceLoader
      .load[ComponentProvider](classLoader)
      .groupBy(_.providerName)
  }

  def extractComponents(
      modelDependencies: ProcessObjectDependencies,
      componentsUiConfig: ComponentsUiConfig,
      determineDesignerWideId: ComponentId => DesignerWideComponentId,
      additionalConfigsFromProvider: Map[DesignerWideComponentId, ComponentAdditionalConfig],
      componentDefinitionExtractionMode: ComponentDefinitionExtractionMode
  ): Components = {
    Components
      .fold(
        componentDefinitionExtractionMode,
        loadCorrectProviders(modelDependencies.config).toList
          .map { case (_, (config, provider)) =>
            extract(
              config,
              provider,
              modelDependencies,
              componentsUiConfig,
              determineDesignerWideId,
              additionalConfigsFromProvider,
              componentDefinitionExtractionMode
            )
          }
      )
  }

  private def loadCorrectProviders(config: Config): Map[String, (ComponentProviderConfig, ComponentProvider)] = {
    val componentsConfig = config.getAs[Map[String, ComponentProviderConfig]](componentConfigPath).getOrElse(Map.empty)
    val manuallyLoadedProvidersWithConfig = loadManuallyLoadedProviders(componentsConfig)
    val autoLoadedProvidersWithConfig     = loadAutoLoadedProviders(componentsConfig, manuallyLoadedProvidersWithConfig)
    manuallyLoadedProvidersWithConfig ++ autoLoadedProvidersWithConfig
  }

  private def loadManuallyLoadedProviders(componentsConfig: Map[String, ComponentProviderConfig]) = {
    componentsConfig.filterNot(_._2.disabled).flatMap { case (name, providerConfig: ComponentProviderConfig) =>
      val providerName = providerConfig.providerType.getOrElse(name)
      val componentProviders = providers.getOrElse(
        providerName,
        throw new IllegalArgumentException(s"Provider $providerName (for component $name) not found")
      )
      val filteredClassloaderProviders = componentProviders.filter(shouldIncludeComponentProvider)
      NonEmptyList.fromList(filteredClassloaderProviders).map { nel =>
        val provider = findSingleCompatible(name, providerName, nel)
        name -> (providerConfig, provider)
      }
    }
  }

  private def loadAutoLoadedProviders(
      componentsConfig: Map[String, ComponentProviderConfig],
      manuallyLoadedProvidersWithConfig: Map[String, (ComponentProviderConfig, ComponentProvider)]
  ) = {
    val manuallyLoadedProviders = manuallyLoadedProvidersWithConfig.values.map(_._2).toSet
    val autoLoadedProvidersWithConfig = providers.values.flatten
      .filter(provider =>
        provider.isAutoLoaded &&
          !manuallyLoadedProviders.contains(provider) &&
          !componentsConfig.get(provider.providerName).exists(_.disabled) &&
          shouldIncludeComponentProvider(provider)
      )
      .map { provider =>
        if (!provider.isCompatible(nussknackerVersion)) {
          throw new IllegalArgumentException(
            s"Auto-loaded component provider ${provider.providerName} is not compatible with $nussknackerVersion, please use correct component provider version or disable it explicitly."
          )
        }
        provider.providerName -> (ComponentProviderConfig(providerType = None, componentPrefix = None), provider)
      }
    autoLoadedProvidersWithConfig
  }

  def loadAdditionalConfig(inputConfig: Config, configWithDefaults: Config): Config = {
    val resolvedConfigs = loadCorrectProviders(configWithDefaults).map { case (name, (config, provider)) =>
      name -> provider.resolveConfigForExecution(config.config)
    }
    resolvedConfigs.foldLeft(inputConfig) { case (acc, (name, conf)) =>
      // We don't load empty configs to avoid changing the behaviour of components loading.
      // (We don't want auto loaded components to be recognized as manually loaded).
      if (conf.isEmpty) {
        acc
      } else {
        acc.withValue(s"$componentConfigPath.$name", conf.root())
      }
    }
  }

  private def findSingleCompatible(
      name: String,
      providerName: String,
      componentProviders: NonEmptyList[ComponentProvider]
  ): ComponentProvider = {
    val (compatible, incompatible) = componentProviders.toList.partition(_.isCompatible(nussknackerVersion))
    compatible match {
      case Nil =>
        incompatible match {
          case Nil => throw new IllegalArgumentException(s"Provider $providerName (for component $name) not found")
          case _ =>
            throw new IllegalArgumentException(
              s"Component provider $name (of type $providerName) is not compatible with $nussknackerVersion, please use correct component provider version or disable it explicitly."
            )
        }
      case x :: Nil => x
      case _ :: _ =>
        throw new IllegalArgumentException(s"Multiple providers for provider name $providerName (for component $name)")
    }
  }

  private def extract(
      config: ComponentProviderConfig,
      provider: ComponentProvider,
      modelDependencies: ProcessObjectDependencies,
      componentsUiConfig: ComponentsUiConfig,
      determineDesignerWideId: ComponentId => DesignerWideComponentId,
      additionalConfigsFromProvider: Map[DesignerWideComponentId, ComponentAdditionalConfig],
      componentDefinitionExtractionMode: ComponentDefinitionExtractionMode
  ): Components = {
    val components = provider.create(config.config, modelDependencies).map { inputComponentDefinition =>
      config.componentPrefix
        .map(prefix => inputComponentDefinition.copy(name = prefix + inputComponentDefinition.name))
        .getOrElse(inputComponentDefinition)
    }

    Components.forList(
      components,
      componentsUiConfig,
      determineDesignerWideId,
      additionalConfigsFromProvider,
      componentDefinitionExtractionMode
    )

  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy