You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2018/10/30 09:48:16 UTC

[isis] 01/03: ISIS-2027: adds HealthCheckService

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

danhaywood pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git

commit d8c22ac9aa9b07ccfb556200a8e63f38372d8dad
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Tue Oct 30 09:35:02 2018 +0000

    ISIS-2027: adds HealthCheckService
    
    - RO viewer, applib
    - Implementation for simpleapp
    - document changes
---
 .../guides/rgsvc/_rgsvc_application-layer-spi.adoc |   8 ++
 ...c_application-layer-spi_HealthCheckService.adoc |  53 +++++++++++
 ...lication-layer-spi_HomePageProviderService.adoc |   4 +-
 .../main/asciidoc/guides/ugbtb/_ugbtb_web-xml.adoc |  79 +++++++++-------
 .../apache/isis/applib/services/health/Health.java |  39 ++++++++
 .../applib/services/health/HealthCheckService.java |  33 +++++++
 .../commons/authentication/HealthAuthSession.java  |  44 +++++++++
 .../apache/isis/core/webapp/IsisSessionFilter.java |  22 ++++-
 .../IsisSessionFilter_lookupPassThru_Test.java     |  80 ++++++++++++++++
 .../restfulobjects/applib/RepresentationType.java  |   6 +-
 .../restfulobjects/applib/RestfulMediaType.java    |   1 +
 .../applib/health/HealthRepresentation.java        |  40 ++++++++
 .../applib/health/HealthResource.java              |  51 +++++++++++
 .../server/RestfulObjectsApplication.java          |   2 +
 .../server/resources/HealthReprRenderer.java       |  54 +++++++++++
 .../server/resources/HealthResourceServerside.java | 102 +++++++++++++++++++++
 .../services/health/HealthCheckServiceImpl.java    |  29 ++++++
 .../modules/simple/dom/impl/SimpleObjects.java     |   9 +-
 .../webapp/src/main/webapp/WEB-INF/isis.properties |  34 +++----
 .../webapp/src/main/webapp/WEB-INF/web.xml         |   2 +-
 20 files changed, 632 insertions(+), 60 deletions(-)

diff --git a/adocs/documentation/src/main/asciidoc/guides/rgsvc/_rgsvc_application-layer-spi.adoc b/adocs/documentation/src/main/asciidoc/guides/rgsvc/_rgsvc_application-layer-spi.adoc
index 691a01f..30be579 100644
--- a/adocs/documentation/src/main/asciidoc/guides/rgsvc/_rgsvc_application-layer-spi.adoc
+++ b/adocs/documentation/src/main/asciidoc/guides/rgsvc/_rgsvc_application-layer-spi.adoc
@@ -49,6 +49,13 @@ It also lists their corresponding implementation, either a default implementatio
 `CommandService-` ++
 `JdoRepository`
 
+|xref:../rgsvc/rgsvc.adoc#_rgsvc_application-layer-spi_HealthCheckService[`o.a.i.applib.` +
+`services.health` +
+`HealthCheckService`]
+|Performs a health check so that the runtime infrastructure can determine if the application is still healthy (and perform remedial action, such as restarting the app, if not).
+|No default implementation.
+|Exposed via REST API, typically on `/restful/health`.
+
 
 |xref:../rgsvc/rgsvc.adoc#_rgsvc_application-layer-spi_HomePageProviderService[`o.a.i.applib.` +
 `services.homepage` +
@@ -78,3 +85,4 @@ Key:
 include::_rgsvc_application-layer-spi_BackgroundCommandService.adoc[leveloffset=+1]
 include::_rgsvc_application-layer-spi_CommandService.adoc[leveloffset=+1]
 include::_rgsvc_application-layer-spi_HomePageProviderService.adoc[leveloffset=+1]
+include::_rgsvc_application-layer-spi_HealthCheckService.adoc[leveloffset=+1]
diff --git a/adocs/documentation/src/main/asciidoc/guides/rgsvc/_rgsvc_application-layer-spi_HealthCheckService.adoc b/adocs/documentation/src/main/asciidoc/guides/rgsvc/_rgsvc_application-layer-spi_HealthCheckService.adoc
new file mode 100644
index 0000000..de215cb
--- /dev/null
+++ b/adocs/documentation/src/main/asciidoc/guides/rgsvc/_rgsvc_application-layer-spi_HealthCheckService.adoc
@@ -0,0 +1,53 @@
+[[_rgsvc_application-layer-spi_HealthCheckService]]
+= `HealthCheckService`
+:Notice: 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 ag [...]
+:_basedir: ../../
+:_imagesdir: images/
+
+
+
+This service, if implemented, is used to performs a health check to determine if the application is still available.
+The results of this service are made available through a REST resource, typically mounted at `/restful/health`.
+
+The service, when called, will be within the context of a special internal user `__health` with the internal role `__health-role`.
+
+This service was introduced to allow deployment infrastructure to monitor the app and (potentially) restart it if required.
+For example, if deploying to Docker then both Docker Swarm and Kubernetes are orchestrators that can perform this task.
+
+
+== SPI & Implementation
+
+The SPI defined by `HealthCheckService` is:
+
+[source,java]
+----
+public interface HealthCheckService {
+    @Programmatic
+    public Health check();
+}
+----
+
+There is no default implementation.
+Any custom implementations should be annotated using `@DomainService(nature = NatureOfService.DOMAIN)`.
+
+
+== REST API Mapping
+
+The `/restful/health` path must be specified as a "passThru" so that no authentication challenge is issued.
+
+[source,xml]
+.web.xml
+----
+<filter>
+    <filter-name>IsisSessionFilterForRestfulObjects</filter-name>
+    <filter-class>org.apache.isis.core.webapp.IsisSessionFilter</filter-class>
+    ...
+    <init-param>
+        <param-name>passThru</param-name>
+        <param-value>/restful/swagger,/restful/health</param-value>
+    </init-param>
+    ...
+</filter>
+----
+
+This is a comma separated list, so there may be other values also (for example `/restful/swagger`, as shown above).
diff --git a/adocs/documentation/src/main/asciidoc/guides/rgsvc/_rgsvc_application-layer-spi_HomePageProviderService.adoc b/adocs/documentation/src/main/asciidoc/guides/rgsvc/_rgsvc_application-layer-spi_HomePageProviderService.adoc
index 1fbe341..10c0343 100644
--- a/adocs/documentation/src/main/asciidoc/guides/rgsvc/_rgsvc_application-layer-spi_HomePageProviderService.adoc
+++ b/adocs/documentation/src/main/asciidoc/guides/rgsvc/_rgsvc_application-layer-spi_HomePageProviderService.adoc
@@ -17,7 +17,6 @@ The SPI defined by `HomePageProviderService` is:
 
 [source,java]
 ----
-@DomainService(nature = NatureOfService.DOMAIN)
 public interface HomePageProviderService {
     @Programmatic
     Object homePage();
@@ -26,4 +25,7 @@ public interface HomePageProviderService {
 
 The default implementation is provided by `o.a.i.core.runtime.services.homepage.HomePageProviderServiceDefault`.
 
+Any custom implementations should be annotated using `@DomainService(nature = NatureOfService.DOMAIN)`.
+
+
 
diff --git a/adocs/documentation/src/main/asciidoc/guides/ugbtb/_ugbtb_web-xml.adoc b/adocs/documentation/src/main/asciidoc/guides/ugbtb/_ugbtb_web-xml.adoc
index d8ab5c2..3806997 100644
--- a/adocs/documentation/src/main/asciidoc/guides/ugbtb/_ugbtb_web-xml.adoc
+++ b/adocs/documentation/src/main/asciidoc/guides/ugbtb/_ugbtb_web-xml.adoc
@@ -6,9 +6,12 @@
 
 
 
-Apache Isis provides two different viewers, the xref:../ugvw/ugvw.adoc#[Wicket viewer] and the xref:../ugvro/ugvro.adoc#[RestfulObjects viewer].  You can deploy both of these concurrently, or deploy just the Wicket viewer, or deploy just the Restful Objects viewer.  The configuration in `web.xml` varies accordingly, both in terms of the servlet context listeners, filters and servlets.
+Apache Isis provides two different viewers, the xref:../ugvw/ugvw.adoc#[Wicket viewer] and the xref:../ugvro/ugvro.adoc#[RestfulObjects viewer].
+You can deploy both of these concurrently, or deploy just the Wicket viewer, or deploy just the Restful Objects viewer.
+The configuration in `web.xml` varies accordingly, both in terms of the servlet context listeners, filters and servlets.
 
-If you are using Apache Isis' integration with Apache Shiro (for security) then this also needs configuring in `web.xml`.  See the xref:../ugsec/ugsec.adoc#_ugsec_configuring-isis-to-use-shiro[security chapter] for full details on this topic.
+If you are using Apache Isis' integration with Apache Shiro (for security) then this also needs configuring in `web.xml`.
+See the xref:../ugsec/ugsec.adoc#_ugsec_configuring-isis-to-use-shiro[security chapter] for full details on this topic.
 
 The servlets and filters are mapped to three main pipelines:
 
@@ -24,15 +27,19 @@ the diagram below shows the components to be configured if deploying both the Wi
 
 image::{_imagesdir}runtime/web-xml/wicket-and-ro.png[width="800px",link="{_imagesdir}runtime/web-xml/wicket-and-ro.png"]
 
-Here the Wicket viewer is responsible for the main bootstrapping of Apache Isis itself, in other words the shared (global) metadata; this is done by the `IsisWicketApplication` class (extending the `WicketApplication` Wicket API).  This class is also responsible for Apache Isis' own session and transaction management.
+Here the Wicket viewer is responsible for the main bootstrapping of Apache Isis itself, in other words the shared (global) metadata; this is done by the `IsisWicketApplication` class (extending the `WicketApplication` Wicket API).
+This class is also responsible for Apache Isis' own session and transaction management.
 
-The Restful Objects viewer - being a JAX-RS application implemented using the RestEasy framework - requires the `RestEasyBootstrapper` servlet context listener.  It is this context listener that also sets up the `RestfulObjectsApplication`, which is then delegated to by the RestEasy `HttpServletDispatcher`.  This pipeline uses the `IsisSessionFilter` and `IsisTransactionFilterForRestfulObjects` to perform the session and transaction management before it hits the RestEasy servlet.
+The Restful Objects viewer - being a JAX-RS application implemented using the RestEasy framework - requires the `RestEasyBootstrapper` servlet context listener.
+It is this context listener that also sets up the `RestfulObjectsApplication`, which is then delegated to by the RestEasy `HttpServletDispatcher`.
+This pipeline uses the `IsisSessionFilter` and `IsisTransactionFilterForRestfulObjects` to perform the session and transaction management before it hits the RestEasy servlet.
 
 If only the Wicket viewer is deployed, then the diagram is more or less the same: the RestEasy servlet, context listener and supporting filters are simply removed:
 
 image::{_imagesdir}runtime/web-xml/wicket-only.png[width="800px",link="{_imagesdir}runtime/web-xml/wicket-only.png"]
 
-Finally, if only the Restful Objects viewer is deployed, then things change a little more subtly.  Here, the Wicket filter is no longer needed.  In its place, though the `IsisWebAppBootstrapper` context listener is required: this is responsible for seting up the shared (global) metadata.
+Finally, if only the Restful Objects viewer is deployed, then things change a little more subtly.  Here, the Wicket filter is no longer needed.
+In its place, though the `IsisWebAppBootstrapper` context listener is required: this is responsible for seting up the shared (global) metadata.
 
 image::{_imagesdir}runtime/web-xml/ro-only.png[width="800px",link="{_imagesdir}runtime/web-xml/ro-only.png"]
 
@@ -43,7 +50,9 @@ The following sections detail these various listeners, filters and servlets in m
 [[__ugbtb_web-xml_servlet-context-listeners]]
 == Servlet Context Listeners
 
-Servlet context listeners are used to perform initialization on application startup.  Both Shiro (if configured as the security mechanism) and RestEasy (for the Restful Objects viewer) require their own context listener.  In addition, if the Wicket viewer is _not_ being used, then additional Apache Isis-specific listener is required for bootstrapping of the Apache Isis framework itself.
+Servlet context listeners are used to perform initialization on application startup.
+Both Shiro (if configured as the security mechanism) and RestEasy (for the Restful Objects viewer) require their own context listener.
+In addition, if the Wicket viewer is _not_ being used, then additional Apache Isis-specific listener is required for bootstrapping of the Apache Isis framework itself.
 
 
 === `EnvironmentLoaderListener` (Shiro)
@@ -63,7 +72,8 @@ Its definition is:
 
 === `IsisWebAppBootstrapper`
 
-The `IsisWebAppBootstrapper` servlet context listener bootstraps the shared (global) metadata for the Apache Isis framework.  This listener is not required (indeed must not be configured) if the Wicket viewer is in use.
+The `IsisWebAppBootstrapper` servlet context listener bootstraps the shared (global) metadata for the Apache Isis framework.
+This listener is not required (indeed must not be configured) if the Wicket viewer is in use.
 
 Its definition is:
 
@@ -91,7 +101,8 @@ Its context parameters are:
 
 === `ResteasyBootstrap` (RestEasy)
 
-The `ResteasyBootstrap` servlet context listener initializes the RestEasy runtime, specifying that classes (namely, those specified in Isis' `RestfulObjectsApplication`) to be exposed as REST resources.  It is required if the Restful Objects viewer is to be deployed.
+The `ResteasyBootstrap` servlet context listener initializes the RestEasy runtime, specifying that classes (namely, those specified in Isis' `RestfulObjectsApplication`) to be exposed as REST resources.
+It is required if the Restful Objects viewer is to be deployed.
 
 Its definition is:
 
@@ -216,14 +227,13 @@ Its mapping is:
 [[__ugbtb_web-xml_filters]]
 == Filters
 
-The order in which filters appear in `web.xml` matters: first to last they define a pipeline.  This is shown in the
-above diagrams, and the subsections also list the in the same order that they should appear in your `web.xml`.
+The order in which filters appear in `web.xml` matters: first to last they define a pipeline.
+This is shown in the above diagrams, and the subsections also list the in the same order that they should appear in your `web.xml`.
 
 
 === `ShiroFilter` (Shiro)
 
-Shiro filter that sets up a Shiro security manager for the request, obtained from the Shiro `WebEnvironment` set up
-by the Shiro `EnvironmentLoaderListener` (discussed above).
+Shiro filter that sets up a Shiro security manager for the request, obtained from the Shiro `WebEnvironment` set up by the Shiro `EnvironmentLoaderListener` (discussed above).
 
 Its definition is:
 
@@ -248,9 +258,8 @@ Its mapping is:
 
 === `IsisLogOnExceptionFilter`
 
-The `IsisLogOnExceptionFilter` filter simply logs the URL of any request that causes an exception to be thrown, then
-re-propagates the exception. The use case is simply to ensure that all exceptions are logged (against the
-`IsisLogOnExceptionFilter` slf4j appender).
+The `IsisLogOnExceptionFilter` filter simply logs the URL of any request that causes an exception to be thrown, then re-propagates the exception.
+The use case is simply to ensure that all exceptions are logged (against the `IsisLogOnExceptionFilter` slf4j appender).
 
 Its definition is:
 
@@ -353,7 +362,7 @@ Its definition is:
     <filter-name>WicketFilter</filter-name>
     <filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
     <init-param>
-        <param-name>applicationClassName</param-name>   <!--1-->
+        <param-name>applicationClassName</param-name>                   <!--1-->
         <param-value>domainapp.webapp.SimpleApplication</param-value>
     </init-param>
 </filter>
@@ -383,7 +392,8 @@ This filter reads one context parameter:
 
 === `IsisSessionFilter`
 
-The `IsisSessionFilter` is responsible for the (persistence) session management; in effect a wrapper around DataNucleus' `PersistenceManager` object.  It is only required for the Restful Objects viewer.
+The `IsisSessionFilter` is responsible for the (persistence) session management; in effect a wrapper around DataNucleus' `PersistenceManager` object.
+It is only required for the Restful Objects viewer.
 
 [source,xml]
 ----
@@ -402,7 +412,7 @@ The `IsisSessionFilter` is responsible for the (persistence) session management;
     </init-param>
     <init-param>
         <param-name>passThru</param-name>                       <!--3-->
-        <param-value>/restful/swagger</param-value>
+        <param-value>/restful/swagger,/restful/health</param-value>
     </init-param>
     <!--
     <init-param>
@@ -417,21 +427,30 @@ The `IsisSessionFilter` is responsible for the (persistence) session management;
 </filter>
 ----
 <1> pluggable strategy for determining what the authentication session (credentials) are of the request
-<2> what the servlet should do if no existing session was found.  Usual values are either `unauthorized`, `basicAuthChallenge` or `auto`.  Discussed in more detail below.
-<3> specify which URIs to ignore and simply passthru.  Originally introduced to allow the `SwaggerSpec` resource (which does not require a session) to be invoked.
-<4> List of paths that are allowed through even if not authenticated.  The servlets mapped to these paths are expected to be able to deal with there being no session. Typically they will be logon pages.  See below for further details.
+<2> what the servlet should do if no existing session was found.
+Usual values are either `unauthorized`, `basicAuthChallenge` or `auto`.
+Discussed in more detail below.
+<3> specify which URIs to ignore and simply pass through.
+Originally introduced to allow the `SwaggerSpec` resource (which does not require a session) to be invoked.
+<4> A comma separated list of paths that are allowed through even if not authenticated.
+The servlets mapped to these paths are expected to be able to deal with there being no session.
+Typically they will be logon pages, or for health checks (as per xref:../rgsvc/rgsvc.adoc#_rgsvc_application-layer-spi_HealthCheckService[`HealthCheckService`] SPI.
+See below for further details.
 <5> where to redirect to if an exception occurs.
 
 
-The `whenNoSession` parameter determines what the behaviour should be if no existing session can be found.  There are a number of predetermined values available:
+The `whenNoSession` parameter determines what the behaviour should be if no existing session can be found.
+There are a number of predetermined values available:
 
 * `unauthorized` will generates a 401 response
 * `basicAuthChallenge` will also generate a 401 response, and also issues a Basic Auth challenge using `WWW-Authenticate` response header
-* `auto` combines the `unauthorized` and `basicAuthChallenge` strategies: it will generate a 401 response, but only issues a Basic Auth challenge if it detects that the request originates from a web browser (ie that the HTTP `Accept` header is set to `text/html`).  This means that custom Javascript apps can perform their authentication correctly, while the REST API can still be explored using the web browser (relying upon the web browser's in-built support for HTTP Basic Auth).
+* `auto` combines the `unauthorized` and `basicAuthChallenge` strategies: it will generate a 401 response, but only issues a Basic Auth challenge if it detects that the request originates from a web browser (ie that the HTTP `Accept` header is set to `text/html`).
+This means that custom Javascript apps can perform their authentication correctly, while the REST API can still be explored using the web browser (relying upon the web browser's in-built support for HTTP Basic Auth).
 * `continue`, in which case the request is allowed to continue but the destination expected to know that there will be no open session
 * `restricted`, which allows access to a restricted list of URLs, otherwise will redirect to the first of that list of URLs
 
-If accessing the REST API through a web browser, then normally `basicAuthChallenge` is appropriate; the browser will automatically display a simple prompt.  If accessing the REST API through a custom Javascript app, then `unauthorized` is usually the one to use.
+If accessing the REST API through a web browser, then normally `basicAuthChallenge` is appropriate; the browser will automatically display a simple prompt.
+If accessing the REST API through a custom Javascript app, then `unauthorized` is usually the one to use.
 
 This filter should be mapped to the `servlet-name` for the RestEasy `HttpServletDispatcher`; for example:
 
@@ -448,8 +467,7 @@ This filter should be mapped to the `servlet-name` for the RestEasy `HttpServlet
 
 === `IsisTransactionFilterForRestfulObjects`
 
-The `IsisTransactionFilterForRestfulObjects` filter simply ensures that a transaction is in progress for all
-calls routed to the xref:../ugvro/ugvro.adoc#[RestfulObjects viewer].
+The `IsisTransactionFilterForRestfulObjects` filter simply ensures that a transaction is in progress for all calls routed to the xref:../ugvro/ugvro.adoc#[RestfulObjects viewer].
 
 Its definition is:
 
@@ -476,10 +494,10 @@ This filter should be mapped to the `servlet-name` for the RestEasy `HttpServlet
 [[__ugbtb_web-xml_context-parameters]]
 == Configuration Files
 
-However Apache Isis is bootstrapped (using the `IsisWicketApplication` or using `IsisWebAppBootstrapper`), it will
-read a number of configuration files, such as `isis.properties`.
+However Apache Isis is bootstrapped (using the `IsisWicketApplication` or using `IsisWebAppBootstrapper`), it will read a number of configuration files, such as `isis.properties`.
 
-By default these are read from `WEB-INF` directory.  This can be overriden using the `isis.config.dir` context parameter:
+By default these are read from `WEB-INF` directory.
+This can be overridden using the `isis.config.dir` context parameter:
 
 [source,xml]
 ----
@@ -489,8 +507,7 @@ By default these are read from `WEB-INF` directory.  This can be overriden using
 </context-param>
 ----
 
-Another context parameter, `isis.viewres` specifies which additional configuration files to search for (over and above
-the default ones of `isis.properties` et al):
+Another context parameter, `isis.viewres` specifies which additional configuration files to search for (over and above the default ones of `isis.properties` et al):
 
 [source,xml]
 ----
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/health/Health.java b/core/applib/src/main/java/org/apache/isis/applib/services/health/Health.java
new file mode 100644
index 0000000..f232320
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/health/Health.java
@@ -0,0 +1,39 @@
+/*
+ *  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.
+ */
+package org.apache.isis.applib.services.health;
+
+public class Health {
+
+    public static Health ok() {
+        return new Health(null);
+    }
+    public static Health error(String message) {
+        return new Health(message);
+    }
+
+    private final String message;
+
+    private Health(final String message) {
+        this.message = message;
+    }
+
+    public boolean getResult() { return message == null; }
+    public String getMessage() { return message; }
+
+}
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/health/HealthCheckService.java b/core/applib/src/main/java/org/apache/isis/applib/services/health/HealthCheckService.java
new file mode 100644
index 0000000..f197b5c
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/health/HealthCheckService.java
@@ -0,0 +1,33 @@
+/*
+ *  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.
+ */
+package org.apache.isis.applib.services.health;
+
+import org.apache.isis.applib.annotation.Programmatic;
+
+/**
+ * SPI.  If implemented then <code>/restful/health</code> resource will invoke this service to determine the health of the system.
+ */
+public interface HealthCheckService {
+
+    @Programmatic
+    public Health check();
+
+}
+
+
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/commons/authentication/HealthAuthSession.java b/core/metamodel/src/main/java/org/apache/isis/core/commons/authentication/HealthAuthSession.java
new file mode 100644
index 0000000..76d5600
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/commons/authentication/HealthAuthSession.java
@@ -0,0 +1,44 @@
+/*
+ *  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.
+ */
+
+package org.apache.isis.core.commons.authentication;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.apache.isis.core.commons.encoding.DataInputExtended;
+
+public class HealthAuthSession extends AuthenticationSessionAbstract {
+
+    private static final long serialVersionUID = 1L;
+
+    private static final String USER_NAME = "__health";
+    private static final String ROLE = "__health-role";
+    private static final String CODE = "";
+
+    public HealthAuthSession() {
+        super(USER_NAME, Arrays.asList(ROLE), CODE);
+    }
+
+    public HealthAuthSession(final DataInputExtended input) throws IOException {
+        super(input);
+    }
+
+
+}
diff --git a/core/runtime/src/main/java/org/apache/isis/core/webapp/IsisSessionFilter.java b/core/runtime/src/main/java/org/apache/isis/core/webapp/IsisSessionFilter.java
index 3704088..6e3e6df 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/webapp/IsisSessionFilter.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/webapp/IsisSessionFilter.java
@@ -20,6 +20,7 @@
 package org.apache.isis.core.webapp;
 
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -135,7 +136,7 @@ public class IsisSessionFilter implements Filter {
      */
     public static final String QUERY_STRING_FORCE_LOGOUT = "__isis_force_logout";
 
-    private String passThru;
+    private List<String> passThruList = Collections.emptyList();
 
     static void redirect(final HttpServletRequest httpRequest, final HttpServletResponse httpResponse, final String redirectTo) throws IOException {
         httpResponse.sendRedirect(StringExtensions.combinePath(httpRequest.getContextPath(), redirectTo));
@@ -271,10 +272,15 @@ public class IsisSessionFilter implements Filter {
 
     }
 
-    private void lookupPassThru(final FilterConfig config) {
-
-        this.passThru = config.getInitParameter(PASS_THRU_KEY);
+    void lookupPassThru(final FilterConfig config) {
+        this.passThruList = lookupAndParsePassThru(config);
+    }
 
+    List<String> lookupAndParsePassThru(final FilterConfig config) {
+        final String passThru = config.getInitParameter(PASS_THRU_KEY);
+        return passThru != null && !passThru.equals("")
+                ? Arrays.asList(passThru.split(","))
+                : Collections.<String>emptyList();
     }
 
     private void lookupRedirectToOnException(final FilterConfig config) {
@@ -375,7 +381,13 @@ public class IsisSessionFilter implements Filter {
 
 
     protected boolean requestIsPassThru(final HttpServletRequest httpServletRequest) {
-        return passThru != null && httpServletRequest.getRequestURI().startsWith(passThru);
+        final String requestURI = httpServletRequest.getRequestURI();
+        for (final String passThru : passThruList) {
+            if(requestURI.startsWith(passThru)) {
+                return true;
+            }
+        }
+        return false;
     }
 
     private boolean requestIsIgnoreExtension(final IsisSessionFilter filter, final HttpServletRequest httpRequest) {
diff --git a/core/runtime/src/test/java/org/apache/isis/core/webapp/IsisSessionFilter_lookupPassThru_Test.java b/core/runtime/src/test/java/org/apache/isis/core/webapp/IsisSessionFilter_lookupPassThru_Test.java
new file mode 100644
index 0000000..e9a18a3
--- /dev/null
+++ b/core/runtime/src/test/java/org/apache/isis/core/webapp/IsisSessionFilter_lookupPassThru_Test.java
@@ -0,0 +1,80 @@
+package org.apache.isis.core.webapp;
+
+import java.util.List;
+
+import javax.servlet.FilterConfig;
+
+import org.jmock.Expectations;
+import org.jmock.auto.Mock;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import org.apache.isis.core.unittestsupport.jmocking.JUnitRuleMockery2;
+
+import static org.hamcrest.CoreMatchers.is;
+
+public class IsisSessionFilter_lookupPassThru_Test {
+
+    @Rule
+    public JUnitRuleMockery2 context = JUnitRuleMockery2.createFor(JUnitRuleMockery2.Mode.INTERFACES_AND_CLASSES);
+
+    IsisSessionFilter isisSessionFilter;
+
+    @Mock
+    FilterConfig mockFilterConfig;
+
+    @Before
+    public void setUp() throws Exception {
+        isisSessionFilter = new IsisSessionFilter();
+    }
+
+    @Test
+    public void when_null() throws Exception {
+        context.checking(new Expectations() {{
+            allowing(mockFilterConfig).getInitParameter(IsisSessionFilter.PASS_THRU_KEY);
+            will(returnValue(null));
+        }});
+
+        final List<String> x = isisSessionFilter.lookupAndParsePassThru(mockFilterConfig);
+        Assert.assertThat(x.size(), is(0));
+    }
+
+    @Test
+    public void when_none() throws Exception {
+        context.checking(new Expectations() {{
+            allowing(mockFilterConfig).getInitParameter(IsisSessionFilter.PASS_THRU_KEY);
+            will(returnValue(""));
+        }});
+
+        final List<String> x = isisSessionFilter.lookupAndParsePassThru(mockFilterConfig);
+        Assert.assertThat(x.size(), is(0));
+    }
+
+    @Test
+    public void when_one() throws Exception {
+        context.checking(new Expectations() {{
+            allowing(mockFilterConfig).getInitParameter(IsisSessionFilter.PASS_THRU_KEY);
+            will(returnValue("/abc"));
+        }});
+
+        final List<String> x = isisSessionFilter.lookupAndParsePassThru(mockFilterConfig);
+        Assert.assertThat(x.size(), is(1));
+        Assert.assertThat(x.get(0), is("/abc"));
+    }
+
+    @Test
+    public void when_several() throws Exception {
+        context.checking(new Expectations() {{
+            allowing(mockFilterConfig).getInitParameter(IsisSessionFilter.PASS_THRU_KEY);
+            will(returnValue("/abc,/def"));
+        }});
+
+        final List<String> x = isisSessionFilter.lookupAndParsePassThru(mockFilterConfig);
+        Assert.assertThat(x.size(), is(2));
+        Assert.assertThat(x.get(0), is("/abc"));
+        Assert.assertThat(x.get(1), is("/def"));
+    }
+
+}
\ No newline at end of file
diff --git a/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/RepresentationType.java b/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/RepresentationType.java
index b1fea7b..70c2794 100644
--- a/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/RepresentationType.java
+++ b/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/RepresentationType.java
@@ -18,7 +18,6 @@
  */
 package org.apache.isis.viewer.restfulobjects.applib;
 
-
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -42,6 +41,7 @@ import org.apache.isis.viewer.restfulobjects.applib.domaintypes.PropertyDescript
 import org.apache.isis.viewer.restfulobjects.applib.domaintypes.TypeActionResultRepresentation;
 import org.apache.isis.viewer.restfulobjects.applib.domaintypes.TypeListRepresentation;
 import org.apache.isis.viewer.restfulobjects.applib.errors.ErrorRepresentation;
+import org.apache.isis.viewer.restfulobjects.applib.health.HealthRepresentation;
 import org.apache.isis.viewer.restfulobjects.applib.homepage.HomePageRepresentation;
 import org.apache.isis.viewer.restfulobjects.applib.user.UserRepresentation;
 import org.apache.isis.viewer.restfulobjects.applib.util.Parser;
@@ -61,6 +61,10 @@ public enum RepresentationType {
             RestfulMediaType.APPLICATION_JSON_VERSION,
             null,
             VersionRepresentation.class),
+    HEALTH(
+            RestfulMediaType.APPLICATION_JSON_HEALTH,
+            null,
+            HealthRepresentation.class),
     LIST(
             RestfulMediaType.APPLICATION_JSON_LIST,
             null,
diff --git a/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/RestfulMediaType.java b/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/RestfulMediaType.java
index 99d5a17..d9db98a 100644
--- a/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/RestfulMediaType.java
+++ b/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/RestfulMediaType.java
@@ -40,6 +40,7 @@ public final class RestfulMediaType {
     public final static String APPLICATION_JSON_HOME_PAGE = JSON_BASE + "homepage";
     public final static String APPLICATION_JSON_USER = JSON_BASE + "user";
     public final static String APPLICATION_JSON_VERSION = JSON_BASE + "version";
+    public final static String APPLICATION_JSON_HEALTH = JSON_BASE + "health";
     public final static String APPLICATION_JSON_LIST = JSON_BASE + "list";
     public final static String APPLICATION_JSON_OBJECT = JSON_BASE + "object";
     public final static String APPLICATION_JSON_OBJECT_PROPERTY = JSON_BASE + "object-property";
diff --git a/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/health/HealthRepresentation.java b/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/health/HealthRepresentation.java
new file mode 100644
index 0000000..5e714af
--- /dev/null
+++ b/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/health/HealthRepresentation.java
@@ -0,0 +1,40 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The 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.
+ */
+package org.apache.isis.viewer.restfulobjects.applib.health;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
+import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation.HasLinkToUp;
+import org.apache.isis.viewer.restfulobjects.applib.LinkRepresentation;
+import org.apache.isis.viewer.restfulobjects.applib.Rel;
+import org.apache.isis.viewer.restfulobjects.applib.domainobjects.DomainRepresentation;
+
+public class HealthRepresentation extends DomainRepresentation implements HasLinkToUp {
+
+    public HealthRepresentation(final JsonNode jsonNode) {
+        super(jsonNode);
+    }
+
+    @Override
+    public LinkRepresentation getUp() {
+        return getLinkWithRel(Rel.UP);
+    }
+
+}
diff --git a/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/health/HealthResource.java b/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/health/HealthResource.java
new file mode 100644
index 0000000..c00aeca
--- /dev/null
+++ b/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/health/HealthResource.java
@@ -0,0 +1,51 @@
+/*
+ *  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.
+ */
+package org.apache.isis.viewer.restfulobjects.applib.health;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.jboss.resteasy.annotations.ClientResponseType;
+
+import org.apache.isis.viewer.restfulobjects.applib.RestfulMediaType;
+
+@Path("/health")
+public interface HealthResource {
+
+    @GET
+    @Produces({ MediaType.APPLICATION_JSON, RestfulMediaType.APPLICATION_JSON_HEALTH })
+    @ClientResponseType(entityType = String.class)
+    public Response health();
+
+    @DELETE
+    public Response deleteHealthNotAllowed();
+
+    @PUT
+    public Response putHealthNotAllowed();
+
+    @POST
+    public Response postHealthNotAllowed();
+
+}
\ No newline at end of file
diff --git a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/RestfulObjectsApplication.java b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/RestfulObjectsApplication.java
index 1e13b32..d4657d4 100644
--- a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/RestfulObjectsApplication.java
+++ b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/RestfulObjectsApplication.java
@@ -26,6 +26,7 @@ import org.apache.isis.viewer.restfulobjects.server.mappers.ExceptionMapperForRu
 import org.apache.isis.viewer.restfulobjects.server.resources.DomainObjectResourceServerside;
 import org.apache.isis.viewer.restfulobjects.server.resources.DomainServiceResourceServerside;
 import org.apache.isis.viewer.restfulobjects.server.resources.DomainTypeResourceServerside;
+import org.apache.isis.viewer.restfulobjects.server.resources.HealthResourceServerside;
 import org.apache.isis.viewer.restfulobjects.server.resources.HomePageResourceServerside;
 import org.apache.isis.viewer.restfulobjects.server.resources.ImageResourceServerside;
 import org.apache.isis.viewer.restfulobjects.server.resources.MenuBarsResourceServerside;
@@ -46,6 +47,7 @@ public class RestfulObjectsApplication extends AbstractJaxRsApplication {
         addClass(DomainObjectResourceServerside.class);
         addClass(DomainServiceResourceServerside.class);
         addClass(VersionResourceServerside.class);
+        addClass(HealthResourceServerside.class);
 
         addClass(SwaggerSpecResource.class);
 
diff --git a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/HealthReprRenderer.java b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/HealthReprRenderer.java
new file mode 100644
index 0000000..f707560
--- /dev/null
+++ b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/HealthReprRenderer.java
@@ -0,0 +1,54 @@
+/*
+ *  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.
+ */
+package org.apache.isis.viewer.restfulobjects.server.resources;
+
+import org.apache.isis.applib.services.health.Health;
+import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
+import org.apache.isis.viewer.restfulobjects.applib.RepresentationType;
+import org.apache.isis.viewer.restfulobjects.rendering.LinkFollowSpecs;
+import org.apache.isis.viewer.restfulobjects.rendering.RendererContext;
+import org.apache.isis.viewer.restfulobjects.rendering.ReprRendererAbstract;
+
+public class HealthReprRenderer extends ReprRendererAbstract<HealthReprRenderer, Health> {
+
+    HealthReprRenderer(
+            final RendererContext resourceContext,
+            final LinkFollowSpecs linkFollower,
+            final JsonRepresentation representation) {
+        super(resourceContext, linkFollower, RepresentationType.HEALTH, representation);
+    }
+
+    @Override
+    public HealthReprRenderer with(final Health health) {
+        final boolean result = health.getResult();
+        representation.mapPut("ok", result);
+        if(!result) {
+            representation.mapPut("message", health.getMessage());
+        }
+        return this;
+    }
+
+    @Override
+    public JsonRepresentation render() {
+
+        return representation;
+    }
+
+
+}
\ No newline at end of file
diff --git a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/HealthResourceServerside.java b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/HealthResourceServerside.java
new file mode 100644
index 0000000..fcc010d
--- /dev/null
+++ b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/HealthResourceServerside.java
@@ -0,0 +1,102 @@
+/*
+ *  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.
+ */
+package org.apache.isis.viewer.restfulobjects.server.resources;
+
+import javax.inject.Inject;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.apache.isis.applib.annotation.Where;
+import org.apache.isis.applib.services.health.Health;
+import org.apache.isis.applib.services.health.HealthCheckService;
+import org.apache.isis.core.commons.authentication.HealthAuthSession;
+import org.apache.isis.core.runtime.sessiontemplate.AbstractIsisSessionTemplate;
+import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
+import org.apache.isis.viewer.restfulobjects.applib.RepresentationType;
+import org.apache.isis.viewer.restfulobjects.applib.RestfulMediaType;
+import org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse.HttpStatusCode;
+import org.apache.isis.viewer.restfulobjects.applib.health.HealthResource;
+import org.apache.isis.viewer.restfulobjects.rendering.Caching;
+import org.apache.isis.viewer.restfulobjects.rendering.Responses;
+import org.apache.isis.viewer.restfulobjects.rendering.RestfulObjectsApplicationException;
+import org.apache.isis.viewer.restfulobjects.rendering.service.RepresentationService;
+
+/**
+ * Implementation note: it seems to be necessary to annotate the implementation
+ * with {@link Path} rather than the interface (at least under RestEasy 1.0.2
+ * and 1.1-RC2).
+ */
+//@Path("/health")
+public class HealthResourceServerside extends ResourceAbstract implements HealthResource {
+
+    @Override
+    @GET
+    @Produces({ MediaType.APPLICATION_JSON, RestfulMediaType.APPLICATION_JSON_HEALTH })
+    public Response health() {
+
+        final Response[] responseHolder = new Response[1];
+        final AbstractIsisSessionTemplate template = new HealthServiceSession();
+
+        final HealthAuthSession authSession = new HealthAuthSession();
+        template.execute(authSession, responseHolder);
+
+        return responseHolder[0];
+    }
+
+    @Override
+    public Response deleteHealthNotAllowed() {
+        throw RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.METHOD_NOT_ALLOWED, "Deleting the health resource is not allowed.");
+    }
+
+    @Override
+    public Response putHealthNotAllowed() {
+        throw RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.METHOD_NOT_ALLOWED, "Putting to the health resource is not allowed.");
+    }
+
+    @Override
+    public Response postHealthNotAllowed() {
+        throw RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.METHOD_NOT_ALLOWED, "Posting to the version resource is not allowed.");
+    }
+
+    private class HealthServiceSession extends AbstractIsisSessionTemplate {
+
+        @Override
+        protected void doExecuteWithTransaction(final Object context) {
+            super.doExecuteWithTransaction(context);
+            init(RepresentationType.VERSION, Where.NOWHERE, RepresentationService.Intent.NOT_APPLICABLE);
+
+            final HealthReprRenderer renderer = new HealthReprRenderer(getResourceContext(), null, JsonRepresentation.newMap());
+            if(healthService != null) {
+                final Health health = healthService.check();
+                renderer.with(health);
+            }
+            renderer.includesSelf();
+
+            final Response[] responseHolder = (Response[]) context;
+            responseHolder[0] = Responses.ofOk(renderer, Caching.ONE_DAY).build();
+        }
+
+        @Inject
+        HealthCheckService healthService;
+
+    }
+}
\ No newline at end of file
diff --git a/example/application/simpleapp/application/src/main/java/domainapp/application/services/health/HealthCheckServiceImpl.java b/example/application/simpleapp/application/src/main/java/domainapp/application/services/health/HealthCheckServiceImpl.java
new file mode 100644
index 0000000..0cd395d
--- /dev/null
+++ b/example/application/simpleapp/application/src/main/java/domainapp/application/services/health/HealthCheckServiceImpl.java
@@ -0,0 +1,29 @@
+package domainapp.application.services.health;
+
+import javax.inject.Inject;
+
+import org.apache.isis.applib.annotation.DomainService;
+import org.apache.isis.applib.annotation.NatureOfService;
+import org.apache.isis.applib.services.health.Health;
+import org.apache.isis.applib.services.health.HealthCheckService;
+
+import domainapp.modules.simple.dom.impl.SimpleObjects;
+
+@DomainService(nature = NatureOfService.DOMAIN)
+public class HealthCheckServiceImpl implements HealthCheckService {
+
+    @Override
+    public Health check() {
+
+        try {
+            simpleObjects.ping();
+            return Health.ok();
+        } catch (Exception ex) {
+            return Health.error(ex.getMessage());
+        }
+
+    }
+
+    @Inject
+    SimpleObjects simpleObjects;
+}
diff --git a/example/application/simpleapp/module-simple/src/main/java/domainapp/modules/simple/dom/impl/SimpleObjects.java b/example/application/simpleapp/module-simple/src/main/java/domainapp/modules/simple/dom/impl/SimpleObjects.java
index 335f9d0..5aa77cf 100644
--- a/example/application/simpleapp/module-simple/src/main/java/domainapp/modules/simple/dom/impl/SimpleObjects.java
+++ b/example/application/simpleapp/module-simple/src/main/java/domainapp/modules/simple/dom/impl/SimpleObjects.java
@@ -35,7 +35,6 @@ import org.apache.isis.applib.annotation.SemanticsOf;
 import org.apache.isis.applib.services.eventbus.ActionDomainEvent;
 import org.apache.isis.applib.services.jdosupport.IsisJdoSupport;
 import org.apache.isis.applib.services.repository.RepositoryService;
-import org.apache.isis.applib.services.xactn.TransactionService;
 
 @DomainService(
         nature = NatureOfService.VIEW_MENU_ONLY,
@@ -83,6 +82,14 @@ public class SimpleObjects {
                 .executeUnique();
     }
 
+    @Programmatic
+    public void ping() {
+        TypesafeQuery<SimpleObject> q = isisJdoSupport.newTypesafeQuery(SimpleObject.class);
+        final QSimpleObject candidate = QSimpleObject.candidate();
+        q.range(0,2);
+        q.orderBy(candidate.name.asc());
+        q.executeList();
+    }
 
     public static class CreateDomainEvent extends ActionDomainEvent<SimpleObjects> {}
     @Action(domainEvent = CreateDomainEvent.class)
diff --git a/example/application/simpleapp/webapp/src/main/webapp/WEB-INF/isis.properties b/example/application/simpleapp/webapp/src/main/webapp/WEB-INF/isis.properties
index 046dd58..6df96e4 100644
--- a/example/application/simpleapp/webapp/src/main/webapp/WEB-INF/isis.properties
+++ b/example/application/simpleapp/webapp/src/main/webapp/WEB-INF/isis.properties
@@ -85,17 +85,18 @@ isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionPassword=
 # PostgreSQL Server
 #
 #isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionDriverName=org.postgresql.Driver
-#isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionURL=jdbc:postgresql://localhost:5432/isis
-#isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionUserName=isis
-#isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionPassword=isis
+#isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionURL=jdbc:postgresql://localhost:5432/simpleapp
+#isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionUserName=simpleapp
+#isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionPassword=simpleapp
+
 
 #
 # PostgreSQL Server (using log4jdbc-remix)
 #
 #isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionDriverName=net.sf.log4jdbc.DriverSpy
-#isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionURL=jdbc:log4jdbc:postgresql://localhost:5432/isis
-#isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionUserName=isis
-#isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionPassword=isis
+#isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionURL=jdbc:log4jdbc:postgresql://localhost:5432/simpleapp
+#isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionUserName=simpleapp
+#isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionPassword=simpleapp
 
 
 
@@ -103,25 +104,18 @@ isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionPassword=
 # MS SQL Server
 #
 #isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionDriverName=com.microsoft.sqlserver.jdbc.SQLServerDriver
-#isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionURL=jdbc:sqlserver://127.0.0.1:1433;instance=.;databaseName=simple
-#isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionUserName=sa
-#isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionPassword=p4ssword
+#isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionURL=jdbc:sqlserver://localhost:1433;instance=.;databaseName=simpleapp
+#isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionUserName=simpleapp
+#isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionPassword=simpleapp
+
 
 #
 # MS SQL Server (using log4jdbc-remix)
 #
 #isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionDriverName=net.sf.log4jdbc.DriverSpy
-#isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionURL=jdbc:log4jdbc:sqlserver://127.0.0.1:1433;instance=SQLEXPRESS;databaseName=jdo
-#isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionUserName=jdo
-#isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionPassword=jdopass
-
-
-
-#
-# neo4j
-# (experimental; run with -P neo4j profile in webapp project)
-#
-#isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionURL=neo4j:neo4j_DB
+#isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionURL=jdbc:log4jdbc:sqlserver://localhost:1433;instance=.;databaseName=simpleapp
+#isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionUserName=simpleapp
+#isis.persistor.datanucleus.impl.javax.jdo.option.ConnectionPassword=simpleapp
 
 
 
diff --git a/example/application/simpleapp/webapp/src/main/webapp/WEB-INF/web.xml b/example/application/simpleapp/webapp/src/main/webapp/WEB-INF/web.xml
index 1adf47b..49cf0f0 100644
--- a/example/application/simpleapp/webapp/src/main/webapp/WEB-INF/web.xml
+++ b/example/application/simpleapp/webapp/src/main/webapp/WEB-INF/web.xml
@@ -295,7 +295,7 @@
         </init-param>
         <init-param>
             <param-name>passThru</param-name>
-            <param-value>/restful/swagger</param-value>
+            <param-value>/restful/swagger,/restful/health</param-value>
         </init-param>
     </filter>
     <filter-mapping>