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

pl.touk.nussknacker.ui.server.AkkaHttpBasedRouteProvider.scala Maven / Gradle / Ivy

There is a newer version: 1.17.0
Show newest version
package pl.touk.nussknacker.ui.server

import akka.actor.ActorSystem
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.{Directives, Route}
import akka.stream.Materializer
import cats.effect.{IO, Resource}
import com.typesafe.config.Config
import com.typesafe.scalalogging.LazyLogging
import io.dropwizard.metrics5.MetricRegistry
import net.ceedubs.ficus.Ficus._
import net.ceedubs.ficus.readers.ArbitraryTypeReader.arbitraryTypeValueReader
import pl.touk.nussknacker.engine.api.component.{
  AdditionalUIConfigProvider,
  AdditionalUIConfigProviderFactory,
  DesignerWideComponentId,
  EmptyAdditionalUIConfigProviderFactory
}
import pl.touk.nussknacker.engine.api.deployment.ProcessingTypeDeployedScenariosProvider
import pl.touk.nussknacker.engine.api.process.ProcessingType
import pl.touk.nussknacker.engine.compile.ProcessValidator
import pl.touk.nussknacker.engine.definition.test.ModelDataTestInfoProvider
import pl.touk.nussknacker.engine.dict.ProcessDictSubstitutor
import pl.touk.nussknacker.engine.util.loader.ScalaServiceLoader
import pl.touk.nussknacker.engine.util.multiplicity.{Empty, Many, Multiplicity, One}
import pl.touk.nussknacker.engine.{ConfigWithUnresolvedVersion, DeploymentManagerDependencies, ModelDependencies}
import pl.touk.nussknacker.processCounts.influxdb.InfluxCountsReporterCreator
import pl.touk.nussknacker.processCounts.{CountsReporter, CountsReporterCreator}
import pl.touk.nussknacker.ui.api._
import pl.touk.nussknacker.ui.config.scenariotoolbar.CategoriesScenarioToolbarsConfigParser
import pl.touk.nussknacker.ui.config.{
  AnalyticsConfig,
  AttachmentsConfig,
  ComponentLinksConfigExtractor,
  FeatureTogglesConfig,
  UsageStatisticsReportsConfig
}
import pl.touk.nussknacker.ui.db.DbRef
import pl.touk.nussknacker.ui.db.timeseries.FEStatisticsRepository
import pl.touk.nussknacker.ui.definition.component.{ComponentServiceProcessingTypeData, DefaultComponentService}
import pl.touk.nussknacker.ui.definition.{
  AlignedComponentsDefinitionProvider,
  DefinitionsService,
  ScenarioPropertiesConfigFinalizer
}
import pl.touk.nussknacker.ui.factory.ProcessingTypeDataStateFactory
import pl.touk.nussknacker.ui.initialization.Initialization
import pl.touk.nussknacker.ui.initialization.Initialization.nussknackerUser
import pl.touk.nussknacker.ui.listener.ProcessChangeListenerLoader
import pl.touk.nussknacker.ui.listener.services.NussknackerServices
import pl.touk.nussknacker.ui.metrics.RepositoryGauges
import pl.touk.nussknacker.ui.migrations.{MigrationApiAdapterService, MigrationService}
import pl.touk.nussknacker.ui.notifications.{NotificationConfig, NotificationServiceImpl}
import pl.touk.nussknacker.ui.process._
import pl.touk.nussknacker.ui.process.deployment.{
  ActionService,
  DefaultProcessingTypeActionService,
  DefaultProcessingTypeDeployedScenariosProvider,
  DeploymentManagerDispatcher,
  DeploymentService => LegacyDeploymentService,
  ScenarioResolver,
  ScenarioTestExecutorServiceImpl
}
import pl.touk.nussknacker.ui.process.fragment.{DefaultFragmentRepository, FragmentResolver}
import pl.touk.nussknacker.ui.process.migrate.{HttpRemoteEnvironment, ProcessModelMigrator, TestModelMigrations}
import pl.touk.nussknacker.ui.process.newactivity.ActivityService
import pl.touk.nussknacker.ui.process.newdeployment.synchronize.{
  DeploymentsStatusesSynchronizationConfig,
  DeploymentsStatusesSynchronizationScheduler,
  DeploymentsStatusesSynchronizer
}
import pl.touk.nussknacker.ui.process.newdeployment.{DeploymentRepository, DeploymentService}
import pl.touk.nussknacker.ui.process.processingtype.{ProcessingTypeData, ProcessingTypeDataReload}
import pl.touk.nussknacker.ui.process.repository._
import pl.touk.nussknacker.ui.process.test.{PreliminaryScenarioTestDataSerDe, ScenarioTestService}
import pl.touk.nussknacker.ui.process.version.{ScenarioGraphVersionRepository, ScenarioGraphVersionService}
import pl.touk.nussknacker.ui.processreport.ProcessCounter
import pl.touk.nussknacker.ui.security.api.CreationError.ImpersonationNotAllowed
import pl.touk.nussknacker.ui.security.api.SecurityError.ImpersonationMissingPermissionError
import pl.touk.nussknacker.ui.security.api.{AuthManager, AuthenticationResources, LoggedUser, NussknackerInternalUser}
import pl.touk.nussknacker.ui.services.{ManagementApiHttpService, NuDesignerExposedApiHttpService}
import pl.touk.nussknacker.ui.statistics.repository.FingerprintRepositoryImpl
import pl.touk.nussknacker.ui.statistics.{FingerprintService, StatisticUrlConfig, UsageStatisticsReportsSettingsService}
import pl.touk.nussknacker.ui.suggester.ExpressionSuggester
import pl.touk.nussknacker.ui.uiresolving.UIProcessResolver
import pl.touk.nussknacker.ui.util.{CorsSupport, OptionsMethodSupport, SecurityHeadersSupport, WithDirectives}
import pl.touk.nussknacker.ui.validation.{NodeValidator, ParametersValidator, UIProcessValidator}
import sttp.client3.SttpBackend
import sttp.client3.asynchttpclient.future.AsyncHttpClientFutureBackend

import java.time.Clock
import java.util.concurrent.atomic.AtomicReference
import java.util.function.Supplier
import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try
import scala.util.control.NonFatal

class AkkaHttpBasedRouteProvider(
    dbRef: DbRef,
    metricsRegistry: MetricRegistry,
    processingTypeDataStateFactory: ProcessingTypeDataStateFactory,
    feStatisticsRepository: FEStatisticsRepository[Future],
    designerClock: Clock
)(implicit system: ActorSystem, materializer: Materializer)
    extends RouteProvider[Route]
    with Directives
    with LazyLogging {

  override def createRoute(config: ConfigWithUnresolvedVersion): Resource[IO, Route] = {
    import system.dispatcher
    for {
      sttpBackend <- createSttpBackend()
      resolvedConfig       = config.resolved
      environment          = resolvedConfig.getString("environment")
      featureTogglesConfig = FeatureTogglesConfig.create(resolvedConfig)
      _                    = logger.info(s"Designer config loaded: \nfeatureTogglesConfig: $featureTogglesConfig")
      countsReporter <- createCountsReporter(featureTogglesConfig, environment, sttpBackend)
      actionServiceSupplier      = new DelayedInitActionServiceSupplier
      additionalUIConfigProvider = createAdditionalUIConfigProvider(resolvedConfig, sttpBackend)
      processingTypeDataProvider <- prepareProcessingTypeDataReload(
        config,
        processingTypeDataStateFactory,
        additionalUIConfigProvider,
        actionServiceSupplier,
        DefaultProcessingTypeDeployedScenariosProvider(dbRef, _),
        sttpBackend,
      )
      deploymentRepository = new DeploymentRepository(dbRef, Clock.systemDefaultZone())
      dbioRunner           = DBIOActionRunner(dbRef)
      deploymentsStatusesSynchronizer = new DeploymentsStatusesSynchronizer(
        deploymentRepository,
        processingTypeDataProvider.mapValues(
          _.deploymentData.validDeploymentManagerOrStub.deploymentSynchronisationSupport
        ),
        dbioRunner
      )
      _ <- Resource.fromAutoCloseable(
        IO {
          val scheduler = new DeploymentsStatusesSynchronizationScheduler(
            system,
            deploymentsStatusesSynchronizer,
            DeploymentsStatusesSynchronizationConfig.parse(resolvedConfig)
          )
          scheduler.start()
          scheduler
        }
      )
    } yield {
      val analyticsConfig = AnalyticsConfig(resolvedConfig)

      val migrations     = processingTypeDataProvider.mapValues(_.designerModelData.modelData.migrations)
      val modelBuildInfo = processingTypeDataProvider.mapValues(_.designerModelData.modelData.buildInfo)

      implicit val implicitDbioRunner: DBIOActionRunner = dbioRunner
      val commentRepository                             = new CommentRepository(dbRef)
      val actionRepository  = new DbProcessActionRepository(dbRef, commentRepository, modelBuildInfo)
      val processRepository = DBFetchingProcessRepository.create(dbRef, actionRepository)
      // TODO: get rid of Future based repositories - it is easier to use everywhere one implementation - DBIOAction based which allows transactions handling
      val futureProcessRepository = DBFetchingProcessRepository.createFutureRepository(dbRef, actionRepository)
      val writeProcessRepository  = ProcessRepository.create(dbRef, commentRepository, migrations)

      val fragmentRepository = new DefaultFragmentRepository(futureProcessRepository)
      val fragmentResolver   = new FragmentResolver(fragmentRepository)

      val scenarioTestServiceDeps = processingTypeDataProvider.mapValues { processingTypeData =>
        val validator = new UIProcessValidator(
          processingTypeData.name,
          ProcessValidator.default(processingTypeData.designerModelData.modelData),
          processingTypeData.deploymentData.scenarioPropertiesConfig,
          new ScenarioPropertiesConfigFinalizer(additionalUIConfigProvider, processingTypeData.name),
          processingTypeData.deploymentData.additionalValidators,
          fragmentResolver
        )
        val substitutor =
          ProcessDictSubstitutor(processingTypeData.designerModelData.modelData.designerDictServices.dictRegistry)
        val resolver         = new UIProcessResolver(validator, substitutor)
        val scenarioResolver = new ScenarioResolver(fragmentResolver, processingTypeData.name)
        (
          validator,
          resolver,
          scenarioResolver,
          processingTypeData.designerModelData.modelData,
          processingTypeData.deploymentData.validDeploymentManagerOrStub
        )
      }

      val counter = new ProcessCounter(fragmentRepository)

      val scenarioTestService = scenarioTestServiceDeps.mapValues {
        case (_, processResolver, scenarioResolver, modelData, deploymentManager) =>
          new ScenarioTestService(
            new ModelDataTestInfoProvider(modelData),
            processResolver,
            featureTogglesConfig.testDataSettings,
            new PreliminaryScenarioTestDataSerDe(featureTogglesConfig.testDataSettings),
            counter,
            new ScenarioTestExecutorServiceImpl(scenarioResolver, deploymentManager)
          )
      }

      val processValidator = scenarioTestServiceDeps.mapValues(_._1)
      val processResolver  = scenarioTestServiceDeps.mapValues(_._2)
      val scenarioResolver = scenarioTestServiceDeps.mapValues(_._3)

      val notificationsConfig = resolvedConfig.as[NotificationConfig]("notifications")
      val processChangeListener = ProcessChangeListenerLoader.loadListeners(
        getClass.getClassLoader,
        resolvedConfig,
        NussknackerServices(new PullProcessRepository(futureProcessRepository))
      )

      val dmDispatcher =
        new DeploymentManagerDispatcher(
          processingTypeDataProvider.mapValues(_.deploymentData.validDeploymentManagerOrStub),
          futureProcessRepository
        )

      val legacyDeploymentService = new LegacyDeploymentService(
        dmDispatcher,
        processRepository,
        actionRepository,
        dbioRunner,
        processValidator,
        scenarioResolver,
        processChangeListener,
        featureTogglesConfig.scenarioStateTimeout,
        featureTogglesConfig.deploymentCommentSettings
      )
      legacyDeploymentService.invalidateInProgressActions()

      actionServiceSupplier.set(legacyDeploymentService)

      // we need to reload processing type data after deployment service creation to make sure that it will be done using
      // correct classloader and that won't cause further delays during handling requests
      processingTypeDataProvider.reloadAll()
      val processActivityRepository = new DbProcessActivityRepository(dbRef, commentRepository)

      val authenticationResources = AuthenticationResources(resolvedConfig, getClass.getClassLoader, sttpBackend)
      val authManager             = new AuthManager(authenticationResources)

      Initialization.init(migrations, dbRef, processRepository, commentRepository, environment)

      val newProcessPreparer = processingTypeDataProvider.mapValues { processingTypeData =>
        new NewProcessPreparer(
          processingTypeData.deploymentData.metaDataInitializer,
          processingTypeData.deploymentData.scenarioPropertiesConfig,
          new ScenarioPropertiesConfigFinalizer(additionalUIConfigProvider, processingTypeData.name),
        )
      }

      val stateDefinitionService = new ProcessStateDefinitionService(
        processingTypeDataProvider
          .mapValues(_.category)
          .mapCombined(_.statusNameToStateDefinitionsMapping)
      )

      val processService = new DBProcessService(
        legacyDeploymentService,
        newProcessPreparer,
        processingTypeDataProvider.mapCombined(_.parametersService),
        processResolver,
        dbioRunner,
        futureProcessRepository,
        actionRepository,
        writeProcessRepository
      )

      val configProcessToolbarService = new ConfigScenarioToolbarService(
        CategoriesScenarioToolbarsConfigParser.parse(resolvedConfig)
      )

      def prepareAlignedComponentsDefinitionProvider(
          processingTypeData: ProcessingTypeData
      ): AlignedComponentsDefinitionProvider =
        AlignedComponentsDefinitionProvider(processingTypeData.designerModelData)

      val componentService = new DefaultComponentService(
        ComponentLinksConfigExtractor.extract(resolvedConfig),
        processingTypeDataProvider
          .mapValues { processingTypeData =>
            val alignedModelDefinitionProvider = prepareAlignedComponentsDefinitionProvider(processingTypeData)
            ComponentServiceProcessingTypeData(alignedModelDefinitionProvider, processingTypeData.category)
          },
        processService,
        fragmentRepository
      )
      val notificationService = new NotificationServiceImpl(actionRepository, dbioRunner, notificationsConfig)
      val processAuthorizer   = new AuthorizeProcess(futureProcessRepository)
      val appApiHttpService = new AppApiHttpService(
        config = resolvedConfig,
        authManager = authManager,
        processingTypeDataReloader = processingTypeDataProvider,
        modelBuildInfos = modelBuildInfo,
        categories = processingTypeDataProvider.mapValues(_.category),
        processService = processService,
        shouldExposeConfig = featureTogglesConfig.enableConfigEndpoint,
      )

      val migrationApiAdapterService = new MigrationApiAdapterService()

      val migrationService = new MigrationService(
        config = resolvedConfig,
        processService = processService,
        processResolver = processResolver,
        processAuthorizer = processAuthorizer,
        processChangeListener = processChangeListener,
        scenarioParametersService = processingTypeDataProvider.mapCombined(_.parametersService),
        useLegacyCreateScenarioApi = true,
        migrationApiAdapterService = migrationApiAdapterService
      )

      val migrationApiHttpService = new MigrationApiHttpService(
        authManager = authManager,
        migrationService = migrationService,
        migrationApiAdapterService = migrationApiAdapterService
      )
      val componentsApiHttpService = new ComponentApiHttpService(
        authManager = authManager,
        componentService = componentService
      )
      val userApiHttpService = new UserApiHttpService(
        authManager = authManager,
        categories = processingTypeDataProvider.mapValues(_.category)
      )

      val managementApiHttpService = new ManagementApiHttpService(
        authManager = authManager,
        dispatcher = dmDispatcher,
        processService = processService
      )

      val notificationApiHttpService = new NotificationApiHttpService(
        authManager = authManager,
        notificationService = notificationService
      )

      val nodesApiHttpService = new NodesApiHttpService(
        authManager = authManager,
        processingTypeToConfig = processingTypeDataProvider.mapValues(_.designerModelData.modelData),
        processingTypeToProcessValidator = processValidator,
        processingTypeToNodeValidator = processingTypeDataProvider.mapValues(v =>
          new NodeValidator(v.designerModelData.modelData, fragmentRepository)
        ),
        processingTypeToExpressionSuggester = processingTypeDataProvider.mapValues(v =>
          ExpressionSuggester(v.designerModelData.modelData, v.deploymentData.scenarioPropertiesConfig.keys)
        ),
        processingTypeToParametersValidator = processingTypeDataProvider.mapValues(v =>
          new ParametersValidator(v.designerModelData.modelData, v.deploymentData.scenarioPropertiesConfig.keys)
        ),
        scenarioService = processService
      )

      val scenarioActivityApiHttpService = new ScenarioActivityApiHttpService(
        authManager = authManager,
        scenarioActivityRepository = processActivityRepository,
        scenarioService = processService,
        scenarioAuthorizer = processAuthorizer,
        new ScenarioAttachmentService(
          AttachmentsConfig.create(resolvedConfig),
          processActivityRepository
        ),
        new AkkaHttpBasedTapirStreamEndpointProvider()
      )
      val scenarioParametersHttpService = new ScenarioParametersApiHttpService(
        authManager = authManager,
        scenarioParametersService = processingTypeDataProvider.mapCombined(_.parametersService)
      )
      val dictApiHttpService = new DictApiHttpService(
        authManager = authManager,
        processingTypeData = processingTypeDataProvider.mapValues { processingTypeData =>
          (
            processingTypeData.designerModelData.modelData.designerDictServices.dictQueryService,
            processingTypeData.designerModelData.modelData.modelDefinition.expressionConfig.dictionaries,
            processingTypeData.designerModelData.modelData.modelClassLoader.classLoader
          )
        }
      )
      val deploymentHttpService = {
        val scenarioMetadataRepository     = new ScenarioMetadataRepository(dbRef)
        val scenarioGraphVersionRepository = new ScenarioGraphVersionRepository(dbRef)
        val scenarioGraphVersionService =
          new ScenarioGraphVersionService(
            scenarioGraphVersionRepository,
            processValidator,
            scenarioResolver,
            dbioRunner
          )
        val deploymentService =
          new DeploymentService(
            scenarioMetadataRepository,
            scenarioGraphVersionService,
            deploymentRepository,
            dmDispatcher,
            dbioRunner,
            Clock.systemDefaultZone()
          )
        val commentRepository = new CommentRepository(dbRef)
        val activityService =
          new ActivityService(
            featureTogglesConfig.deploymentCommentSettings,
            commentRepository,
            deploymentService,
            dbioRunner
          )
        new DeploymentApiHttpService(authManager, activityService, deploymentService)
      }

      initMetrics(metricsRegistry, resolvedConfig, futureProcessRepository)

      val apiResourcesWithAuthentication: List[RouteWithUser] = {
        val routes = List(
          new ProcessesResources(
            processService = processService,
            processStateService = legacyDeploymentService,
            processToolbarService = configProcessToolbarService,
            processAuthorizer = processAuthorizer,
            processChangeListener = processChangeListener
          ),
          new ProcessesExportResources(
            futureProcessRepository,
            processService,
            processActivityRepository,
            processResolver
          ),
          new ManagementResources(
            processAuthorizer,
            processService,
            legacyDeploymentService,
            dmDispatcher,
            metricsRegistry,
            scenarioTestService,
            processingTypeDataProvider.mapValues(_.designerModelData.modelData)
          ),
          new ValidationResources(processService, processResolver),
          new DefinitionResources(
            processingTypeDataProvider.mapValues { processingTypeData =>
              (
                DefinitionsService(
                  processingTypeData,
                  prepareAlignedComponentsDefinitionProvider(processingTypeData),
                  new ScenarioPropertiesConfigFinalizer(additionalUIConfigProvider, processingTypeData.name),
                  fragmentRepository
                )
              )
            }
          ),
          new TestInfoResources(processAuthorizer, processService, scenarioTestService),
          new StatusResources(stateDefinitionService),
        )

        val optionalRoutes = List(
          featureTogglesConfig.remoteEnvironment
            .map(migrationConfig =>
              new HttpRemoteEnvironment(
                migrationConfig,
                new TestModelMigrations(
                  migrations.mapValues(new ProcessModelMigrator(_)),
                  processValidator
                ),
                environment
              )
            )
            .map { remoteEnvironment =>
              new RemoteEnvironmentResources(
                remoteEnvironment,
                processService,
                processAuthorizer
              )
            },
          countsReporter.map(reporter =>
            new ProcessReportResources(reporter, counter, futureProcessRepository, processService)
          ),
        ).flatten
        routes ++ optionalRoutes
      }

      val usageStatisticsReportsConfig = resolvedConfig.as[UsageStatisticsReportsConfig]("usageStatisticsReports")
      val fingerprintService           = new FingerprintService(new FingerprintRepositoryImpl(dbRef))
      val usageStatisticsReportsSettingsService = UsageStatisticsReportsSettingsService(
        usageStatisticsReportsConfig,
        processService,
        processingTypeDataProvider.mapValues(_.deploymentData.deploymentManagerType),
        fingerprintService,
        processActivityRepository,
        componentService,
        feStatisticsRepository,
        processingTypeDataProvider
          .mapValues { processingTypeData =>
            prepareAlignedComponentsDefinitionProvider(processingTypeData)
              .getAlignedComponentsWithBuiltInComponentsAndFragments(forFragment = false, List.empty)
          }
          .all
          .values
          .flatten
          .toList,
        designerClock
      )

      val statisticsApiHttpService = new StatisticsApiHttpService(
        authManager,
        usageStatisticsReportsSettingsService,
        feStatisticsRepository,
        StatisticUrlConfig()
      )

      // TODO: WARNING now all settings are available for not sign in user. In future we should show only basic settings
      val settingsResources = new SettingsResources(
        featureTogglesConfig,
        authenticationResources.name,
        analyticsConfig,
        usageStatisticsReportsConfig
      )
      val apiResourcesWithoutAuthentication: List[Route] = List(
        settingsResources.publicRoute(),
        authenticationResources.routeWithPathPrefix,
      )

      val nuDesignerApi =
        new NuDesignerExposedApiHttpService(
          appApiHttpService,
          componentsApiHttpService,
          dictApiHttpService,
          deploymentHttpService,
          managementApiHttpService,
          migrationApiHttpService,
          nodesApiHttpService,
          notificationApiHttpService,
          scenarioActivityApiHttpService,
          scenarioParametersHttpService,
          userApiHttpService,
          statisticsApiHttpService
        )

      val akkaHttpServerInterpreter = {
        import system.dispatcher
        new NuAkkaHttpServerInterpreterForTapirPurposes()
      }

      createAppRoute(
        resolvedConfig = resolvedConfig,
        authManager = authManager,
        tapirRelatedRoutes = akkaHttpServerInterpreter.toRoute(nuDesignerApi.allEndpoints) :: Nil,
        apiResourcesWithAuthentication = apiResourcesWithAuthentication,
        apiResourcesWithoutAuthentication = apiResourcesWithoutAuthentication,
        developmentMode = featureTogglesConfig.development
      )
    }
  }

  private def createSttpBackend()(implicit executionContext: ExecutionContext) = {
    Resource
      .make(
        acquire = IO(AsyncHttpClientFutureBackend.usingConfigBuilder(identity))
      )(
        release = backend => IO.fromFuture(IO(backend.close()))
      )
  }

  private def initMetrics(
      metricsRegistry: MetricRegistry,
      config: Config,
      processRepository: DBFetchingProcessRepository[Future] with BasicRepository
  ): Unit = {
    new RepositoryGauges(metricsRegistry, config.getDuration("repositoryGaugesCacheDuration"), processRepository)
      .prepareGauges()
  }

  private def createAppRoute(
      resolvedConfig: Config,
      authManager: AuthManager,
      tapirRelatedRoutes: List[Route],
      apiResourcesWithAuthentication: List[RouteWithUser],
      apiResourcesWithoutAuthentication: List[Route],
      developmentMode: Boolean
  ): Route = {
    // TODO: In the future will be nice to have possibility to pass authenticator.directive to resource and there us it at concrete path resource
    val webResources = new WebResources(resolvedConfig.getString("http.publicPath"))
    WithDirectives(CorsSupport.cors(developmentMode), SecurityHeadersSupport(), OptionsMethodSupport()) {
      tapirRelatedRoutes.reduce(_ ~ _) ~
        pathPrefixTest(!"api") {
          webResources.route
        } ~ pathPrefix("api") {
          apiResourcesWithoutAuthentication.reduce(_ ~ _)
        } ~ pathPrefix("api") {
          authManager.authenticate() { authenticatedUser =>
            authManager.authorizeRoute(authenticatedUser) { loggedUser =>
              apiResourcesWithAuthentication
                .map(_.securedRouteWithErrorHandling(loggedUser))
                .reduce(_ ~ _)
            }
          }
        }
    }
  }

  private def createCountsReporter(
      featureTogglesConfig: FeatureTogglesConfig,
      environment: String,
      backend: SttpBackend[Future, Any]
  ) = {

    featureTogglesConfig.counts match {
      case Some(config) => prepareCountsReporter(environment, config, backend)
      case None         => Resource.pure[IO, None.type](None)
    }
  }

  // by default, we use InfluxCountsReporterCreator
  private def prepareCountsReporter(
      env: String,
      config: Config,
      backend: SttpBackend[Future, Any]
  ): Resource[IO, Option[CountsReporter[Future]]] = {
    Resource
      .make(
        acquire = IO {
          val configAtKey = config.atKey(CountsReporterCreator.reporterCreatorConfigPath)
          val creator = Multiplicity(ScalaServiceLoader.load[CountsReporterCreator](getClass.getClassLoader)) match {
            case One(cr) =>
              cr
            case Empty() =>
              new InfluxCountsReporterCreator
            case Many(many) =>
              throw new IllegalArgumentException(s"Many CountsReporters found: ${many.mkString(", ")}")
          }

          Try(Option(creator.createReporter(env, configAtKey)(backend))).recover { case NonFatal(ex) =>
            logger.warn(
              s"Error while setting up counts mechanism: ${ex.getMessage}. Counts mechanism will be disabled."
            )
            None
          }.get
        }
      )(
        release = counter => IO(counter.foreach(_.close()))
      )
  }

  private def prepareProcessingTypeDataReload(
      designerConfig: ConfigWithUnresolvedVersion,
      processingTypeDataStateFactory: ProcessingTypeDataStateFactory,
      additionalUIConfigProvider: AdditionalUIConfigProvider,
      actionServiceProvider: Supplier[ActionService],
      createDeployedScenariosProvider: ProcessingType => ProcessingTypeDeployedScenariosProvider,
      sttpBackend: SttpBackend[Future, Any],
  ): Resource[IO, ProcessingTypeDataReload] = {
    Resource
      .make(
        acquire = IO(
          new ProcessingTypeDataReload({ () =>
            def getDeploymentManagerDependencies(processingType: ProcessingType) = {
              DeploymentManagerDependencies(
                createDeployedScenariosProvider(processingType),
                new DefaultProcessingTypeActionService(
                  processingType,
                  actionServiceProvider.get(),
                ),
                system.dispatcher,
                system,
                sttpBackend
              )
            }
            def getModelDependencies(processingType: ProcessingType) = {
              val additionalConfigsFromProvider = additionalUIConfigProvider.getAllForProcessingType(processingType)
              ModelDependencies(
                additionalConfigsFromProvider,
                DesignerWideComponentId.default(processingType, _),
                workingDirectoryOpt = None, // we use the default working directory
                _ => true
              )
            }
            processingTypeDataStateFactory.create(
              designerConfig,
              getModelDependencies,
              getDeploymentManagerDependencies,
            )
          })
        )
      )(
        release = reload =>
          IO {
            reload
              .all(NussknackerInternalUser.instance)
              .values
              .foreach(_.close())
          }
      )
  }

  private def createAdditionalUIConfigProvider(config: Config, sttpBackend: SttpBackend[Future, Any])(
      implicit ec: ExecutionContext
  ) = {
    val additionalUIConfigProviderFactory: AdditionalUIConfigProviderFactory = {
      Multiplicity(
        ScalaServiceLoader.load[AdditionalUIConfigProviderFactory](getClass.getClassLoader)
      ) match {
        case Empty()              => new EmptyAdditionalUIConfigProviderFactory
        case One(providerFactory) => providerFactory
        case Many(moreThanOne) =>
          throw new IllegalArgumentException(
            s"More than one AdditionalUIConfigProviderFactory instance found: $moreThanOne"
          )
      }
    }

    additionalUIConfigProviderFactory.create(config, sttpBackend)
  }

  private class DelayedInitActionServiceSupplier extends Supplier[ActionService] {
    private val actionServiceRef = new AtomicReference[Option[ActionService]](None)

    override def get(): ActionService = {
      val actionService = actionServiceRef.get()
      actionService.getOrElse(
        throw new IllegalStateException(
          "Illegal initialization: ActionService should be initialized before ProcessingTypeData"
        )
      )
    }

    def set(actionService: ActionService): Unit = actionServiceRef.set(Some(actionService))
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy