From 148d2da8543e1b2a30169870f15a502514d00c2c Mon Sep 17 00:00:00 2001 From: Matthew Ball Date: Sun, 28 Jun 2026 12:44:22 -0700 Subject: [PATCH] refactor(asf): unify Dropwizard service bootstrap into common/auth" --- .../texera/service/AccessControlService.scala | 42 ++++------- common/auth/build.sbt | 1 + .../apache/texera/auth/ServiceBootstrap.scala | 72 +++++++++++++++++++ .../texera/auth/ServiceBootstrapSpec.scala | 60 ++++++++++++++++ .../ComputingUnitManagingService.scala | 41 ++++------- .../apache/texera/service/ConfigService.scala | 41 +++-------- .../apache/texera/service/FileService.scala | 38 +++------- .../service/NotebookMigrationService.scala | 62 +++------------- .../NotebookMigrationServiceRunSpec.scala | 2 +- .../service/WorkflowCompilingService.scala | 65 ++++------------- 10 files changed, 200 insertions(+), 224 deletions(-) create mode 100644 common/auth/src/main/scala/org/apache/texera/auth/ServiceBootstrap.scala create mode 100644 common/auth/src/test/scala/org/apache/texera/auth/ServiceBootstrapSpec.scala diff --git a/access-control-service/src/main/scala/org/apache/texera/service/AccessControlService.scala b/access-control-service/src/main/scala/org/apache/texera/service/AccessControlService.scala index 1f50c86c9f5..4f32e383478 100644 --- a/access-control-service/src/main/scala/org/apache/texera/service/AccessControlService.scala +++ b/access-control-service/src/main/scala/org/apache/texera/service/AccessControlService.scala @@ -17,14 +17,15 @@ package org.apache.texera.service -import com.fasterxml.jackson.module.scala.DefaultScalaModule import com.typesafe.scalalogging.LazyLogging -import io.dropwizard.configuration.{EnvironmentVariableSubstitutor, SubstitutingSourceProvider} import io.dropwizard.core.Application import io.dropwizard.core.setup.{Bootstrap, Environment} -import org.apache.texera.common.config.StorageConfig -import org.apache.texera.auth.{AuthFeatures, RequestLoggingFilter, RoleAnnotationEnforcer} -import org.apache.texera.dao.SqlServer +import org.apache.texera.auth.{ + AuthFeatures, + RequestLoggingFilter, + RoleAnnotationEnforcer, + ServiceBootstrap +} import org.apache.texera.service.activity.UserActivityEventListener import org.apache.texera.service.resource.{ AccessControlResource, @@ -33,25 +34,11 @@ import org.apache.texera.service.resource.{ LiteLLMProxyResource } import org.eclipse.jetty.server.session.SessionHandler -import java.nio.file.Path class AccessControlService extends Application[AccessControlServiceConfiguration] with LazyLogging { override def initialize(bootstrap: Bootstrap[AccessControlServiceConfiguration]): Unit = { - // enable environment variable substitution in YAML config - bootstrap.setConfigurationSourceProvider( - new SubstitutingSourceProvider( - bootstrap.getConfigurationSourceProvider, - new EnvironmentVariableSubstitutor(false) - ) - ) - // Register Scala module to Dropwizard default object mapper - bootstrap.getObjectMapper.registerModule(DefaultScalaModule) - - SqlServer.initConnection( - StorageConfig.jdbcUrl, - StorageConfig.jdbcUsername, - StorageConfig.jdbcPassword - ) + ServiceBootstrap.configure(bootstrap) + ServiceBootstrap.initDatabase() } override def run( @@ -85,15 +72,10 @@ class AccessControlService extends Application[AccessControlServiceConfiguration } object AccessControlService { def main(args: Array[String]): Unit = { - val accessControlPath = Path - .of(sys.env.getOrElse("TEXERA_HOME", ".")) - .resolve("access-control-service") - .resolve("src") - .resolve("main") - .resolve("resources") - .resolve("access-control-service-web-config.yaml") - .toAbsolutePath - .toString + val accessControlPath = ServiceBootstrap.configFilePath( + "access-control-service", + "access-control-service-web-config.yaml" + ) // Start the Dropwizard application new AccessControlService().run("server", accessControlPath) diff --git a/common/auth/build.sbt b/common/auth/build.sbt index 4f325bf77ef..cf7da619434 100644 --- a/common/auth/build.sbt +++ b/common/auth/build.sbt @@ -63,6 +63,7 @@ libraryDependencies ++= Seq( "org.glassfish.jersey.core" % "jersey-server" % "3.0.12" % "provided", // for RoleAnnotationEnforcer's ResourceConfig overload and AuthFeatures' RolesAllowedDynamicFeature "io.dropwizard" % "dropwizard-core" % "4.0.7" % "provided", // for AuthFeatures' Environment "io.dropwizard" % "dropwizard-auth" % "4.0.7" % "provided", // for AuthFeatures' AuthDynamicFeature/AuthValueFactoryProvider + "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.18.6" % "provided", // for ServiceBootstrap's DefaultScalaModule "org.scalatest" %% "scalatest" % "3.2.17" % Test, "org.mockito" % "mockito-core" % "5.4.0" % Test // for mocking the Jersey environment in AuthFeaturesSpec ) \ No newline at end of file diff --git a/common/auth/src/main/scala/org/apache/texera/auth/ServiceBootstrap.scala b/common/auth/src/main/scala/org/apache/texera/auth/ServiceBootstrap.scala new file mode 100644 index 00000000000..1f676856625 --- /dev/null +++ b/common/auth/src/main/scala/org/apache/texera/auth/ServiceBootstrap.scala @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.texera.auth + +import com.fasterxml.jackson.module.scala.DefaultScalaModule +import io.dropwizard.configuration.{EnvironmentVariableSubstitutor, SubstitutingSourceProvider} +import io.dropwizard.core.Configuration +import io.dropwizard.core.setup.Bootstrap +import org.apache.texera.common.config.StorageConfig +import org.apache.texera.dao.SqlServer + +import java.nio.file.Path + +/** Shared Dropwizard service bootstrap steps, identical across every Texera + * service. Kept here so the services don't drift apart. + */ +object ServiceBootstrap { + + /** Enable `${ENV_VAR}` substitution in the YAML config and register the Scala + * module on Dropwizard's default object mapper. + */ + def configure[T <: Configuration](bootstrap: Bootstrap[T]): Unit = { + // enable environment variable substitution in YAML config + bootstrap.setConfigurationSourceProvider( + new SubstitutingSourceProvider( + bootstrap.getConfigurationSourceProvider, + new EnvironmentVariableSubstitutor(false) + ) + ) + // register Scala module to Dropwizard default object mapper + bootstrap.getObjectMapper.registerModule(DefaultScalaModule) + } + + /** Open the shared SQL connection pool using the storage configuration. */ + def initDatabase(): Unit = + SqlServer.initConnection( + StorageConfig.jdbcUrl, + StorageConfig.jdbcUsername, + StorageConfig.jdbcPassword + ) + + /** Resolve `$TEXERA_HOME//src/main/resources/` to + * an absolute path string, the convention every service `main` uses. + */ + def configFilePath(serviceDir: String, configFileName: String): String = + Path + .of(sys.env.getOrElse("TEXERA_HOME", ".")) + .resolve(serviceDir) + .resolve("src") + .resolve("main") + .resolve("resources") + .resolve(configFileName) + .toAbsolutePath + .toString +} diff --git a/common/auth/src/test/scala/org/apache/texera/auth/ServiceBootstrapSpec.scala b/common/auth/src/test/scala/org/apache/texera/auth/ServiceBootstrapSpec.scala new file mode 100644 index 00000000000..b0319890059 --- /dev/null +++ b/common/auth/src/test/scala/org/apache/texera/auth/ServiceBootstrapSpec.scala @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.texera.auth + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.scala.DefaultScalaModule +import io.dropwizard.configuration.ConfigurationSourceProvider +import io.dropwizard.core.Configuration +import io.dropwizard.core.setup.Bootstrap +import org.mockito.ArgumentMatchers.{any, isA} +import org.mockito.Mockito.{mock, verify, when} +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +import java.nio.file.Paths + +class ServiceBootstrapSpec extends AnyFlatSpec with Matchers { + + // Every service shares this bootstrap helper, so its behavior is verified once here + // rather than per service. + "ServiceBootstrap.configure" should "wrap the config source provider and register the Scala module" in { + val bootstrap = mock(classOf[Bootstrap[Configuration]]) + val objectMapper = mock(classOf[ObjectMapper]) + val sourceProvider = mock(classOf[ConfigurationSourceProvider]) + when(bootstrap.getObjectMapper).thenReturn(objectMapper) + when(bootstrap.getConfigurationSourceProvider).thenReturn(sourceProvider) + + ServiceBootstrap.configure(bootstrap) + + verify(bootstrap).setConfigurationSourceProvider(any(classOf[ConfigurationSourceProvider])) + verify(objectMapper).registerModule(isA(classOf[DefaultScalaModule])) + } + + "ServiceBootstrap.configFilePath" should "resolve the conventional resources path under the service dir" in { + val result = ServiceBootstrap.configFilePath("file-service", "file-service-web-config.yaml") + + val expectedSuffix = Paths + .get("file-service", "src", "main", "resources", "file-service-web-config.yaml") + .toString + result should endWith(expectedSuffix) + Paths.get(result).isAbsolute shouldBe true + } +} diff --git a/computing-unit-managing-service/src/main/scala/org/apache/texera/service/ComputingUnitManagingService.scala b/computing-unit-managing-service/src/main/scala/org/apache/texera/service/ComputingUnitManagingService.scala index db63bbf2eb2..3711274fed3 100644 --- a/computing-unit-managing-service/src/main/scala/org/apache/texera/service/ComputingUnitManagingService.scala +++ b/computing-unit-managing-service/src/main/scala/org/apache/texera/service/ComputingUnitManagingService.scala @@ -19,34 +19,26 @@ package org.apache.texera.service -import com.fasterxml.jackson.module.scala.DefaultScalaModule -import io.dropwizard.configuration.{EnvironmentVariableSubstitutor, SubstitutingSourceProvider} import io.dropwizard.core.Application import io.dropwizard.core.setup.{Bootstrap, Environment} -import org.apache.texera.common.config.StorageConfig -import org.apache.texera.auth.{AuthFeatures, RequestLoggingFilter, RoleAnnotationEnforcer} -import org.apache.texera.dao.SqlServer +import org.apache.texera.auth.{ + AuthFeatures, + RequestLoggingFilter, + RoleAnnotationEnforcer, + ServiceBootstrap +} import org.apache.texera.service.resource.{ ComputingUnitAccessResource, ComputingUnitManagingResource, HealthCheckResource } -import java.nio.file.Path class ComputingUnitManagingService extends Application[ComputingUnitManagingServiceConfiguration] { override def initialize( bootstrap: Bootstrap[ComputingUnitManagingServiceConfiguration] ): Unit = { - // enable environment variable substitution in YAML config - bootstrap.setConfigurationSourceProvider( - new SubstitutingSourceProvider( - bootstrap.getConfigurationSourceProvider, - new EnvironmentVariableSubstitutor(false) - ) - ) - // register scala module to dropwizard default object mapper - bootstrap.getObjectMapper.registerModule(DefaultScalaModule) + ServiceBootstrap.configure(bootstrap) } override def run( configuration: ComputingUnitManagingServiceConfiguration, @@ -58,11 +50,7 @@ class ComputingUnitManagingService extends Application[ComputingUnitManagingServ AuthFeatures.register(environment) - SqlServer.initConnection( - StorageConfig.jdbcUrl, - StorageConfig.jdbcUsername, - StorageConfig.jdbcPassword - ) + ServiceBootstrap.initDatabase() environment.jersey().register(new ComputingUnitManagingResource) environment.jersey().register(new ComputingUnitAccessResource) @@ -79,15 +67,10 @@ class ComputingUnitManagingService extends Application[ComputingUnitManagingServ object ComputingUnitManagingService { def main(args: Array[String]): Unit = { - val configFilePath = Path - .of(sys.env.getOrElse("TEXERA_HOME", ".")) - .resolve("computing-unit-managing-service") - .resolve("src") - .resolve("main") - .resolve("resources") - .resolve("computing-unit-managing-service-config.yaml") - .toAbsolutePath - .toString + val configFilePath = ServiceBootstrap.configFilePath( + "computing-unit-managing-service", + "computing-unit-managing-service-config.yaml" + ) new ComputingUnitManagingService().run("server", configFilePath) } diff --git a/config-service/src/main/scala/org/apache/texera/service/ConfigService.scala b/config-service/src/main/scala/org/apache/texera/service/ConfigService.scala index b45c6ce62b0..e2d3ba30f38 100644 --- a/config-service/src/main/scala/org/apache/texera/service/ConfigService.scala +++ b/config-service/src/main/scala/org/apache/texera/service/ConfigService.scala @@ -19,37 +19,25 @@ package org.apache.texera.service -import com.fasterxml.jackson.module.scala.DefaultScalaModule import com.typesafe.scalalogging.LazyLogging -import io.dropwizard.configuration.{EnvironmentVariableSubstitutor, SubstitutingSourceProvider} import io.dropwizard.core.Application import io.dropwizard.core.setup.{Bootstrap, Environment} -import org.apache.texera.auth.{AuthFeatures, RequestLoggingFilter, RoleAnnotationEnforcer} -import org.apache.texera.common.config.{DefaultsConfig, StorageConfig} +import org.apache.texera.auth.{ + AuthFeatures, + RequestLoggingFilter, + RoleAnnotationEnforcer, + ServiceBootstrap +} +import org.apache.texera.common.config.DefaultsConfig import org.apache.texera.dao.SqlServer import org.apache.texera.service.resource.{ConfigResource, HealthCheckResource} import org.eclipse.jetty.server.session.SessionHandler import org.jooq.impl.DSL -import java.nio.file.Path - class ConfigService extends Application[ConfigServiceConfiguration] with LazyLogging { override def initialize(bootstrap: Bootstrap[ConfigServiceConfiguration]): Unit = { - // enable environment variable substitution in YAML config - bootstrap.setConfigurationSourceProvider( - new SubstitutingSourceProvider( - bootstrap.getConfigurationSourceProvider, - new EnvironmentVariableSubstitutor(false) - ) - ) - // Register Scala module to Dropwizard default object mapper - bootstrap.getObjectMapper.registerModule(DefaultScalaModule) - - SqlServer.initConnection( - StorageConfig.jdbcUrl, - StorageConfig.jdbcUsername, - StorageConfig.jdbcPassword - ) + ServiceBootstrap.configure(bootstrap) + ServiceBootstrap.initDatabase() } override def run(configuration: ConfigServiceConfiguration, environment: Environment): Unit = { @@ -104,15 +92,8 @@ class ConfigService extends Application[ConfigServiceConfiguration] with LazyLog object ConfigService { def main(args: Array[String]): Unit = { - val configFilePath = Path - .of(sys.env.getOrElse("TEXERA_HOME", ".")) - .resolve("config-service") - .resolve("src") - .resolve("main") - .resolve("resources") - .resolve("config-service-web-config.yaml") - .toAbsolutePath - .toString + val configFilePath = + ServiceBootstrap.configFilePath("config-service", "config-service-web-config.yaml") // Start the Dropwizard application new ConfigService().run("server", configFilePath) diff --git a/file-service/src/main/scala/org/apache/texera/service/FileService.scala b/file-service/src/main/scala/org/apache/texera/service/FileService.scala index fe3c3b87413..7dd4ddf62c9 100644 --- a/file-service/src/main/scala/org/apache/texera/service/FileService.scala +++ b/file-service/src/main/scala/org/apache/texera/service/FileService.scala @@ -20,15 +20,17 @@ package org.apache.texera.service import com.fasterxml.jackson.databind.module.SimpleModule -import com.fasterxml.jackson.module.scala.DefaultScalaModule import com.typesafe.scalalogging.LazyLogging -import io.dropwizard.configuration.{EnvironmentVariableSubstitutor, SubstitutingSourceProvider} import io.dropwizard.core.Application import io.dropwizard.core.setup.{Bootstrap, Environment} import org.apache.texera.common.config.StorageConfig import org.apache.texera.amber.core.storage.util.LakeFSStorageClient -import org.apache.texera.auth.{AuthFeatures, RequestLoggingFilter, RoleAnnotationEnforcer} -import org.apache.texera.dao.SqlServer +import org.apache.texera.auth.{ + AuthFeatures, + RequestLoggingFilter, + RoleAnnotationEnforcer, + ServiceBootstrap +} import org.apache.texera.service.`type`.DatasetFileNode import org.apache.texera.service.`type`.serde.DatasetFileNodeSerializer import org.apache.texera.service.resource.{ @@ -39,19 +41,10 @@ import org.apache.texera.service.resource.{ import org.apache.texera.service.util.S3StorageClient import org.apache.texera.service.util.LargeBinaryManager import org.eclipse.jetty.server.session.SessionHandler -import java.nio.file.Path class FileService extends Application[FileServiceConfiguration] with LazyLogging { override def initialize(bootstrap: Bootstrap[FileServiceConfiguration]): Unit = { - // enable environment variable substitution in YAML config - bootstrap.setConfigurationSourceProvider( - new SubstitutingSourceProvider( - bootstrap.getConfigurationSourceProvider, - new EnvironmentVariableSubstitutor(false) - ) - ) - // Register Scala module to Dropwizard default object mapper - bootstrap.getObjectMapper.registerModule(DefaultScalaModule) + ServiceBootstrap.configure(bootstrap) // register a new custom module just for DatasetFileNode serde/deserde val customSerializerModule = new SimpleModule("CustomSerializers") @@ -62,11 +55,7 @@ class FileService extends Application[FileServiceConfiguration] with LazyLogging override def run(configuration: FileServiceConfiguration, environment: Environment): Unit = { // Serve backend at /api environment.jersey.setUrlPattern("/api/*") - SqlServer.initConnection( - StorageConfig.jdbcUrl, - StorageConfig.jdbcUsername, - StorageConfig.jdbcPassword - ) + ServiceBootstrap.initDatabase() // check if the texera dataset bucket exists, if not create it awaitDependency("texera dataset bucket") { @@ -147,15 +136,8 @@ class FileService extends Application[FileServiceConfiguration] with LazyLogging object FileService { def main(args: Array[String]): Unit = { // Set the configuration file's path - val configFilePath = Path - .of(sys.env.getOrElse("TEXERA_HOME", ".")) - .resolve("file-service") - .resolve("src") - .resolve("main") - .resolve("resources") - .resolve("file-service-web-config.yaml") - .toAbsolutePath - .toString + val configFilePath = + ServiceBootstrap.configFilePath("file-service", "file-service-web-config.yaml") // Start the Dropwizard application new FileService().run("server", configFilePath) diff --git a/notebook-migration-service/src/main/scala/org/apache/texera/service/NotebookMigrationService.scala b/notebook-migration-service/src/main/scala/org/apache/texera/service/NotebookMigrationService.scala index a94e728b00f..a8ebf498967 100644 --- a/notebook-migration-service/src/main/scala/org/apache/texera/service/NotebookMigrationService.scala +++ b/notebook-migration-service/src/main/scala/org/apache/texera/service/NotebookMigrationService.scala @@ -17,43 +17,18 @@ package org.apache.texera.service -import com.fasterxml.jackson.module.scala.DefaultScalaModule import com.typesafe.scalalogging.LazyLogging -import io.dropwizard.auth.AuthDynamicFeature -import io.dropwizard.configuration.{EnvironmentVariableSubstitutor, SubstitutingSourceProvider} import io.dropwizard.core.Application import io.dropwizard.core.setup.{Bootstrap, Environment} -import org.apache.texera.common.config.StorageConfig -import org.apache.texera.auth.{ - JwtAuthFilter, - RequestLoggingFilter, - SessionUser, - UnauthorizedExceptionMapper -} -import org.apache.texera.dao.SqlServer -import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature -import java.nio.file.Path +import org.apache.texera.auth.{AuthFeatures, RequestLoggingFilter, ServiceBootstrap} import org.apache.texera.service.resource.{HealthCheckResource, NotebookMigrationResource} class NotebookMigrationService extends Application[NotebookMigrationServiceConfiguration] with LazyLogging { override def initialize(bootstrap: Bootstrap[NotebookMigrationServiceConfiguration]): Unit = { - // enable environment variable substitution in YAML config - bootstrap.setConfigurationSourceProvider( - new SubstitutingSourceProvider( - bootstrap.getConfigurationSourceProvider, - new EnvironmentVariableSubstitutor(false) - ) - ) - // Register Scala module to Dropwizard default object mapper - bootstrap.getObjectMapper.registerModule(DefaultScalaModule) - - SqlServer.initConnection( - StorageConfig.jdbcUrl, - StorageConfig.jdbcUsername, - StorageConfig.jdbcPassword - ) + ServiceBootstrap.configure(bootstrap) + ServiceBootstrap.initDatabase() } override def run( @@ -65,7 +40,7 @@ class NotebookMigrationService environment.jersey.register(classOf[HealthCheckResource]) - NotebookMigrationService.registerAuthFeatures(environment) + AuthFeatures.register(environment) environment.jersey.register(classOf[NotebookMigrationResource]) @@ -74,32 +49,11 @@ class NotebookMigrationService } } object NotebookMigrationService { - // Registers JWT auth, @Auth injection, and @RolesAllowed enforcement. - // Mirrors the other Dropwizard services' registerAuthFeatures so they don't drift apart. - def registerAuthFeatures(environment: Environment): Unit = { - // Register JWT authentication filter - environment.jersey.register(new AuthDynamicFeature(classOf[JwtAuthFilter])) - environment.jersey.register(classOf[UnauthorizedExceptionMapper]) - - // Enable @Auth annotation for injecting SessionUser - environment.jersey.register( - new io.dropwizard.auth.AuthValueFactoryProvider.Binder(classOf[SessionUser]) - ) - - // Enforce @RolesAllowed annotations on resource methods - environment.jersey.register(classOf[RolesAllowedDynamicFeature]) - } - def main(args: Array[String]): Unit = { - val notebookMigrationPath = Path - .of(sys.env.getOrElse("TEXERA_HOME", ".")) - .resolve("notebook-migration-service") - .resolve("src") - .resolve("main") - .resolve("resources") - .resolve("notebook-migration-service-web-config.yaml") - .toAbsolutePath - .toString + val notebookMigrationPath = ServiceBootstrap.configFilePath( + "notebook-migration-service", + "notebook-migration-service-web-config.yaml" + ) // Start the Dropwizard application new NotebookMigrationService().run("server", notebookMigrationPath) diff --git a/notebook-migration-service/src/test/scala/org/apache/texera/service/NotebookMigrationServiceRunSpec.scala b/notebook-migration-service/src/test/scala/org/apache/texera/service/NotebookMigrationServiceRunSpec.scala index e71529a367d..01f60afabfd 100644 --- a/notebook-migration-service/src/test/scala/org/apache/texera/service/NotebookMigrationServiceRunSpec.scala +++ b/notebook-migration-service/src/test/scala/org/apache/texera/service/NotebookMigrationServiceRunSpec.scala @@ -49,7 +49,7 @@ class NotebookMigrationServiceRunSpec extends AnyFlatSpec with Matchers { verify(jersey).setUrlPattern("/api/*") verify(jersey).register(classOf[HealthCheckResource]) verify(jersey).register(classOf[NotebookMigrationResource]) - // Auth stack from registerAuthFeatures — without these, @RolesAllowed / @Auth are ignored. + // Auth stack from AuthFeatures.register; without these, @RolesAllowed / @Auth are ignored. verify(jersey).register(isA(classOf[AuthDynamicFeature])) verify(jersey).register(classOf[UnauthorizedExceptionMapper]) verify(jersey).register(classOf[RolesAllowedDynamicFeature]) diff --git a/workflow-compiling-service/src/main/scala/org/apache/texera/service/WorkflowCompilingService.scala b/workflow-compiling-service/src/main/scala/org/apache/texera/service/WorkflowCompilingService.scala index a69ef545246..4b6353fbdb9 100644 --- a/workflow-compiling-service/src/main/scala/org/apache/texera/service/WorkflowCompilingService.scala +++ b/workflow-compiling-service/src/main/scala/org/apache/texera/service/WorkflowCompilingService.scala @@ -19,30 +19,20 @@ package org.apache.texera.service -import com.fasterxml.jackson.module.scala.DefaultScalaModule -import io.dropwizard.configuration.{EnvironmentVariableSubstitutor, SubstitutingSourceProvider} import io.dropwizard.core.Application import io.dropwizard.core.setup.{Bootstrap, Environment} -import org.apache.texera.common.config.StorageConfig import org.apache.texera.amber.util.ObjectMapperUtils -import org.apache.texera.auth.{AuthFeatures, RoleAnnotationEnforcer} -import org.apache.texera.dao.SqlServer +import org.apache.texera.auth.{ + AuthFeatures, + RequestLoggingFilter, + RoleAnnotationEnforcer, + ServiceBootstrap +} import org.apache.texera.service.resource.{HealthCheckResource, WorkflowCompilationResource} -import org.eclipse.jetty.servlet.FilterHolder - -import java.nio.file.Path class WorkflowCompilingService extends Application[WorkflowCompilingServiceConfiguration] { override def initialize(bootstrap: Bootstrap[WorkflowCompilingServiceConfiguration]): Unit = { - // enable environment variable substitution in YAML config - bootstrap.setConfigurationSourceProvider( - new SubstitutingSourceProvider( - bootstrap.getConfigurationSourceProvider, - new EnvironmentVariableSubstitutor(false) - ) - ) - // register scala module to dropwizard default object mapper - bootstrap.getObjectMapper.registerModule(DefaultScalaModule) + ServiceBootstrap.configure(bootstrap) } override def run( @@ -58,11 +48,7 @@ class WorkflowCompilingService extends Application[WorkflowCompilingServiceConfi AuthFeatures.register(environment) - SqlServer.initConnection( - StorageConfig.jdbcUrl, - StorageConfig.jdbcUsername, - StorageConfig.jdbcPassword - ) + ServiceBootstrap.initDatabase() // register the compilation endpoint environment.jersey.register(classOf[WorkflowCompilationResource]) @@ -73,42 +59,17 @@ class WorkflowCompilingService extends Application[WorkflowCompilingServiceConfi ) // Route request logs through SLF4J, controlled by TEXERA_SERVICE_LOG_LEVEL - val requestLogger = org.slf4j.LoggerFactory.getLogger("org.eclipse.jetty.server.RequestLog") - environment.getApplicationContext.addFilter( - new FilterHolder(new jakarta.servlet.Filter { - override def doFilter( - request: jakarta.servlet.ServletRequest, - response: jakarta.servlet.ServletResponse, - chain: jakarta.servlet.FilterChain - ): Unit = { - chain.doFilter(request, response) - if (requestLogger.isInfoEnabled) { - val req = request.asInstanceOf[jakarta.servlet.http.HttpServletRequest] - val resp = response.asInstanceOf[jakarta.servlet.http.HttpServletResponse] - requestLogger.info( - s"""${req.getRemoteAddr} - "${req.getMethod} ${req.getRequestURI} ${req.getProtocol}" ${resp.getStatus}""" - ) - } - } - }), - "/*", - java.util.EnumSet.allOf(classOf[jakarta.servlet.DispatcherType]) - ) + RequestLoggingFilter.register(environment.getApplicationContext) } } object WorkflowCompilingService { def main(args: Array[String]): Unit = { // set the configuration file's path - val configFilePath = Path - .of(sys.env.getOrElse("TEXERA_HOME", ".")) - .resolve("workflow-compiling-service") - .resolve("src") - .resolve("main") - .resolve("resources") - .resolve("workflow-compiling-service-config.yaml") - .toAbsolutePath - .toString + val configFilePath = ServiceBootstrap.configFilePath( + "workflow-compiling-service", + "workflow-compiling-service-config.yaml" + ) // Start the Dropwizard application new WorkflowCompilingService().run("server", configFilePath)