You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by gh...@apache.org on 2018/12/18 22:58:17 UTC

svn commit: r1849246 [2/6] - in /felix/trunk/healthcheck: ./ api/ api/src/ api/src/main/ api/src/main/java/ api/src/main/java/org/ api/src/main/java/org/apache/ api/src/main/java/org/apache/felix/ api/src/main/java/org/apache/felix/hc/ api/src/main/jav...

Added: felix/trunk/healthcheck/core/bnd.bnd
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/bnd.bnd?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/bnd.bnd (added)
+++ felix/trunk/healthcheck/core/bnd.bnd Tue Dec 18 22:58:15 2018
@@ -0,0 +1,17 @@
+Bundle-Category: felix
+
+Bundle-Description: ${project.description}
+
+Bundle-DocURL: https://felix.apache.org
+
+Bundle-License: Apache License, Version 2.0
+
+Bundle-Vendor: The Apache Software Foundation
+
+Import-Package: org.quartz*;resolution:="optional", *
+
+Conditional-Package: org.apache.felix.utils.*
+
+-removeheaders:\
+  Include-Resource,\
+  Private-Package

Added: felix/trunk/healthcheck/core/pom.xml
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/pom.xml?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/pom.xml (added)
+++ felix/trunk/healthcheck/core/pom.xml Tue Dec 18 22:58:15 2018
@@ -0,0 +1,264 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+    
+    http://www.apache.org/licenses/LICENSE-2.0
+    
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>felix-parent</artifactId>
+        <version>6</version>
+        <relativePath/>
+    </parent>
+
+    <artifactId>org.apache.felix.healthcheck.core</artifactId>
+    <version>2.0.0-SNAPSHOT</version>
+
+    <name>Apache Felix Health Check Core</name>
+    <inceptionYear>2013</inceptionYear>
+
+    <description>
+        The Felix Health Check Core
+    </description>
+
+    <properties>
+        <pax-exam.version>4.11.0</pax-exam.version>
+        <pax-link.version>2.4.3</pax-link.version>
+        <org.ops4j.pax.logging.DefaultServiceLog.level>INFO</org.ops4j.pax.logging.DefaultServiceLog.level>
+        <felix.shell>false</felix.shell>
+        <project.bundle.file>${project.build.directory}/${project.build.finalName}.jar</project.bundle.file>
+    </properties>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/felix/trunk/healthcheck/core</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/felix/trunk/healthcheck/core</developerConnection>
+        <url>http://svn.apache.org/viewvc/felix/trunk/http/core/</url>
+    </scm>
+
+    <build>
+        <plugins>
+
+            <plugin>
+                <groupId>biz.aQute.bnd</groupId>
+                <artifactId>bnd-maven-plugin</artifactId>
+                <version>4.0.0</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>bnd-process</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+                    </archive>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>biz.aQute.bnd</groupId>
+                <artifactId>bnd-baseline-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.jacoco</groupId>
+                <artifactId>jacoco-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>prepare-agent-integration</id>
+                        <goals>
+                            <goal>prepare-agent-integration</goal>
+                        </goals>
+                        <configuration>
+                            <propertyName>coverage.command</propertyName>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.servicemix.tooling</groupId>
+                <artifactId>depends-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>generate-depends-file</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-failsafe-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>integration-test</goal>
+                            <goal>verify</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <systemProperties>
+                        <org.ops4j.pax.logging.DefaultServiceLog.level>${org.ops4j.pax.logging.DefaultServiceLog.level}</org.ops4j.pax.logging.DefaultServiceLog.level>
+                        <felix.shell>${felix.shell}</felix.shell>
+                        <coverage.command>${coverage.command}</coverage.command>
+                        <project.bundle.file>${project.bundle.file}</project.bundle.file>
+                    </systemProperties>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.core</artifactId>
+            <version>6.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.cmpn</artifactId>
+            <version>6.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.healthcheck.api</artifactId>
+            <version>2.0.0-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>1.7.6</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.servicemix.bundles</groupId>
+            <artifactId>org.apache.servicemix.bundles.quartz</artifactId>
+            <version>2.3.0_2</version>
+            <scope>provided</scope>
+            <!-- if not present in runtime, there is no support for async health checks -->
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.4</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+            <version>3.1.0</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- used for JSONWriter, included in bundle via Conditional-Package -->
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.utils</artifactId>
+            <version>1.11.0</version>
+            <scope>provided</scope>
+        </dependency>      
+        
+
+        <!-- START test scope dependencies -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.12</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>1.9.5</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>rhino</groupId>
+            <artifactId>js</artifactId>
+            <version>1.6R6</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>1.7.6</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-container-forked</artifactId>
+            <version>${pax-exam.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-junit4</artifactId>
+            <version>${pax-exam.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-link-mvn</artifactId>
+            <version>${pax-exam.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.url</groupId>
+            <artifactId>pax-url-aether</artifactId>
+            <version>${pax-link.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.url</groupId>
+            <artifactId>pax-url-wrap</artifactId>
+            <version>${pax-link.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.geronimo.specs</groupId>
+            <artifactId>geronimo-atinject_1.0_spec</artifactId>
+            <version>1.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.framework</artifactId>
+            <version>5.6.10</version>
+            <scope>test</scope>
+        </dependency>
+        <!-- END test scope dependencies -->
+
+    </dependencies>
+</project>

Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/CompositeHealthCheck.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/CompositeHealthCheck.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/CompositeHealthCheck.java (added)
+++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/CompositeHealthCheck.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The SF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.felix.hc.core.impl;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.felix.hc.api.HealthCheck;
+import org.apache.felix.hc.api.Result;
+import org.apache.felix.hc.api.Result.Status;
+import org.apache.felix.hc.api.execution.HealthCheckExecutionOptions;
+import org.apache.felix.hc.api.execution.HealthCheckExecutionResult;
+import org.apache.felix.hc.api.execution.HealthCheckExecutor;
+import org.apache.felix.hc.api.execution.HealthCheckSelector;
+import org.apache.felix.hc.util.FormattingResultLog;
+import org.apache.felix.hc.util.HealthCheckFilter;
+import org.apache.felix.hc.util.HealthCheckMetadata;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentConstants;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ConfigurationPolicy;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.metatype.annotations.Designate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** {@link HealthCheck} that executes a number of other HealthChecks, selected by their tags, and merges their Results. */
+
+@Component(service = HealthCheck.class, configurationPolicy = ConfigurationPolicy.REQUIRE)
+@Designate(ocd = CompositeHealthCheckConfiguration.class, factory = true)
+public class CompositeHealthCheck implements HealthCheck {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    static final String PROP_FILTER_TAGS = "filter.tags";
+
+    private String[] filterTags;
+
+    private boolean combineTagsWithOr;
+
+    @Reference
+    private HealthCheckExecutor healthCheckExecutor;
+
+    private BundleContext bundleContext;
+    private HealthCheckFilter healthCheckFilter;
+
+    private volatile ComponentContext componentContext;
+
+    @Activate
+    protected void activate(final CompositeHealthCheckConfiguration configuration, final ComponentContext ctx) {
+        bundleContext = ctx.getBundleContext();
+        componentContext = ctx;
+        healthCheckFilter = new HealthCheckFilter(bundleContext);
+
+        filterTags = configuration.filter_tags();
+        combineTagsWithOr = configuration.filter_combineTagsWithOr();
+        log.debug("Activated, will select HealthCheck having tags {} {}", Arrays.asList(filterTags),
+                combineTagsWithOr ? "using OR" : "using AND");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        bundleContext = null;
+        healthCheckFilter = null;
+        componentContext = null;
+    }
+
+    @Override
+    public Result execute() {
+        final ComponentContext localCtx = this.componentContext;
+        final ServiceReference referenceToThis = localCtx == null ? null : localCtx.getServiceReference();
+        Result result = referenceToThis == null ? null : checkForRecursion(referenceToThis, new HashSet<String>());
+        if (result != null) {
+            // return recursion error
+            return result;
+        }
+
+        FormattingResultLog resultLog = new FormattingResultLog();
+        HealthCheckExecutionOptions options = new HealthCheckExecutionOptions();
+        options.setCombineTagsWithOr(combineTagsWithOr);
+        List<HealthCheckExecutionResult> executionResults = healthCheckExecutor.execute(HealthCheckSelector.tags(filterTags), options);
+        resultLog.debug("Executing {} HealthChecks selected by tags {}", executionResults.size(), Arrays.asList(filterTags));
+        result = new CompositeResult(resultLog, executionResults);
+
+        return result;
+    }
+
+    Result checkForRecursion(ServiceReference hcReference, Set<String> alreadyBannedTags) {
+
+        HealthCheckMetadata thisCheckMetadata = new HealthCheckMetadata(hcReference);
+
+        Set<String> bannedTagsForThisCompositeCheck = new HashSet<String>();
+        bannedTagsForThisCompositeCheck.addAll(alreadyBannedTags);
+        bannedTagsForThisCompositeCheck.addAll(thisCheckMetadata.getTags());
+
+        String[] tagsForIncludedChecksArr = toStringArray(hcReference.getProperty(PROP_FILTER_TAGS), new String[0]);
+        Set<String> tagsForIncludedChecks = new HashSet<String>(Arrays.asList(tagsForIncludedChecksArr));
+
+        log.debug("HC {} has banned tags {}", thisCheckMetadata.getName(), bannedTagsForThisCompositeCheck);
+        log.debug("tagsForIncludedChecks {}", tagsForIncludedChecks);
+
+        // is this HC ok?
+        Set<String> intersection = new HashSet<String>();
+        intersection.addAll(bannedTagsForThisCompositeCheck);
+        intersection.retainAll(tagsForIncludedChecks);
+
+        if (!intersection.isEmpty()) {
+            return new Result(Status.HEALTH_CHECK_ERROR,
+                    "INVALID CONFIGURATION: Cycle detected in composite health check hierarchy. Health check '"
+                            + thisCheckMetadata.getName()
+                            + "' (" + hcReference.getProperty(Constants.SERVICE_ID) + ") must not have tag(s) " + intersection
+                            + " as a composite check in the hierarchy is itself already tagged alike (tags assigned to composite checks: "
+                            + bannedTagsForThisCompositeCheck + ")");
+        }
+
+        // check each sub composite check
+        ServiceReference[] hcRefsOfCompositeCheck = healthCheckFilter
+                .getHealthCheckServiceReferences(HealthCheckSelector.tags(tagsForIncludedChecksArr), combineTagsWithOr);
+        for (ServiceReference hcRefOfCompositeCheck : hcRefsOfCompositeCheck) {
+            if (CompositeHealthCheck.class.getName().equals(hcRefOfCompositeCheck.getProperty(ComponentConstants.COMPONENT_NAME))) {
+                log.debug("Checking sub composite HC {}, {}", hcRefOfCompositeCheck,
+                        hcRefOfCompositeCheck.getProperty(ComponentConstants.COMPONENT_NAME));
+                Result result = checkForRecursion(hcRefOfCompositeCheck, bannedTagsForThisCompositeCheck);
+                if (result != null) {
+                    // found recursion
+                    return result;
+                }
+            }
+
+        }
+
+        // no recursion detected
+        return null;
+
+    }
+
+    public static String[] toStringArray(Object propValue, String[] defaultArray) {
+        if (propValue instanceof String[]) {
+            return (String[]) propValue;
+        } else if (propValue instanceof String) {
+            return new String[] { (String) propValue };
+        } else if (propValue == null) {
+            return defaultArray;
+        }
+        return defaultArray;
+    }
+
+    void setHealthCheckFilter(HealthCheckFilter healthCheckFilter) {
+        this.healthCheckFilter = healthCheckFilter;
+    }
+
+    void setFilterTags(String[] filterTags) {
+        this.filterTags = filterTags;
+    }
+
+    void setCombineTagsWithOr(boolean combineTagsWithOr) {
+        this.combineTagsWithOr = combineTagsWithOr;
+    }
+
+    void setHealthCheckExecutor(HealthCheckExecutor healthCheckExecutor) {
+        this.healthCheckExecutor = healthCheckExecutor;
+    }
+
+    void setComponentContext(ComponentContext ctx) {
+        this.componentContext = ctx;
+    }
+}

Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/CompositeHealthCheckConfiguration.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/CompositeHealthCheckConfiguration.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/CompositeHealthCheckConfiguration.java (added)
+++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/CompositeHealthCheckConfiguration.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The SF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.felix.hc.core.impl;
+
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+@ObjectClassDefinition(name = "Apache Felix Composite Health Check", description = "Executes a set of health checks, selected by tags.")
+@interface CompositeHealthCheckConfiguration {
+
+    @AttributeDefinition(name = "Name", description = "Name of this health check.")
+    String hc_name() default "";
+
+    @AttributeDefinition(name = "Tags", description = "List of tags for this health check, used to select subsets of health checks for execution e.g. by a composite health check.")
+    String[] hc_tags() default {};
+
+    @AttributeDefinition(name = "MBean Name", description = "Name of the MBean to create for this health check. If empty, no MBean is registered.")
+    String hc_mbean_name() default "";
+
+    //
+
+    @AttributeDefinition(name = "Filter Tags", description = "Tags used to select which health checks the composite health check executes.")
+    String[] filter_tags() default {};
+
+    @AttributeDefinition(name = "Combine Tags With Or", description = "Tags used to select which health checks the composite health check executes.")
+    boolean filter_combineTagsWithOr() default false;
+
+}

Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/CompositeResult.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/CompositeResult.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/CompositeResult.java (added)
+++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/CompositeResult.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The SF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.felix.hc.core.impl;
+
+import static org.apache.felix.hc.util.FormattingResultLog.msHumanReadable;
+
+import java.util.List;
+
+import org.apache.felix.hc.api.Result;
+import org.apache.felix.hc.api.ResultLog;
+import org.apache.felix.hc.api.ResultLog.Entry;
+import org.apache.felix.hc.api.execution.HealthCheckExecutionResult;
+import org.apache.felix.hc.util.HealthCheckMetadata;
+
+public class CompositeResult extends Result {
+
+    public CompositeResult(ResultLog log, List<HealthCheckExecutionResult> executionResults) {
+        super(log);
+
+        for (HealthCheckExecutionResult executionResult : executionResults) {
+            HealthCheckMetadata healthCheckMetadata = executionResult.getHealthCheckMetadata();
+            Result healthCheckResult = executionResult.getHealthCheckResult();
+            for (Entry entry : healthCheckResult) {
+                resultLog.add(new ResultLog.Entry(entry.getStatus(), healthCheckMetadata.getName() + ": " + entry.getMessage(),
+                        entry.getException()));
+            }
+            resultLog.add(new ResultLog.Entry(healthCheckMetadata.getName() + " finished after "
+                    + msHumanReadable(executionResult.getElapsedTimeInMs()) + (executionResult.hasTimedOut() ? " (timed out)" : ""), true));
+        }
+    }
+
+}

Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/JmxAdjustableStatusHealthCheck.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/JmxAdjustableStatusHealthCheck.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/JmxAdjustableStatusHealthCheck.java (added)
+++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/JmxAdjustableStatusHealthCheck.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,251 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The SF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.felix.hc.core.impl;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.AttributeNotFoundException;
+import javax.management.DynamicMBean;
+import javax.management.InvalidAttributeValueException;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanParameterInfo;
+import javax.management.ReflectionException;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.felix.hc.api.HealthCheck;
+import org.apache.felix.hc.api.Result;
+import org.apache.felix.hc.api.Result.Status;
+import org.apache.felix.hc.api.ResultLog.Entry;
+import org.apache.felix.hc.util.FormattingResultLog;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Allows to dynamically add a health check that returns WARN or CRITICAL for certain tags for testing purposes or go-live sequences. Uses an MBean to
+ * add/remove the DynamicTestingHealthCheck dynamically. */
+@Component
+public class JmxAdjustableStatusHealthCheck {
+    private static final Logger LOG = LoggerFactory.getLogger(JmxAdjustableStatusHealthCheck.class);
+
+    public static final String OBJECT_NAME = "org.apache.felix.healthcheck:type=AdjustableStatusHealthCheck";
+
+    private BundleContext bundleContext;
+
+    private ServiceRegistration mbeanRegistration = null;
+    private ServiceRegistration healthCheckRegistration = null;
+
+    @Activate
+    protected final void activate(final ComponentContext context) {
+        this.bundleContext = context.getBundleContext();
+        registerMbean();
+    }
+
+    @Deactivate
+    protected final void deactivate(final ComponentContext context) {
+        unregisterMbean();
+        unregisterDynamicTestingHealthCheck();
+    }
+
+    private void registerMbean() {
+        final Dictionary<String, String> mbeanProps = new Hashtable<String, String>();
+        mbeanProps.put("jmx.objectname", OBJECT_NAME);
+        AdjustableHealthCheckStatusMBean adjustableHealthCheckStatusMBean = new AdjustableHealthCheckStatusMBean();
+        this.mbeanRegistration = bundleContext.registerService(DynamicMBean.class.getName(), adjustableHealthCheckStatusMBean, mbeanProps);
+        LOG.debug("Registered mbean {} as {}", adjustableHealthCheckStatusMBean, OBJECT_NAME);
+    }
+
+    private void unregisterMbean() {
+        if (this.mbeanRegistration != null) {
+            this.mbeanRegistration.unregister();
+            this.mbeanRegistration = null;
+            LOG.debug("Unregistered mbean AdjustableHealthCheckStatusMBean");
+        }
+    }
+
+    /* synchronized as potentially multiple users can run JMX operations */
+    private synchronized void registerDynamicTestingHealthCheck(Result.Status status, String[] tags) {
+        unregisterDynamicTestingHealthCheck();
+        HealthCheck healthCheck = new DynamicTestingHealthCheck(status);
+        Dictionary<String, Object> props = new Hashtable<String, Object>();
+        props.put(HealthCheck.NAME, "JMX-adjustable Check");
+        props.put(HealthCheck.TAGS, tags);
+
+        healthCheckRegistration = bundleContext.registerService(HealthCheck.class.getName(), healthCheck, props);
+
+    }
+
+    /* synchronized as potentially multiple users can run JMX operations */
+    private synchronized void unregisterDynamicTestingHealthCheck() {
+        if (this.healthCheckRegistration != null) {
+            this.healthCheckRegistration.unregister();
+            this.healthCheckRegistration = null;
+            LOG.debug("Unregistered DynamicTestingHealthCheck");
+        }
+    }
+
+    class DynamicTestingHealthCheck implements HealthCheck {
+
+        private final Result.Status status;
+
+        DynamicTestingHealthCheck(Result.Status status) {
+            this.status = status;
+        }
+
+        @Override
+        public Result execute() {
+            FormattingResultLog resultLog = new FormattingResultLog();
+            resultLog.add(
+                    new Entry(status, "Set dynamically via JMX bean " + OBJECT_NAME));
+            return new Result(resultLog);
+        }
+
+    }
+
+    private class AdjustableHealthCheckStatusMBean implements DynamicMBean {
+
+        private static final String OP_RESET = "reset";
+        private static final String OP_ADD_WARN_RESULT_FOR_TAGS = "addWarnResultForTags";
+        private static final String OP_ADD_CRITICAL_RESULT_FOR_TAGS = "addCriticalResultForTags";
+
+        private static final String ATT_TAGS = "tags";
+        private static final String ATT_STATUS = "status";
+
+        private static final String STATUS_INACTIVE = "INACTIVE";
+
+        /** The mbean info. */
+        private final MBeanInfo mbeanInfo;
+
+        private List<String> tags = new ArrayList<String>();
+        private String status = STATUS_INACTIVE;
+
+        public AdjustableHealthCheckStatusMBean() {
+            this.mbeanInfo = this.createMBeanInfo();
+        }
+
+        @Override
+        public Object getAttribute(final String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException {
+
+            if (ATT_TAGS.equals(attribute)) {
+                return StringUtils.join(tags, ",");
+            } else if (ATT_STATUS.equals(attribute)) {
+                return status.toString();
+            } else {
+                throw new AttributeNotFoundException("Attribute " + attribute + " not found.");
+            }
+        }
+
+        @Override
+        public AttributeList getAttributes(final String[] attributes) {
+            final AttributeList result = new AttributeList();
+            for (String att : attributes) {
+                try {
+                    result.add(new Attribute(att, getAttribute(att)));
+                } catch (Exception e) {
+                    throw new IllegalArgumentException(e.getMessage(), e);
+                }
+            }
+            return result;
+        }
+
+        private MBeanInfo createMBeanInfo() {
+            final List<MBeanAttributeInfo> attrs = new ArrayList<MBeanAttributeInfo>();
+
+            attrs.add(new MBeanAttributeInfo(ATT_TAGS, String.class.getName(), "Tags", true, false, false));
+            attrs.add(new MBeanAttributeInfo(ATT_STATUS, String.class.getName(), "Status", true, false, false));
+
+            MBeanParameterInfo[] params = new MBeanParameterInfo[] {
+                    new MBeanParameterInfo(ATT_TAGS, "java.lang.String", "Comma separated list of tags") };
+
+            final List<MBeanOperationInfo> ops = new ArrayList<MBeanOperationInfo>();
+            ops.add(new MBeanOperationInfo(OP_RESET, "Resets this testing mechanism and removes the failing HC",
+                    new MBeanParameterInfo[0], "java.lang.String", MBeanOperationInfo.ACTION));
+            ops.add(new MBeanOperationInfo(OP_ADD_CRITICAL_RESULT_FOR_TAGS, "Adds a critical result for the given tags",
+                    params, "java.lang.String", MBeanOperationInfo.ACTION));
+            ops.add(new MBeanOperationInfo(OP_ADD_WARN_RESULT_FOR_TAGS, "Adds a warn result for the given tags",
+                    params, "java.lang.String", MBeanOperationInfo.ACTION));
+
+            return new MBeanInfo(this.getClass().getName(),
+                    "Adjustable Health Check", attrs.toArray(new MBeanAttributeInfo[attrs.size()]), null,
+                    ops.toArray(new MBeanOperationInfo[ops.size()]), null);
+        }
+
+        @Override
+        public MBeanInfo getMBeanInfo() {
+            return this.mbeanInfo;
+        }
+
+        @Override
+        public Object invoke(final String actionName, final Object[] params, final String[] signature)
+                throws MBeanException, ReflectionException {
+            if (OP_RESET.equals(actionName)) {
+                tags = Arrays.asList("");
+                status = STATUS_INACTIVE;
+                unregisterDynamicTestingHealthCheck();
+                LOG.info("JMX-adjustable Health Check for testing was reset");
+                return "Reset successful";
+            } else if (OP_ADD_CRITICAL_RESULT_FOR_TAGS.equals(actionName)) {
+                String[] newTags = params[0].toString().split("[,; ]+");
+                tags = Arrays.asList(newTags);
+                Status critical = Result.Status.CRITICAL;
+                status = critical.toString();
+                registerDynamicTestingHealthCheck(critical, newTags);
+                LOG.info("Activated JMX-adjustable Health Check with status CRITICAL and tags " + StringUtils.join(tags, ","));
+                return "Added check with result CRITICAL";
+            } else if (OP_ADD_WARN_RESULT_FOR_TAGS.equals(actionName)) {
+                String[] newTags = params[0].toString().split("[,; ]+");
+                tags = Arrays.asList(newTags);
+                Status warn = Result.Status.WARN;
+                status = warn.toString();
+                registerDynamicTestingHealthCheck(warn, newTags);
+                LOG.info("Activated JMX-adjustable Health Check with status WARN and tags " + StringUtils.join(tags, ","));
+                return "Added check with result WARN";
+            } else {
+                throw new MBeanException(
+                        new UnsupportedOperationException(getClass().getSimpleName() + " does not support operation " + actionName));
+            }
+        }
+
+        @Override
+        public void setAttribute(final Attribute attribute)
+                throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException,
+                ReflectionException {
+            throw new MBeanException(
+                    new UnsupportedOperationException(getClass().getSimpleName() + " does not support setting attributes."));
+        }
+
+        @Override
+        public AttributeList setAttributes(final AttributeList attributes) {
+            return new AttributeList();
+        }
+    }
+
+}

Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheck.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheck.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheck.java (added)
+++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheck.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The SF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.felix.hc.core.impl;
+
+import java.lang.management.ManagementFactory;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.apache.felix.hc.api.HealthCheck;
+import org.apache.felix.hc.api.Result;
+import org.apache.felix.hc.util.FormattingResultLog;
+import org.apache.felix.hc.util.SimpleConstraintChecker;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ConfigurationPolicy;
+import org.osgi.service.metatype.annotations.Designate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** {@link HealthCheck} that checks a single JMX attribute */
+@Component(service = HealthCheck.class, configurationPolicy = ConfigurationPolicy.REQUIRE)
+@Designate(ocd = JmxAttributeHealthCheckConfiguration.class, factory = true)
+public class JmxAttributeHealthCheck implements HealthCheck {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private String mbeanName;
+    private String attributeName;
+    private String constraint;
+
+    @Activate
+    protected void activate(final JmxAttributeHealthCheckConfiguration configuration) {
+        mbeanName = configuration.mbean_name();
+        attributeName = configuration.attribute_name();
+        constraint = configuration.attribute_value_constraint();
+
+        log.debug("Activated with HealthCheck name={}, objectName={}, attribute={}, constraint={}",
+                new Object[] { configuration.hc_name(), mbeanName, attributeName, constraint });
+    }
+
+    @Override
+    public Result execute() {
+        final FormattingResultLog resultLog = new FormattingResultLog();
+        resultLog.debug("Checking {} / {} with constraint {}", mbeanName, attributeName, constraint);
+        try {
+            final MBeanServer jmxServer = ManagementFactory.getPlatformMBeanServer();
+            final ObjectName objectName = new ObjectName(mbeanName);
+            if (jmxServer.queryNames(objectName, null).size() == 0) {
+                resultLog.warn("MBean not found: {}", objectName);
+            } else {
+                final Object value = jmxServer.getAttribute(objectName, attributeName);
+                resultLog.debug("{} {} returns {}", mbeanName, attributeName, value);
+                new SimpleConstraintChecker().check(value, constraint, resultLog);
+            }
+        } catch (final Exception e) {
+            log.warn("JMX attribute {}/{} check failed: {}", new Object[] { mbeanName, attributeName, e });
+            resultLog.healthCheckError("JMX attribute check failed: {}", e);
+        }
+        return new Result(resultLog);
+    }
+}

Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheckConfiguration.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheckConfiguration.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheckConfiguration.java (added)
+++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/JmxAttributeHealthCheckConfiguration.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The SF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.felix.hc.core.impl;
+
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+@ObjectClassDefinition(name = "Apache Felix JMX Attribute Health Check", description = "Checks the value of a single JMX attribute.")
+@interface JmxAttributeHealthCheckConfiguration {
+
+    @AttributeDefinition(name = "Name", description = "Name of this health check.")
+    String hc_name() default "";
+
+    @AttributeDefinition(name = "Tags", description = "List of tags for this health check, used to select subsets of health checks for execution e.g. by a composite health check.")
+    String[] hc_tags() default {};
+
+    @AttributeDefinition(name = "MBean Name", description = "Name of the MBean to create for this health check. If empty, no MBean is registered.")
+    String hc_mbean_name() default "";
+
+    //
+
+    @AttributeDefinition(name = "Check MBean Name", description = "The name of the MBean to check by this health check.")
+    String mbean_name() default "";
+
+    @AttributeDefinition(name = "Check Attribute Name", description = "The name of the MBean attribute to check by this health check.")
+    String attribute_name() default "";
+
+    @AttributeDefinition(name = "Check Attribute Constraint", description = "Constraint on the MBean attribute value.")
+    String attribute_value_constraint() default "";
+
+}

Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/ExecutionResult.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/ExecutionResult.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/ExecutionResult.java (added)
+++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/ExecutionResult.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The SF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.felix.hc.core.impl.executor;
+
+import java.text.Collator;
+import java.util.Date;
+
+import org.apache.felix.hc.api.HealthCheck;
+import org.apache.felix.hc.api.Result;
+import org.apache.felix.hc.api.execution.HealthCheckExecutionResult;
+import org.apache.felix.hc.util.HealthCheckMetadata;
+
+/** The result of executing a {@link HealthCheck}. */
+public class ExecutionResult implements Comparable<ExecutionResult>, HealthCheckExecutionResult {
+
+    private final Result resultFromHC;
+
+    private final HealthCheckMetadata metaData;
+
+    private final Date finishedAt;
+
+    private final long elapsedTimeInMs;
+
+    private final boolean timedOut;
+
+    /** Full constructor */
+    public ExecutionResult(final HealthCheckMetadata metadata,
+            final Result simpleResult,
+            final long elapsedTimeInMs,
+            final boolean timedout) {
+        this.metaData = metadata;
+        this.resultFromHC = simpleResult;
+        this.finishedAt = new Date();
+        this.timedOut = timedout;
+        this.elapsedTimeInMs = elapsedTimeInMs;
+    }
+
+    /** Shortcut constructor for a result */
+    public ExecutionResult(final HealthCheckMetadata metadata,
+            final Result simpleResult,
+            final long elapsedTimeInMs) {
+        this(metadata, simpleResult, elapsedTimeInMs, false);
+    }
+
+    /** Shortcut constructor to create error/timed out result. */
+    public ExecutionResult(final HealthCheckMetadata metadata,
+            final Result.Status status,
+            final String errorMessage,
+            final long elapsedTime, boolean timedOut) {
+        this(metadata, new Result(status, errorMessage), elapsedTime, timedOut);
+    }
+
+    @Override
+    public Result getHealthCheckResult() {
+        return this.resultFromHC;
+    }
+
+    @Override
+    public String toString() {
+        return "ExecutionResult [status=" + this.resultFromHC.getStatus() +
+                ", finishedAt=" + finishedAt +
+                ", elapsedTimeInMs=" + elapsedTimeInMs +
+                ", timedOut=" + timedOut +
+                "]";
+    }
+
+    @Override
+    public long getElapsedTimeInMs() {
+        return elapsedTimeInMs;
+    }
+
+    @Override
+    public HealthCheckMetadata getHealthCheckMetadata() {
+        return this.metaData;
+    }
+
+    @Override
+    public Date getFinishedAt() {
+        return finishedAt;
+    }
+
+    @Override
+    public boolean hasTimedOut() {
+        return this.timedOut;
+    }
+
+    /** Natural order of results (failed results are sorted before ok results). */
+    @Override
+    public int compareTo(ExecutionResult otherResult) {
+        int retVal = otherResult.getHealthCheckResult().getStatus().compareTo(this.getHealthCheckResult().getStatus());
+        if (retVal == 0) {
+            retVal = Collator.getInstance().compare(this.getHealthCheckMetadata().getTitle(),
+                    otherResult.getHealthCheckMetadata().getTitle());
+        }
+        return retVal;
+    }
+
+    long getServiceId() {
+        return this.metaData.getServiceId();
+    }
+}
\ No newline at end of file

Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/ExtendedHealthCheckExecutor.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/ExtendedHealthCheckExecutor.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/ExtendedHealthCheckExecutor.java (added)
+++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/ExtendedHealthCheckExecutor.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The SF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.felix.hc.core.impl.executor;
+
+import org.apache.felix.hc.api.HealthCheck;
+import org.apache.felix.hc.api.execution.HealthCheckExecutionResult;
+import org.apache.felix.hc.api.execution.HealthCheckExecutor;
+import org.osgi.framework.ServiceReference;
+
+/** Internal service used by the JMX stuff */
+public interface ExtendedHealthCheckExecutor extends HealthCheckExecutor {
+
+    HealthCheckExecutionResult execute(ServiceReference<HealthCheck> ref);
+}
\ No newline at end of file

Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImpl.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImpl.java (added)
+++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImpl.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,467 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The SF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.felix.hc.core.impl.executor;
+
+import static org.apache.felix.hc.core.impl.executor.HealthCheckExecutorImplConfiguration.LONGRUNNING_FUTURE_THRESHOLD_CRITICAL_DEFAULT_MS;
+import static org.apache.felix.hc.core.impl.executor.HealthCheckExecutorImplConfiguration.RESULT_CACHE_TTL_DEFAULT_MS;
+import static org.apache.felix.hc.core.impl.executor.HealthCheckExecutorImplConfiguration.TIMEOUT_DEFAULT_MS;
+import static org.apache.felix.hc.util.FormattingResultLog.msHumanReadable;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.time.StopWatch;
+import org.apache.felix.hc.api.HealthCheck;
+import org.apache.felix.hc.api.Result;
+import org.apache.felix.hc.api.ResultLog;
+import org.apache.felix.hc.api.execution.HealthCheckExecutionOptions;
+import org.apache.felix.hc.api.execution.HealthCheckExecutionResult;
+import org.apache.felix.hc.api.execution.HealthCheckExecutor;
+import org.apache.felix.hc.api.execution.HealthCheckSelector;
+import org.apache.felix.hc.core.impl.executor.async.AsyncHealthCheckExecutor;
+import org.apache.felix.hc.util.FormattingResultLog;
+import org.apache.felix.hc.util.HealthCheckFilter;
+import org.apache.felix.hc.util.HealthCheckMetadata;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.metatype.annotations.Designate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Runs health checks for a given list of tags in parallel. */
+@Component(service = { HealthCheckExecutor.class, ExtendedHealthCheckExecutor.class }, immediate = true // immediate = true to keep the
+                                                                                                        // cache!
+)
+@Designate(ocd = HealthCheckExecutorImplConfiguration.class)
+public class HealthCheckExecutorImpl implements ExtendedHealthCheckExecutor, ServiceListener {
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    private long timeoutInMs;
+
+    private long longRunningFutureThresholdForRedMs;
+
+    private long resultCacheTtlInMs;
+
+    private HealthCheckResultCache healthCheckResultCache = new HealthCheckResultCache();
+
+    private final Map<HealthCheckMetadata, HealthCheckFuture> stillRunningFutures = new HashMap<HealthCheckMetadata, HealthCheckFuture>();
+
+    // optional dependency on quartz - if not present checks are just executed synchronously
+    @Reference
+    private AsyncHealthCheckExecutor asyncHealthCheckExecutor;
+
+    @Reference
+    HealthCheckExecutorThreadPool healthCheckExecutorThreadPool;
+
+    private BundleContext bundleContext;
+
+    @Activate
+    protected final void activate(final HealthCheckExecutorImplConfiguration configuration, final BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+
+        configure(configuration);
+
+        try {
+            this.bundleContext.addServiceListener(this, "("
+                    + Constants.OBJECTCLASS + "=" + HealthCheck.class.getName() + ")");
+        } catch (final InvalidSyntaxException ise) {
+            // this should really never happen as the expression above is constant
+            throw new RuntimeException("Unexpected exception occured.", ise);
+        }
+    }
+
+    @Modified
+    protected final void modified(final HealthCheckExecutorImplConfiguration configuration) {
+        configure(configuration);
+    }
+
+    @Deactivate
+    protected final void deactivate() {
+        this.bundleContext.removeServiceListener(this);
+        this.bundleContext = null;
+        this.healthCheckResultCache.clear();
+    }
+
+    protected final void configure(final HealthCheckExecutorImplConfiguration configuration) {
+        this.timeoutInMs = configuration.timeoutInMs();
+        if (this.timeoutInMs <= 0L) {
+            this.timeoutInMs = TIMEOUT_DEFAULT_MS;
+        }
+
+        this.longRunningFutureThresholdForRedMs = configuration.longRunningFutureThresholdForCriticalMs();
+        if (this.longRunningFutureThresholdForRedMs <= 0L) {
+            this.longRunningFutureThresholdForRedMs = LONGRUNNING_FUTURE_THRESHOLD_CRITICAL_DEFAULT_MS;
+        }
+
+        this.resultCacheTtlInMs = configuration.resultCacheTtlInMs();
+        if (this.resultCacheTtlInMs <= 0L) {
+            this.resultCacheTtlInMs = RESULT_CACHE_TTL_DEFAULT_MS;
+        }
+
+    }
+
+    @Override
+    public void serviceChanged(final ServiceEvent event) {
+        if (event.getType() == ServiceEvent.UNREGISTERING) {
+            final Long serviceId = (Long) event.getServiceReference().getProperty(Constants.SERVICE_ID);
+            this.healthCheckResultCache.removeCachedResult(serviceId);
+        }
+    }
+
+    @Override
+    public List<HealthCheckExecutionResult> execute(HealthCheckSelector selector) {
+        return execute(selector, new HealthCheckExecutionOptions());
+    }
+
+    @Override
+    public List<HealthCheckExecutionResult> execute(HealthCheckSelector selector, HealthCheckExecutionOptions options) {
+        logger.debug("Starting executing checks for filter selector {} and execution options {}", selector, options);
+
+        final HealthCheckFilter filter = new HealthCheckFilter(this.bundleContext);
+        try {
+            final ServiceReference<HealthCheck>[] healthCheckReferences = filter.getHealthCheckServiceReferences(selector,
+                    options.isCombineTagsWithOr());
+
+            return this.execute(healthCheckReferences, options);
+        } finally {
+            filter.dispose();
+        }
+    }
+
+    /** @see org.apache.felix.hc.core.impl.executor.ExtendedHealthCheckExecutor#execute(org.osgi.framework.ServiceReference) */
+    @Override
+    public HealthCheckExecutionResult execute(final ServiceReference<HealthCheck> ref) {
+        final HealthCheckMetadata metadata = this.getHealthCheckMetadata(ref);
+        return createResultsForDescriptor(metadata);
+    }
+
+    /** Execute a set of health checks */
+    private List<HealthCheckExecutionResult> execute(final ServiceReference<HealthCheck>[] healthCheckReferences,
+            HealthCheckExecutionOptions options) {
+        final StopWatch stopWatch = new StopWatch();
+        stopWatch.start();
+
+        final List<HealthCheckExecutionResult> results = new ArrayList<HealthCheckExecutionResult>();
+        final List<HealthCheckMetadata> healthCheckDescriptors = getHealthCheckMetadata(healthCheckReferences);
+
+        createResultsForDescriptors(healthCheckDescriptors, results, options);
+
+        stopWatch.stop();
+        if (logger.isDebugEnabled()) {
+            logger.debug("Time consumed for all checks: {}", msHumanReadable(stopWatch.getTime()));
+        }
+        // sort result
+        Collections.sort(results, new Comparator<HealthCheckExecutionResult>() {
+
+            @Override
+            public int compare(final HealthCheckExecutionResult arg0,
+                    final HealthCheckExecutionResult arg1) {
+                return ((ExecutionResult) arg0).compareTo((ExecutionResult) arg1);
+            }
+
+        });
+        return results;
+    }
+
+    private void createResultsForDescriptors(final List<HealthCheckMetadata> healthCheckDescriptors,
+            final List<HealthCheckExecutionResult> results, HealthCheckExecutionOptions options) {
+        // -- All methods below check if they can transform a healthCheckDescriptor into a result
+        // -- if yes the descriptor is removed from the list and the result added
+
+        // get async results
+        if (!options.isForceInstantExecution()) {
+            if (asyncHealthCheckExecutor != null) {
+                asyncHealthCheckExecutor.collectAsyncResults(healthCheckDescriptors, results, healthCheckResultCache);
+            } else {
+                for (HealthCheckMetadata hcDescriptior : healthCheckDescriptors) {
+                    if (StringUtils.isNotBlank(hcDescriptior.getAsyncCronExpression())) {
+                        logger.warn(
+                                "Health check '{}' is configured for asynchronous execution, but async executor is not available (quartz bundle missing)",
+                                hcDescriptior.getName());
+                    }
+                }
+            }
+        }
+
+        // reuse cached results where possible
+        if (!options.isForceInstantExecution()) {
+            healthCheckResultCache.useValidCacheResults(healthCheckDescriptors, results, resultCacheTtlInMs);
+        }
+
+        // everything else is executed in parallel via futures
+        List<HealthCheckFuture> futures = createOrReuseFutures(healthCheckDescriptors);
+
+        // wait for futures at most until timeout (but will return earlier if all futures are finished)
+        waitForFuturesRespectingTimeout(futures, options);
+        collectResultsFromFutures(futures, results);
+
+        // respect sticky results if configured via HealthCheck.WARNINGS_STICK_FOR_MINUTES
+        appendStickyResultLogIfConfigured(results);
+
+    }
+
+    private void appendStickyResultLogIfConfigured(List<HealthCheckExecutionResult> results) {
+        ListIterator<HealthCheckExecutionResult> resultsIt = results.listIterator();
+        while (resultsIt.hasNext()) {
+            HealthCheckExecutionResult result = resultsIt.next();
+            Long warningsStickForMinutes = result.getHealthCheckMetadata().getWarningsStickForMinutes();
+            if (warningsStickForMinutes != null && warningsStickForMinutes > 0) {
+                result = healthCheckResultCache.createExecutionResultWithStickyResults(result);
+                resultsIt.set(result);
+            }
+        }
+    }
+
+    private HealthCheckExecutionResult createResultsForDescriptor(final HealthCheckMetadata metadata) {
+        // create result for a single descriptor
+
+        // reuse cached results where possible
+        HealthCheckExecutionResult result;
+
+        result = healthCheckResultCache.getValidCacheResult(metadata, resultCacheTtlInMs);
+
+        if (result == null) {
+            final HealthCheckFuture future;
+            synchronized (this.stillRunningFutures) {
+                future = createOrReuseFuture(metadata);
+            }
+
+            // wait for futures at most until timeout (but will return earlier if all futures are finished)
+            waitForFuturesRespectingTimeout(Collections.singletonList(future), null);
+            result = collectResultFromFuture(future);
+        }
+
+        return result;
+    }
+
+    /** Create the health check meta data */
+    private List<HealthCheckMetadata> getHealthCheckMetadata(final ServiceReference... healthCheckReferences) {
+        final List<HealthCheckMetadata> descriptors = new LinkedList<HealthCheckMetadata>();
+        for (final ServiceReference serviceReference : healthCheckReferences) {
+            final HealthCheckMetadata descriptor = getHealthCheckMetadata(serviceReference);
+
+            descriptors.add(descriptor);
+        }
+
+        return descriptors;
+    }
+
+    /** Create the health check meta data */
+    private HealthCheckMetadata getHealthCheckMetadata(final ServiceReference healthCheckReference) {
+        final HealthCheckMetadata descriptor = new HealthCheckMetadata(healthCheckReference);
+        return descriptor;
+    }
+
+    /** Create or reuse future for the list of health checks */
+    private List<HealthCheckFuture> createOrReuseFutures(final List<HealthCheckMetadata> healthCheckDescriptors) {
+        final List<HealthCheckFuture> futuresForResultOfThisCall = new LinkedList<HealthCheckFuture>();
+
+        synchronized (this.stillRunningFutures) {
+            for (final HealthCheckMetadata md : healthCheckDescriptors) {
+
+                futuresForResultOfThisCall.add(createOrReuseFuture(md));
+
+            }
+        }
+        return futuresForResultOfThisCall;
+    }
+
+    /** Create or reuse future for the health check This method must be synchronized by the caller(!) on stillRunningFutures */
+    private HealthCheckFuture createOrReuseFuture(final HealthCheckMetadata metadata) {
+        HealthCheckFuture future = this.stillRunningFutures.get(metadata);
+        if (future != null) {
+            logger.debug("Found a future that is still running for {}", metadata);
+        } else {
+            logger.debug("Creating future for {}", metadata);
+            future = new HealthCheckFuture(metadata, bundleContext, new HealthCheckFuture.Callback() {
+
+                @Override
+                public void finished(final HealthCheckExecutionResult result) {
+                    healthCheckResultCache.updateWith(result);
+                    asyncHealthCheckExecutor.updateWith(result);
+                    synchronized (stillRunningFutures) {
+                        stillRunningFutures.remove(metadata);
+                    }
+                }
+            });
+            this.stillRunningFutures.put(metadata, future);
+
+            final HealthCheckFuture newFuture = future;
+
+            healthCheckExecutorThreadPool.execute(new Runnable() {
+                @Override
+                public void run() {
+                    newFuture.run();
+                    synchronized (stillRunningFutures) {
+                        // notify executor threads that newFuture is finished. Wrapping it in another runnable
+                        // ensures that newFuture.isDone() will return true (if e.g. done in callback above, there are
+                        // still a few lines of code until the future is really done and hence then the executor thread
+                        // is sometime notified a bit too early, still receives the result isDone()=false and then waits
+                        // for another 50ms, even though the future was about to be done one ms later)
+                        stillRunningFutures.notifyAll();
+                    }
+                }
+            });
+        }
+
+        return future;
+    }
+
+    /** Wait for the futures until the timeout is reached */
+    private void waitForFuturesRespectingTimeout(final List<HealthCheckFuture> futuresForResultOfThisCall,
+            HealthCheckExecutionOptions options) {
+        final StopWatch callExcutionTimeStopWatch = new StopWatch();
+        callExcutionTimeStopWatch.start();
+        boolean allFuturesDone;
+
+        long effectiveTimeout = this.timeoutInMs;
+        if (options != null && options.getOverrideGlobalTimeout() > 0) {
+            effectiveTimeout = options.getOverrideGlobalTimeout();
+        }
+
+        if (futuresForResultOfThisCall.isEmpty()) {
+            return; // nothing to wait for (usually because of cached results)
+        }
+
+        do {
+            try {
+                synchronized (stillRunningFutures) {
+                    stillRunningFutures.wait(50); // wait for notifications of callbacks of HealthCheckFutures
+                }
+            } catch (final InterruptedException ie) {
+                logger.warn("Unexpected InterruptedException while waiting for healthCheckContributors", ie);
+            }
+
+            allFuturesDone = true;
+            for (final HealthCheckFuture healthCheckFuture : futuresForResultOfThisCall) {
+                allFuturesDone &= healthCheckFuture.isDone();
+            }
+        } while (!allFuturesDone && callExcutionTimeStopWatch.getTime() < effectiveTimeout);
+    }
+
+    /** Collect the results from all futures
+     * 
+     * @param futuresForResultOfThisCall The list of futures
+     * @param results The result collection */
+    void collectResultsFromFutures(final List<HealthCheckFuture> futuresForResultOfThisCall,
+            final Collection<HealthCheckExecutionResult> results) {
+
+        final Set<HealthCheckExecutionResult> resultsFromFutures = new HashSet<HealthCheckExecutionResult>();
+
+        final Iterator<HealthCheckFuture> futuresIt = futuresForResultOfThisCall.iterator();
+        while (futuresIt.hasNext()) {
+            final HealthCheckFuture future = futuresIt.next();
+            final HealthCheckExecutionResult result = this.collectResultFromFuture(future);
+
+            resultsFromFutures.add(result);
+            futuresIt.remove();
+        }
+
+        logger.debug("Adding {} results from futures", resultsFromFutures.size());
+        results.addAll(resultsFromFutures);
+    }
+
+    /** Collect the result from a single future
+     * 
+     * @param future The future
+     * @return The execution result or a result for a reached timeout */
+    HealthCheckExecutionResult collectResultFromFuture(final HealthCheckFuture future) {
+
+        HealthCheckExecutionResult result;
+        HealthCheckMetadata hcMetadata = future.getHealthCheckMetadata();
+        if (future.isDone()) {
+            logger.debug("Health Check is done: {}", hcMetadata);
+
+            try {
+                result = future.get();
+            } catch (final Exception e) {
+                logger.warn("Unexpected Exception during future.get(): " + e, e);
+                long futureElapsedTimeMs = new Date().getTime() - future.getCreatedTime().getTime();
+                result = new ExecutionResult(hcMetadata, Result.Status.HEALTH_CHECK_ERROR,
+                        "Unexpected Exception during future.get(): " + e, futureElapsedTimeMs, false);
+            }
+
+        } else {
+            logger.debug("Health Check timed out: {}", hcMetadata);
+            // Futures must not be cancelled as interrupting a health check might leave the system in invalid state
+            // (worst case could be a corrupted repository index if using write operations)
+
+            // normally we turn the check into WARN (normal timeout), but if the threshold time for CRITICAL is reached for a certain
+            // future we turn the result CRITICAL
+            long futureElapsedTimeMs = new Date().getTime() - future.getCreatedTime().getTime();
+            FormattingResultLog resultLog = new FormattingResultLog();
+            if (futureElapsedTimeMs < this.longRunningFutureThresholdForRedMs) {
+                resultLog.warn("Timeout: Check still running after " + msHumanReadable(futureElapsedTimeMs));
+            } else {
+                resultLog.critical("Timeout: Check still running after " + msHumanReadable(futureElapsedTimeMs)
+                        + " (exceeding the configured threshold for CRITICAL: "
+                        + msHumanReadable(this.longRunningFutureThresholdForRedMs) + ")");
+            }
+
+            // add logs from previous, cached result if exists (using a 1 year TTL)
+            HealthCheckExecutionResult lastCachedResult = healthCheckResultCache.getValidCacheResult(hcMetadata, 1000 * 60 * 60 * 24 * 365);
+            if (lastCachedResult != null) {
+                DateFormat df = new SimpleDateFormat("HH:mm:ss.SSS");
+                resultLog.info("*** Result log of last execution finished at {} after {} ***",
+                        df.format(lastCachedResult.getFinishedAt()),
+                        FormattingResultLog.msHumanReadable(lastCachedResult.getElapsedTimeInMs()));
+                for (ResultLog.Entry entry : lastCachedResult.getHealthCheckResult()) {
+                    resultLog.add(entry);
+                }
+            }
+
+            result = new ExecutionResult(hcMetadata, new Result(resultLog), futureElapsedTimeMs, true);
+
+        }
+
+        return result;
+    }
+
+    public void setTimeoutInMs(final long timeoutInMs) {
+        this.timeoutInMs = timeoutInMs;
+    }
+
+    public void setLongRunningFutureThresholdForRedMs(
+            final long longRunningFutureThresholdForRedMs) {
+        this.longRunningFutureThresholdForRedMs = longRunningFutureThresholdForRedMs;
+    }
+}

Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImplConfiguration.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImplConfiguration.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImplConfiguration.java (added)
+++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorImplConfiguration.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The SF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.felix.hc.core.impl.executor;
+
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+@ObjectClassDefinition(name = "Apache Felix Health Check Executor", description = "Runs health checks for a given list of tags in parallel.")
+@interface HealthCheckExecutorImplConfiguration {
+
+    long TIMEOUT_DEFAULT_MS = 2000L;
+
+    long LONGRUNNING_FUTURE_THRESHOLD_CRITICAL_DEFAULT_MS = 1000L * 60 * 5;
+
+    long RESULT_CACHE_TTL_DEFAULT_MS = 1000L * 2;
+
+    @AttributeDefinition(name = "Timeout", description = "Timeout in ms until a check is marked as timed out")
+    long timeoutInMs() default TIMEOUT_DEFAULT_MS;
+
+    @AttributeDefinition(name = "Timeout threshold for CRITICAL", description = "Threshold in ms until a check is marked as 'exceedingly' timed out and will marked CRITICAL instead of WARN only")
+    long longRunningFutureThresholdForCriticalMs() default LONGRUNNING_FUTURE_THRESHOLD_CRITICAL_DEFAULT_MS;
+
+    @AttributeDefinition(name = "Results Cache TTL in Ms", description = "Result Cache time to live - results will be cached for the given time")
+    long resultCacheTtlInMs() default RESULT_CACHE_TTL_DEFAULT_MS;
+}

Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPool.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPool.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPool.java (added)
+++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPool.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The SF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.felix.hc.core.impl.executor;
+
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.metatype.annotations.Designate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Creates a thread pool via standard java.util.concurrent package to be used for parallel execution of health checks in
+ * HealthCheckExecutorImpl and AsyncHealthCheckExecutor */
+@Component(service = { HealthCheckExecutorThreadPool.class })
+@Designate(ocd = HealthCheckExecutorThreadPoolConfiguration.class)
+public class HealthCheckExecutorThreadPool {
+    private final static Logger LOG = LoggerFactory.getLogger(HealthCheckExecutorThreadPool.class);
+
+    private int threadPoolSize;
+
+    private ScheduledThreadPoolExecutor executor;
+
+    @Activate
+    protected final void activate(final HealthCheckExecutorThreadPoolConfiguration configuration, final BundleContext bundleContext) {
+
+        this.threadPoolSize = configuration.threadPoolSize();
+
+        executor = new ScheduledThreadPoolExecutor(threadPoolSize, new HcThreadFactory(), new HcRejectedExecutionHandler());
+
+        LOG.info("Created HC Thread Pool: threadPoolSize={}", threadPoolSize);
+
+    }
+
+    @Deactivate
+    protected final void deactivate() {
+        executor.shutdown();
+    }
+
+    // Method called by HealthCheckExecutorImpl (regular synchronous checks)
+    public void execute(final Runnable job) {
+        this.executor.execute(job);
+    }
+
+    // used for interval execution (asynchronous checks)
+    public ScheduledFuture<?> scheduleAtFixedRate(final Runnable job, long intervalInSec) {
+        ScheduledFuture<?> scheduleFuture = executor.scheduleAtFixedRate(job, 0, intervalInSec, TimeUnit.SECONDS);
+        return scheduleFuture;
+    }
+
+    // methods below are used by AsyncHealthCheckExecutor.QuartzThreadPool
+    public int getPoolSize() {
+        return this.executor.getPoolSize();
+    }
+
+    public int getMaxCurrentlyAvailableThreads() {
+        return this.threadPoolSize - executor.getQueue().size();
+    }
+
+    static class HcThreadFactory implements ThreadFactory {
+        private final ThreadGroup group;
+        private final AtomicInteger threadNumber = new AtomicInteger(1);
+
+        HcThreadFactory() {
+            group = Thread.currentThread().getThreadGroup();
+        }
+
+        public Thread newThread(Runnable r) {
+            Thread t = new Thread(group, r, "hc-thread-" + threadNumber.getAndIncrement());
+            t.setDaemon(true); // using daemon thread to not delay JVM shutdown (HC status is non-transactional and only in memory)
+            t.setPriority(Thread.NORM_PRIORITY);
+            return t;
+        }
+    }
+
+    private final class HcRejectedExecutionHandler implements RejectedExecutionHandler {
+        @Override
+        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
+            LOG.warn("Thread Pool {} rejected to run runnable {}", executor, r);
+        }
+    }
+
+}

Added: felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPoolConfiguration.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPoolConfiguration.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPoolConfiguration.java (added)
+++ felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPoolConfiguration.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The SF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.felix.hc.core.impl.executor;
+
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+@ObjectClassDefinition(name = "Apache Felix Health Check Executor Thread Pool", description = "Runs health checks for a given list of tags in parallel.")
+@interface HealthCheckExecutorThreadPoolConfiguration {
+
+    int THREAD_POOL_SIZE_DEFAULT = 25;
+
+    @AttributeDefinition(name = "Thread Pool Size", description = "Number of threads to be used for parallel health check execution")
+    int threadPoolSize() default THREAD_POOL_SIZE_DEFAULT;
+
+}