This is an automated email from the ASF dual-hosted git repository.

fmariani pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-spring-boot.git


The following commit(s) were added to refs/heads/main by this push:
     new 583e137550a CAMEL-20371: Asynchrnous Camel health checks
583e137550a is described below

commit 583e137550a75fa65ee552cdec2358b112bd6c73
Author: Croway <federico.mariani.1...@gmail.com>
AuthorDate: Fri Jan 26 16:32:16 2024 +0100

    CAMEL-20371: Asynchrnous Camel health checks
---
 .../src/main/docs/spring-boot.adoc                 |  18 +++
 .../src/main/docs/spring-boot.json                 |  28 +++++
 .../AsyncHealthIndicatorAutoConfiguration.java     | 126 +++++++++++++++++++++
 .../CamelHealthCheckConfigurationProperties.java   |  54 +++++++++
 ...rk.boot.autoconfigure.AutoConfiguration.imports |   1 +
 5 files changed, 227 insertions(+)

diff --git a/core/camel-spring-boot/src/main/docs/spring-boot.adoc 
b/core/camel-spring-boot/src/main/docs/spring-boot.adoc
index e29685efea7..684bbc10ca9 100644
--- a/core/camel-spring-boot/src/main/docs/spring-boot.adoc
+++ b/core/camel-spring-boot/src/main/docs/spring-boot.adoc
@@ -417,6 +417,24 @@ or by providing GraalVM JSON hint files that can be 
generated by the https://doc
 
 For more details about `GraalVM Native Image Support` in Spring Boot please 
refer to 
https://docs.spring.io/spring-boot/docs/current/reference/html/native-image.html
 
+== Camel Asynchronous Health Checks
+
+Camel health checks can be executed asynchronously via a Task Scheduler so 
that the result can be cached and the actual health check is executed in 
background every few seconds. Asynchronous Camel health checks are disabled by 
default but can be enabled with the following property:
+
+[source,properties]
+----
+camel.health.async-camel-health-check=true
+----
+
+moreover the Camel health check task scheduler can be customized with the 
following properties:
+
+[source,properties]
+----
+camel.health.healthCheckPoolSize=5
+camel.health.healthCheckFrequency=10
+camel.health.healthCheckThreadNamePrefix=CamelHealthTaskScheduler
+----
+
 == Camel Readiness and Liveness State Indicators
 
 Camel specific Readiness and Liveness checks can be added to a Spring Boot 3 
application including respectively in the
diff --git a/core/camel-spring-boot/src/main/docs/spring-boot.json 
b/core/camel-spring-boot/src/main/docs/spring-boot.json
index 878e51ec9a0..8ddf6bb8106 100644
--- a/core/camel-spring-boot/src/main/docs/spring-boot.json
+++ b/core/camel-spring-boot/src/main/docs/spring-boot.json
@@ -513,6 +513,13 @@
       "sourceType": 
"org.apache.camel.spring.boot.debug.CamelDebugConfigurationProperties",
       "defaultValue": false
     },
+    {
+      "name": "camel.health.async-camel-health-check",
+      "type": "java.lang.Boolean",
+      "description": "Whether Camel Health Checks are executed asynchronously 
<p> disabled by default",
+      "sourceType": 
"org.apache.camel.spring.boot.actuate.health.CamelHealthCheckConfigurationProperties",
+      "defaultValue": false
+    },
     {
       "name": "camel.health.consumers-enabled",
       "type": "java.lang.Boolean",
@@ -538,6 +545,27 @@
       "sourceType": 
"org.apache.camel.spring.boot.actuate.health.CamelHealthCheckConfigurationProperties",
       "defaultValue": "default"
     },
+    {
+      "name": "camel.health.health-check-frequency",
+      "type": "java.lang.Integer",
+      "description": "Camel's HealthCheck frequency in seconds",
+      "sourceType": 
"org.apache.camel.spring.boot.actuate.health.CamelHealthCheckConfigurationProperties",
+      "defaultValue": 10
+    },
+    {
+      "name": "camel.health.health-check-pool-size",
+      "type": "java.lang.Integer",
+      "description": "Camel HealthCheck pool size",
+      "sourceType": 
"org.apache.camel.spring.boot.actuate.health.CamelHealthCheckConfigurationProperties",
+      "defaultValue": 5
+    },
+    {
+      "name": "camel.health.health-check-thread-name-prefix",
+      "type": "java.lang.String",
+      "description": "Camel HealthCheck thread name prefix",
+      "sourceType": 
"org.apache.camel.spring.boot.actuate.health.CamelHealthCheckConfigurationProperties",
+      "defaultValue": "CamelHealthTaskScheduler"
+    },
     {
       "name": "camel.health.initial-state",
       "type": "java.lang.String",
diff --git 
a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/health/AsyncHealthIndicatorAutoConfiguration.java
 
b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/health/AsyncHealthIndicatorAutoConfiguration.java
new file mode 100644
index 00000000000..0a58d9e3b3c
--- /dev/null
+++ 
b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/health/AsyncHealthIndicatorAutoConfiguration.java
@@ -0,0 +1,126 @@
+package org.apache.camel.spring.boot.actuate.health;
+
+import 
org.apache.camel.spring.boot.actuate.health.liveness.CamelLivenessStateHealthIndicator;
+import 
org.apache.camel.spring.boot.actuate.health.readiness.CamelReadinessStateHealthIndicator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.boot.actuate.health.Health;
+import org.springframework.boot.actuate.health.HealthContributorRegistry;
+import org.springframework.boot.actuate.health.HealthIndicator;
+import org.springframework.boot.actuate.health.NamedContributor;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.TaskScheduler;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+
+import java.time.Duration;
+import java.time.ZonedDateTime;
+
+/**
+ * Configuration class that replace synchronous Camel Health Checks with 
asynchronous ones.
+ *
+ * This implementation is based on 
https://github.com/spring-projects/spring-boot/issues/2652 that most probably
+ * will be added in spring boot 3.2.x as a new feature in the future.
+ *
+ * TODO: To be refactored once async health contributors feature will be added 
in spring boot.
+ *
+ */
+@Configuration
+@ConditionalOnProperty(prefix = "camel.health", name = 
"async-camel-health-check", havingValue = "true")
+public class AsyncHealthIndicatorAutoConfiguration implements InitializingBean 
{
+       private static final Logger log = 
LoggerFactory.getLogger(AsyncHealthIndicatorAutoConfiguration.class);
+
+       private HealthContributorRegistry healthContributorRegistry;
+       private TaskScheduler taskScheduler;
+       private CamelHealthCheckConfigurationProperties config;
+
+       public AsyncHealthIndicatorAutoConfiguration(HealthContributorRegistry 
healthContributorRegistry,
+                                                                               
                 CamelHealthCheckConfigurationProperties config) {
+               this.healthContributorRegistry = healthContributorRegistry;
+               this.config = config;
+
+               ThreadPoolTaskScheduler threadPoolTaskScheduler = new 
ThreadPoolTaskScheduler();
+               
threadPoolTaskScheduler.setPoolSize(config.getHealthCheckPoolSize());
+               
threadPoolTaskScheduler.setThreadNamePrefix(config.getHealthCheckThreadNamePrefix());
+               threadPoolTaskScheduler.initialize();
+               taskScheduler = threadPoolTaskScheduler;
+       }
+
+       @Override
+       public void afterPropertiesSet() throws Exception {
+               for (NamedContributor<?> namedContributor : 
healthContributorRegistry) {
+                       final String name = namedContributor.getName();
+                       final Object contributor = 
namedContributor.getContributor();
+                       if (contributor instanceof CamelHealthCheckIndicator
+                               || contributor instanceof 
CamelLivenessStateHealthIndicator
+                               || contributor instanceof 
CamelReadinessStateHealthIndicator) {
+                               HealthIndicator camelHealthCheckIndicator = 
(HealthIndicator) contributor;
+                               
healthContributorRegistry.unregisterContributor(name);
+                               log.debug(
+                                               "Wrapping " + 
contributor.getClass().getSimpleName() + " for async health scheduling");
+                               WrappedHealthIndicator wrappedHealthIndicator =
+                                               new 
WrappedHealthIndicator(camelHealthCheckIndicator);
+                               
healthContributorRegistry.registerContributor(name, wrappedHealthIndicator);
+                               taskScheduler.scheduleWithFixedDelay(
+                                               wrappedHealthIndicator, 
Duration.ofSeconds(config.getHealthCheckFrequency()));
+                       }
+               }
+       }
+
+       /**
+        * Health Check Indicator that executes Health Checks within a Task 
Scheduler
+        */
+       private static class WrappedHealthIndicator implements HealthIndicator, 
Runnable {
+               private static final String LAST_CHECKED_KEY = "lastChecked";
+               private static final String LAST_DURATION_KEY = "lastDuration";
+
+               private HealthIndicator wrappedHealthIndicator;
+
+               private Health lastHealth;
+
+               public WrappedHealthIndicator(HealthIndicator 
wrappedHealthIndicator) {
+                       this.wrappedHealthIndicator = wrappedHealthIndicator;
+               }
+
+               @Override
+               public Health health() {
+                       Health lastHealth = getLastHealth();
+                       if (lastHealth == null) {
+                               setLastHealth(getAndWrapHealth());
+                               lastHealth = getLastHealth();
+                       }
+
+                       return lastHealth;
+               }
+
+               private Health getAndWrapHealth() {
+                       ZonedDateTime startTime = ZonedDateTime.now();
+                       Health baseHealth = 
getWrappedHealthIndicator().health();
+                       ZonedDateTime endTime = ZonedDateTime.now();
+                       Duration duration = Duration.between(startTime, 
endTime);
+                       return Health.status(baseHealth.getStatus())
+                                       .withDetails(baseHealth.getDetails())
+                                       .withDetail(LAST_CHECKED_KEY, startTime)
+                                       .withDetail(LAST_DURATION_KEY, duration)
+                                       .build();
+               }
+
+               @Override
+               public void run() {
+                       setLastHealth(getAndWrapHealth());
+               }
+
+               public HealthIndicator getWrappedHealthIndicator() {
+                       return wrappedHealthIndicator;
+               }
+
+               public Health getLastHealth() {
+                       return lastHealth;
+               }
+
+               public void setLastHealth(Health lastHealth) {
+                       this.lastHealth = lastHealth;
+               }
+       }
+}
diff --git 
a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/health/CamelHealthCheckConfigurationProperties.java
 
b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/health/CamelHealthCheckConfigurationProperties.java
index d4c7f6c47c6..fd45283a36b 100644
--- 
a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/health/CamelHealthCheckConfigurationProperties.java
+++ 
b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/health/CamelHealthCheckConfigurationProperties.java
@@ -63,6 +63,28 @@ public class CamelHealthCheckConfigurationProperties {
      */
     private String excludePattern;
 
+    /**
+     * Camel's HealthCheck frequency in seconds
+     */
+    private int healthCheckFrequency = 10;
+
+    /**
+     * Camel HealthCheck pool size
+     */
+    private int healthCheckPoolSize = 5;
+
+    /**
+     * Camel HealthCheck thread name prefix
+     */
+    private String healthCheckThreadNamePrefix = "CamelHealthTaskScheduler";
+
+    /**
+     * Whether Camel Health Checks are executed asynchronously
+     * <p>
+     * disabled by default
+     */
+    private boolean asyncCamelHealthCheck = false;
+
     /**
      * Sets the level of details to exposure as result of invoking health 
checks. There are the following levels: full,
      * default, oneline
@@ -158,6 +180,38 @@ public class CamelHealthCheckConfigurationProperties {
     public void setInitialState(String initialState) {
         this.initialState = initialState;
     }
+
+    public int getHealthCheckFrequency() {
+        return healthCheckFrequency;
+    }
+
+    public void setHealthCheckFrequency(int healthCheckFrequency) {
+        this.healthCheckFrequency = healthCheckFrequency;
+    }
+
+    public int getHealthCheckPoolSize() {
+        return healthCheckPoolSize;
+    }
+
+    public void setHealthCheckPoolSize(int healthCheckPoolSize) {
+        this.healthCheckPoolSize = healthCheckPoolSize;
+    }
+
+    public String getHealthCheckThreadNamePrefix() {
+        return healthCheckThreadNamePrefix;
+    }
+
+    public void setHealthCheckThreadNamePrefix(String 
healthCheckThreadNamePrefix) {
+        this.healthCheckThreadNamePrefix = healthCheckThreadNamePrefix;
+    }
+
+    public boolean isAsyncCamelHealthCheck() {
+        return asyncCamelHealthCheck;
+    }
+
+    public void setAsyncCamelHealthCheck(boolean asyncCamelHealthCheck) {
+        this.asyncCamelHealthCheck = asyncCamelHealthCheck;
+    }
 }
 
 
diff --git 
a/core/camel-spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
 
b/core/camel-spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
index 13a8efb52ca..cf103fe6f22 100644
--- 
a/core/camel-spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ 
b/core/camel-spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -19,6 +19,7 @@ org.apache.camel.spring.boot.CamelAutoConfiguration
 org.apache.camel.spring.boot.actuate.console.CamelDevConsoleAutoConfiguration
 
org.apache.camel.spring.boot.actuate.endpoint.CamelRouteControllerEndpointAutoConfiguration
 
org.apache.camel.spring.boot.actuate.endpoint.CamelRoutesEndpointAutoConfiguration
+org.apache.camel.spring.boot.actuate.health.AsyncHealthIndicatorAutoConfiguration
 org.apache.camel.spring.boot.actuate.health.CamelHealthCheckAutoConfiguration
 
org.apache.camel.spring.boot.actuate.health.CamelAvailabilityCheckAutoConfiguration
 org.apache.camel.spring.boot.actuate.info.CamelInfoAutoConfiguration

Reply via email to