You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 09:39:48 UTC

[sling-org-apache-sling-hc-support] 01/31: SLING-2987 - split into several bundles

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

rombert pushed a commit to annotated tag org.apache.sling.hc.support-1.0.4
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-hc-support.git

commit 730094505f2d9e4791a9bf76fe2627a0155424fb
Author: Bertrand Delacretaz <bd...@apache.org>
AuthorDate: Mon Aug 12 16:01:50 2013 +0000

    SLING-2987 - split into several bundles
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/healthcheck/healthchecks@1513179 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            | 154 ++++++++++
 .../impl/DefaultLoginsHealthCheck.java             | 129 +++++++++
 .../hc/healthchecks/impl/HealthCheckInfo.java      |  42 +++
 .../hc/healthchecks/impl/InternalRequest.java      | 319 +++++++++++++++++++++
 .../hc/healthchecks/impl/InternalResponse.java     | 185 ++++++++++++
 .../healthchecks/impl/JmxAttributeHealthCheck.java | 115 ++++++++
 .../hc/healthchecks/impl/JmxScriptBinding.java     |  62 ++++
 .../hc/healthchecks/impl/OsgiScriptBinding.java    |  73 +++++
 .../healthchecks/impl/ScriptableHealthCheck.java   | 131 +++++++++
 .../impl/SlingRequestStatusHealthCheck.java        | 153 ++++++++++
 src/test/java/org/apache/sling/hc/healthcheck      |  44 +++
 .../healthchecks/DefaultLoginsHealthCheckTest.java |  77 +++++
 .../healthchecks/JmxAttributeHealthCheckTest.java  |  57 ++++
 .../hc/healthchecks/JmxScriptBindingTest.java      |  41 +++
 .../hc/healthchecks/OsgiScriptBindingTest.java     |  67 +++++
 .../hc/healthchecks/ScriptableHealthCheckTest.java |  94 ++++++
 16 files changed, 1743 insertions(+)

diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..b146639
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project 
+    xmlns="http://maven.apache.org/POM/4.0.0" 
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>16</version>
+        <relativePath>16</relativePath>
+    </parent>
+
+    <groupId>org.apache.sling</groupId>
+    <artifactId>org.apache.sling.hc.healthchecks</artifactId>
+    <packaging>bundle</packaging>
+    <version>0.0.2-SNAPSHOT</version>
+
+    <name>Sling Health Check Module - Health Check Services</name>
+    <inceptionYear>2013</inceptionYear>
+    
+    <description>
+        Default Sling Health Check Services
+    </description>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Private-Package>
+                            org.apache.sling.hc.healthchecks.*
+                        </Private-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.6</source>
+                    <target>1.6</target>
+                </configuration>
+            </plugin>
+           <plugin>
+              <groupId>org.codehaus.mojo</groupId>
+              <artifactId>animal-sniffer-maven-plugin</artifactId>
+              <configuration>
+                <signature>
+                  <groupId>org.codehaus.mojo.signature</groupId>
+                  <artifactId>java16</artifactId>
+                  <version>1.0</version>
+                </signature>
+              </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.scr.annotations</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.hc.api</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.osgi</artifactId>
+            <version>2.2.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.api</artifactId>
+            <version>2.1.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.jcr.api</artifactId>
+            <version>2.0.4</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.engine</artifactId>
+            <version>2.2.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>1.6.2</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+       <dependency>
+            <groupId>javax.jcr</groupId>
+            <artifactId>jcr</artifactId>
+            <scope>provided</scope>
+        </dependency>
+         <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>1.6.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.11</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>1.9.5</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>rhino</groupId>
+            <artifactId>js</artifactId>
+            <version>1.6R6</version>
+            <scope>test</scope>
+        </dependency>
+     </dependencies>
+</project>
diff --git a/src/main/java/org/apache/sling/hc/healthchecks/impl/DefaultLoginsHealthCheck.java b/src/main/java/org/apache/sling/hc/healthchecks/impl/DefaultLoginsHealthCheck.java
new file mode 100644
index 0000000..357c5c1
--- /dev/null
+++ b/src/main/java/org/apache/sling/hc/healthchecks/impl/DefaultLoginsHealthCheck.java
@@ -0,0 +1,129 @@
+/*
+ * 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.sling.hc.healthchecks.impl;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.Credentials;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.ConfigurationPolicy;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.apache.sling.hc.api.Constants;
+import org.apache.sling.hc.api.HealthCheck;
+import org.apache.sling.hc.api.Result;
+import org.apache.sling.hc.api.ResultLogEntry;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** {@link HealthCheck} that checks that Sling default logins fail.
+ *  Used to verify that those logins are disabled on production systems */
+@Component(
+        name="org.apache.sling.hc.DefaultLoginsHealthCheck",
+        configurationFactory=true, 
+        policy=ConfigurationPolicy.REQUIRE, 
+        metatype=true)
+@Service
+public class DefaultLoginsHealthCheck implements HealthCheck {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private Map<String, String> info;
+    
+    @Property(cardinality=500)
+    public static final String PROP_LOGINS = "logins";
+    private List<String> logins;
+    
+    @Property(cardinality=50)
+    public static final String PROP_TAGS = Constants.HC_TAGS;
+    
+    @Property
+    public static final String PROP_NAME = Constants.HC_NAME;
+    
+    @Property
+    public static final String PROP_MBEAN_NAME = Constants.HC_MBEAN_NAME;
+    
+    @Reference
+    private SlingRepository repository;
+    
+    @Activate
+    public void activate(ComponentContext ctx) {
+        info = new HealthCheckInfo(ctx.getProperties());
+        logins = Arrays.asList(PropertiesUtil.toStringArray(ctx.getProperties().get(PROP_LOGINS), new String[] {}));
+        log.info("Activated, logins={}", logins);
+    }
+    
+    @Override
+    public Result execute() {
+        final Result result = new Result(log);
+        int checked=0;
+        int failures=0;
+        
+        for(String login : logins) {
+            final String [] parts = login.split(":");
+            if(parts.length != 2) {
+                result.log(ResultLogEntry.LT_WARN, "Expected login in the form username:password, got " + login);
+                continue;
+            }
+            checked++;
+            final String username = parts[0].trim();
+            final String password = parts[1].trim();
+            final Credentials creds = new SimpleCredentials(username, password.toCharArray());
+            Session s = null;
+            try {
+                s = repository.login(creds);
+                if(s != null) {
+                    failures++;
+                    result.log(ResultLogEntry.LT_WARN_SECURITY, "Login as [" + username + "] succeeded, was expecting it to fail");
+                } else {
+                    result.log(ResultLogEntry.LT_DEBUG, "Login as [" + username + "] didn't throw an Exception but returned null Session");
+                }
+            } catch(RepositoryException re) {
+                result.log(ResultLogEntry.LT_DEBUG, "Login as [" + username + "] failed, as expected");
+            } finally {
+                if(s != null) {
+                    s.logout();
+                }
+            }
+        }
+        
+        if(checked==0) {
+            result.log(ResultLogEntry.LT_WARN, "Did not check any logins, configured logins=" + logins);
+        } else if(failures != 0){
+            result.log(ResultLogEntry.LT_WARN_SECURITY, "Checked " + checked + " logins, " + failures + " tests failed");
+        } else {
+            result.log(ResultLogEntry.LT_DEBUG, "Checked " + checked + " logins, all tests successful");
+        }
+        return result;
+    }
+
+    @Override
+    public Map<String, String> getInfo() {
+        return info;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/hc/healthchecks/impl/HealthCheckInfo.java b/src/main/java/org/apache/sling/hc/healthchecks/impl/HealthCheckInfo.java
new file mode 100644
index 0000000..ac3dc61
--- /dev/null
+++ b/src/main/java/org/apache/sling/hc/healthchecks/impl/HealthCheckInfo.java
@@ -0,0 +1,42 @@
+/*
+ * 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.sling.hc.healthchecks.impl;
+
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.HashMap;
+
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.apache.sling.hc.api.Constants;
+
+// TODO move to services bundle
+
+/** Utility that copies useful HealthCheck service
+ *  properties to a Map that can be used as the Result's
+ *  info(). Copies all service properties that have names
+ *  that start with the {#Constants.HC_PROP_PREFIX} */
+public class HealthCheckInfo extends HashMap<String, String> {
+    private static final long serialVersionUID = 8661195387931574705L;
+
+    public HealthCheckInfo(Dictionary<?, ?> serviceProperties) {
+        put(Constants.HC_NAME, PropertiesUtil.toString(serviceProperties.get(Constants.HC_NAME), ""));
+        put(Constants.HC_MBEAN_NAME, PropertiesUtil.toString(serviceProperties.get(Constants.HC_MBEAN_NAME), ""));
+        put(Constants.HC_TAGS, 
+                Arrays.asList(PropertiesUtil.toStringArray(serviceProperties.get(Constants.HC_TAGS), new String[] {})).toString());
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/hc/healthchecks/impl/InternalRequest.java b/src/main/java/org/apache/sling/hc/healthchecks/impl/InternalRequest.java
new file mode 100644
index 0000000..570e57c
--- /dev/null
+++ b/src/main/java/org/apache/sling/hc/healthchecks/impl/InternalRequest.java
@@ -0,0 +1,319 @@
+/*
+ * 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.sling.hc.healthchecks.impl;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Vector;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+public class InternalRequest implements HttpServletRequest {
+
+    private final String path;
+    private final Map<String, Object> attributes = new HashMap<String, Object>();
+    
+    public InternalRequest(String path) {
+        this.path = path;
+    }
+    
+    @Override
+    public Object getAttribute(String key) {
+        return attributes.get(key);
+    }
+
+    @Override
+    public Enumeration<?> getAttributeNames() {
+        return new Vector<String>(attributes.keySet()).elements();
+    }
+
+    @Override
+    public String getCharacterEncoding() {
+        return "UTF-8";
+    }
+
+    @Override
+    public int getContentLength() {
+        return 0;
+    }
+
+    @Override
+    public String getContentType() {
+        return "text/plain";
+    }
+
+    @Override
+    public ServletInputStream getInputStream() throws IOException {
+        return new ServletInputStream() {
+            @Override
+            public int read() throws IOException {
+                return 0;
+            }
+        };
+    }
+
+    @Override
+    public String getLocalAddr() {
+        return "127.0.0.1";
+    }
+
+    @Override
+    public String getLocalName() {
+        return "localhost";
+    }
+
+    @Override
+    public int getLocalPort() {
+        return 0;
+    }
+
+    @Override
+    public Locale getLocale() {
+        return Locale.getDefault();
+    }
+
+    @Override
+    public Enumeration<?> getLocales() {
+        return new Vector<Locale>().elements();
+    }
+
+    @Override
+    public String getParameter(String arg0) {
+        return null;
+    }
+
+    @Override
+    public Map<?,?> getParameterMap() {
+        return new HashMap<String, Object>();
+    }
+
+    @Override
+    public Enumeration<?> getParameterNames() {
+        return new Vector<String>().elements();
+    }
+
+    @Override
+    public String[] getParameterValues(String arg0) {
+        return null;
+    }
+
+    @Override
+    public String getProtocol() {
+        return "http";
+    }
+
+    @Override
+    public BufferedReader getReader() throws IOException {
+        return new BufferedReader(new StringReader(""));
+    }
+
+    @Override
+    public String getRealPath(String arg0) {
+        return path;
+    }
+
+    @Override
+    public String getRemoteAddr() {
+        return "127.0.0.1";
+    }
+
+    @Override
+    public String getRemoteHost() {
+        return "localhost";
+    }
+
+    @Override
+    public int getRemotePort() {
+        return 1234;
+    }
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(String arg0) {
+        return null;
+    }
+
+    @Override
+    public String getScheme() {
+        return "http";
+    }
+
+    @Override
+    public String getServerName() {
+        return "localhost";
+    }
+
+    @Override
+    public int getServerPort() {
+        return 80;
+    }
+
+    @Override
+    public boolean isSecure() {
+        return false;
+    }
+
+    @Override
+    public void removeAttribute(String arg0) {
+    }
+
+    @Override
+    public void setAttribute(String key, Object value) {
+        attributes.put(key, value);
+    }
+
+    @Override
+    public void setCharacterEncoding(String arg0)
+            throws UnsupportedEncodingException {
+    }
+
+    @Override
+    public String getAuthType() {
+        return null;
+    }
+
+    @Override
+    public String getContextPath() {
+        return "";
+    }
+
+    @Override
+    public Cookie[] getCookies() {
+        return null;
+    }
+
+    @Override
+    public long getDateHeader(String arg0) {
+        return 0;
+    }
+
+    @Override
+    public String getHeader(String arg0) {
+        return null;
+    }
+
+    @Override
+    public Enumeration<?> getHeaderNames() {
+        return new Vector<String>().elements();
+    }
+
+    @Override
+    public Enumeration<?> getHeaders(String arg0) {
+        return new Vector<String>().elements();
+    }
+
+    @Override
+    public int getIntHeader(String arg0) {
+        return 0;
+    }
+
+    @Override
+    public String getMethod() {
+        return "GET";
+    }
+
+    @Override
+    public String getPathInfo() {
+        return path;
+    }
+
+    @Override
+    public String getPathTranslated() {
+        return path;
+    }
+
+    @Override
+    public String getQueryString() {
+        return "";
+    }
+
+    @Override
+    public String getRemoteUser() {
+        return "remoteuser";
+    }
+
+    @Override
+    public String getRequestURI() {
+        return "http://localhost" + path;
+    }
+
+    @Override
+    public StringBuffer getRequestURL() {
+        return new StringBuffer(getRequestURI());
+    }
+
+    @Override
+    public String getRequestedSessionId() {
+        return "";
+    }
+
+    @Override
+    public String getServletPath() {
+        return "";
+    }
+
+    @Override
+    public HttpSession getSession() {
+        return null;
+    }
+
+    @Override
+    public HttpSession getSession(boolean arg0) {
+        return null;
+    }
+
+    @Override
+    public Principal getUserPrincipal() {
+        return null;
+    }
+
+    @Override
+    public boolean isRequestedSessionIdFromCookie() {
+        return false;
+    }
+
+    @Override
+    public boolean isRequestedSessionIdFromURL() {
+        return false;
+    }
+
+    @Override
+    public boolean isRequestedSessionIdFromUrl() {
+        return false;
+    }
+
+    @Override
+    public boolean isRequestedSessionIdValid() {
+        return false;
+    }
+
+    @Override
+    public boolean isUserInRole(String arg0) {
+        return false;
+    }
+}
diff --git a/src/main/java/org/apache/sling/hc/healthchecks/impl/InternalResponse.java b/src/main/java/org/apache/sling/hc/healthchecks/impl/InternalResponse.java
new file mode 100644
index 0000000..ead7da5
--- /dev/null
+++ b/src/main/java/org/apache/sling/hc/healthchecks/impl/InternalResponse.java
@@ -0,0 +1,185 @@
+/*
+ * 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.sling.hc.healthchecks.impl;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Locale;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+public class InternalResponse implements HttpServletResponse {
+
+    private int status = 200;
+    
+    private static class DevNullOutputStream extends ServletOutputStream {
+        @Override
+        public void write(int b) {
+        }
+    }
+    
+    @Override
+    public void flushBuffer() throws IOException {
+    }
+
+    @Override
+    public int getBufferSize() {
+        return 0;
+    }
+
+    @Override
+    public String getCharacterEncoding() {
+        return "UTF-8";
+    }
+
+    @Override
+    public String getContentType() {
+        return "text/plain";
+    }
+
+    @Override
+    public Locale getLocale() {
+        return Locale.getDefault();
+    }
+
+    @Override
+    public ServletOutputStream getOutputStream() throws IOException {
+        return new DevNullOutputStream();
+    }
+
+    @Override
+    public PrintWriter getWriter() throws IOException {
+        return new PrintWriter(new DevNullOutputStream());
+    }
+
+    @Override
+    public boolean isCommitted() {
+        return false;
+    }
+
+    @Override
+    public void reset() {
+    }
+
+    @Override
+    public void resetBuffer() {
+    }
+
+    @Override
+    public void setBufferSize(int arg0) {
+    }
+
+    @Override
+    public void setCharacterEncoding(String arg0) {
+    }
+
+    @Override
+    public void setContentLength(int arg0) {
+    }
+
+    @Override
+    public void setContentType(String arg0) {
+    }
+
+    @Override
+    public void setLocale(Locale arg0) {
+    }
+
+    @Override
+    public void addCookie(Cookie arg0) {
+    }
+
+    @Override
+    public void addDateHeader(String arg0, long arg1) {
+    }
+
+    @Override
+    public void addHeader(String arg0, String arg1) {
+    }
+
+    @Override
+    public void addIntHeader(String arg0, int arg1) {
+    }
+
+    @Override
+    public boolean containsHeader(String arg0) {
+        return false;
+    }
+
+    @Override
+    public String encodeRedirectURL(String url) {
+        return url;
+    }
+
+    @Override
+    public String encodeRedirectUrl(String url) {
+        return url;
+    }
+
+    @Override
+    public String encodeURL(String url) {
+        return url;
+    }
+
+    @Override
+    public String encodeUrl(String url) {
+        return url;
+    }
+
+    @Override
+    public void sendError(int s, String arg1) throws IOException {
+        status = s;
+    }
+
+    @Override
+    public void sendError(int s) throws IOException {
+        status = s;
+    }
+
+    @Override
+    public void sendRedirect(String arg0) throws IOException {
+    }
+
+    @Override
+    public void setDateHeader(String arg0, long arg1) {
+    }
+
+    @Override
+    public void setHeader(String arg0, String arg1) {
+    }
+
+    @Override
+    public void setIntHeader(String arg0, int arg1) {
+    }
+
+    @Override
+    public void setStatus(int s, String arg1) {
+        status = s;
+    }
+
+    @Override
+    public void setStatus(int s) {
+        status = s;
+    }
+    
+    public int getStatus() {
+        return status;
+    }
+}
diff --git a/src/main/java/org/apache/sling/hc/healthchecks/impl/JmxAttributeHealthCheck.java b/src/main/java/org/apache/sling/hc/healthchecks/impl/JmxAttributeHealthCheck.java
new file mode 100644
index 0000000..a536fbd
--- /dev/null
+++ b/src/main/java/org/apache/sling/hc/healthchecks/impl/JmxAttributeHealthCheck.java
@@ -0,0 +1,115 @@
+/*
+ * 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.sling.hc.healthchecks.impl;
+
+import java.lang.management.ManagementFactory;
+import java.util.Map;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.ConfigurationPolicy;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.apache.sling.hc.api.Constants;
+import org.apache.sling.hc.api.HealthCheck;
+import org.apache.sling.hc.api.Result;
+import org.apache.sling.hc.api.ResultLogEntry;
+import org.apache.sling.hc.util.SimpleConstraintChecker;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.helpers.MessageFormatter;
+
+/** {@link HealthCheck} that checks a single JMX attribute */
+@Component(
+        name="org.apache.sling.hc.JmxAttributeHealthCheck",
+        configurationFactory=true, 
+        policy=ConfigurationPolicy.REQUIRE, 
+        metatype=true)
+@Service
+public class JmxAttributeHealthCheck implements HealthCheck {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private Map<String, String> info;
+    private String mbeanName;
+    private String attributeName;
+    private String constraint;
+
+    @Property
+    public static final String PROP_OBJECT_NAME = "mbean.name";
+    
+    @Property
+    public static final String PROP_ATTRIBUTE_NAME = "attribute.name";
+    
+    @Property
+    public static final String PROP_CONSTRAINT = "attribute.value.constraint";
+    
+    @Property(cardinality=50)
+    public static final String PROP_TAGS = Constants.HC_TAGS;
+    
+    @Property
+    public static final String PROP_NAME = Constants.HC_NAME;
+    
+    @Property
+    public static final String PROP_MBEAN_NAME = Constants.HC_MBEAN_NAME;
+    
+    @Activate
+    public void activate(ComponentContext ctx) {
+        info = new HealthCheckInfo(ctx.getProperties());
+        mbeanName = PropertiesUtil.toString(ctx.getProperties().get(PROP_OBJECT_NAME), "");
+        attributeName = PropertiesUtil.toString(ctx.getProperties().get(PROP_ATTRIBUTE_NAME), "");
+        constraint = PropertiesUtil.toString(ctx.getProperties().get(PROP_CONSTRAINT), "");
+        
+        info.put(PROP_OBJECT_NAME, mbeanName);
+        info.put(PROP_ATTRIBUTE_NAME, attributeName);
+        info.put(PROP_CONSTRAINT, constraint);
+        
+        log.info("Activated with HealthCheck name={}, objectName={}, attribute={}, constraint={}", 
+                new Object[] { info.get(Constants.HC_NAME), mbeanName, attributeName, constraint });
+    }
+    
+    @Override
+    public Result execute() {
+        final Result result = new Result(log);
+        result.log(ResultLogEntry.LT_DEBUG, 
+                MessageFormatter.format("Checking {} / {} with constraint {}", 
+                        new Object[] { mbeanName, attributeName, constraint }).getMessage());
+        try {
+            final MBeanServer jmxServer = ManagementFactory.getPlatformMBeanServer();
+            final ObjectName objectName = new ObjectName(mbeanName);
+            if(jmxServer.queryNames(objectName, null).size() == 0) {
+                log.error("MBean not found: {}", objectName);
+            }
+            final Object value = jmxServer.getAttribute(objectName, attributeName);
+            log.debug("{} {} returns {}", new Object[] { mbeanName, attributeName, value });
+            new SimpleConstraintChecker().check(value, constraint, result);
+        } catch(Exception e) {
+            log.warn(e.toString(), e);
+        }
+        return result;
+    }
+
+    @Override
+    public Map<String, String> getInfo() {
+        return info;
+    }
+}
diff --git a/src/main/java/org/apache/sling/hc/healthchecks/impl/JmxScriptBinding.java b/src/main/java/org/apache/sling/hc/healthchecks/impl/JmxScriptBinding.java
new file mode 100644
index 0000000..bbfc95e
--- /dev/null
+++ b/src/main/java/org/apache/sling/hc/healthchecks/impl/JmxScriptBinding.java
@@ -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.sling.hc.healthchecks.impl;
+
+import java.lang.management.ManagementFactory;
+
+import javax.management.AttributeNotFoundException;
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanException;
+import javax.management.MBeanServer;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+import javax.management.ReflectionException;
+
+import org.apache.sling.hc.api.Result;
+import org.apache.sling.hc.api.ResultLogEntry;
+import org.slf4j.helpers.MessageFormatter;
+
+/** The JmxBinding is meant to be bound as "jmx" global variables
+ *  in scripted rules, to allow for writing scripted expressions
+ *  like jmx.attribute("java.lang:type=ClassLoading", "LoadedClassCount") > 100
+ */
+public class JmxScriptBinding {
+    private MBeanServer jmxServer = ManagementFactory.getPlatformMBeanServer();
+    private final Result result;
+    
+    public JmxScriptBinding(Result result) {
+        this.result = result;
+    }
+    
+    public Object attribute(String objectNameString, String attributeName) 
+            throws MalformedObjectNameException, AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException {
+        final ObjectName name = new ObjectName(objectNameString);
+        if(jmxServer.queryNames(name, null).size() == 0) {
+            final String msg = "JMX object name not found: [" + objectNameString + "]";
+            result.log(ResultLogEntry.LT_WARN, msg);
+            throw new IllegalStateException(msg);
+        }
+        result.log(ResultLogEntry.LT_DEBUG, MessageFormatter.format("Got JMX Object [{}]", name).getMessage());
+        final Object value = jmxServer.getAttribute(name, attributeName);
+        result.log(ResultLogEntry.LT_DEBUG, 
+                MessageFormatter.format(
+                        "JMX Object [{}] Attribute [{}] = [{}]", 
+                        new Object[] { name, attributeName, value }).getMessage());
+        return value;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/hc/healthchecks/impl/OsgiScriptBinding.java b/src/main/java/org/apache/sling/hc/healthchecks/impl/OsgiScriptBinding.java
new file mode 100644
index 0000000..27e9f28
--- /dev/null
+++ b/src/main/java/org/apache/sling/hc/healthchecks/impl/OsgiScriptBinding.java
@@ -0,0 +1,73 @@
+/*
+ * 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.sling.hc.healthchecks.impl;
+
+import org.apache.sling.hc.api.Result;
+import org.apache.sling.hc.api.ResultLogEntry;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.slf4j.helpers.MessageFormatter;
+
+/** The OsgiBinding is meant to be bound as an "osgi" global variable
+ *  in scripted rules, to allow for checking some OSGi states in
+ *  a simple way
+ */
+public class OsgiScriptBinding {
+    private final Result result;
+    private final BundleContext bundleContext;
+    
+    public OsgiScriptBinding(BundleContext ctx, Result result) {
+        this.result = result;
+        this.bundleContext = ctx;
+    }
+    
+    public int inactiveBundlesCount() {
+        int count = 0;
+        for(Bundle b : bundleContext.getBundles()) {
+            if(!isActive(b)) {
+                count++;
+            }
+        }
+        result.log(ResultLogEntry.LT_DEBUG, MessageFormatter.format("inactiveBundlesCount={}", count).getMessage());
+        return count;
+    }
+    
+    private boolean isActive(Bundle b) {
+        boolean active = true;
+        if(!isFragment(b) && Bundle.ACTIVE != b.getState()) {
+            active = false;
+            result.log(ResultLogEntry.LT_INFO, 
+                    MessageFormatter.format(
+                            "Bundle {} is not active, state={} ({})", 
+                            new Object[] { b.getSymbolicName(), b.getState(), b.getState()}).getMessage());
+        }
+        return active;
+    }
+    
+    private boolean isFragment(Bundle b) {
+        final String header = (String) b.getHeaders().get( Constants.FRAGMENT_HOST );
+        if(header!= null && header.trim().length() > 0) {
+            result.log(ResultLogEntry.LT_DEBUG, 
+                    MessageFormatter.format("{} is a fragment bundle, state won't be checked", b).getMessage());
+            return true;
+        } else {
+            return false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/hc/healthchecks/impl/ScriptableHealthCheck.java b/src/main/java/org/apache/sling/hc/healthchecks/impl/ScriptableHealthCheck.java
new file mode 100644
index 0000000..6e5d82f
--- /dev/null
+++ b/src/main/java/org/apache/sling/hc/healthchecks/impl/ScriptableHealthCheck.java
@@ -0,0 +1,131 @@
+/*
+ * 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.sling.hc.healthchecks.impl;
+import java.util.Map;
+
+import javax.script.Bindings;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.ConfigurationPolicy;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.apache.sling.hc.api.Constants;
+import org.apache.sling.hc.api.HealthCheck;
+import org.apache.sling.hc.api.Result;
+import org.apache.sling.hc.api.ResultLogEntry;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.helpers.MessageFormatter;
+
+/** {@link HealthCheck} that checks a scriptable expression */
+@Component(
+        name="org.apache.sling.hc.ScriptableHealthCheck",
+        configurationFactory=true, 
+        policy=ConfigurationPolicy.REQUIRE, 
+        metatype=true)
+@Service
+public class ScriptableHealthCheck implements HealthCheck {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private Map<String, String> info;
+    private String expression;
+    private String languageExtension;
+    private BundleContext bundleContext;
+    
+    public static final String DEFAULT_LANGUAGE_EXTENSION = "ecma";
+
+    @Property
+    public static final String PROP_EXPRESSION = "expression";
+    
+    @Property
+    public static final String PROP_LANGUAGE_EXTENSION = "language.extension";
+    
+    @Property(cardinality=50)
+    public static final String PROP_TAGS = Constants.HC_TAGS;
+    
+    @Property
+    public static final String PROP_NAME = Constants.HC_NAME;
+    
+    @Property
+    public static final String PROP_MBEAN_NAME = Constants.HC_MBEAN_NAME;
+    
+    @Reference
+    private ScriptEngineManager scriptEngineManager;
+    
+    @Activate
+    public void activate(ComponentContext ctx) {
+        info = new HealthCheckInfo(ctx.getProperties());
+        bundleContext = ctx.getBundleContext();
+        expression = PropertiesUtil.toString(ctx.getProperties().get(PROP_EXPRESSION), "");
+        languageExtension = PropertiesUtil.toString(ctx.getProperties().get(PROP_LANGUAGE_EXTENSION), DEFAULT_LANGUAGE_EXTENSION);
+        
+        info.put(PROP_EXPRESSION, expression);
+        info.put(PROP_LANGUAGE_EXTENSION, languageExtension);
+        
+        log.info("Activated, name={}, languageExtension={}, expression={}", languageExtension, expression);
+    }
+    
+    @Override
+    public Result execute() {
+        final Result result = new Result(log);
+        result.log(ResultLogEntry.LT_DEBUG, 
+                MessageFormatter.format(
+                        "Checking expression [{}], language extension=[{}]", 
+                        expression, languageExtension).getMessage());
+        try {
+            final ScriptEngine engine = scriptEngineManager.getEngineByExtension(languageExtension);
+            if(engine == null) {
+                result.log(ResultLogEntry.LT_WARN, 
+                        MessageFormatter.format(
+                                "No ScriptEngine available for extension {}", 
+                                languageExtension).getMessage());
+            } else {
+                // TODO pluggable Bindings? Reuse the Sling bindings providers?
+                final Bindings b = engine.createBindings();
+                b.put("jmx", new JmxScriptBinding(result));
+                b.put("osgi", new OsgiScriptBinding(bundleContext, result));
+                final Object value = engine.eval(expression, b);
+                if(value!=null && "true".equals(value.toString())) {
+                    result.log(ResultLogEntry.LT_DEBUG, 
+                            MessageFormatter.format(
+                                    "Expression [{}] evaluates to true as expected", expression).getMessage());
+                } else {
+                    result.log(ResultLogEntry.LT_WARN, 
+                            MessageFormatter.format(
+                                    "Expression [{}] does not evaluate to true, value={}", 
+                                    expression, value).getMessage());
+                }
+            }
+        } catch(Exception e) {
+            result.log(ResultLogEntry.LT_WARN, e.toString()); 
+        }
+        return result;
+    }
+
+    @Override
+    public Map<String, String> getInfo() {
+        return info;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/hc/healthchecks/impl/SlingRequestStatusHealthCheck.java b/src/main/java/org/apache/sling/hc/healthchecks/impl/SlingRequestStatusHealthCheck.java
new file mode 100644
index 0000000..744561d
--- /dev/null
+++ b/src/main/java/org/apache/sling/hc/healthchecks/impl/SlingRequestStatusHealthCheck.java
@@ -0,0 +1,153 @@
+/*
+ * 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.sling.hc.healthchecks.impl;
+
+import java.util.Arrays;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.ConfigurationPolicy;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.apache.sling.engine.SlingRequestProcessor;
+import org.apache.sling.hc.api.Constants;
+import org.apache.sling.hc.api.HealthCheck;
+import org.apache.sling.hc.api.Result;
+import org.apache.sling.hc.api.ResultLogEntry;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.helpers.MessageFormatter;
+
+/** {@link HealthCheck} that checks the HTTP status of Sling requests */
+@Component(
+        name="org.apache.sling.hc.SlingRequestStatusHealthCheck",
+        configurationFactory=true, 
+        policy=ConfigurationPolicy.REQUIRE, 
+        metatype=true)
+@Service
+public class SlingRequestStatusHealthCheck implements HealthCheck {
+
+    private static final Logger log = LoggerFactory.getLogger(SlingRequestStatusHealthCheck.class);
+    private Map<String, String> info;
+    private String [] paths;
+    
+    static class PathSpec {
+        int status;
+        String path;
+        
+        PathSpec(String configuredPath) {
+            path = configuredPath;
+            status = 200;
+            
+            final String [] parts  = configuredPath.split(":");
+            if(parts.length == 2) {
+                try {
+                    status = Integer.valueOf(parts[1].trim());
+                    path = parts[0].trim();
+                } catch(NumberFormatException nfe) {
+                    log.warn("NumberFormatException while parsing [{}], invalid status value?", configuredPath);
+                }
+            } 
+        }
+    }
+    
+    @Property(cardinality=Integer.MAX_VALUE)
+    public static final String PROP_PATH = "path";
+    
+    @Property(cardinality=50)
+    public static final String PROP_TAGS = Constants.HC_TAGS;
+    
+    @Property
+    public static final String PROP_NAME = Constants.HC_NAME;
+    
+    @Property
+    public static final String PROP_MBEAN_NAME = Constants.HC_MBEAN_NAME;
+    
+    @Reference
+    private SlingRequestProcessor requestProcessor;
+    
+    @Reference
+    private ResourceResolverFactory resolverFactory;  
+    
+    @Activate
+    public void activate(ComponentContext ctx) {
+        info = new HealthCheckInfo(ctx.getProperties());
+        paths = PropertiesUtil.toStringArray(ctx.getProperties().get(PROP_PATH), new String [] {});
+        log.info("Activated, paths={}", Arrays.asList(paths));
+    }
+    
+    @Override
+    public Result execute() {
+        final Result result = new Result(log);
+        
+        ResourceResolver resolver = null;
+        int checked = 0;
+        int failed = 0;
+        
+        try {
+            resolver = resolverFactory.getAdministrativeResourceResolver(null);
+            for(String p : paths) {
+                final PathSpec ps = new PathSpec(p);
+                final HttpServletRequest request = new InternalRequest(ps.path);
+                final InternalResponse response = new InternalResponse();
+                requestProcessor.processRequest(request, response, resolver);
+                final int status = response.getStatus();
+                if(status != ps.status) {
+                    failed++;
+                    result.log(ResultLogEntry.LT_WARN,
+                            MessageFormatter.format(
+                            "[{}] returns status {}, expected {}", new Object[] { ps.path, status, ps.status }).getMessage());
+                } else {
+                    result.log(ResultLogEntry.LT_DEBUG,
+                            MessageFormatter.format(
+                            "[{}] returns status {} as expected", ps.path, status).getMessage());
+                }
+                checked++;
+            }
+        } catch(Exception e) {
+            result.log(ResultLogEntry.LT_WARN, "Exception while executing request: " + e.toString());
+        } finally {
+            if(resolver != null) {
+                resolver.close();
+            }
+        }
+        
+        if(checked == 0) {
+            result.log(ResultLogEntry.LT_WARN, "No paths checked, empty paths list?");
+        } else {
+            result.log(ResultLogEntry.LT_DEBUG, 
+                    MessageFormatter.format(
+                            "{} paths checked, {} failures", checked, failed).getMessage());
+        }
+        
+        return result;
+    }
+
+    @Override
+    public Map<String, String> getInfo() {
+        return info;
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/hc/healthcheck b/src/test/java/org/apache/sling/hc/healthcheck
new file mode 100644
index 0000000..954f535
--- /dev/null
+++ b/src/test/java/org/apache/sling/hc/healthcheck
@@ -0,0 +1,44 @@
+package org.apache.sling.hc.impl.healthchecks;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PathSpecTest {
+    private final String pathSpec;
+    private final String expectedPath;
+    private final int expectedStatus;
+    
+    @Parameters(name="{1}")
+    public static List<Object[]> data() {
+        final List<Object[]> result = new ArrayList<Object[]>();
+
+        result.add(new Object[] { "/one.html", "/one.html", 200 } ); 
+        result.add(new Object[] { "/two.html:404", "/two.html", 404 } ); 
+        result.add(new Object[] { "three.html : 404 ", "three.html", 404 } ); 
+        result.add(new Object[] { "four.html:not an integer", "four.html:not an integer", 200 } ); 
+        result.add(new Object[] { "", "", 200 } ); 
+
+        return result;
+    }
+
+    public PathSpecTest(String pathSpec, String expectedPath, int expectedStatus) {
+        this.pathSpec = pathSpec;
+        this.expectedPath = expectedPath;
+        this.expectedStatus = expectedStatus;
+    }
+    
+    @Test
+    public void testParsing() {
+        final SlingRequestStatusHealthCheck.PathSpec ps = new SlingRequestStatusHealthCheck.PathSpec(pathSpec);
+        assertEquals(expectedPath, ps.path);
+        assertEquals(expectedStatus, ps.status);
+    }
+}
diff --git a/src/test/java/org/apache/sling/hc/healthchecks/DefaultLoginsHealthCheckTest.java b/src/test/java/org/apache/sling/hc/healthchecks/DefaultLoginsHealthCheckTest.java
new file mode 100644
index 0000000..1c48f00
--- /dev/null
+++ b/src/test/java/org/apache/sling/hc/healthchecks/DefaultLoginsHealthCheckTest.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The SF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.sling.hc.healthchecks;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+
+import javax.jcr.Credentials;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.sling.hc.api.Result;
+import org.apache.sling.hc.healthchecks.impl.DefaultLoginsHealthCheck;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.junit.Test;
+import org.mockito.Matchers;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+public class DefaultLoginsHealthCheckTest {
+    
+    private Result getTestResult(String login) throws Exception {
+        final DefaultLoginsHealthCheck c = new DefaultLoginsHealthCheck();
+        setField(c, "logins", Arrays.asList(new String[] { login }));
+        
+        final SlingRepository repo = Mockito.mock(SlingRepository.class);
+        setField(c, "repository", repo);
+        final Session s = Mockito.mock(Session.class);
+        Mockito.when(repo.login(Matchers.any(Credentials.class))).thenAnswer(new Answer<Session>() {
+            @Override
+            public Session answer(InvocationOnMock invocation) {
+                final SimpleCredentials c = (SimpleCredentials)invocation.getArguments()[0];
+                if("admin".equals(c.getUserID())) {
+                    return s;
+                }
+                return null;
+            }
+        });
+        
+        return c.execute();
+    }
+    
+    private void setField(Object o, String name, Object value) throws Exception {
+        final Field f = o.getClass().getDeclaredField(name);
+        f.setAccessible(true);
+        f.set(o, value);
+    }
+    
+    @Test
+    public void testHealthCheckFails() throws Exception {
+        assertFalse("Expecting failed check", getTestResult("admin:admin").isOk());
+    }
+    
+    @Test
+    public void testHealthCheckSucceeds() throws Exception {
+        assertTrue("Expecting successful check", getTestResult("FOO:bar").isOk());
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/hc/healthchecks/JmxAttributeHealthCheckTest.java b/src/test/java/org/apache/sling/hc/healthchecks/JmxAttributeHealthCheckTest.java
new file mode 100644
index 0000000..81f68fc
--- /dev/null
+++ b/src/test/java/org/apache/sling/hc/healthchecks/JmxAttributeHealthCheckTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.sling.hc.healthchecks;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.sling.hc.api.Result;
+import org.apache.sling.hc.healthchecks.impl.JmxAttributeHealthCheck;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.osgi.service.component.ComponentContext;
+
+public class JmxAttributeHealthCheckTest {
+    
+    static void assertJmxValue(String objectName, String attributeName, String constraint, boolean expected) {
+        final JmxAttributeHealthCheck hc = new JmxAttributeHealthCheck();
+        
+        final ComponentContext ctx = Mockito.mock(ComponentContext.class);
+        final Dictionary<String, String> props = new Hashtable<String, String>();
+        props.put(JmxAttributeHealthCheck.PROP_OBJECT_NAME, objectName);
+        props.put(JmxAttributeHealthCheck.PROP_ATTRIBUTE_NAME, attributeName);
+        props.put(JmxAttributeHealthCheck.PROP_CONSTRAINT, constraint);
+        Mockito.when(ctx.getProperties()).thenReturn(props);
+        hc.activate(ctx);
+        
+        final Result r = hc.execute();
+        assertEquals("Expected result " + expected, expected, r.isOk());
+    }
+    
+    @Test
+    public void testJmxAttributeMatch() {
+        assertJmxValue("java.lang:type=ClassLoading", "LoadedClassCount", "> 10", true);
+    }
+    
+    @Test
+    public void testJmxAttributeNoMatch() {
+        assertJmxValue("java.lang:type=ClassLoading", "LoadedClassCount", "< 10", false);
+    }
+}
diff --git a/src/test/java/org/apache/sling/hc/healthchecks/JmxScriptBindingTest.java b/src/test/java/org/apache/sling/hc/healthchecks/JmxScriptBindingTest.java
new file mode 100644
index 0000000..7cad43b
--- /dev/null
+++ b/src/test/java/org/apache/sling/hc/healthchecks/JmxScriptBindingTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.sling.hc.healthchecks;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.sling.hc.api.Result;
+import org.apache.sling.hc.healthchecks.impl.JmxScriptBinding;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class JmxScriptBindingTest {
+    
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+    
+    @Test
+    public void testJmxAttribute() throws Exception {
+        final Result r = new Result(logger);
+        final JmxScriptBinding b = new JmxScriptBinding(r);
+        final Object value= b.attribute("java.lang:type=ClassLoading", "LoadedClassCount");
+        assertNotNull("Expecting non-null attribute value", value);
+        assertTrue("Expecting non-empty value", value.toString().length() > 0);
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/hc/healthchecks/OsgiScriptBindingTest.java b/src/test/java/org/apache/sling/hc/healthchecks/OsgiScriptBindingTest.java
new file mode 100644
index 0000000..b9dcf83
--- /dev/null
+++ b/src/test/java/org/apache/sling/hc/healthchecks/OsgiScriptBindingTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.sling.hc.healthchecks;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.sling.hc.api.Result;
+import org.apache.sling.hc.healthchecks.impl.OsgiScriptBinding;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class OsgiScriptBindingTest {
+    
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+    
+    private Bundle mockBundle(boolean isFragment, boolean isActive) {
+        final Bundle b = Mockito.mock(Bundle.class);
+        Mockito.when(b.getState()).thenReturn(isActive ? Bundle.ACTIVE : Bundle.RESOLVED);
+        
+        final Dictionary<String, String> headers = new Hashtable<String, String>();
+        if(isFragment) {
+            headers.put(Constants.FRAGMENT_HOST, "FOO");
+        }
+        Mockito.when(b.getHeaders()).thenReturn(headers);
+        
+        return b;
+    }
+    
+    @Test
+    public void testInactiveBundles() throws Exception {
+        final BundleContext ctx = Mockito.mock(BundleContext.class);
+        final Bundle [] bundles = { 
+                mockBundle(false, true), 
+                mockBundle(false, false), 
+                mockBundle(false, true),
+                mockBundle(true, false)
+        };
+        Mockito.when(ctx.getBundles()).thenReturn(bundles);
+        
+        final Result r = new Result(logger);
+        final OsgiScriptBinding b = new OsgiScriptBinding(ctx, r);
+        assertEquals(1, b.inactiveBundlesCount());
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/hc/healthchecks/ScriptableHealthCheckTest.java b/src/test/java/org/apache/sling/hc/healthchecks/ScriptableHealthCheckTest.java
new file mode 100644
index 0000000..351b88f
--- /dev/null
+++ b/src/test/java/org/apache/sling/hc/healthchecks/ScriptableHealthCheckTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.sling.hc.healthchecks;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.lang.reflect.Field;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+
+import org.apache.sling.hc.api.Result;
+import org.apache.sling.hc.healthchecks.impl.ScriptableHealthCheck;
+import org.junit.Test;
+import org.mockito.Matchers;
+import org.mockito.Mockito;
+import org.osgi.service.component.ComponentContext;
+
+public class ScriptableHealthCheckTest {
+    
+    private ScriptableHealthCheck hc;
+    private Dictionary<String, String> props;
+    private ComponentContext ctx;
+
+    private void assertExpression(String expression, String languageExtension, boolean expected) throws Exception {
+        hc = new ScriptableHealthCheck();
+        ctx = Mockito.mock(ComponentContext.class);
+        props = new Hashtable<String, String>();
+        
+        final ScriptEngine rhino = new ScriptEngineManager().getEngineByExtension("js");
+        assertNotNull("With the rhino jar in our classpath, we should get a js script engine", rhino);
+        final ScriptEngineManager manager = Mockito.mock(ScriptEngineManager.class);
+        Mockito.when(manager.getEngineByExtension(Matchers.same("ecma"))).thenReturn(rhino);
+        final Field f = hc.getClass().getDeclaredField("scriptEngineManager");
+        f.setAccessible(true);
+        f.set(hc, manager);
+        
+        props.put(ScriptableHealthCheck.PROP_EXPRESSION, expression);
+        if(languageExtension != null) {
+            props.put(ScriptableHealthCheck.PROP_LANGUAGE_EXTENSION, languageExtension);
+        }
+        Mockito.when(ctx.getProperties()).thenReturn(props);
+        hc.activate(ctx);
+        final Result r = hc.execute();
+        assertEquals("Expecting result " + expected, expected, r.isOk());
+    }
+    
+    @Test
+    public void testSimpleExpression() throws Exception {
+        assertExpression("2 + 3 == 5", null, true);
+    }
+    
+    @Test
+    public void testJmxExpression() throws Exception {
+        assertExpression(
+                "jmx.attribute('java.lang:type=ClassLoading', 'LoadedClassCount') > 10"
+                + " && jmx.attribute('java.lang:type=Runtime', 'ManagementSpecVersion') > 1", 
+                "ecma", true);
+    }
+    
+    @Test
+    public void testFalseExpression() throws Exception {
+        assertExpression("2 + 3 == 15", null, false);
+    }
+    
+    @Test
+    public void testSyntaxError() throws Exception {
+        assertExpression("{not [valid ok?", null, false);
+    }
+    
+    @Test
+    public void testNoEngine() throws Exception {
+        assertExpression("2 + 3 == 5", null, true);
+        assertExpression("2 + 3 == 5", "groovy", false);
+    }
+}

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.