Migrate common module DI, scheduling and API markers to CDI/Quarkus (WIP)

This commit is contained in:
a
2026-06-12 16:09:28 +01:00
parent 266aed750d
commit 74474fa967
39 changed files with 181 additions and 195 deletions
+3
View File
@@ -38,6 +38,9 @@ dependencies {
api 'io.quarkus:quarkus-undertow'
// Bean Validation (was transitively in spring-boot-starter-webmvc).
api 'io.quarkus:quarkus-hibernate-validator'
// @Scheduled support (was spring-context scheduling). quarkus-scheduler manages its own
// executor; the former SchedulingConfig TaskScheduler bean is no longer needed.
api 'io.quarkus:quarkus-scheduler'
// Swagger/OpenAPI annotations (io.swagger.v3.oas.annotations.*) used by common's API marker
// interfaces; was transitive via springdoc. Quarkus' SmallRye OpenAPI also understands these.
api 'io.swagger.core.v3:swagger-core-jakarta:2.2.46'
@@ -5,8 +5,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -16,8 +14,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/account")
// MIGRATION (Spring->JAX-RS): controllers using this annotation must declare @jakarta.ws.rs.Path("/api/v1/account").
// JAX-RS does not honour @Path via meta-annotations, so the path is not inherited from here.
@Tag(
name = "Account Security",
description =
@@ -5,19 +5,20 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* Combined annotation for Admin Settings API controllers.
* Includes @RestController, @RequestMapping("/api/v1/admin/settings"), and OpenAPI @Tag.
*
* <p>MIGRATION (Spring -> JAX-RS): JAX-RS/RESTEasy does NOT process {@code @Path} via custom
* meta-annotations (Spring honoured composed {@code @RestController}/{@code @RequestMapping} through
* {@code @AliasFor}; JAX-RS has no equivalent). This annotation therefore now carries only the
* OpenAPI {@code @Tag}. Each controller annotated with {@code @AdminApi} MUST additionally declare
* its own {@code @jakarta.ws.rs.Path("/api/v1/admin/settings")} (the path the removed
* {@code @RequestMapping} used to supply).
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/admin/settings")
@Tag(
name = "Admin Settings",
description =
@@ -5,8 +5,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -16,8 +14,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/admin/server-certificate")
// MIGRATION (Spring->JAX-RS): controllers using this annotation must declare @jakarta.ws.rs.Path("/api/v1/admin/server-certificate").
// JAX-RS does not honour @Path via meta-annotations, so the path is not inherited from here.
@Tag(
name = "Admin - Server Certificate",
description =
@@ -5,8 +5,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -16,8 +14,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/analysis")
// MIGRATION (Spring->JAX-RS): controllers using this annotation must declare @jakarta.ws.rs.Path("/api/v1/analysis").
// JAX-RS does not honour @Path via meta-annotations, so the path is not inherited from here.
@Tag(
name = "Analysis",
description =
@@ -5,8 +5,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -16,8 +14,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/config")
// MIGRATION (Spring->JAX-RS): controllers using this annotation must declare @jakarta.ws.rs.Path("/api/v1/config").
// JAX-RS does not honour @Path via meta-annotations, so the path is not inherited from here.
@Tag(
name = "Config",
description =
@@ -5,8 +5,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -16,8 +14,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/convert")
// MIGRATION (Spring->JAX-RS): controllers using this annotation must declare @jakarta.ws.rs.Path("/api/v1/convert").
// JAX-RS does not honour @Path via meta-annotations, so the path is not inherited from here.
@Tag(
name = "Convert",
description =
@@ -5,8 +5,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -16,8 +14,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/database")
// MIGRATION (Spring->JAX-RS): controllers using this annotation must declare @jakarta.ws.rs.Path("/api/v1/database").
// JAX-RS does not honour @Path via meta-annotations, so the path is not inherited from here.
@Tag(
name = "Database",
description =
@@ -5,8 +5,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -16,8 +14,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/admin/database")
// MIGRATION (Spring->JAX-RS): controllers using this annotation must declare @jakarta.ws.rs.Path("/api/v1/admin/database").
// JAX-RS does not honour @Path via meta-annotations, so the path is not inherited from here.
@Tag(
name = "Database Management",
description =
@@ -5,8 +5,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -16,8 +14,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/filter")
// MIGRATION (Spring->JAX-RS): controllers using this annotation must declare @jakarta.ws.rs.Path("/api/v1/filter").
// JAX-RS does not honour @Path via meta-annotations, so the path is not inherited from here.
@Tag(
name = "Filter",
description =
@@ -5,8 +5,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -16,8 +14,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/general")
// MIGRATION (Spring->JAX-RS): controllers using this annotation must declare @jakarta.ws.rs.Path("/api/v1/general").
// JAX-RS does not honour @Path via meta-annotations, so the path is not inherited from here.
@Tag(
name = "General",
description =
@@ -5,8 +5,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -16,8 +14,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/info")
// MIGRATION (Spring->JAX-RS): controllers using this annotation must declare @jakarta.ws.rs.Path("/api/v1/info").
// JAX-RS does not honour @Path via meta-annotations, so the path is not inherited from here.
@Tag(
name = "Info",
description =
@@ -5,8 +5,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -16,8 +14,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/invite")
// MIGRATION (Spring->JAX-RS): controllers using this annotation must declare @jakarta.ws.rs.Path("/api/v1/invite").
// JAX-RS does not honour @Path via meta-annotations, so the path is not inherited from here.
@Tag(
name = "Invite",
description =
@@ -5,8 +5,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -16,8 +14,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/misc")
// MIGRATION (Spring->JAX-RS): controllers using this annotation must declare @jakarta.ws.rs.Path("/api/v1/misc").
// JAX-RS does not honour @Path via meta-annotations, so the path is not inherited from here.
@Tag(
name = "Misc",
description =
@@ -5,8 +5,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -16,8 +14,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/pipeline")
// MIGRATION (Spring->JAX-RS): controllers using this annotation must declare @jakarta.ws.rs.Path("/api/v1/pipeline").
// JAX-RS does not honour @Path via meta-annotations, so the path is not inherited from here.
@Tag(
name = "Pipeline",
description =
@@ -5,8 +5,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -17,8 +15,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/proprietary/ui-data")
// MIGRATION (Spring->JAX-RS): controllers using this annotation must declare @jakarta.ws.rs.Path("/api/v1/proprietary/ui-data").
// JAX-RS does not honour @Path via meta-annotations, so the path is not inherited from here.
@Tag(
name = "Proprietary UI Data",
description =
@@ -5,8 +5,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -16,8 +14,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/security")
// MIGRATION (Spring->JAX-RS): controllers using this annotation must declare @jakarta.ws.rs.Path("/api/v1/security").
// JAX-RS does not honour @Path via meta-annotations, so the path is not inherited from here.
@Tag(
name = "Security",
description =
@@ -5,8 +5,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -16,8 +14,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/settings")
// MIGRATION (Spring->JAX-RS): controllers using this annotation must declare @jakarta.ws.rs.Path("/api/v1/settings").
// JAX-RS does not honour @Path via meta-annotations, so the path is not inherited from here.
@Tag(
name = "Settings",
description =
@@ -5,8 +5,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -16,8 +14,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/team")
// MIGRATION (Spring->JAX-RS): controllers using this annotation must declare @jakarta.ws.rs.Path("/api/v1/team").
// JAX-RS does not honour @Path via meta-annotations, so the path is not inherited from here.
@Tag(
name = "Team",
description =
@@ -5,8 +5,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -16,8 +14,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/ui-data")
// MIGRATION (Spring->JAX-RS): controllers using this annotation must declare @jakarta.ws.rs.Path("/api/v1/ui-data").
// JAX-RS does not honour @Path via meta-annotations, so the path is not inherited from here.
@Tag(
name = "UI Data",
description =
@@ -5,8 +5,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -16,8 +14,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/user")
// MIGRATION (Spring->JAX-RS): controllers using this annotation must declare @jakarta.ws.rs.Path("/api/v1/user").
// JAX-RS does not honour @Path via meta-annotations, so the path is not inherited from here.
@Tag(
name = "User",
description =
@@ -5,8 +5,8 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Set;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.stereotype.Component;
import jakarta.annotation.PreDestroy;
import jakarta.enterprise.context.ApplicationScoped;
import lombok.extern.slf4j.Slf4j;
@@ -14,12 +14,12 @@ import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.TempFileRegistry;
/**
* Handles cleanup of temporary files on application shutdown. Implements Spring's DisposableBean
* interface to ensure cleanup happens during normal application shutdown.
* Handles cleanup of temporary files on application shutdown. Uses a CDI {@code @PreDestroy} method
* (migrated from Spring's {@code DisposableBean}) to ensure cleanup happens during normal shutdown.
*/
@Slf4j
@Component
public class TempFileShutdownHook implements DisposableBean {
@ApplicationScoped
public class TempFileShutdownHook {
private final TempFileRegistry registry;
@@ -31,8 +31,8 @@ public class TempFileShutdownHook implements DisposableBean {
Runtime.getRuntime().addShutdownHook(new Thread(this::cleanupTempFiles));
}
/** Spring's DisposableBean interface method. Called during normal application shutdown. */
@Override
/** CDI pre-destroy callback (was DisposableBean#destroy). Called during normal shutdown. */
@PreDestroy
public void destroy() {
log.info("Application shutting down, cleaning up temporary files");
cleanupTempFiles();
@@ -1,13 +1,13 @@
package stirling.software.common.configuration;
import org.springframework.stereotype.Component;
import jakarta.enterprise.context.ApplicationScoped;
import com.posthog.java.PostHogLogger;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
@ApplicationScoped
public class PostHogLoggerImpl implements PostHogLogger {
@Override
@@ -1,23 +1,19 @@
package stirling.software.common.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.SimpleAsyncTaskScheduler;
/**
* Configures the scheduler used by all {@code @Scheduled} methods. Uses virtual threads so that
* long-running scheduled tasks (e.g. cleanup, license checks, file monitoring) never block each
* other — each runs on its own lightweight virtual thread.
*
* <p>MIGRATION (Spring -> Quarkus): the custom Spring {@code TaskScheduler} bean has been removed.
* Quarkus' {@code quarkus-scheduler} extension owns the scheduling thread pool, so no application
* bean is required. To keep the "each scheduled task on its own virtual thread" behaviour, annotate
* the individual {@code @io.quarkus.scheduler.Scheduled} methods with
* {@code @io.smallrye.common.annotation.RunOnVirtualThread} (or configure
* {@code quarkus.scheduler.use-virtual-threads=true} where supported).
*
* <p>TODO: Migration required - any injection point that received the former Spring
* {@code TaskScheduler} bean must be rewritten to use the Quarkus scheduler API or a CDI-managed
* {@code java.util.concurrent.ScheduledExecutorService}.
*/
@Configuration
public class SchedulingConfig {
@Bean
public TaskScheduler taskScheduler() {
SimpleAsyncTaskScheduler scheduler = new SimpleAsyncTaskScheduler();
scheduler.setVirtualThreads(true);
scheduler.setThreadNamePrefix("scheduled-vt-");
return scheduler;
}
}
public class SchedulingConfig {}
@@ -9,7 +9,7 @@ import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationText;
import org.springframework.stereotype.Service;
import jakarta.enterprise.context.ApplicationScoped;
import lombok.extern.slf4j.Slf4j;
@@ -28,7 +28,7 @@ import stirling.software.common.model.api.comments.StickyNoteSpec;
* </ul>
*/
@Slf4j
@Service
@ApplicationScoped
public class PdfAnnotationService {
/** Yellow sticky-note fill colour (R, G, B in 0..1 range). */
@@ -7,26 +7,31 @@ import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Instance;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.PdfMetadata;
@Service
@ApplicationScoped
public class PdfMetadataService {
private final ApplicationProperties applicationProperties;
private final String stirlingPDFLabel;
private final UserServiceInterface userService;
// MIGRATION: Spring's @Autowired(required=false) optional bean -> CDI Instance<> (UserServiceInterface
// is only present in security-enabled flavors). Resolve via isResolvable()/get().
private final Instance<UserServiceInterface> userService;
private final boolean runningProOrHigher;
@Inject
public PdfMetadataService(
ApplicationProperties applicationProperties,
@Qualifier("StirlingPDFLabel") String stirlingPDFLabel,
@Qualifier("runningProOrHigher") boolean runningProOrHigher,
@Autowired(required = false) UserServiceInterface userService) {
@Named("StirlingPDFLabel") String stirlingPDFLabel,
@Named("runningProOrHigher") boolean runningProOrHigher,
Instance<UserServiceInterface> userService) {
this.applicationProperties = applicationProperties;
this.stirlingPDFLabel = stirlingPDFLabel;
this.userService = userService;
@@ -168,8 +173,8 @@ public class PdfMetadataService {
.getCustomMetadata()
.getAuthor();
if (userService != null) {
String username = userService.getCurrentUsername();
if (userService.isResolvable()) {
String username = userService.get().getCurrentUsername();
if (username != null) {
author = author.replace("username", username);
}
@@ -15,39 +15,44 @@ import java.util.Map;
import java.util.TimeZone;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import org.eclipse.microprofile.config.Config;
import com.posthog.java.PostHog;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Instance;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import stirling.software.common.model.ApplicationProperties;
@Service
@ApplicationScoped
public class PostHogService {
private final PostHog postHog;
private final String uniqueId;
private final String appVersion;
private final ApplicationProperties applicationProperties;
private final UserServiceInterface userService;
private final Environment env;
// MIGRATION: optional bean (@Autowired(required=false)) -> CDI Instance<>.
private final Instance<UserServiceInterface> userService;
// MIGRATION: Spring Environment -> MicroProfile Config.
private final Config config;
private boolean configDirMounted;
@Inject
public PostHogService(
PostHog postHog,
@Qualifier("UUID") String uuid,
@Qualifier("configDirMounted") boolean configDirMounted,
@Qualifier("appVersion") String appVersion,
@Named("UUID") String uuid,
@Named("configDirMounted") boolean configDirMounted,
@Named("appVersion") String appVersion,
ApplicationProperties applicationProperties,
@Autowired(required = false) UserServiceInterface userService,
Environment env) {
Instance<UserServiceInterface> userService,
Config config) {
this.postHog = postHog;
this.uniqueId = uuid;
this.appVersion = appVersion;
this.applicationProperties = applicationProperties;
this.userService = userService;
this.env = env;
this.config = config;
this.configDirMounted = configDirMounted;
captureSystemInfo();
}
@@ -79,7 +84,8 @@ public class PostHogService {
// Application version
metrics.put("app_version", appVersion);
String deploymentType = "JAR"; // default
if ("true".equalsIgnoreCase(env.getProperty("BROWSER_OPEN"))) {
if ("true".equalsIgnoreCase(
config.getOptionalValue("BROWSER_OPEN", String.class).orElse(null))) {
deploymentType = "EXE";
} else if (isRunningInDocker()) {
deploymentType = "DOCKER";
@@ -148,8 +154,8 @@ public class PostHogService {
}
metrics.put("application_properties", captureApplicationProperties());
if (userService != null) {
metrics.put("total_users_created", userService.getTotalUsersCount());
if (userService.isResolvable()) {
metrics.put("total_users_created", userService.get().getTotalUsersCount());
}
} catch (Exception e) {
@@ -11,11 +11,11 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.enterprise.context.ApplicationScoped;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@@ -24,24 +24,24 @@ import lombok.extern.slf4j.Slf4j;
* Monitors system resources (CPU, memory) to inform job scheduling decisions. Provides information
* about available resources to prevent overloading the system.
*/
@Service
@ApplicationScoped
@Slf4j
public class ResourceMonitor {
@Value("${stirling.resource.memory.critical-threshold:0.9}")
private double memoryCriticalThreshold = 0.9; // 90% usage is critical
@ConfigProperty(name = "stirling.resource.memory.critical-threshold", defaultValue = "0.9")
double memoryCriticalThreshold; // 90% usage is critical
@Value("${stirling.resource.memory.high-threshold:0.75}")
private double memoryHighThreshold = 0.75; // 75% usage is high
@ConfigProperty(name = "stirling.resource.memory.high-threshold", defaultValue = "0.75")
double memoryHighThreshold; // 75% usage is high
@Value("${stirling.resource.cpu.critical-threshold:0.9}")
private double cpuCriticalThreshold = 0.9; // 90% usage is critical
@ConfigProperty(name = "stirling.resource.cpu.critical-threshold", defaultValue = "0.9")
double cpuCriticalThreshold; // 90% usage is critical
@Value("${stirling.resource.cpu.high-threshold:0.75}")
private double cpuHighThreshold = 0.75; // 75% usage is high
@ConfigProperty(name = "stirling.resource.cpu.high-threshold", defaultValue = "0.75")
double cpuHighThreshold; // 75% usage is high
@Value("${stirling.resource.monitor.interval-ms:60000}")
private long monitorIntervalMs = 60000; // 60 seconds
@ConfigProperty(name = "stirling.resource.monitor.interval-ms", defaultValue = "60000")
long monitorIntervalMs; // 60 seconds
private final ScheduledExecutorService scheduler =
Executors.newSingleThreadScheduledExecutor(
@@ -8,7 +8,7 @@ import java.net.UnknownHostException;
import java.util.Locale;
import java.util.regex.Pattern;
import org.springframework.stereotype.Service;
import jakarta.enterprise.context.ApplicationScoped;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -16,7 +16,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.util.RegexPatternUtils;
@Service
@ApplicationScoped
@RequiredArgsConstructor
@Slf4j
public class SsrfProtectionService {
@@ -11,12 +11,12 @@ import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import io.quarkus.scheduler.Scheduled;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -32,7 +32,7 @@ import stirling.software.common.util.TempFileRegistry;
* and directories.
*/
@Slf4j
@Service
@ApplicationScoped
@RequiredArgsConstructor
public class TempFileCleanupService {
@@ -40,9 +40,9 @@ public class TempFileCleanupService {
private final TempFileManager tempFileManager;
private final ApplicationProperties applicationProperties;
@Autowired
@Qualifier("machineType")
private String machineType;
@Inject
@Named("machineType")
String machineType;
// Maximum recursion depth for directory traversal
private static final int MAX_RECURSION_DEPTH = 5;
@@ -127,11 +127,17 @@ public class TempFileCleanupService {
}
}
/** Scheduled task to clean up old temporary files. Runs at the configured interval. */
@Scheduled(
fixedDelayString =
"#{applicationProperties.system.tempFileManagement.cleanupIntervalMinutes}",
timeUnit = TimeUnit.MINUTES)
/**
* Scheduled task to clean up old temporary files. Runs at the configured interval.
*
* <p>TODO: Migration required - the Spring form used a SpEL expression
* ({@code fixedDelayString="#{applicationProperties.system.tempFileManagement.cleanupIntervalMinutes}"}).
* Quarkus {@code @Scheduled} cannot reference an arbitrary bean property; {@code every} only
* resolves a MicroProfile Config placeholder. The cleanup interval must therefore be exposed as a
* config key (e.g. {@code stirling.temp.cleanup-interval}) bound to the same value, and the
* minutes->duration mapping handled in config. Default below is 30m.
*/
@Scheduled(every = "{stirling.temp.cleanup-interval:30m}")
public void scheduledCleanup() {
log.info("Running scheduled temporary file cleanup");
long maxAgeMillis = tempFileManager.getMaxAgeMillis();
@@ -3,27 +3,33 @@ package stirling.software.common.util;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import io.quarkus.runtime.StartupEvent;
import io.quarkus.runtime.annotations.CommandLineArguments;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j;
/**
* Captures application command-line arguments at startup so they can be reused for restart
* operations. This allows the application to restart with the same configuration.
*
* <p>MIGRATION (Spring -> Quarkus): replaced Spring's {@code ApplicationRunner}/{@code
* ApplicationArguments} with a CDI startup observer ({@code @Observes StartupEvent}) and Quarkus'
* {@code @CommandLineArguments String[]} injection.
*/
@Slf4j
@Component
public class AppArgsCapture implements ApplicationRunner {
@ApplicationScoped
public class AppArgsCapture {
public static final AtomicReference<List<String>> APP_ARGS = new AtomicReference<>(List.of());
@Override
public void run(ApplicationArguments args) {
APP_ARGS.set(List.of(args.getSourceArgs()));
log.debug(
"Captured {} application arguments for restart capability",
args.getSourceArgs().length);
@Inject @CommandLineArguments String[] args;
void onStart(@Observes StartupEvent event) {
APP_ARGS.set(List.of(args));
log.debug("Captured {} application arguments for restart capability", args.length);
}
}
@@ -4,12 +4,12 @@ import org.owasp.html.AttributePolicy;
import org.owasp.html.HtmlPolicyBuilder;
import org.owasp.html.PolicyFactory;
import org.owasp.html.Sanitizers;
import org.springframework.stereotype.Component;
import jakarta.enterprise.context.ApplicationScoped;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.service.SsrfProtectionService;
@Component
@ApplicationScoped
public class CustomHtmlSanitizer {
private final SsrfProtectionService ssrfProtectionService;
@@ -12,15 +12,16 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import io.quarkus.scheduler.Scheduled;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Named;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.configuration.RuntimePathConfig;
@Component
@ApplicationScoped
@Slf4j
public class FileMonitor {
@@ -38,7 +39,7 @@ public class FileMonitor {
* monitored, false otherwise
*/
public FileMonitor(
@Qualifier("directoryFilter") Predicate<Path> pathFilter,
@Named("directoryFilter") Predicate<Path> pathFilter,
RuntimePathConfig runtimePathConfig)
throws IOException {
this.newlyDiscoveredFiles = new HashSet<>();
@@ -106,7 +107,7 @@ public class FileMonitor {
}
}
@Scheduled(fixedRate = 5000)
@Scheduled(every = "5s")
public void trackFiles() {
/*
All files observed changes in the last iteration will be considered as staging files.
@@ -10,7 +10,7 @@ import java.nio.file.Path;
import java.util.List;
import java.util.Locale;
import org.springframework.stereotype.Component;
import jakarta.enterprise.context.ApplicationScoped;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -40,7 +40,7 @@ import stirling.software.common.model.ApplicationProperties.AutoPipeline.FileRea
* ApplicationProperties.AutoPipeline}. Setting {@code enabled: false} makes every call return
* {@code true} so the checker is a no-op drop-in.
*/
@Component
@ApplicationScoped
@RequiredArgsConstructor
@Slf4j
public class FileReadinessChecker {
@@ -22,7 +22,7 @@ import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.springframework.stereotype.Component;
import jakarta.enterprise.context.ApplicationScoped;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
@@ -38,7 +38,7 @@ import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.service.SsrfProtectionService;
// Strips external refs from OOXML/ODF uploads so LibreOffice can't be made to fetch them.
@Component
@ApplicationScoped
@Slf4j
public class OfficeDocumentSanitizer {
@@ -5,13 +5,13 @@ import java.util.List;
import org.apache.pdfbox.multipdf.PDFMergerUtility;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.stereotype.Service;
import jakarta.enterprise.context.ApplicationScoped;
import lombok.RequiredArgsConstructor;
import stirling.software.common.service.CustomPDFDocumentFactory;
@Service
@ApplicationScoped
@RequiredArgsConstructor
public class PDFService {
@@ -10,7 +10,7 @@ import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.TextPosition;
import org.springframework.stereotype.Component;
import jakarta.enterprise.context.ApplicationScoped;
import lombok.extern.slf4j.Slf4j;
@@ -25,7 +25,7 @@ import lombok.extern.slf4j.Slf4j;
* "Total Revenue"} matches {@code "Total Revenue."}.
*/
@Slf4j
@Component
@ApplicationScoped
public class PdfTextLocator {
/** One found line of text with its user-space bounding box. */
@@ -20,7 +20,7 @@ import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.springframework.stereotype.Component;
import jakarta.enterprise.context.ApplicationScoped;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
@@ -34,7 +34,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.service.SsrfProtectionService;
@Component
@ApplicationScoped
@RequiredArgsConstructor
@Slf4j
public class SvgSanitizer {
@@ -11,7 +11,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
import org.springframework.stereotype.Component;
import jakarta.enterprise.context.ApplicationScoped;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@@ -21,7 +21,7 @@ import lombok.extern.slf4j.Slf4j;
* collection of paths with their creation timestamps.
*/
@Slf4j
@Component
@ApplicationScoped
public class TempFileRegistry {
private final ConcurrentMap<Path, Instant> registeredFiles = new ConcurrentHashMap<>();