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 [5/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/src/test/java/org/apache/felix/hc/core/it/AsyncHealthCheckIT.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/AsyncHealthCheckIT.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/AsyncHealthCheckIT.java (added)
+++ felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/AsyncHealthCheckIT.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,216 @@
+/*
+ * 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.it;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.inject.Inject;
+
+import org.apache.felix.hc.api.HealthCheck;
+import org.apache.felix.hc.api.Result;
+import org.apache.felix.hc.api.execution.HealthCheckExecutor;
+import org.apache.felix.hc.api.execution.HealthCheckSelector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+@RunWith(PaxExam.class)
+public class AsyncHealthCheckIT {
+
+    @Inject
+    private HealthCheckExecutor executor;
+
+    @Inject
+    private BundleContext bundleContext;
+
+    @Configuration
+    public Option[] config() {
+        return U.config();
+    }
+
+    final AtomicInteger counter = new AtomicInteger(Integer.MIN_VALUE);
+
+    final static int MAX_VALUE = 12345678;
+
+    class TestHC implements HealthCheck {
+        @Override
+        public Result execute() {
+            final int v = counter.incrementAndGet();
+            return new Result(v > MAX_VALUE ? Result.Status.WARN : Result.Status.OK, "counter is now " + v);
+        }
+    }
+
+    private ServiceRegistration registerAsyncHc(HealthCheck hc, String id, Object async, int stickyMinutes) {
+        final Dictionary<String, Object> props = new Hashtable<String, Object>();
+        props.put(HealthCheck.NAME, "async_HC_" + id);
+        props.put(HealthCheck.TAGS, id);
+        if (async instanceof String) {
+            props.put(HealthCheck.ASYNC_CRON_EXPRESSION, async);
+        } else if (async instanceof Long) {
+            props.put(HealthCheck.ASYNC_INTERVAL_IN_SEC, async);
+        }
+
+        if (stickyMinutes > 0) {
+            props.put(HealthCheck.WARNINGS_STICK_FOR_MINUTES, stickyMinutes);
+        }
+
+        final ServiceRegistration result = bundleContext.registerService(HealthCheck.class.getName(), hc, props);
+
+        // Wait for HC to be registered
+        U.expectHealthChecks(1, executor, id);
+
+        return result;
+    }
+
+    private void assertStatus(String id, Result.Status expected, long maxMsec, String msg) throws InterruptedException {
+        final long timeout = System.currentTimeMillis() + 5000L;
+        while (System.currentTimeMillis() < timeout) {
+            final Result.Status actual = executor.execute(HealthCheckSelector.tags(id)).get(0).getHealthCheckResult().getStatus();
+            if (actual == expected) {
+                return;
+            }
+            Thread.sleep(100L);
+        }
+        fail("Did not get status " + expected + " after " + maxMsec + " msec " + msg);
+    }
+
+    @Test
+    public void testAsyncHealthCheckExecution() throws InterruptedException {
+
+        final String id = UUID.randomUUID().toString();
+        final HealthCheck hc = new TestHC();
+        final ServiceRegistration reg = registerAsyncHc(hc, id, "*/1 * * * * ?", 0);
+        final long maxMsec = 5000L;
+
+        try {
+            // Reset the counter and check that HC increments it without explicitly calling the executor
+            {
+                counter.set(0);
+                final long timeout = System.currentTimeMillis() + maxMsec;
+                while (System.currentTimeMillis() < timeout) {
+                    int currentVal = counter.get();
+                    if (currentVal > 0) {
+                        break;
+                    }
+                    Thread.sleep(100L);
+                }
+                assertTrue("Expecting counter to be incremented", counter.get() > 0);
+            }
+
+            // Verify that we get the right log
+            final String msg = executor.execute(HealthCheckSelector.tags(id)).get(0).getHealthCheckResult().iterator().next().getMessage();
+            assertTrue("Expecting the right message: " + msg, msg.contains("counter is now"));
+
+            // And verify that calling executor lots of times doesn't increment as much
+            final int previous = counter.get();
+            final int n = 100;
+            for (int i = 0; i < n; i++) {
+                executor.execute(HealthCheckSelector.tags(id));
+            }
+            assertTrue("Expecting counter to increment asynchronously", counter.get() < previous + n);
+
+            // Verify that results are not sticky
+            assertStatus(id, Result.Status.OK, maxMsec, "before WARN");
+            counter.set(MAX_VALUE + 1);
+            assertStatus(id, Result.Status.WARN, maxMsec, "right after WARN");
+            counter.set(0);
+            assertStatus(id, Result.Status.OK, maxMsec, "after resetting counter");
+
+        } finally {
+            reg.unregister();
+        }
+
+    }
+
+    @Test
+    public void testAsyncHealthCheckExecutionWithInterval() throws InterruptedException {
+
+        final String id = UUID.randomUUID().toString();
+        final HealthCheck hc = new TestHC();
+        final ServiceRegistration reg = registerAsyncHc(hc, id, new Long(2), 0);
+        final long maxMsec = 5000L;
+
+        try {
+            // Reset the counter and check that HC increments it without explicitly calling the executor
+            {
+                counter.set(0);
+                final long timeout = System.currentTimeMillis() + maxMsec;
+                while (System.currentTimeMillis() < timeout) {
+                    int currentVal = counter.get();
+                    if (currentVal > 0) {
+                        break;
+                    }
+                    Thread.sleep(100L);
+                }
+                assertTrue("Expecting counter to be incremented", counter.get() > 0);
+            }
+
+            // Verify that we get the right log
+            final String msg = executor.execute(HealthCheckSelector.tags(id)).get(0).getHealthCheckResult().iterator().next().getMessage();
+            assertTrue("Expecting the right message: " + msg, msg.contains("counter is now"));
+
+        } finally {
+            reg.unregister();
+        }
+
+    }
+
+    @Test
+    public void testAsyncHealthCheckWithStickyResults() throws InterruptedException {
+        final String id = UUID.randomUUID().toString();
+        final HealthCheck hc = new TestHC();
+        final long maxMsec = 5000L;
+        final int stickyMinutes = 1;
+        final ServiceRegistration reg = registerAsyncHc(hc, id, "*/1 * * * * ?", stickyMinutes);
+
+        try {
+            assertStatus(id, Result.Status.OK, maxMsec, "before WARN");
+            counter.set(MAX_VALUE + 1);
+            assertStatus(id, Result.Status.WARN, maxMsec, "right after WARN");
+            counter.set(0);
+
+            // Counter should be incremented after a while, and in range, but with sticky WARN result
+            final long timeout = System.currentTimeMillis() + maxMsec;
+            boolean ok = false;
+            while (System.currentTimeMillis() < timeout) {
+                if (counter.get() > 0 && counter.get() < MAX_VALUE) {
+                    ok = true;
+                    break;
+                }
+                Thread.sleep(100L);
+            }
+
+            assertTrue("expecting counter to be incremented", ok);
+            assertStatus(id, Result.Status.WARN, maxMsec, "after resetting counter, expecting sticky result");
+
+        } finally {
+            reg.unregister();
+        }
+    }
+
+}

Added: felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/ExtendedHealthCheckExecutorIT.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/ExtendedHealthCheckExecutorIT.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/ExtendedHealthCheckExecutorIT.java (added)
+++ felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/ExtendedHealthCheckExecutorIT.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,113 @@
+/*
+ * 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.it;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.UUID;
+
+import javax.inject.Inject;
+
+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.api.execution.HealthCheckExecutor;
+import org.apache.felix.hc.api.execution.HealthCheckSelector;
+import org.apache.felix.hc.util.HealthCheckFilter;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+/** Additional executor tests */
+@RunWith(PaxExam.class)
+public class ExtendedHealthCheckExecutorIT {
+
+    @Inject
+    private HealthCheckExecutor executor;
+
+    @Inject
+    private BundleContext bundleContext;
+
+    @SuppressWarnings("rawtypes")
+    private List<ServiceRegistration> regs = new ArrayList<ServiceRegistration>();
+
+    private String testTag;
+    private final Result.Status testResult = Result.Status.OK;
+
+    @Configuration
+    public Option[] config() {
+        return U.config();
+    }
+
+    private void registerHC(final String... tags) {
+        final HealthCheck hc = new HealthCheck() {
+            @Override
+            public Result execute() {
+                return new Result(testResult, "Returning " + testResult + " for " + tags[0]);
+            }
+
+        };
+
+        final Dictionary<String, Object> props = new Hashtable<String, Object>();
+        props.put(HealthCheck.NAME, "name_" + tags[0]);
+        props.put(HealthCheck.TAGS, tags);
+
+        regs.add(bundleContext.registerService(HealthCheck.class.getName(), hc, props));
+    }
+
+    @Before
+    public void setup() {
+        testTag = "TEST_" + UUID.randomUUID().toString();
+        registerHC(testTag);
+        U.expectHealthChecks(1, executor, testTag);
+    }
+
+    @After
+    public void cleanup() {
+        for (ServiceRegistration reg : regs) {
+            reg.unregister();
+        }
+    }
+
+    @Test
+    public void testSingleExecution() throws Exception {
+        final HealthCheckFilter filter = new HealthCheckFilter(bundleContext);
+        final ServiceReference[] refs = filter.getHealthCheckServiceReferences(HealthCheckSelector.tags(testTag));
+        assertNotNull(refs);
+        assertEquals(1, refs.length);
+
+        // The ExtendedHealthCheckExecutor interface is not public, so we cheat
+        // to be able to test its implementation
+        final Method m = executor.getClass().getMethod("execute", ServiceReference.class);
+        final HealthCheckExecutionResult result = (HealthCheckExecutionResult) m.invoke(executor, refs[0]);
+        assertEquals(testResult, result.getHealthCheckResult().getStatus());
+    }
+}
\ No newline at end of file

Added: felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/HealthCheckExecutorSelectionIT.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/HealthCheckExecutorSelectionIT.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/HealthCheckExecutorSelectionIT.java (added)
+++ felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/HealthCheckExecutorSelectionIT.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,147 @@
+/*
+ * 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.it;
+
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.UUID;
+
+import javax.inject.Inject;
+
+import org.apache.felix.hc.api.HealthCheck;
+import org.apache.felix.hc.api.Result;
+import org.apache.felix.hc.api.execution.HealthCheckExecutionOptions;
+import org.apache.felix.hc.api.execution.HealthCheckExecutor;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+/** Test the HealthCheckExecutor selection mechanism */
+@RunWith(PaxExam.class)
+public class HealthCheckExecutorSelectionIT {
+
+    @Inject
+    private HealthCheckExecutor executor;
+
+    @Inject
+    private BundleContext bundleContext;
+
+    private static String idA;
+    private static String idB;
+    private HealthCheckExecutionOptions options;
+
+    @SuppressWarnings("rawtypes")
+    private List<ServiceRegistration> regs = new ArrayList<ServiceRegistration>();
+
+    @Configuration
+    public Option[] config() {
+        return U.config();
+    }
+
+    private void registerHC(final String... tags) {
+        final HealthCheck hc = new HealthCheck() {
+            @Override
+            public Result execute() {
+                return new Result(Result.Status.OK, "All good for " + tags[0]);
+            }
+
+        };
+
+        final Dictionary<String, Object> props = new Hashtable<String, Object>();
+        props.put(HealthCheck.NAME, "name_" + tags[0]);
+        props.put(HealthCheck.TAGS, tags);
+
+        regs.add(bundleContext.registerService(HealthCheck.class.getName(), hc, props));
+    }
+
+    @BeforeClass
+    public static void setId() {
+        idA = UUID.randomUUID().toString();
+        idB = UUID.randomUUID().toString();
+    }
+
+    @Before
+    public void setup() {
+        options = new HealthCheckExecutionOptions();
+
+        U.expectHealthChecks(0, executor, idA);
+        U.expectHealthChecks(0, executor, idB);
+
+        registerHC(idA);
+        registerHC(idB);
+        registerHC(idB);
+        registerHC(idA, idB);
+    }
+
+    @After
+    @SuppressWarnings("rawtypes")
+    public void cleanup() {
+        for (ServiceRegistration r : regs) {
+            r.unregister();
+        }
+        regs.clear();
+
+        U.expectHealthChecks(0, executor, idA);
+        U.expectHealthChecks(0, executor, idB);
+    }
+
+    @Test
+    public void testDefaultSelectionA() {
+        U.expectHealthChecks(2, executor, idA);
+        U.expectHealthChecks(2, executor, options, idA);
+    }
+
+    @Test
+    public void testDefaultSelectionB() {
+        U.expectHealthChecks(3, executor, idB);
+        U.expectHealthChecks(3, executor, options, idB);
+    }
+
+    @Test
+    public void testDefaultSelectionAB() {
+        U.expectHealthChecks(1, executor, idA, idB);
+        U.expectHealthChecks(1, executor, options, idA, idB);
+    }
+
+    @Test
+    public void testOrSelectionA() {
+        options.setCombineTagsWithOr(true);
+        U.expectHealthChecks(1, executor, options, idA);
+    }
+
+    @Test
+    public void testOrSelectionB() {
+        options.setCombineTagsWithOr(true);
+        U.expectHealthChecks(3, executor, options, idB);
+    }
+
+    @Test
+    public void testOrSelectionAB() {
+        options.setCombineTagsWithOr(true);
+        U.expectHealthChecks(4, executor, options, idA, idB);
+    }
+}

Added: felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/HealthCheckFilterIT.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/HealthCheckFilterIT.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/HealthCheckFilterIT.java (added)
+++ felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/HealthCheckFilterIT.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,269 @@
+/*
+ * 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.it;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.apache.felix.hc.api.HealthCheck;
+import org.apache.felix.hc.api.Result;
+import org.apache.felix.hc.api.execution.HealthCheckSelector;
+import org.apache.felix.hc.util.HealthCheckFilter;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@RunWith(PaxExam.class)
+public class HealthCheckFilterIT {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private HealthCheckFilter filter;
+
+    @Inject
+    private BundleContext bundleContext;
+
+    private List<TestHealthCheck> testServices = new ArrayList<TestHealthCheck>();
+    private static int instanceCounter = 0;
+
+    class TestHealthCheckBuilder {
+
+        String[] tags;
+        String name;
+
+        TestHealthCheckBuilder withTags(String... tags) {
+            this.tags = tags;
+            return this;
+        }
+
+        TestHealthCheckBuilder withName(String name) {
+            this.name = name;
+            return this;
+        }
+
+        TestHealthCheck build() {
+            final Dictionary<String, Object> props = new Hashtable<String, Object>();
+            if (tags != null) {
+                props.put(HealthCheck.TAGS, tags);
+            }
+            if (name != null) {
+                props.put(HealthCheck.NAME, name);
+            }
+
+            return new TestHealthCheck(props);
+
+        }
+    }
+
+    class TestHealthCheck implements HealthCheck {
+
+        private final int id;
+        private final ServiceRegistration reg;
+
+        TestHealthCheck(Dictionary<String, Object> props) {
+            id = instanceCounter++;
+            reg = bundleContext.registerService(HealthCheck.class.getName(),
+                    this, props);
+            log.info("Registered {} with name {} and tags {}",
+                    new Object[] { this, props.get(HealthCheck.NAME), Arrays.toString((String[]) props.get(HealthCheck.TAGS)) });
+        }
+
+        @Override
+        public String toString() {
+            return "TestHealthCheck#" + id;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            return other instanceof TestHealthCheck
+                    && ((TestHealthCheck) other).id == id;
+        }
+
+        @Override
+        public int hashCode() {
+            return id;
+        }
+
+        @Override
+        public Result execute() {
+            return null;
+        }
+
+        void unregister() {
+            reg.unregister();
+        }
+    }
+
+    private TestHealthCheckBuilder builder() {
+        return new TestHealthCheckBuilder();
+    }
+
+    @Configuration
+    public Option[] config() {
+        return U.config();
+    }
+
+    @Before
+    public void setup() {
+        testServices.add(builder().withTags("foo").withName("test1").build());
+        testServices.add(builder().withTags("bar").withName("test2").build());
+        testServices.add(builder().withTags("foo", "bar").withName("test3").build());
+        testServices.add(builder().withTags("other", "thing").withName("test4").build());
+        testServices.add(builder().withName("test5").build());
+        filter = new HealthCheckFilter(bundleContext);
+    }
+
+    @After
+    public void cleanup() {
+        for (TestHealthCheck tc : testServices) {
+            tc.unregister();
+        }
+    }
+
+    /** @param included true or false, in the same order as testServices */
+    private void assertServices(List<HealthCheck> s, boolean... included) {
+        final Iterator<TestHealthCheck> it = testServices.iterator();
+        for (boolean inc : included) {
+            final TestHealthCheck thc = it.next();
+            if (inc) {
+                assertTrue("Expecting list of services to include " + thc,
+                        s.contains(thc));
+            } else {
+                assertFalse("Not expecting list of services to include " + thc,
+                        s.contains(thc));
+            }
+        }
+    }
+
+    @Test
+    public void testSelectorService() {
+        assertNotNull("Expecting HealthCheckSelector service to be provided",
+                filter);
+    }
+
+    @Test
+    public void testAllServices() {
+        final List<HealthCheck> s = filter.getHealthChecks(null);
+        assertServices(s, true, true, true, true, true);
+    }
+
+    @Test
+    public void testEmptyTags() {
+        final List<HealthCheck> s = filter.getHealthChecks(HealthCheckSelector.tags("", "", ""));
+        assertServices(s, true, true, true, true, true);
+    }
+
+    @Test
+    public void testFooTag() {
+        final List<HealthCheck> s = filter.getHealthChecks(HealthCheckSelector.tags("foo"));
+        assertServices(s, true, false, true, false, false);
+    }
+
+    @Test
+    public void testBarTag() {
+        final List<HealthCheck> s = filter.getHealthChecks(HealthCheckSelector.tags("bar"));
+        assertServices(s, false, true, true, false, false);
+    }
+
+    @Test
+    public void testFooAndBar() {
+        final List<HealthCheck> s = filter.getHealthChecks(HealthCheckSelector.tags("foo", "bar"));
+        assertServices(s, false, false, true, false, false);
+    }
+
+    @Test
+    public void testFooMinusBar() {
+        final List<HealthCheck> s = filter
+                .getHealthChecks(HealthCheckSelector.tags("foo", "-bar"));
+        assertServices(s, true, false, false, false, false);
+    }
+
+    @Test
+    public void testWhitespace() {
+        final List<HealthCheck> s = filter.getHealthChecks(HealthCheckSelector.tags(
+                "\t \n\r foo  \t", "", " \t-bar\n", ""));
+        assertServices(s, true, false, false, false, false);
+    }
+
+    @Test
+    public void testOther() {
+        final List<HealthCheck> s = filter.getHealthChecks(HealthCheckSelector.tags("other"));
+        assertServices(s, false, false, false, true, false);
+    }
+
+    @Test
+    public void testMinusOther() {
+        final List<HealthCheck> s = filter.getHealthChecks(HealthCheckSelector.tags("-other"));
+        assertServices(s, true, true, true, false, true);
+    }
+
+    @Test
+    public void testMinusOtherFoo() {
+        final List<HealthCheck> s = filter.getHealthChecks(HealthCheckSelector.tags("-other",
+                "-foo"));
+        assertServices(s, false, true, false, false, true);
+    }
+
+    @Test
+    public void testNoResults() {
+        final List<HealthCheck> s = filter.getHealthChecks(HealthCheckSelector.tags("NOT A TAG"));
+        assertTrue("Expecting no services", s.isEmpty());
+    }
+
+    @Test
+    public void testSingleName() {
+        final List<HealthCheck> s = filter.getHealthChecks(HealthCheckSelector.names("test1"));
+        assertServices(s, true, false, false, false, false);
+    }
+
+    @Test
+    public void testMultipleNames() {
+        final List<HealthCheck> s = filter.getHealthChecks(HealthCheckSelector.names("test1", "test3"));
+        assertServices(s, true, false, true, false, false);
+    }
+
+    @Test
+    public void testExcludeName() {
+        final List<HealthCheck> s = filter.getHealthChecks(HealthCheckSelector.tags("foo").withNames("-test1"));
+        assertServices(s, false, false, true, false, false);
+    }
+
+    @Test
+    public void testNameOrTag() {
+        final List<HealthCheck> s = filter.getHealthChecks(HealthCheckSelector.tags("foo").withNames("test4"));
+        assertServices(s, true, false, true, true, false);
+    }
+}

Added: felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/HealthCheckServletIT.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/HealthCheckServletIT.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/HealthCheckServletIT.java (added)
+++ felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/HealthCheckServletIT.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,123 @@
+/*
+ * 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.it;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.UUID;
+
+import javax.inject.Inject;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.http.HttpService;
+
+/** Verify that the HealthCheckExecutorServlet becomes available after creating the corresponding config */
+@RunWith(PaxExam.class)
+public class HealthCheckServletIT {
+
+    @Inject
+    private ConfigurationAdmin configAdmin;
+
+    @Inject
+    private BundleContext bundleContext;
+
+    private MockHttpService httpService;
+    private ServiceRegistration reg;
+
+    @Configuration
+    public Option[] config() {
+        return U.config();
+    }
+
+    private int countServletServices(String packageNamePrefix) throws InvalidSyntaxException {
+        final List<String> classNames = httpService.getServletClassNames();
+        int count = 0;
+        for (final String className : classNames) {
+            if (className.startsWith(packageNamePrefix)) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    @Before
+    public void setup() {
+        httpService = new MockHttpService();
+        reg = bundleContext.registerService(HttpService.class.getName(), httpService, null);
+    }
+
+    @After
+    public void cleanup() {
+        reg.unregister();
+        reg = null;
+        httpService = null;
+    }
+
+    @Test
+    public void testServletBecomesActive() throws InvalidSyntaxException, IOException, InterruptedException {
+        final String property = "servletPath";
+        final String path = "/test/" + UUID.randomUUID();
+        final String packagePrefix = "org.apache.felix.hc";
+        assertEquals("Initially expecting no servlet from " + packagePrefix, 0, countServletServices(packagePrefix));
+        final int pathsBefore = httpService.getPaths().size();
+
+        // Activate servlet and wait for it to show up
+        final String pid = "org.apache.felix.hc.core.impl.servlet.HealthCheckExecutorServlet";
+        final org.osgi.service.cm.Configuration cfg = configAdmin.getConfiguration(pid, null);
+        final Dictionary<String, Object> props = new Hashtable<String, Object>();
+        props.put(property, path);
+        cfg.update(props);
+
+        final long timeoutMsec = 5000L;
+        final long endTime = System.currentTimeMillis() + timeoutMsec;
+        while (System.currentTimeMillis() < endTime) {
+            if (countServletServices(packagePrefix) > 0) {
+                break;
+            }
+            Thread.sleep(50L);
+        }
+
+        int expectedServletCount = 6;
+        assertEquals("After adding configuration, expecting six servlets from " + packagePrefix, expectedServletCount,
+                countServletServices(packagePrefix));
+        final List<String> paths = httpService.getPaths();
+        assertEquals("Expecting six new servlet registration", pathsBefore + expectedServletCount, paths.size());
+        assertEquals("Expecting the HC servlet to be registered at " + path, path, paths.get(paths.size() - 6)); // paths list is longer,
+                                                                                                                 // use last entries in list
+        assertEquals("Expecting the HTML HC servlet to be registered at " + path + ".html", path + ".html", paths.get(paths.size() - 5));
+        assertEquals("Expecting the JSON HC servlet to be registered at " + path + ".json", path + ".json", paths.get(paths.size() - 4));
+        assertEquals("Expecting the JSONP HC servlet to be registered at " + path + ".jsonp", path + ".jsonp", paths.get(paths.size() - 3));
+        assertEquals("Expecting the TXT HC servlet to be registered at " + path + ".txt", path + ".txt", paths.get(paths.size() - 2));
+        assertEquals("Expecting the verbose TXT HC servlet to be registered at " + path + ".verbose.txt", path + ".verbose.txt",
+                paths.get(paths.size() - 1));
+    }
+}

Added: felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/JmxAdjustableStatusHealthCheckIT.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/JmxAdjustableStatusHealthCheckIT.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/JmxAdjustableStatusHealthCheckIT.java (added)
+++ felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/JmxAdjustableStatusHealthCheckIT.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,157 @@
+/*
+ * 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.it;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.management.DynamicMBean;
+
+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.ResultLog.Entry;
+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.JmxAdjustableStatusHealthCheck;
+import org.apache.felix.hc.util.FormattingResultLog;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+/** Test jmx-adjustable status for testing HC. */
+@RunWith(PaxExam.class)
+public class JmxAdjustableStatusHealthCheckIT {
+
+    @Inject
+    private HealthCheckExecutor executor;
+
+    @Inject
+    private BundleContext bundleContext;
+
+    private static String testTag = "testTagName";
+
+    @SuppressWarnings("rawtypes")
+    private List<ServiceRegistration> regs = new ArrayList<ServiceRegistration>();
+
+    @Configuration
+    public Option[] config() {
+        return U.config();
+    }
+
+    private void assertResult(String tag, Result.Status expected) {
+        final Result result = getOverallResult(executor.execute(HealthCheckSelector.tags(tag)));
+        assertEquals("Expected status " + expected + " for tag " + tag, expected, result.getStatus());
+    }
+
+    @Before
+    public void setup() {
+        U.expectHealthChecks(0, executor, testTag);
+        registerHC(testTag);
+        U.expectHealthChecks(1, executor, testTag);
+        assertResult(testTag, Result.Status.OK);
+    }
+
+    @After
+    @SuppressWarnings("rawtypes")
+    public void cleanup() throws Exception {
+        invokeMBean("reset", new Object[] {}, new String[] {});
+
+        U.expectHealthChecks(1, executor, testTag);
+        assertResult(testTag, Result.Status.OK);
+
+        for (ServiceRegistration r : regs) {
+            r.unregister();
+        }
+        regs.clear();
+        U.expectHealthChecks(0, executor, testTag);
+    }
+
+    @Test
+    public void testWarnStatus() throws Exception {
+        invokeMBean("addWarnResultForTags", new Object[] { testTag }, new String[] { String.class.getName() });
+        U.expectHealthChecks(2, executor, testTag);
+        assertResult(testTag, Result.Status.WARN);
+    }
+
+    @Test
+    public void testCriticalStatus() throws Exception {
+        invokeMBean("addCriticalResultForTags", new Object[] { "anotherTag," + testTag },
+                new String[] { String.class.getName() });
+
+        final String[] tags = { "anotherTag", testTag };
+        for (String tag : tags) {
+            U.expectHealthChecks(2, executor, testTag);
+            assertResult(tag, Result.Status.CRITICAL);
+        }
+    }
+
+    @Test
+    public void testAnotherTag() throws Exception {
+        // Selecting an unused tag returns WARN - not sure why but
+        // if that changes we should detect it.
+        assertResult("some_unused_tag", Result.Status.WARN);
+    }
+
+    private void registerHC(final String... tags) {
+        final HealthCheck hc = new HealthCheck() {
+            @Override
+            public Result execute() {
+                return new Result(Result.Status.OK, "All good for " + tags[0]);
+            }
+        };
+
+        final Dictionary<String, Object> props = new Hashtable<String, Object>();
+        props.put(HealthCheck.NAME, "name_" + tags[0]);
+        props.put(HealthCheck.TAGS, tags);
+
+        regs.add(bundleContext.registerService(HealthCheck.class.getName(), hc, props));
+    }
+
+    private void invokeMBean(String operation, Object[] args, String[] signature) throws Exception {
+
+        ServiceReference[] serviceReference = bundleContext.getServiceReferences(DynamicMBean.class.getName(),
+                "(jmx.objectname=" + JmxAdjustableStatusHealthCheck.OBJECT_NAME + ")");
+        DynamicMBean mBean = (DynamicMBean) bundleContext.getService(serviceReference[0]);
+        mBean.invoke(operation, args, signature);
+
+    }
+
+    private Result getOverallResult(List<HealthCheckExecutionResult> results) {
+        FormattingResultLog resultLog = new FormattingResultLog();
+        for (HealthCheckExecutionResult executionResult : results) {
+            for (Entry entry : executionResult.getHealthCheckResult()) {
+                resultLog.add(new ResultLog.Entry(entry.getStatus(), entry.getMessage(), entry.getException()));
+            }
+        }
+        return new Result(resultLog);
+    }
+}

Added: felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/MockHttpService.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/MockHttpService.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/MockHttpService.java (added)
+++ felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/MockHttpService.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,62 @@
+/*
+ * 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.it;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.List;
+
+import javax.servlet.Servlet;
+
+import org.osgi.service.http.HttpContext;
+import org.osgi.service.http.HttpService;
+
+class MockHttpService implements HttpService {
+
+    private List<String> paths = new ArrayList<String>();
+
+    private List<String> classNames = new ArrayList<String>();
+
+    @Override
+    public void registerResources(String alias, String name, HttpContext context) {
+    }
+
+    @Override
+    public void registerServlet(String alias, Servlet servlet, Dictionary initparams, HttpContext context) {
+        paths.add(alias);
+        classNames.add(servlet.getClass().getName());
+    }
+
+    public void unregister(String alias) {
+        paths.remove(alias);
+    }
+
+    @Override
+    public HttpContext createDefaultHttpContext() {
+        return null;
+    }
+
+    List<String> getPaths() {
+        return Collections.unmodifiableList(paths);
+    }
+
+    List<String> getServletClassNames() {
+        return Collections.unmodifiableList(classNames);
+    }
+}

Added: felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/U.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/U.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/U.java (added)
+++ felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/core/it/U.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,108 @@
+/*
+ * 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.it;
+
+import static org.junit.Assert.fail;
+import static org.ops4j.pax.exam.CoreOptions.bundle;
+import static org.ops4j.pax.exam.CoreOptions.junitBundles;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.provision;
+import static org.ops4j.pax.exam.CoreOptions.systemProperty;
+import static org.ops4j.pax.exam.CoreOptions.when;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+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.ops4j.pax.exam.CoreOptions;
+import org.ops4j.pax.exam.Option;
+
+/** Test utilities */
+public class U {
+
+    // the name of the system property providing the bundle file to be installed and tested
+    private static final String BUNDLE_JAR_SYS_PROP = "project.bundle.file";
+
+    /** Wait until the specified number of health checks are seen by supplied executor */
+    static void expectHealthChecks(int howMany, HealthCheckExecutor executor, String... tags) {
+        expectHealthChecks(howMany, executor, new HealthCheckExecutionOptions(), tags);
+    }
+
+    /** Wait until the specified number of health checks are seen by supplied executor */
+    static void expectHealthChecks(int howMany, HealthCheckExecutor executor, HealthCheckExecutionOptions options, String... tags) {
+        final long timeout = System.currentTimeMillis() + 10000L;
+        int count = 0;
+        while (System.currentTimeMillis() < timeout) {
+            final List<HealthCheckExecutionResult> results = executor.execute(HealthCheckSelector.tags(tags), options);
+            count = results.size();
+            if (count == howMany) {
+                return;
+            }
+            try {
+                Thread.sleep(100L);
+            } catch (InterruptedException iex) {
+                throw new RuntimeException("Unexpected InterruptedException");
+            }
+        }
+        fail("Did not get " + howMany + " health checks with tags " + Arrays.asList(tags) + " after " + timeout + " msec (last count="
+                + count + ")");
+    }
+
+    static Option[] config() {
+        final String localRepo = System.getProperty("maven.repo.local", "");
+        final boolean felixShell = "true".equals(System.getProperty("felix.shell", "false"));
+
+        final String bundleFileName = System.getProperty(BUNDLE_JAR_SYS_PROP);
+        final File bundleFile = new File(bundleFileName);
+        if (!bundleFile.canRead()) {
+            throw new IllegalArgumentException("Cannot read from bundle file " + bundleFileName + " specified in the "
+                    + BUNDLE_JAR_SYS_PROP + " system property");
+        }
+
+        // As we're using the forked pax exam container, we need to add a VM
+        // option to activate the jacoco test coverage agent.
+        final String coverageCommand = System.getProperty("coverage.command");
+
+        return options(
+                when(localRepo.length() > 0).useOptions(
+                        systemProperty("org.ops4j.pax.url.mvn.localRepository").value(localRepo)),
+                junitBundles(),
+                when(coverageCommand != null && coverageCommand.trim().length() > 0).useOptions(
+                        CoreOptions.vmOption(coverageCommand)),
+                when(felixShell).useOptions(
+                        provision(
+                                mavenBundle("org.apache.felix", "org.apache.felix.gogo.shell", "0.10.0"),
+                                mavenBundle("org.apache.felix", "org.apache.felix.gogo.runtime", "0.10.0"),
+                                mavenBundle("org.apache.felix", "org.apache.felix.gogo.command", "0.12.0"),
+                                mavenBundle("org.apache.felix", "org.apache.felix.shell.remote", "1.1.2"))),
+                provision(
+                        bundle(bundleFile.toURI().toString()),
+                        mavenBundle("org.apache.felix", "org.apache.felix.scr", "2.0.14"),
+                        mavenBundle("org.apache.felix", "org.apache.felix.configadmin", "1.8.16"),
+                        mavenBundle("org.apache.felix", "org.apache.felix.healthcheck.api").versionAsInProject(),
+                        mavenBundle().groupId("org.apache.commons").artifactId("commons-lang3").versionAsInProject(),
+                        mavenBundle().groupId("javax.servlet").artifactId("javax.servlet-api").versionAsInProject(),
+                        mavenBundle().groupId("org.apache.servicemix.bundles").artifactId("org.apache.servicemix.bundles.quartz")
+                                .versionAsInProject()));
+    }
+}

Added: felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/jmx/impl/HealthCheckMBeanTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/jmx/impl/HealthCheckMBeanTest.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/jmx/impl/HealthCheckMBeanTest.java (added)
+++ felix/trunk/healthcheck/core/src/test/java/org/apache/felix/hc/jmx/impl/HealthCheckMBeanTest.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,168 @@
+/*
+ * 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.jmx.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.lang.management.ManagementFactory;
+import java.util.Date;
+import java.util.List;
+
+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.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.HealthCheckSelector;
+import org.apache.felix.hc.core.impl.executor.ExtendedHealthCheckExecutor;
+import org.apache.felix.hc.util.HealthCheckMetadata;
+import org.apache.felix.hc.util.SimpleConstraintChecker;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceReference;
+
+public class HealthCheckMBeanTest {
+    private final MBeanServer jmxServer = ManagementFactory.getPlatformMBeanServer();
+    private boolean resultOk;
+    public static final String OBJECT_NAME = "org.apache.sling.testing:type=HealthCheckMBeanTest";
+
+    private HealthCheck testHealthCheck = new HealthCheck() {
+
+        @Override
+        public Result execute() {
+            if (resultOk) {
+                return new Result(Result.Status.OK, "Nothing to report, result ok");
+            } else {
+                return new Result(Result.Status.WARN, "Result is not ok!");
+            }
+        }
+    };
+
+    private void assertJmxValue(String mbeanName, String attributeName, String constraint, boolean expected) throws Exception {
+        final MBeanServer jmxServer = ManagementFactory.getPlatformMBeanServer();
+        final ObjectName objectName = new ObjectName(mbeanName);
+        if (jmxServer.queryNames(objectName, null).size() == 0) {
+            fail("MBean not found: " + objectName);
+        }
+        final Object value = jmxServer.getAttribute(objectName, attributeName);
+        final ResultLog resultLog = new ResultLog();
+        new SimpleConstraintChecker().check(value, constraint, resultLog);
+        assertEquals("Expecting result " + expected + "(" + resultLog + ")", expected,
+                resultLog.getAggregateStatus().equals(Result.Status.OK));
+
+    }
+
+    @Test
+    public void testBean() throws Exception {
+        final ServiceReference ref = new ServiceReference() {
+
+            @Override
+            public boolean isAssignableTo(Bundle bundle, String className) {
+                return false;
+            }
+
+            @Override
+            public Bundle[] getUsingBundles() {
+                return null;
+            }
+
+            @Override
+            public String[] getPropertyKeys() {
+                return null;
+            }
+
+            @Override
+            public Object getProperty(String key) {
+                return null;
+            }
+
+            @Override
+            public Bundle getBundle() {
+                return null;
+            }
+
+            @Override
+            public int compareTo(Object reference) {
+                return 0;
+            }
+        };
+        final HealthCheckMBean mbean = new HealthCheckMBean(ref, new ExtendedHealthCheckExecutor() {
+
+            @Override
+            public HealthCheckExecutionResult execute(ServiceReference ref) {
+                return new HealthCheckExecutionResult() {
+
+                    @Override
+                    public Result getHealthCheckResult() {
+                        return testHealthCheck.execute();
+                    }
+
+                    @Override
+                    public HealthCheckMetadata getHealthCheckMetadata() {
+                        return null;
+                    }
+
+                    @Override
+                    public Date getFinishedAt() {
+                        return null;
+                    }
+
+                    @Override
+                    public long getElapsedTimeInMs() {
+                        return 0;
+                    }
+
+                    @Override
+                    public boolean hasTimedOut() {
+                        return false;
+                    }
+                };
+            }
+
+            @Override
+            public List<HealthCheckExecutionResult> execute(HealthCheckSelector selector) {
+                return null;
+            }
+
+            @Override
+            public List<HealthCheckExecutionResult> execute(HealthCheckSelector selector, HealthCheckExecutionOptions options) {
+                return null;
+            }
+        });
+        final ObjectName name = new ObjectName(OBJECT_NAME);
+        jmxServer.registerMBean(mbean, name);
+        try {
+            resultOk = true;
+            assertJmxValue(OBJECT_NAME, "ok", "true", true);
+
+            Thread.sleep(1500);
+            resultOk = false;
+            assertJmxValue(OBJECT_NAME, "ok", "true", false);
+
+            Thread.sleep(1500);
+            assertJmxValue(OBJECT_NAME, "log", "contains message=Result is not ok!", true);
+        } finally {
+            jmxServer.unregisterMBean(name);
+        }
+    }
+
+}
\ No newline at end of file

Added: felix/trunk/healthcheck/docs/felix-health-checks.md
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/docs/felix-health-checks.md?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/docs/felix-health-checks.md (added)
+++ felix/trunk/healthcheck/docs/felix-health-checks.md Tue Dec 18 22:58:15 2018
@@ -0,0 +1,151 @@
+
+# Felix Health Checks
+
+Based on simple `HealthCheck` OSGi services, the Felix Health Check Tools ("hc" in short form) are used to 
+check the health of live Felix systems, based on inputs like  OSGi framework status, JMX MBean attribute values, or any context information retrieved via any API.
+
+Health checks are easily extensible either by configuring the supplied default `HealthCheck` services, or 
+by implementing your own `HealthCheck` services to cater for project specific requirements.
+
+However for simple setups, the out of the box health checks are often sufficient. [Executing Health Checks](#executing-health-checks)
+is a good starting point to run existing checks and to get familiar with how health checks work.
+
+See also:
+
+* [Source code for the HealthCheck modules](http://svn.apache.org/repos/asf/felix/trunk/healthcheck)
+* adaptTo() slides about Health Checks (from the time when they were part of Apache Sling):
+    * [adaptTo() 2013 - Automated self-testing and health check of live Sling instances](https://adapt.to/2013/en/schedule/18_healthcheck.html)
+    * [adaptTo() 2014 - New features of the sling health check](https://adapt.to/2014/en/schedule/new-features-of-the-sling-health-check.html)
+
+## Use cases
+Generally health checks have two high level use cases:
+
+* **Load balancers can query the health of an instance** and decide to take it outor back into the list of used backends automatically
+* **Operations teams checking instances** for their internal state **manually**
+
+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 
+* 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 
+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
+attribute values as input and make the results accessible via JMX MBeans.
+
+## Implementing `HealthCheck`s
+
+Health checks checks can be contributed by any bundle via the provided SPI interface. It is best practice to implement a health check as part of the bundle that contains the functionality being checked.
+
+## The `HealthCheck` SPI interface
+
+A `HealthCheck` is just an OSGi service that returns a `Result`.
+
+```
+    public interface HealthCheck {
+        
+        /** 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:
+
+```
+    public class SampleHealthCheck implements HealthCheck {
+
+        @Override
+        public Result execute() {
+            FormattingResultLog log = new FormattingResultLog();
+            ...
+            log.info("Checking my context {}", myContextObject);
+            if(myContextObject.retrieveStatus() != ...expected value...) {
+                log.warn("Problem with ...");
+            }
+            if(myContextObject.retrieveOtherStatus() != ...expected value...) {
+                log.critical("Cricital Problem with ...");
+            }
+            return new Result(log);
+        }
+
+    }
+```
+
+The `Result` is a simple immutable class that provides a `Status` via `getStatus()` (OK, WARN, CRITICAL etc.) and one or more log-like messages that
+can provide more info about what, if anything, went wrong.
+
+### Configuring Health Checks
+
+`HealthCheck` services are created via OSGi configurations. Generic health check service properties are interpreted by the health check executor service. Custom health check service properties can be used by the health check implementation itself to configure its behaviour.
+
+The following generic Health Check properties may be used for all checks:
+
+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 | Used to schedule the execution of a `HealthCheck` at regular intervals, using a cron expression as supported by the [Quartz Cron Trigger](http://www.quartz-scheduler.org/api/previous_versions/1.8.5/org/quartz/CronTrigger.html) module. 
+hc.async.intervalInSec | Long | Used to schedule the execution of a `HealthCheck` at regular intervals, specifying a period in seconds
+hc.resultCacheTtlInMs | Long | Overrides the global default TTL as configured in health check executor for health check responses (since v1.2.6 of core)
+hc.warningsStickForMinutes | Long | This property will make WARN/CRITICAL results stay visible for future executions, even if the current state has returned to status OK. It is useful to keep attention on issues that might still require action after the state went back to OK, e.g. if an event pool has overflown and some events might have been lost (since v1.2.10 of core)
+
+All service properties are optional.
+
+## 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. 
+
+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`.
+
+### Configuring the Health Check Executor
+The health check executor can **optionally** be configured via service PID `org.apache.felix.hc.core.impl.executor.HealthCheckExecutorImpl`:
+
+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
+resultCacheTtlInMs | Long | 2000ms | Result Cache time to live - results will be cached for the given time
+
+
+### JMX access to health checks
+If the `org.apache.felix.hc.jmx` bundle is active, a JMX MBean is created for each `HealthCheck` which has the 
+service property `hc.mbean.name` service property set. All health check MBeans are registered in the 
+domain `org.apache.felix.healthcheck` with a type of `HealthCheck`.
+
+The MBean gives access to the `Result` and the log, as shown on the screenshot below.   
+
+### Health Check Servlet
+Starting with version 1.2.4 of the `org.apache.felix.healthcheck.core` bundle, a flexible Health Checks execution servlet is available. It provides
+similar features to the Web Console plugin described above, with output in HTML, JSON (plain or jsonp) and TXT (concise or verbose) formats (see HTML format rendering page for more documentation).
+
+The Health Checks Servlet is disabled by default, to enable it create an OSGi configuration like
+
+    PID = org.apache.felix.hc.core.impl.servlet.HealthCheckExecutorServlet
+    servletPath = /system/health
+
+which specifies the servlet's base path. That URL then returns an HTML page, by default with the results of all active health checks and
+with instructions at the end of the page about URL parameters which can be used to select specific Health Checks and control their execution and output format.
+
+Note that by design **the Health Checks Servlet doesn't do any access control by itself** to ensure it can detect unhealthy states of the authentication itself. Make sure the configured path is only accessible to relevant infrastructure and operations people. Usually all `/system/*` paths are only accessible from a local network and not routed to the Internet.
+
+By default the HC servlet sends the CORS header `Access-Control-Allow-Origin: *` to allow for client-side browser integrations. The behaviour can be configured using the OSGi config property `cors.accessControlAllowOrigin` (a blank value disables the header).
+
+### Webconsole plugin
+
+If the `org.apache.felix.hc.webconsole` bundle is active, 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).
+
+The DEBUG logs of health checks can optionally be displayed, and an option allows for showing only health checks that have a non-OK status.
\ No newline at end of file

Added: felix/trunk/healthcheck/webconsoleplugin/pom.xml
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/webconsoleplugin/pom.xml?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/webconsoleplugin/pom.xml (added)
+++ felix/trunk/healthcheck/webconsoleplugin/pom.xml Tue Dec 18 22:58:15 2018
@@ -0,0 +1,142 @@
+<?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/xsd/maven-4.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.webconsoleplugin</artifactId>
+    <version>2.0.0-SNAPSHOT</version>
+
+    <name>Apache Felix Health Check Webconsole Plugin</name>
+    <inceptionYear>2013</inceptionYear>
+
+    <description>
+        Plugin for the felix web console
+    </description>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/felix/trunk/healthcheck/webconsoleplugin</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/felix/trunk/healthcheck/webconsoleplugin</developerConnection>
+        <url>http://svn.apache.org/viewvc/felix/trunk/http/webconsoleplugin/</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>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+            </plugin>            
+        </plugins>
+    </build>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.healthcheck.api</artifactId>
+            <version>2.0.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.healthcheck.core</artifactId>
+            <version>2.0.0-SNAPSHOT</version>
+        </dependency>
+
+        <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>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>2.4</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.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.4</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>
+        <!-- END test scope dependencies -->
+        
+     </dependencies>
+
+</project>
\ No newline at end of file

Added: felix/trunk/healthcheck/webconsoleplugin/src/main/java/org/apache/felix/hc/webconsole/impl/HealthCheckWebconsolePlugin.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/webconsoleplugin/src/main/java/org/apache/felix/hc/webconsole/impl/HealthCheckWebconsolePlugin.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/webconsoleplugin/src/main/java/org/apache/felix/hc/webconsole/impl/HealthCheckWebconsolePlugin.java (added)
+++ felix/trunk/healthcheck/webconsoleplugin/src/main/java/org/apache/felix/hc/webconsole/impl/HealthCheckWebconsolePlugin.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,294 @@
+/*
+ * 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.webconsole.impl;
+
+import static org.apache.commons.lang3.StringEscapeUtils.escapeHtml4;
+import static org.apache.felix.hc.util.FormattingResultLog.msHumanReadable;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Collection;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+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.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/** Webconsole plugin to execute health check services */
+@Component(service=Servlet.class, property={
+        org.osgi.framework.Constants.SERVICE_DESCRIPTION + "=Apache Felix Health Check Web Console Plugin",
+        "felix.webconsole.label=" + HealthCheckWebconsolePlugin.LABEL,
+        "felix.webconsole.title="+ HealthCheckWebconsolePlugin.TITLE,
+        "felix.webconsole.category="+ HealthCheckWebconsolePlugin.CATEGORY,
+        "felix.webconsole.css=/healthcheck/res/ui/healthcheck.css"
+})
+@SuppressWarnings("serial")
+public class HealthCheckWebconsolePlugin extends HttpServlet {
+
+    public static final String TITLE = "Health Check";
+    public static final String LABEL = "healthcheck";
+    public static final String CATEGORY = "Main";
+    public static final String PARAM_TAGS = "tags";
+    public static final String PARAM_DEBUG = "debug";
+    public static final String PARAM_QUIET = "quiet";
+
+    public static final String PARAM_FORCE_INSTANT_EXECUTION = "forceInstantExecution";
+    public static final String PARAM_COMBINE_TAGS_WITH_OR = "combineTagsWithOr";
+    public static final String PARAM_OVERRIDE_GLOBAL_TIMEOUT = "overrideGlobalTimeout";
+
+    @Reference
+    private HealthCheckExecutor healthCheckExecutor;
+
+    /** Serve static resource if applicable, and return true in that case */
+    private boolean getStaticResource(final HttpServletRequest req, final HttpServletResponse resp)
+   throws ServletException, IOException {
+        final String pathInfo = req.getPathInfo();
+        if (pathInfo!= null && pathInfo.contains("res/ui")) {
+            final String prefix = "/" + LABEL;
+            final InputStream is = getClass().getResourceAsStream(pathInfo.substring(prefix.length()));
+            if (is == null) {
+                resp.sendError(HttpServletResponse.SC_NOT_FOUND, pathInfo);
+            } else {
+                final OutputStream os = resp.getOutputStream();
+                try {
+                    final byte [] buffer = new byte[16384];
+                    int n=0;
+                    while( (n = is.read(buffer, 0, buffer.length)) > 0) {
+                        os.write(buffer, 0, n);
+                    }
+                } finally {
+                    try {
+                        is.close();
+                    } catch ( final IOException ignore ) {
+                        // ignore
+                    }
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    protected void doGet(final HttpServletRequest req, final HttpServletResponse resp)
+    throws ServletException, IOException {
+        if (getStaticResource(req, resp)) {
+            return;
+        }
+
+        final String tags = getParam(req, PARAM_TAGS, null);
+        final boolean debug = Boolean.valueOf(getParam(req, PARAM_DEBUG, "false"));
+        final boolean quiet = Boolean.valueOf(getParam(req, PARAM_QUIET, "false"));
+        final boolean combineTagsWithOr = Boolean.valueOf(getParam(req, PARAM_COMBINE_TAGS_WITH_OR, "false"));
+        final boolean forceInstantExecution = Boolean.valueOf(getParam(req, PARAM_FORCE_INSTANT_EXECUTION, "false"));
+        final String overrideGlobalTimeoutStr = getParam(req, PARAM_OVERRIDE_GLOBAL_TIMEOUT, "");
+
+        final PrintWriter pw = resp.getWriter();
+        doForm(pw, tags, debug, quiet, combineTagsWithOr, forceInstantExecution, overrideGlobalTimeoutStr);
+
+        // Execute health checks only if tags are specified (even if empty)
+        if (tags != null) {
+            HealthCheckExecutionOptions options = new HealthCheckExecutionOptions();
+            options.setCombineTagsWithOr(combineTagsWithOr);
+            options.setForceInstantExecution(forceInstantExecution);
+            try {
+                options.setOverrideGlobalTimeout(Integer.valueOf(overrideGlobalTimeoutStr));
+            } catch (NumberFormatException nfe) {
+                // override not set in UI
+            }
+
+            Collection<HealthCheckExecutionResult> results = healthCheckExecutor.execute(HealthCheckSelector.tags(tags.split(",")), options);
+
+            pw.println("<table class='content healthcheck' cellpadding='0' cellspacing='0' width='100%'>");
+            int total = 0;
+            int failed = 0;
+            for (final HealthCheckExecutionResult exR : results) {
+
+                final Result r = exR.getHealthCheckResult();
+                total++;
+                if (!r.isOk()) {
+                    failed++;
+                }
+                if (!quiet || !r.isOk()) {
+                    renderResult(pw, exR, debug);
+                }
+
+            }
+            final WebConsoleHelper c = new WebConsoleHelper(resp.getWriter());
+            c.titleHtml("Summary", total + " HealthCheck executed, " + failed + " failures");
+            pw.println("</table>");
+            pw.println("<a href='configMgr/org.apache.sling.hc.core.impl.executor.HealthCheckExecutorImpl'>Configure executor</a><br/><br/>");
+
+        }
+    }
+
+    void renderResult(final PrintWriter pw, final HealthCheckExecutionResult exResult, final boolean debug)  throws IOException {
+        final Result result = exResult.getHealthCheckResult();
+        final WebConsoleHelper c = new WebConsoleHelper(pw);
+
+        final StringBuilder status = new StringBuilder();
+
+        status.append("Tags: ").append(exResult.getHealthCheckMetadata().getTags());
+        status.append(" Finished: ").append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(exResult.getFinishedAt()) + " after "
+                + msHumanReadable(exResult.getElapsedTimeInMs()));
+
+        c.titleHtml(exResult.getHealthCheckMetadata().getTitle(), null);
+
+        c.tr();
+        c.tdContent();
+        c.writer().print(escapeHtml4(status.toString()));
+        c.writer().print("<br/>Result: <span class='resultOk");
+        c.writer().print(result.isOk());
+        c.writer().print("'>");
+        c.writer().print(result.getStatus().toString());
+        c.writer().print("</span>");
+        c.closeTd();
+        c.closeTr();
+
+        c.tr();
+        c.tdContent();
+        for(final ResultLog.Entry e : result) {
+            if (!debug && e.isDebug()) {
+                continue;
+            }
+            c.writer().print("<div class='log");
+            c.writer().print(e.isDebug() ? "DEBUG" : e.getStatus().toString());
+            c.writer().print("'>");
+            c.writer().print(e.getStatus().toString());
+            c.writer().print(' ');
+            c.writer().print(escapeHtml4(e.getMessage()));
+            if (e.getException() != null) {
+                c.writer().print(" ");
+                c.writer().print(escapeHtml4(e.getException().toString()));
+            }
+            c.writer().println("</div>");
+        }
+        c.closeTd();
+    }
+
+    private void doForm(final PrintWriter pw,
+            final String tags,
+            final boolean debug,
+            final boolean quiet,
+            final boolean combineTagsWithOr,
+            final boolean forceInstantExecution,
+            final String overrideGlobalTimeoutStr)
+    throws IOException {
+        final WebConsoleHelper c = new WebConsoleHelper(pw);
+        pw.print("<form method='get'>");
+        pw.println("<table class='content' cellpadding='0' cellspacing='0' width='100%'>");
+        c.titleHtml(TITLE, "To execute health check services, enter "
+                + " an optional list of tags, to select specific health checks, or no tags for all checks."
+                + " Prefix a tag with a minus sign (-) to omit checks having that tag.");
+
+        c.tr();
+        c.tdLabel("Health Check tags (comma-separated)");
+        c.tdContent();
+        c.writer().print("<input type='text' name='" + PARAM_TAGS + "' value='");
+        if ( tags != null ) {
+            c.writer().print(escapeHtml4(tags));
+        }
+        c.writer().println("' class='input' size='80'>");
+        c.closeTd();
+        c.closeTr();
+
+        c.tr();
+        c.tdLabel("Combine tags with logical 'OR' instead of the default 'AND'");
+        c.tdContent();
+        c.writer().print("<input type='checkbox' name='" + PARAM_COMBINE_TAGS_WITH_OR + "' class='input' value='true'");
+        if (combineTagsWithOr) {
+            c.writer().print(" checked=true");
+        }
+        c.writer().println(">");
+        c.closeTd();
+        c.closeTr();
+
+        c.tr();
+        c.tdLabel("Show DEBUG logs");
+        c.tdContent();
+        c.writer().print("<input type='checkbox' name='" + PARAM_DEBUG + "' class='input' value='true'");
+        if ( debug ) {
+            c.writer().print(" checked=true");
+        }
+        c.writer().println(">");
+        c.closeTd();
+        c.closeTr();
+
+        c.tr();
+        c.tdLabel("Show failed checks only");
+        c.tdContent();
+        c.writer().print("<input type='checkbox' name='" + PARAM_QUIET + "' class='input' value='true'");
+        if ( quiet ) {
+            c.writer().print(" checked=true");
+        }
+        c.writer().println(">");
+        c.closeTd();
+        c.closeTr();
+
+        c.tr();
+        c.tdLabel("Force instant execution (no cache, async checks are executed)");
+        c.tdContent();
+        c.writer().print("<input type='checkbox' name='" + PARAM_FORCE_INSTANT_EXECUTION + "' class='input' value='true'");
+        if (forceInstantExecution) {
+            c.writer().print(" checked=true");
+        }
+        c.writer().println(">");
+        c.closeTd();
+        c.closeTr();
+
+        c.tr();
+        c.tdLabel("Override global timeout");
+        c.tdContent();
+        c.writer().print("<input type='text' name='" + PARAM_OVERRIDE_GLOBAL_TIMEOUT + "' value='");
+        if (overrideGlobalTimeoutStr != null) {
+            c.writer().print(escapeHtml4(overrideGlobalTimeoutStr));
+        }
+        c.writer().println("' class='input' size='80'>");
+        c.closeTd();
+        c.closeTr();
+
+        c.tr();
+        c.tdContent();
+        c.writer().println("<input type='submit' value='Execute selected health checks'/>");
+        c.closeTd();
+        c.closeTr();
+
+        c.writer().println("</table></form>");
+    }
+
+    private String getParam(final HttpServletRequest req, final String name, final String defaultValue) {
+        String result = req.getParameter(name);
+        if(result == null) {
+            result = defaultValue;
+        }
+        return result;
+    }
+}

Added: felix/trunk/healthcheck/webconsoleplugin/src/main/java/org/apache/felix/hc/webconsole/impl/WebConsoleHelper.java
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/webconsoleplugin/src/main/java/org/apache/felix/hc/webconsole/impl/WebConsoleHelper.java?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/webconsoleplugin/src/main/java/org/apache/felix/hc/webconsole/impl/WebConsoleHelper.java (added)
+++ felix/trunk/healthcheck/webconsoleplugin/src/main/java/org/apache/felix/hc/webconsole/impl/WebConsoleHelper.java Tue Dec 18 22:58:15 2018
@@ -0,0 +1,74 @@
+/*
+ * 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.webconsole.impl;
+
+import java.io.PrintWriter;
+
+import static org.apache.commons.lang3.StringEscapeUtils.escapeHtml4;
+
+/** Webconsole plugin helper for writing html. */
+class WebConsoleHelper {
+
+    final PrintWriter pw;
+
+    WebConsoleHelper(final PrintWriter w) {
+        pw = w;
+    }
+
+    PrintWriter writer() {
+        return pw;
+    }
+
+    void tdContent() {
+        pw.print("<td class='content' colspan='2'>");
+    }
+
+    void closeTd() {
+        pw.print("</td>");
+    }
+
+    void closeTr() {
+        pw.println("</tr>");
+    }
+
+    void tdLabel(final String label) {
+        pw.print("<td class='content'>");
+        pw.print(escapeHtml4(label));
+        pw.println("</td>");
+    }
+
+    void tr() {
+        pw.println("<tr class='content'>");
+    }
+
+    void titleHtml(String title, String description) {
+        tr();
+        pw.print("<th colspan='3' class='content container'>");
+        pw.print(escapeHtml4(title));
+        pw.println("</th>");
+        closeTr();
+
+        if (description != null) {
+            tr();
+            pw.print("<td colspan='3' class='content'>");
+            pw.print(escapeHtml4(description));
+            pw.println("</th>");
+            closeTr();
+        }
+    }
+}

Added: felix/trunk/healthcheck/webconsoleplugin/src/main/resources/res/ui/healthcheck.css
URL: http://svn.apache.org/viewvc/felix/trunk/healthcheck/webconsoleplugin/src/main/resources/res/ui/healthcheck.css?rev=1849246&view=auto
==============================================================================
--- felix/trunk/healthcheck/webconsoleplugin/src/main/resources/res/ui/healthcheck.css (added)
+++ felix/trunk/healthcheck/webconsoleplugin/src/main/resources/res/ui/healthcheck.css 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 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.
+ */
+
+.healthcheck .logDEBUG {
+    color:grey;
+}
+
+.healthcheck .logINFO {
+    color:blue;
+}
+
+.healthcheck .logWARN,
+.healthcheck .logCRITICAL,
+.healthcheck .logHEALTH_CHECK_ERROR
+{
+    color:red;
+}
+
+.healthcheck .logERROR {
+    color:red;
+    font-weight:bold;
+}
+
+.healthcheck .resultOktrue {
+	color:green;
+    font-weight:bold;
+}
+
+.healthcheck .resultOkfalse {
+    color:red;
+    font-weight:bold;
+}