FELIX-6272 List all available health checks in Web Console
diff --git a/healthcheck/webconsoleplugin/src/main/java/org/apache/felix/hc/webconsole/impl/HealthCheckWebconsolePlugin.java b/healthcheck/webconsoleplugin/src/main/java/org/apache/felix/hc/webconsole/impl/HealthCheckWebconsolePlugin.java
index 46ff61e..2838f96 100644
--- a/healthcheck/webconsoleplugin/src/main/java/org/apache/felix/hc/webconsole/impl/HealthCheckWebconsolePlugin.java
+++ b/healthcheck/webconsoleplugin/src/main/java/org/apache/felix/hc/webconsole/impl/HealthCheckWebconsolePlugin.java
@@ -24,7 +24,10 @@
 import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 
 import javax.servlet.Servlet;
 import javax.servlet.ServletException;
@@ -32,12 +35,20 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+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.HealthCheckMetadata;
 import org.apache.felix.hc.api.execution.HealthCheckSelector;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentConstants;
+import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
 
@@ -58,17 +69,28 @@
     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_SHOWLIST = "showList";
+    
+    
     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";
 
+    public static final String HC_FILTER_OBJECT_CLASS = "(|(objectClass="+HealthCheck.class.getName()+")(objectClass=org.apache.sling.hc.api.HealthCheck))";
+    
     @Reference
     private HealthCheckExecutor healthCheckExecutor;
 
+    private BundleContext bundleContext;
+    
+    @Activate
+    protected void activate(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+    }
+
+    
     /** Serve static resource if applicable, and return true in that case */
-    private boolean getStaticResource(final HttpServletRequest req, final HttpServletResponse resp)
-   throws ServletException, IOException {
+    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;
@@ -97,8 +119,7 @@
     }
 
     @Override
-    protected void doGet(final HttpServletRequest req, final HttpServletResponse resp)
-    throws ServletException, IOException {
+    protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
         if (getStaticResource(req, resp)) {
             return;
         }
@@ -111,6 +132,12 @@
         final String overrideGlobalTimeoutStr = getParam(req, PARAM_OVERRIDE_GLOBAL_TIMEOUT, "");
 
         final PrintWriter pw = resp.getWriter();
+
+        if(Boolean.valueOf(req.getParameter(PARAM_SHOWLIST))) {
+            doHealthCheckList(pw);
+            return;
+        }
+        
         doForm(pw, tags, debug, quiet, combineTagsWithOr, forceInstantExecution, overrideGlobalTimeoutStr);
 
         // Execute health checks only if tags are specified (even if empty)
@@ -145,7 +172,7 @@
             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.felix.hc.core.impl.executor.HealthCheckExecutorImpl'>Configure executor</a><br/><br/>");
+            pw.println("<br/><br/>");
 
         }
     }
@@ -198,14 +225,92 @@
         c.closeTd();
     }
 
+    private void doHealthCheckList(final PrintWriter pw) throws IOException {
+        
+        try {
+
+            pw.println("<br/>");
+            pw.println("<table id=\"healthCheckList\" class=\"tablesorter nicetable\">");
+            pw.println("<thead><tr><th>Name</th><th>Tags</th><th>Properties</th><th>Links</th><th>Bundle</th></thead><tbody>");
+            
+            ServiceReference<?>[] serviceReferences = bundleContext.getServiceReferences((String) null, HC_FILTER_OBJECT_CLASS);
+            for (ServiceReference<?> serviceReference : serviceReferences) {
+                HealthCheckMetadata metadata = new HealthCheckMetadata(serviceReference);
+                
+                pw.println("<tr>");
+                pw.println("<td>"+metadata.getTitle()+"</td>");
+
+                pw.println("<td>");
+                for(String tag: metadata.getTags()) {
+                    String link = LABEL+"?tags="+tag;
+                    pw.println("<a href=\""+link+"\">"+tag+"</a><br/>");
+                }
+                pw.println("</td>");
+                
+                List<String> links = new ArrayList<>();
+
+                pw.println("<td>");
+                String[] propertyKeys = serviceReference.getPropertyKeys();
+                if(propertyKeys!=null) {
+                    for(String propertyKey: propertyKeys) {
+                        if(propertyKey.equals(HealthCheck.NAME)
+                            || propertyKey.equals(HealthCheck.TAGS)
+                            || propertyKey.equals(Constants.OBJECTCLASS) 
+                            || propertyKey.equals(ComponentConstants.COMPONENT_ID) 
+                            ) {
+                            continue;
+                        }
+                        Object value = serviceReference.getProperty(propertyKey);
+                        if(value.getClass().isArray()) {
+                            value = Arrays.asList((Object[]) value);
+                        }
+                        if(HealthCheck.MBEAN_NAME.equals(propertyKey)) {
+                            String link = "jmx/org.apache.felix.healthcheck%3Aname%3D"+value+"%2Ctype%3DHealthCheck";
+                            links.add("<a href=\""+link+"\">JMX Bean "+value+"</a>");
+                            continue;
+                        }
+                        if(ComponentConstants.COMPONENT_NAME.equals(propertyKey)) {
+                            String link = "components/"+value;
+                            links.add("<a href=\""+link+"\">Component "+value+"</a>");
+                            continue;
+                        }
+                        if(Constants.SERVICE_ID.equals(propertyKey)) {
+                            String link = "services/"+value;
+                            links.add("<a href=\""+link+"\">Service "+value+"</a>");
+                            continue;
+                        } else if(propertyKey.startsWith("service.")) {
+                            continue;
+                        }
+                        pw.println(propertyKey+" = "+value+"<br/>");
+                    }
+                }
+                pw.println("</td>");
+                pw.println("<td>"+String.join("<br/>", links)+"</td>");
+
+                pw.println("<td>");
+                String symbolicBundleName = serviceReference.getBundle().getSymbolicName();
+                String link = "bundles/"+symbolicBundleName;
+                pw.println("<a href=\""+link+"\">"+symbolicBundleName+"</a><br/>");
+                pw.println("</td>");
+
+                pw.println("</tr>");
+            }
+
+            pw.println("</thead></table>");
+
+        } catch (InvalidSyntaxException e) {
+            throw new IllegalStateException("Could not render list of health checks: "+e, e);
+        }
+
+    }
+
     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 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%'>");
@@ -213,74 +318,83 @@
                 + " Prefix a tag with a minus sign (-) to omit checks having that tag (can be also used in combination with '*', e.g. '*,-excludedtag').");
 
         c.tr();
-        c.tdLabel("Health Check tags (comma-separated)");
+        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(c.escapeHtml(tags));
         }
-        c.writer().println("' class='input' size='80'>");
+        c.writer().println("' class='input' size='80'> <a href='"+LABEL+"?"+PARAM_SHOWLIST+"=true'>Show list</a>");
         c.closeTd();
         c.closeTr();
 
+
         c.tr();
-        c.tdLabel("Combine tags with logical 'OR' instead of the default 'AND'");
+        c.tdLabel("");
         c.tdContent();
+        c.writer().print("<table id='settingsTable'>");
+        c.writer().print("<tr>");
+        c.writer().print("<td>");
         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.writer().print("</td>");
+        c.writer().print("<td>Combine tags with logical 'OR' instead of the default 'AND'</td>");
 
-        c.tr();
-        c.tdLabel("Show DEBUG logs");
-        c.tdContent();
+        c.writer().print("<td>");
         c.writer().print("<input type='checkbox' name='" + PARAM_DEBUG + "' class='input' value='true'");
-        if ( debug ) {
+        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("</td>");
+        c.writer().print("<td>Show DEBUG logs</td>");
+        c.writer().print("</tr>");
+        
+        c.writer().print("<tr>");
+        c.writer().print("<td>");
         c.writer().print("<input type='checkbox' name='" + PARAM_QUIET + "' class='input' value='true'");
-        if ( quiet ) {
+        if (quiet) {
             c.writer().print(" checked=true");
         }
         c.writer().println(">");
-        c.closeTd();
-        c.closeTr();
+        c.writer().print("</td>");
+        c.writer().print("<td>Show failed checks only</td>");
 
-        c.tr();
-        c.tdLabel("Force instant execution (no cache, async checks are executed)");
-        c.tdContent();
+        c.writer().print("<td>");
         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("</td>");
+        c.writer().print("<td>Force instant execution (no cache, async checks are executed)</td>");
+        c.writer().print("</tr>");
+        
+        
+        c.writer().print("<tr>");
+        c.writer().print("<td colspan='4'>Override global timeout ");
         c.writer().print("<input type='text' name='" + PARAM_OVERRIDE_GLOBAL_TIMEOUT + "' value='");
         if (overrideGlobalTimeoutStr != null) {
             c.writer().print(c.escapeHtml(overrideGlobalTimeoutStr));
         }
-        c.writer().println("' class='input' size='80'>");
+        c.writer().println("' class='input' size='10'> ms");
+        c.writer().print("</td>");
+
+        c.writer().print("</tr>");
+
+        
+        c.writer().print("</table>");
+        
         c.closeTd();
         c.closeTr();
 
         c.tr();
+        c.tdLabel("");
         c.tdContent();
-        c.writer().println("<input type='submit' value='Execute selected health checks'/>");
+        c.writer().println("<input type='submit' value=' Execute '/>  <a href='configMgr/org.apache.felix.hc.core.impl.executor.HealthCheckExecutorImpl'>Configure executor</a>");
         c.closeTd();
         c.closeTr();
 
diff --git a/healthcheck/webconsoleplugin/src/main/java/org/apache/felix/hc/webconsole/impl/WebConsoleHelper.java b/healthcheck/webconsoleplugin/src/main/java/org/apache/felix/hc/webconsole/impl/WebConsoleHelper.java
index f3257bc..cdc73c1 100644
--- a/healthcheck/webconsoleplugin/src/main/java/org/apache/felix/hc/webconsole/impl/WebConsoleHelper.java
+++ b/healthcheck/webconsoleplugin/src/main/java/org/apache/felix/hc/webconsole/impl/WebConsoleHelper.java
@@ -71,6 +71,9 @@
     }
     
     String escapeHtml(String text) {
+        if(text==null) {
+            return null;
+        }
         return text
                 .replace("&", "&amp;")
                 .replace("\"", "&quot;")
diff --git a/healthcheck/webconsoleplugin/src/main/resources/res/ui/healthcheck.css b/healthcheck/webconsoleplugin/src/main/resources/res/ui/healthcheck.css
index 75b79cf..dde1e03 100644
--- a/healthcheck/webconsoleplugin/src/main/resources/res/ui/healthcheck.css
+++ b/healthcheck/webconsoleplugin/src/main/resources/res/ui/healthcheck.css
@@ -44,3 +44,8 @@
     color:red;
     font-weight:bold;
 }
+
+#settingsTable td {
+    padding: 2px;
+}
+}