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

cziegeler pushed a commit to branch issue-6768
in repository https://gitbox.apache.org/repos/asf/felix-dev.git


The following commit(s) were added to refs/heads/issue-6768 by this push:
     new deea465d54 FELIX-6768 : Support OSGi Conditions
deea465d54 is described below

commit deea465d541f66e589780d214281bf23f9398749
Author: Carsten Ziegeler <cziege...@apache.org>
AuthorDate: Sat Apr 12 16:39:03 2025 +0200

    FELIX-6768 : Support OSGi Conditions
---
 healthcheck/README.md                              | 116 +++++++++++++--------
 healthcheck/core/pom.xml                           |  19 ++--
 .../felix/hc/core/impl/monitor/HealthState.java    |  39 ++++---
 .../hc/core/impl/CompositeHealthCheckTest.java     |  19 +++-
 .../core/impl/monitor/HealthCheckMonitorTest.java  |  88 ++++++++++------
 5 files changed, 174 insertions(+), 107 deletions(-)

diff --git a/healthcheck/README.md b/healthcheck/README.md
index af8418e2c1..02fbaff9e3 100644
--- a/healthcheck/README.md
+++ b/healthcheck/README.md
@@ -34,11 +34,11 @@ The strength of Health Checks are to surface internal state 
for external use:
 * Check that all OSGi bundles are up and running
 * Verify that performance counters are in range
 * Ping external systems and raise alarms if they are down
-* Run smoke tests at system startup 
+* Run smoke tests at system startup
 * Check that demo content has been removed from a production system
 * Check that demo accounts are disabled
 
-The health check subsystem uses tags to select which health checks to execute 
so you can for example execute just the _performance_ or _security_ health 
+The health check subsystem uses tags to select which health checks to execute 
so you can for example execute just the _performance_ or _security_ health
 checks once they are configured with the corresponding tags.
 
 The out of the box health check services also allow for using them as JMX 
aggregators and processors, which take JMX
@@ -54,15 +54,15 @@ A `HealthCheck` is just an OSGi service that returns a 
`Result`.
 
 ```
     public interface HealthCheck {
-        
-        /** Execute this health check and return a {@link Result} 
+
+        /** Execute this health check and return a {@link Result}
          *  This is meant to execute quickly, access to external
          *  systems, for example, should be managed asynchronously.
          */
         public Result execute();
     }
 ```
-    
+
 A simple health check implementation might look like follows:
 
 ```
@@ -93,9 +93,9 @@ Instead of using Log4j side by side with 
ResultLog/FormattingResultLog it is rec
 ### Semantic meaning of health check results
 In order to make health check results aggregatable in a reasonable way, it is 
important that result status values are used in a consistent way across 
different checks. When implementing custom health checks, comply to the 
following table:
 
-Status | System is functional | Meaning | Actions possible for machine clients 
| Actions possible for human clients  
---- | --- | --- | --- | ---  
-OK | yes | Everything is ok. | <ul><li>If system is not actively used yet, a 
load balancer might decide to take the system to production after receiving 
this status for the first time.</li><li>Otherwise no action needed</li></ul> | 
Response logs might still provide information to a human on why the system 
currently is healthy. E.g. it might show 30% disk used which indicates that no 
action will be required for a long time  
+Status | System is functional | Meaning | Actions possible for machine clients 
| Actions possible for human clients
+--- | --- | --- | --- | ---
+OK | yes | Everything is ok. | <ul><li>If system is not actively used yet, a 
load balancer might decide to take the system to production after receiving 
this status for the first time.</li><li>Otherwise no action needed</li></ul> | 
Response logs might still provide information to a human on why the system 
currently is healthy. E.g. it might show 30% disk used which indicates that no 
action will be required for a long time
 WARN | yes | **Tendency to CRITICAL** <br>System is fully functional but 
actions are needed to avoid a CRITICAL status in the future | <ul><li>Certain 
actions can be configured for known, actionable warnings, e.g. if disk space is 
low, it could be dynamically extended using infrastructure APIs if on virtual 
infrastructure)</li><li>Pass on information to monitoring system to be 
available to humans (in other aggregator UIs)</li></ul> | Any manual steps that 
a human can perform based on the [...]
 TEMPORARILY_UNAVAILABLE *) | no | **Tendency to OK** <br>System is not 
functional at the moment but is expected to become OK (or at least WARN) 
without action. An health check using this status is expected to turn CRITICAL 
after a certain period returning TEMPORARILY_UNAVAILABLE | <ul><li>Take out 
system from load balancing</li><li>Wait until TEMPORARILY_UNAVAILABLE status 
turns into either OK or CRITICAL</li></ul> | Wait and monitor result logs of 
health check returning TEMPORARILY_UNAVAILABLE
 CRITICAL | no | System is not functional and must not be used | <ul><li>Take 
out system from load balancing</li><li>Decommission system entirely and 
re-provision from scratch</li></ul>  | Any manual steps that a human can 
perform based on their knowledge to bring the system back to state OK
@@ -109,12 +109,12 @@ HEALTH\_CHECK\_ERROR | no | **Actual status unknown** 
<br>There was an error in
 
 The following generic Health Check properties may be used for all checks 
(**all service properties are optional**):
 
-Property    | Type     | Description  
+Property    | Type     | Description
 ----------- | -------- | ------------
 hc.name     | String   | The name of the health check as shown in UI
 hc.tags     | String[] | List of tags: Both Felix Console Plugin and Health 
Check servlet support selecting relevant checks by providing a list of tags
 hc.mbean.name | String | Makes the HC result available via given MBean name. 
If not provided no MBean is created for that `HealthCheck`
-hc.async.cronExpression | String | Executes the health check asynchronously 
using the cron expression provided. Use this for **long running health checks** 
to avoid execution every time the tag/name is queried. Prefer configuring a 
HealthCheckMonitor if you only want to regularly execute a HC. 
+hc.async.cronExpression | String | Executes the health check asynchronously 
using the cron expression provided. Use this for **long running health checks** 
to avoid execution every time the tag/name is queried. Prefer configuring a 
HealthCheckMonitor if you only want to regularly execute a HC.
 hc.async.intervalInSec | Long | Async execution like `hc.async.cronExpression` 
but using an interval
 hc.resultCacheTtlInMs | Long | Overrides the global default TTL as configured 
in health check executor for health check responses
 hc.keepNonOkResultsStickyForSec | Long | If given, non-ok results from past 
executions will be taken into account as well for the given seconds (use 
Long.MAX_VALUE for indefinitely). Useful for unhealthy system states that 
disappear but might leave the system at an inconsistent state (e.g. an event 
queue overflow where somebody needs to intervene manually) or for checks that 
should only go back to OK with a delay (can be useful for load balancers).
@@ -124,18 +124,18 @@ hc.keepNonOkResultsStickyForSec | Long | If given, non-ok 
results from past exec
 To configure the defaults for the service properties 
[above](#configuring-health-checks), the following annotations can be used:
 
     // standard OSGi
-    @Component 
-    @Designate(ocd = MyCustomCheckConfig.class, factory = true)  
-    
+    @Component
+    @Designate(ocd = MyCustomCheckConfig.class, factory = true)
+
     // to set `hc.name` and `hc.tags`
     @HealthCheckService(name = "Custom Check Name", tags= {"tag1", "tag2"})
-    
+
     // to set `hc.async.cronExpression` or  `hc.async.intervalInSec`
     @Async(cronExpression="0 0 12 1 * ?" /*, intervalInSec = 60 */)
-    
+
     // to set `hc.resultCacheTtlInMs`:
     @ResultTTL(resultCacheTtlInMs = 10000)
-    
+
     // to set `hc.mbean.name`:
     @HealthCheckMBean(name = "MyCustomCheck")
 
@@ -145,20 +145,20 @@ To configure the defaults for the service properties 
[above](#configuring-health
     ...
 
 
-## General purpose health checks available out-of-the-box 
+## General purpose health checks available out-of-the-box
 
 The following checks are contained in bundle 
`org.apache.felix.healthcheck.generalchecks` and can be activated by simple 
configuration:
 
-Check | PID | Factory | Description  
+Check | PID | Factory | Description
 --- | --- | --- | ---
-Framework Startlevel | org.apache.felix.hc.generalchecks.FrameworkStartCheck | 
no | Checks the OSGi framework startlevel - `targetStartLevel` allows to 
configure a target start level, `targetStartLevel.propName` can be used to read 
it from the framework/system properties. 
-Services Ready | org.apache.felix.hc.generalchecks.ServicesCheck | yes | 
Checks for the existance of the given services. `services.list` can contain 
simple service names or filter expressions 
-Components Ready | org.apache.felix.hc.generalchecks.DsComponentsCheck | yes | 
Checks for the existance of the given components. Use `components.list` to list 
required active components (use component names) 
-Bundles Started | org.apache.felix.hc.generalchecks.BundlesStartedCheck | yes 
| Checks for started bundles - `includesRegex` and `excludesRegex` control what 
bundles are checked. 
+Framework Startlevel | org.apache.felix.hc.generalchecks.FrameworkStartCheck | 
no | Checks the OSGi framework startlevel - `targetStartLevel` allows to 
configure a target start level, `targetStartLevel.propName` can be used to read 
it from the framework/system properties.
+Services Ready | org.apache.felix.hc.generalchecks.ServicesCheck | yes | 
Checks for the existance of the given services. `services.list` can contain 
simple service names or filter expressions
+Components Ready | org.apache.felix.hc.generalchecks.DsComponentsCheck | yes | 
Checks for the existance of the given components. Use `components.list` to list 
required active components (use component names)
+Bundles Started | org.apache.felix.hc.generalchecks.BundlesStartedCheck | yes 
| Checks for started bundles - `includesRegex` and `excludesRegex` control what 
bundles are checked.
 Disk Space | org.apache.felix.hc.generalchecks.DiskSpaceCheck | yes | Checks 
for disk space usage at the given paths `diskPaths` and checks them against 
thresholds `diskUsedThresholdWarn` (default 90%) and diskUsedThresholdCritical 
(default 97%)
 Memory | org.apache.felix.hc.generalchecks.MemoryCheck | no | Checks for 
Memory usage - `heapUsedPercentageThresholdWarn` (default 90%) and 
`heapUsedPercentageThresholdCritical` (default 99%) can be set to control what 
memory usage produces status `WARN` and `CRITICAL`
 CPU | org.apache.felix.hc.generalchecks.CpuCheck | no | Checks for CPU usage - 
`cpuPercentageThresholdWarn` (default 95%) can be set to control what CPU usage 
produces status `WARN` (check never results in `CRITICAL`)
-Thread Usage | org.apache.felix.hc.generalchecks.ThreadUsageCheck | no | 
Checks via `ThreadMXBean.findDeadlockedThreads()` for deadlocks and analyses 
the CPU usage of each thread via a configurable time period (`samplePeriodInMs` 
defaults to 200ms). Uses `cpuPercentageThresholdWarn` (default 95%) to `WARN` 
about high thread utilisation.   
+Thread Usage | org.apache.felix.hc.generalchecks.ThreadUsageCheck | no | 
Checks via `ThreadMXBean.findDeadlockedThreads()` for deadlocks and analyses 
the CPU usage of each thread via a configurable time period (`samplePeriodInMs` 
defaults to 200ms). Uses `cpuPercentageThresholdWarn` (default 95%) to `WARN` 
about high thread utilisation.
 JMX Attribute Check | org.apache.felix.hc.generalchecks.JmxAttributeCheck | 
yes | Allows to check an arbitrary JMX attribute (using the configured mbean 
`mbean.name`'s attribute `attribute.name`) against a given constraint 
`attribute.value.constraint` (see [Constraints](#constraints)). Can check 
multiple attributes by providing additional config properties with numbers: 
`mbean2.name` (defaults to `mbean.name` if ommitted), `attribute2.name` and 
`attribute2.value.constraint` and `mbean3.n [...]
 Http Requests Check | org.apache.felix.hc.generalchecks.HttpRequestsCheck | 
yes | Allows to check a list of URLs against response code, response headers, 
timing, response content (plain content via RegEx or JSON via path expression). 
See [Request Spec Syntax](#request-spec-syntax)
 Scripted Check | org.apache.felix.hc.generalchecks.ScriptedHealthCheck | yes | 
Allows to run an arbitrary script. To configure use either `script` to provide 
a script directly or `scriptUrl` to link to an external script (may be a file 
URL or a link to a JCR file if a Sling Repository exists, e.g. 
`jcr:/etc/hc/check1.groovy`). Use the `language` property to refer to a 
registered script engine (e.g. install bundle `groovy-all` to be able to use 
language `groovy`). The script has the bindi [...]
@@ -168,7 +168,7 @@ Scripted Check | 
org.apache.felix.hc.generalchecks.ScriptedHealthCheck | yes | A
 The `JMX Attribute Check` and `Http Requests Check` allow to check values 
against contraints. See the following examples:
 
 * value `string value` (checks for equality)
-* value ` = 0` 
+* value ` = 0`
 * value ` > 0`
 * value ` < 100`
 * value ` BETWEEN 3 AND 7`
@@ -183,7 +183,7 @@ Also see class 
`org.apache.felix.hc.generalchecks.util.SimpleConstraintsChecker`
 
 ### Request Spec Syntax
 
-The `Http Requests Check` allows to configure a list of request specs. 
Requests specs have two parts: Before `=>` can be a simple URL/path with 
curl-syntax advanced options (e.g. setting a header with `-H "Test: Test 
val"`), after the `=>` it is a simple response code that can be followed ` && 
MATCHES <RegEx>` to match the response entity against or other matchers like 
HEADER, TIME or JSON. 
+The `Http Requests Check` allows to configure a list of request specs. 
Requests specs have two parts: Before `=>` can be a simple URL/path with 
curl-syntax advanced options (e.g. setting a header with `-H "Test: Test 
val"`), after the `=>` it is a simple response code that can be followed ` && 
MATCHES <RegEx>` to match the response entity against or other matchers like 
HEADER, TIME or JSON.
 
 Examples:
 
@@ -205,9 +205,9 @@ This is a health check that can be dynamically controlled 
via JMX bean `org.apac
 
 ## Executing Health Checks
 
-Health Checks can be executed via a [webconsole plugin](#webconsole-plugin), 
the [health check servlet](#health-check-servlet) or via 
[JMX](#jmx-access-to-health-checks). `HealthCheck` services can be selected for 
execution based on their `hc.tags` multi-value service property. 
+Health Checks can be executed via a [webconsole plugin](#webconsole-plugin), 
the [health check servlet](#health-check-servlet) or via 
[JMX](#jmx-access-to-health-checks). `HealthCheck` services can be selected for 
execution based on their `hc.tags` multi-value service property.
 
-The `HealthCheckFilter` utility accepts positive and negative tag parameters, 
so that `osgi,-security` 
+The `HealthCheckFilter` utility accepts positive and negative tag parameters, 
so that `osgi,-security`
 selects all `HealthCheck` having the `osgi` tag but not the `security` tag, 
for example.
 
 For advanced use cases it is also possible to use the API directly by using 
the interface `org.apache.felix.hc.api.execution.HealthCheckExecutor`.
@@ -216,7 +216,7 @@ For advanced use cases it is also possible to use the API 
directly by using the
 
 The health check executor can **optionally** be configured via service PID 
`org.apache.felix.hc.core.impl.executor.HealthCheckExecutorImpl`:
 
-Property    | Type     | Default | Description  
+Property    | Type     | Default | Description
 ----------- | -------- | ------ | ------------
 `timeoutInMs`    | Long   | 2000ms | Timeout in ms until a check is marked as 
timed out
 `longRunningFutureThresholdForCriticalMs` | Long | 300000ms (5min) | Threshold 
in ms until a check is marked as 'exceedingly' timed out and will marked 
CRITICAL instead of WARN only
@@ -248,7 +248,7 @@ By default the HC servlet sends the CORS header 
`Access-Control-Allow-Origin: *`
 
 ### Webconsole plugin
 
-If the `org.apache.felix.hc.webconsole` bundle is installed, a webconsole 
plugin 
+If the `org.apache.felix.hc.webconsole` bundle is installed, a webconsole 
plugin
 at `/system/console/healthcheck` allows for executing health checks, 
optionally selected
 based on their tags (positive and negative selection, see the 
`HealthCheckFilter` mention above).
 
@@ -261,7 +261,7 @@ The Gogo command `hc:exec` can be used as follows:
     hc:exec [-v] [-a] tag1,tag2
       -v verbose/debug
       -a combine tags with and logic (instead of or logic)
-      
+
 The command is available without installing additional bundles (it is included 
in the core bundle `org.apache.felix.healthcheck.core`)
 
 ## Monitoring Health Checks
@@ -270,7 +270,7 @@ The command is available without installing additional 
bundles (it is included i
 
 By default, health checks are only executed if explicitly triggered via one of 
the mechanisms as described in [Executing Health 
Checks](#executing-health-checks) (servlet, web console plugin, JMX, executor 
API). With the `HealthCheckMonitor`, Health checks can be regularly monitored 
by configuring the the **factory PID** 
`org.apache.felix.hc.core.impl.monitor.HealthCheckMonitor` with the following 
properties:
 
-Property    | Type     | Default | Description  
+Property    | Type     | Default | Description
 ----------- | -------- | ------ | ------------
 `tags` and/or `names` | String[] | none, at least one of the two is required | 
**Will regularly call all given tags and/or names**. All given tags/names are 
executed in parallel. If the set of tags/names include some checks multiple 
times it does not matter, the `HealthCheckExecutor` will always ensure checks 
are executed once at a time only.
 `intervalInSec` or `cronExpression` | Long or String (cron) | none, one of the 
two is required | The interval in which the given tags/names will be executed
@@ -282,15 +282,48 @@ Property    | Type     | Default | Description
 `logAllResultsAsInfo` | boolean | false | If `logResults` is enabled and this 
is enabled, all results will be logged with INFO log level. Otherwise WARN and 
INFO are used depending on the health state.
 `isDynamic` | boolean | false | In dynamic mode all checks for names/tags are 
monitored individually (this means events are sent/services registered for name 
only, never for given tags). This mode allows to use `*` in tags to query for 
all health checks in system. It is also possible to query for all except 
certain tags by using `-`, e.g. by configuring the values `*`, `-tag1` and 
`-tag2` for `tags`.
 
-### Marker Service to depend on a health status in SCR Components
+### OSGi Condition to depend on a health status in
+
+It is possible to use [OSGi 
Conditions](https://docs.osgi.org/specification/osgi.core/8.0.0/service.condition.html)
 to depend on the health status of a certain tag or name. For that to work, a 
`HealthCheckMonitor` needs to be configured for the relevant tag or name. An 
OSGi Condition service is registered using the tag or name prefixed by 
`felix.hc.`.
+
+For example, to depend on a health status in a Declarative Service component, 
use `@SatisfyingConditionTarget`. The below will only activate the component on 
healthiness of a certain tag/name:
+
+```
+@Component
+@SatisfyingConditionTarget("(osgi.condition.id=felix.hc.dbavail)")
+public class MyComponent {
+   ...
+}
+```
+
+It is also possible to use a Condition in a reference and later on in the code 
figure out if healthiness is reached:
+
+```
+@Component
+public class MyComponent {
 
-It is possible to use OSGi service references to depend on the health status 
of a certain  
-tag or name. For that to work, a `HealthCheckMonitor` needs to be configured 
for the relevant tag or name. To depend on a health status in a component, use 
a `@Reference` to one of the marker services `Healthy`, `Unhealthy` and 
`SystemReady` - this will then automatically activate/deactivate the component 
based on the certain health status. To activate a component only upon 
healthiness of a certain tag/name use the following code:
+    @Reference(target="(osgi.condition.id=felix.hc.dbavail)")
+    volatile Condition healthy;
+
+    public void mymethod() {
+        if (healthy == null) {
+            // not healthy
+        } else {
+            // healthy
+        }
+    }
+}
+```
+
+
+### Marker Service to depend on a health status in Declarative Service 
Components
+
+It is possible to use OSGi service references to depend on the health status 
of a certain tag or name. For that to work, a `HealthCheckMonitor` needs to be 
configured for the relevant tag or name. To depend on a health status in a 
component, use a `@Reference` to one of the marker services `Healthy`, 
`Unhealthy` and `SystemReady` - this will then automatically 
activate/deactivate the component based on the certain health status. To 
activate a component only upon healthiness of a certain  [...]
 
 ```
    @Reference(target="(tag=dbavail)")
    Healthy healthy;
- 
+
    @Reference(target="(name=My Health Check)")
    Healthy healthy;
 ```
@@ -302,12 +335,11 @@ For the special tag `systemready`, there is a convenience 
marker interface avail
 ```
 It is also possible to depend on a unhealthy state (e.g. for fallback 
functionality or self-healing):
 
-``` 
+```
    @Reference(target="(tag=dbavail)")
    Unhealthy unhealthy;
 ```
 
-NOTE: This does not support the [RFC 242 Condition 
Service](https://github.com/osgi/design/blob/master/rfcs/rfc0242/rfc-0242-Condition-Service.pdf)
 yet - however once final the marker services will also be able to implement 
the `Condition` interface.
 
 ### OSGi events for Health Check status changes and updates
 
@@ -318,13 +350,13 @@ OSGi events with topic `org/apache/felix/health/*` are 
sent for tags/names that
 
 All events sent generally carry the properties `executionResult`, `status` and 
`previousStatus`.
 
-| Example | Description  
+| Example | Description
 ------- | -----
 `org/apache/felix/health/tag/mytag/STATUS_CHANGED` | Status for tag `mytag` 
has changed compared to last execution
 `org/apache/felix/health/tag/My_HC_Name/UPDATED ` (spaces in names are 
replaced with underscores to ensure valid topic names) | Status for name `My HC 
Name` has not changed but HC was executed and execution result is available in 
event property `executionResult`.
 `org/apache/felix/health/component/com/myprj/MyHealthCheck/UPDATED` (`.` are 
replaced with slashes to produce valid topic names) | HC based on SCR component 
`com.myprj.MyHealthCheck` was executed without having the status changed. The 
SCR component event is sent in addition to the name event
 
-Event listener example: 
+Event listener example:
 
 ```
 @Component(property = { EventConstants.EVENT_TOPIC + 
"=org/apache/felix/health/*"})
@@ -346,9 +378,9 @@ public class HealthEventHandler implements EventHandler {
 
 ### Service Unavailable Filter
 
-For health states of the system that mean that requests can only fail it is 
possible to configure a Service Unavailable Filter that will cut off all 
requests if certain tags are in a `CRITICAL` or `TEMPORARILY_UNAVAILABLE` 
status. Typical usecases are startup/shutdown and deployments. Other scenarios 
include maintenance processes that require request processing of certain 
servlets to be stalled (the filter can be configured to be active on arbitrary 
paths). It is possible to configure a  [...]
+For health states of the system that mean that requests can only fail it is 
possible to configure a Service Unavailable Filter that will cut off all 
requests if certain tags are in a `CRITICAL` or `TEMPORARILY_UNAVAILABLE` 
status. Typical usecases are startup/shutdown and deployments. Other scenarios 
include maintenance processes that require request processing of certain 
servlets to be stalled (the filter can be configured to be active on arbitrary 
paths). It is possible to configure a  [...]
 
-Configure the factory configuration with PID 
+Configure the factory configuration with PID
 `org.apache.felix.hc.core.impl.filter.ServiceUnavailableFilter` with specific 
parameters to activate the Service Unavailable Filter:
 
 | Name | Default/Required | Description |
@@ -371,7 +403,7 @@ Configure the factory configuration with PID
 
 For certain scenarios it is useful to add a health check dynamically for a 
specific tag durign request processing, e.g. it can be useful during deployment 
requests (the tag(s) being added can be queried by e.g. load balancer or 
Service Unavailable Filter.
 
-To achieve this configure the factory configuration with PID 
+To achieve this configure the factory configuration with PID
 
`org.apache.felix.hc.core.impl.filter.AdhocResultDuringRequestProcessingFilter` 
with specific parameters:
 
 | Name | Default/Required | Description |
diff --git a/healthcheck/core/pom.xml b/healthcheck/core/pom.xml
index b1ecca7027..35f8252507 100644
--- a/healthcheck/core/pom.xml
+++ b/healthcheck/core/pom.xml
@@ -7,9 +7,9 @@
     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
@@ -23,7 +23,7 @@
     <parent>
         <groupId>org.apache.felix</groupId>
         <artifactId>felix-parent</artifactId>
-        <version>7</version>
+        <version>9</version>
         <relativePath />
     </parent>
 
@@ -38,7 +38,7 @@
     </description>
 
     <properties>
-        <felix.java.version>8</felix.java.version>
+        <felix.java.version>11</felix.java.version>
         <pax-exam.version>4.13.4</pax-exam.version>
         <pax-link.version>2.6.7</pax-link.version>
         
<org.ops4j.pax.logging.DefaultServiceLog.level>INFO</org.ops4j.pax.logging.DefaultServiceLog.level>
@@ -52,8 +52,7 @@
         
<connection>scm:git:https://github.com/apache/felix-dev.git</connection>
         
<developerConnection>scm:git:https://github.com/apache/felix-dev.git</developerConnection>
         <url>https://gitbox.apache.org/repos/asf?p=felix-dev.git</url>
-      <tag>org.apache.felix.healthcheck.core-2.2.0</tag>
-  </scm>
+    </scm>
 
     <build>
         <plugins>
@@ -61,7 +60,7 @@
             <plugin>
                 <groupId>biz.aQute.bnd</groupId>
                 <artifactId>bnd-maven-plugin</artifactId>
-                <version>5.3.0</version>
+                <version>6.4.0</version>
                 <executions>
                     <execution>
                         <goals>
@@ -111,7 +110,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-failsafe-plugin</artifactId>
-                <version>3.0.0-M5</version>
+                <version>3.5.3</version>
                 <executions>
                     <execution>
                         <goals>
@@ -136,7 +135,7 @@
         <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>osgi.core</artifactId>
-            <version>6.0.0</version>
+            <version>8.0.0</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -225,7 +224,7 @@
         <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-core</artifactId>
-            <version>3.11.2</version>
+            <version>5.17.0</version>
             <scope>test</scope>
         </dependency>
         <dependency>
diff --git 
a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/monitor/HealthState.java
 
b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/monitor/HealthState.java
index 3f2c3452db..d399c3c476 100644
--- 
a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/monitor/HealthState.java
+++ 
b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/monitor/HealthState.java
@@ -18,6 +18,7 @@
 package org.apache.felix.hc.core.impl.monitor;
 
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
 import java.util.Dictionary;
@@ -40,13 +41,14 @@ import org.apache.felix.hc.core.impl.util.lang.StringUtils;
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
 import org.osgi.service.component.ComponentConstants;
+import org.osgi.service.condition.Condition;
 import org.osgi.service.event.Event;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 class HealthState {
     private static final Logger LOG = 
LoggerFactory.getLogger(HealthState.class);
-    
+
     public static final String TAG_SYSTEMREADY = "systemready";
 
     public static final String EVENT_TOPIC_PREFIX = "org/apache/felix/health";
@@ -57,15 +59,16 @@ class HealthState {
     public static final String EVENT_PROP_STATUS = "status";
     public static final String EVENT_PROP_PREVIOUS_STATUS = "previousStatus";
 
-    static final Healthy MARKER_SERVICE_HEALTHY = new Healthy() {
-    };
+    static final class HealthyCondition implements Condition, Healthy {};
+    static final class SystemReadyCondition implements Condition, SystemReady 
{};
+
+    static final Healthy MARKER_SERVICE_HEALTHY = new HealthyCondition();
     static final Unhealthy MARKER_SERVICE_UNHEALTHY = new Unhealthy() {
     };
-    static final SystemReady MARKER_SERVICE_SYSTEMREADY = new SystemReady() {
-    };
-    
+    static final SystemReady MARKER_SERVICE_SYSTEMREADY = new 
SystemReadyCondition();
+
     private final HealthCheckMonitor monitor;
-    
+
     private final String tagOrName;
     private final ServiceReference<HealthCheck> healthCheckRef;
     private final boolean isTag;
@@ -140,7 +143,7 @@ class HealthState {
         update(result);
 
     }
-    
+
     synchronized void update(HealthCheckExecutionResult executionResult) {
         if(!isLive) {
             LOG.trace("Not live anymore, skipping result update for {}", this);
@@ -177,20 +180,24 @@ class HealthState {
 
     private void registerHealthyService() {
         if (healthyRegistration == null) {
+            final boolean isSystemReady = TAG_SYSTEMREADY.equals(tagOrName);
             LOG.debug("HealthCheckMonitor: registerHealthyService() {} ", 
tagOrName);
             Dictionary<String, String> registrationProps = new Hashtable<>();
             registrationProps.put(propertyName, tagOrName);
             registrationProps.put("activated", new 
SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
+            registrationProps.put(Condition.CONDITION_ID, 
"felix.hc.".concat(tagOrName));
 
-            if (TAG_SYSTEMREADY.equals(tagOrName)) {
+            if (isSystemReady) {
                 LOG.debug("HealthCheckMonitor: SYSTEM READY");
-                healthyRegistration = 
monitor.getBundleContext().registerService(
-                        new String[] { SystemReady.class.getName(), 
Healthy.class.getName() },
-                        MARKER_SERVICE_SYSTEMREADY, registrationProps);
-            } else {
-                healthyRegistration = 
monitor.getBundleContext().registerService(Healthy.class, 
MARKER_SERVICE_HEALTHY,
-                        registrationProps);
             }
+            final List<String> services = new ArrayList<>();
+            services.add(Healthy.class.getName());
+            services.add(Condition.class.getName());
+            if (isSystemReady) {
+                services.add(SystemReady.class.getName());
+            }
+            final Object service = isSystemReady ? MARKER_SERVICE_SYSTEMREADY 
: MARKER_SERVICE_HEALTHY;
+            healthyRegistration = 
monitor.getBundleContext().registerService(services.toArray(new String[0]), 
service, registrationProps);
             LOG.debug("HealthCheckMonitor: Healthy service for {} '{}' 
registered", propertyName, tagOrName);
         }
     }
@@ -224,7 +231,7 @@ class HealthState {
 
     private void sendEvents(HealthCheckExecutionResult executionResult, 
Result.Status previousStatus) {
         ChangeType sendEventsConfig = monitor.getSendEvents();
-        if (sendEventsConfig == ChangeType.ALL 
+        if (sendEventsConfig == ChangeType.ALL
                 || (statusChanged && (sendEventsConfig == 
ChangeType.STATUS_CHANGES || sendEventsConfig == 
ChangeType.STATUS_CHANGES_OR_NOT_OK))
                 || (!executionResult.getHealthCheckResult().isOk() && 
sendEventsConfig == ChangeType.STATUS_CHANGES_OR_NOT_OK)) {
 
diff --git 
a/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/CompositeHealthCheckTest.java
 
b/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/CompositeHealthCheckTest.java
index 6c11dd89ee..933b81220a 100644
--- 
a/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/CompositeHealthCheckTest.java
+++ 
b/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/CompositeHealthCheckTest.java
@@ -27,9 +27,9 @@ import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import java.util.Arrays;
+import java.util.Dictionary;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Set;
 
 import org.apache.felix.hc.api.HealthCheck;
 import org.apache.felix.hc.api.Result;
@@ -43,7 +43,6 @@ import org.apache.felix.hc.core.impl.util.HealthCheckFilter;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentMatcher;
-import org.mockito.Matchers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
@@ -66,7 +65,7 @@ public class CompositeHealthCheckTest {
 
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        MockitoAnnotations.openMocks(this);
         compositeHealthCheck.setHealthCheckExecutor(healthCheckExecutor);
         compositeHealthCheck.setFilterTags(new String[] {});
         compositeHealthCheck.setComponentContext(componentContext);
@@ -75,8 +74,7 @@ public class CompositeHealthCheckTest {
     @Test
     public void testExecution() {
 
-        doReturn((Result) 
null).when(compositeHealthCheck).checkForRecursion(Matchers.<ServiceReference> 
any(),
-                Matchers.<Set<String>> any());
+        doReturn((Result) 
null).when(compositeHealthCheck).checkForRecursion(any(), any());
         String[] testTags = new String[] { "tag1" };
         compositeHealthCheck.setFilterTags(testTags);
 
@@ -287,5 +285,16 @@ public class CompositeHealthCheckTest {
             throw new UnsupportedOperationException();
         }
 
+        @Override
+        public Object adapt(Class type) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Dictionary getProperties() {
+            // TODO Auto-generated method stub
+            return null;
+        }
     }
 }
diff --git 
a/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitorTest.java
 
b/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitorTest.java
index 47f69b5e7b..69d7df0a4b 100644
--- 
a/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitorTest.java
+++ 
b/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitorTest.java
@@ -18,6 +18,7 @@
 package org.apache.felix.hc.core.impl.monitor;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -67,6 +68,7 @@ import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
 import org.osgi.service.component.ComponentConstants;
 import org.osgi.service.component.ComponentContext;
+import org.osgi.service.condition.Condition;
 import org.osgi.service.event.Event;
 import org.osgi.service.event.EventAdmin;
 
@@ -85,7 +87,7 @@ public class HealthCheckMonitorTest {
 
     @Mock
     private ComponentContext componentContext;
-    
+
     @Mock
     private EventAdmin eventAdmin;
 
@@ -97,38 +99,42 @@ public class HealthCheckMonitorTest {
 
     @Mock
     private ExtendedHealthCheckExecutor healthCheckExecutor;
-    
+
     @Mock
     private HealthCheckMetadata healthCheckMetadata;
 
     @Mock
     private ServiceReference<HealthCheck> healthCheckServiceRef;
-    
+
     @Captor
     private ArgumentCaptor<Event> postedEventsCaptor1;
 
     @Captor
     private ArgumentCaptor<Event> postedEventsCaptor2;
-        
+
+    @Captor
+    private ArgumentCaptor<Dictionary<String, Object>> propsCaptor;
+
+    @SuppressWarnings("rawtypes")
     @Mock
-    private ServiceRegistration<? extends Healthy> healthyRegistration;
-    
+    private ServiceRegistration healthyRegistration;
+
     @Mock
     private ServiceRegistration<Unhealthy> unhealthyRegistration;
-    
+
     @Mock
     private ResultTxtVerboseSerializer resultTxtVerboseSerializer;
-    
+
     @Before
     public void before() throws ReflectiveOperationException {
 
         for (Method m : HealthCheckMonitor.Config.class.getDeclaredMethods()) {
             when(m.invoke(config)).thenReturn(m.getDefaultValue());
         }
-        
+
         when(config.intervalInSec()).thenReturn(1000L);
         when(config.tags()).thenReturn(new String[] { TEST_TAG });
-        
+
         
when(healthCheckMetadata.getServiceReference()).thenReturn(healthCheckServiceRef);
         when(healthCheckMetadata.getTitle()).thenReturn("Test Check");
 
@@ -152,15 +158,14 @@ public class HealthCheckMonitorTest {
 
         assertEquals(1, healthCheckMonitor.healthStates.size());
         assertEquals("[HealthState tagOrName=test-tag, isTag=true, 
status=null, isHealthy=false, statusChanged=false]", 
healthCheckMonitor.healthStates.get(TEST_TAG).toString());
-        
+
         healthCheckMonitor.deactivate();
         assertEquals(0, healthCheckMonitor.healthStates.size());
-        
+
     }
 
     @Test
     public void testRunRegisterMarkerServices() throws InvalidSyntaxException {
-
         when(config.registerHealthyMarkerService()).thenReturn(true);
         when(config.registerUnhealthyMarkerService()).thenReturn(true);
         healthCheckMonitor.activate(bundleContext, config, componentContext);
@@ -170,10 +175,16 @@ public class HealthCheckMonitorTest {
         setHcResult(Result.Status.OK);
 
         healthCheckMonitor.run();
-        
+
         
verify(healthCheckExecutor).execute(HealthCheckSelector.tags(TEST_TAG));
-        
-        verify(bundleContext).registerService(eq(Healthy.class), 
eq(HealthState.MARKER_SERVICE_HEALTHY), any());
+
+        verify(bundleContext).registerService(eq(new String[] 
{Healthy.class.getName(), Condition.class.getName()}),
+            eq(HealthState.MARKER_SERVICE_HEALTHY), propsCaptor.capture());
+        final Dictionary<String, Object> capturedProps1 = 
propsCaptor.getValue();
+        assertEquals("test-tag", capturedProps1.get("tag"));
+        assertEquals("felix.hc.test-tag", 
capturedProps1.get("osgi.condition.id"));
+        assertNotNull(capturedProps1.get("activated"));
+
         verify(bundleContext, never()).registerService(eq(Unhealthy.class), 
eq(HealthState.MARKER_SERVICE_UNHEALTHY), any());
         verifyNoInteractions(healthyRegistration, unhealthyRegistration);
 
@@ -181,30 +192,39 @@ public class HealthCheckMonitorTest {
         healthCheckMonitor.run();
         // no status change, no interaction
         verifyNoInteractions(bundleContext, healthyRegistration, 
unhealthyRegistration);
-        
+
         // change, unhealthy should be registered
         resetMarkerServicesContext();
         setHcResult(Result.Status.TEMPORARILY_UNAVAILABLE);
         healthCheckMonitor.run();
-        
-        verify(bundleContext, never()).registerService(eq(Healthy.class), 
eq(HealthState.MARKER_SERVICE_HEALTHY), any());
+
+        verify(bundleContext, never()).registerService(eq(new String[] 
{Healthy.class.getName(), Condition.class.getName()}),
+            eq(HealthState.MARKER_SERVICE_HEALTHY), any());
         verify(bundleContext).registerService(eq(Unhealthy.class), 
eq(HealthState.MARKER_SERVICE_UNHEALTHY), any());
         verify(healthyRegistration).unregister();
         verifyNoInteractions(unhealthyRegistration);
-        
+
         // change, health should be registered
         resetMarkerServicesContext();
         setHcResult(Result.Status.WARN); // WARN is healthy by default config
         healthCheckMonitor.run();
-        verify(bundleContext).registerService(eq(Healthy.class), 
eq(HealthState.MARKER_SERVICE_HEALTHY), any());
+        verify(bundleContext).registerService(eq(new String[] 
{Healthy.class.getName(), Condition.class.getName()}),
+            eq(HealthState.MARKER_SERVICE_HEALTHY), propsCaptor.capture());
+        final Dictionary<String, Object> capturedProps2 = 
propsCaptor.getValue();
+        assertEquals("test-tag", capturedProps2.get("tag"));
+        assertEquals("felix.hc.test-tag", 
capturedProps2.get("osgi.condition.id"));
+        assertNotNull(capturedProps2.get("activated"));
+
         verify(bundleContext, never()).registerService(eq(Unhealthy.class), 
eq(HealthState.MARKER_SERVICE_UNHEALTHY), any());
         verify(unhealthyRegistration).unregister();
         verifyNoInteractions(healthyRegistration);
     }
 
+    @SuppressWarnings("unchecked")
     private void resetMarkerServicesContext() {
         reset(bundleContext, healthyRegistration, unhealthyRegistration);
-        when(bundleContext.registerService(eq(Healthy.class), 
eq(HealthState.MARKER_SERVICE_HEALTHY), 
any())).thenReturn((ServiceRegistration<Healthy>) healthyRegistration);
+        when(bundleContext.registerService(eq(new String[] 
{Healthy.class.getName(), Condition.class.getName()}),
+            eq(HealthState.MARKER_SERVICE_HEALTHY), 
any())).thenReturn(healthyRegistration);
         lenient().when(bundleContext.registerService(eq(Unhealthy.class), 
eq(HealthState.MARKER_SERVICE_UNHEALTHY), 
any())).thenReturn(unhealthyRegistration);
     }
 
@@ -219,9 +239,9 @@ public class HealthCheckMonitorTest {
         setHcResult(Result.Status.OK);
 
         healthCheckMonitor.run();
-        
+
         
verify(healthCheckExecutor).execute(HealthCheckSelector.tags(TEST_TAG));
-        
+
         verify(eventAdmin, times(2)).postEvent(postedEventsCaptor1.capture());
         List<Event> postedEvents = postedEventsCaptor1.getAllValues();
         assertEquals(2, postedEvents.size());
@@ -234,7 +254,7 @@ public class HealthCheckMonitorTest {
         healthCheckMonitor.run();
         // no event
         verifyNoInteractions(eventAdmin);
-        
+
         setHcResult(Result.Status.CRITICAL);
         reset(eventAdmin);
         // with status change
@@ -246,7 +266,7 @@ public class HealthCheckMonitorTest {
         assertEquals(Result.Status.CRITICAL, 
postedEvents.get(0).getProperty(HealthState.EVENT_PROP_STATUS));
         assertEquals(Result.Status.OK, 
postedEvents.get(0).getProperty(HealthState.EVENT_PROP_PREVIOUS_STATUS));
         
assertEquals("org/apache/felix/health/component/org/apache/felix/TestHealthCheck/STATUS_CHANGED",
 postedEvents.get(1).getTopic());
-        
+
         reset(eventAdmin);
         // without status change
         healthCheckMonitor.run();
@@ -262,13 +282,13 @@ public class HealthCheckMonitorTest {
         
when(healthCheckServiceRef.getProperty(ComponentConstants.COMPONENT_NAME)).thenReturn("org.apache.felix.TestHealthCheck");
 
         healthCheckMonitor.activate(bundleContext, config, componentContext);
-        
+
         setHcResult(Result.Status.OK);
 
         healthCheckMonitor.run();
-        
+
         
verify(healthCheckExecutor).execute(HealthCheckSelector.tags(TEST_TAG));
-        
+
         verify(eventAdmin, times(2)).postEvent(postedEventsCaptor1.capture());
         List<Event> postedEvents = postedEventsCaptor1.getAllValues();
         assertEquals(2, postedEvents.size());
@@ -319,7 +339,7 @@ public class HealthCheckMonitorTest {
         setHcResult(Result.Status.CRITICAL);
         healthCheckMonitor.run();
         verify(healthCheckMonitor).logResultItem(eq(false), 
matches(".*healthy:false hasChanged:true .*" + HC_RESULT_SERIALIZED));
-        
+
         reset(healthCheckMonitor);
         setHcResult(Result.Status.CRITICAL);
         healthCheckMonitor.run();
@@ -367,7 +387,7 @@ public class HealthCheckMonitorTest {
         setHcResult(Result.Status.CRITICAL);
         healthCheckMonitor.run();
         verify(healthCheckMonitor).logResultItem(eq(false), 
matches(".*healthy:false hasChanged:true .*" + HC_RESULT_SERIALIZED));
-        
+
         reset(healthCheckMonitor);
         setHcResult(Result.Status.CRITICAL);
         healthCheckMonitor.run();
@@ -414,7 +434,7 @@ public class HealthCheckMonitorTest {
         setHcResult(Result.Status.CRITICAL);
         healthCheckMonitor.run();
         verify(healthCheckMonitor).logResultItem(eq(false), 
matches(".*healthy:false hasChanged:true .*" + HC_RESULT_SERIALIZED));
-        
+
         reset(healthCheckMonitor);
         setHcResult(Result.Status.CRITICAL);
         healthCheckMonitor.run();
@@ -452,7 +472,7 @@ public class HealthCheckMonitorTest {
         verify(healthCheckMonitor, never()).logResultItem(anyBoolean(), 
anyString());
 
     }
-    
+
     private void prepareLoggingTest(HealthCheckMonitor.ChangeType 
loggingChangeType) throws InvalidSyntaxException {
         
when(config.sendEvents()).thenReturn(HealthCheckMonitor.ChangeType.NONE);
         when(config.logResults()).thenReturn(loggingChangeType);
@@ -467,7 +487,7 @@ public class HealthCheckMonitorTest {
             }
           });
     }
-    
+
     private void setHcResult(Result.Status status) {
         when(healthCheckExecutor.execute(HealthCheckSelector.tags(TEST_TAG)))
             .thenReturn(Arrays.asList(new ExecutionResult(healthCheckMetadata, 
new Result(status, status.toString()), 1)));


Reply via email to