diff --git a/pom.xml b/pom.xml
index 09f21e5f5..c986b6a8d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -61,6 +61,7 @@
vpro-shared-hibernate-search
vpro-shared-jackson2
vpro-shared-logging
+ vpro-shared-monitoring
vpro-shared-persistence
vpro-shared-resteasy
vpro-shared-rs
@@ -394,6 +395,12 @@
${assertj.version}
test
+
+ com.jayway.jsonpath
+ json-path-assert
+ 2.4.0
+ test
+
org.springframework
spring-web
@@ -736,7 +743,6 @@
org.apache.maven.plugins
maven-javadoc-plugin
- 3.1.0
attach-javadocs
diff --git a/vpro-shared-bom/pom.xml b/vpro-shared-bom/pom.xml
index 985391562..0d058102f 100644
--- a/vpro-shared-bom/pom.xml
+++ b/vpro-shared-bom/pom.xml
@@ -112,6 +112,11 @@
vpro-shared-logging
${project.version}
+
+ nl.vpro.shared
+ vpro-shared-monitoring
+ ${project.version}
+
nl.vpro.shared
vpro-shared-persistence
diff --git a/vpro-shared-monitoring/pom.xml b/vpro-shared-monitoring/pom.xml
new file mode 100644
index 000000000..7657a2373
--- /dev/null
+++ b/vpro-shared-monitoring/pom.xml
@@ -0,0 +1,62 @@
+
+ 4.0.0
+
+
+ nl.vpro.shared
+ vpro-shared-parent
+ 2.16-SNAPSHOT
+
+ vpro-shared-monitoring
+ This modules contains utilities and endpoints for application deployments and monitoring.
+
+
+ com.jayway.jsonpath
+ json-path-assert
+ 2.4.0
+
+
+ nl.vpro.shared
+ vpro-shared-test
+
+
+ javax.servlet
+ javax.servlet-api
+ 3.1.0
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+
+
+ org.slf4j
+ slf4j-api
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+ compile
+
+
+ org.projectlombok
+ lombok
+
+
+ org.springframework
+ spring-context
+
+
+ org.springframework
+ spring-web
+
+
+ org.springframework
+ spring-webmvc
+ test
+
+
+ org.springframework
+ spring-test
+
+
+
diff --git a/vpro-shared-monitoring/src/main/java/nl/vpro/monitoring/domain/Health.java b/vpro-shared-monitoring/src/main/java/nl/vpro/monitoring/domain/Health.java
new file mode 100644
index 000000000..893354074
--- /dev/null
+++ b/vpro-shared-monitoring/src/main/java/nl/vpro/monitoring/domain/Health.java
@@ -0,0 +1,11 @@
+package nl.vpro.monitoring.domain;
+
+import lombok.Builder;
+import lombok.Data;
+
+@Data
+@Builder
+public class Health {
+ private int status;
+ private String message;
+}
diff --git a/vpro-shared-monitoring/src/main/java/nl/vpro/monitoring/web/HealthController.java b/vpro-shared-monitoring/src/main/java/nl/vpro/monitoring/web/HealthController.java
new file mode 100644
index 000000000..494a30e01
--- /dev/null
+++ b/vpro-shared-monitoring/src/main/java/nl/vpro/monitoring/web/HealthController.java
@@ -0,0 +1,46 @@
+package nl.vpro.monitoring.web;
+
+import org.springframework.context.annotation.Lazy;
+import org.springframework.context.event.*;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import nl.vpro.monitoring.domain.Health;
+
+@Lazy(false)
+@RestController
+@RequestMapping(value = "/health", produces = MediaType.APPLICATION_JSON_VALUE)
+public class HealthController {
+
+ private Status status = Status.STARTING;
+
+ @EventListener
+ public void onApplicationEvent(ContextRefreshedEvent refreshedEvent) {
+ status = Status.READY;
+ }
+
+ @EventListener
+ public void onApplicationEvent(ContextStoppedEvent stoppedEvent) {
+ status = Status.STOPPING;
+ }
+
+ @GetMapping
+ public ResponseEntity health() {
+ return ResponseEntity.status(status.code).body(Health.builder().status(status.code).message(status.message).build());
+ }
+
+ private enum Status {
+ STARTING(503, "Application starting"),
+ READY(200, "Application ready"),
+ STOPPING(503, "Application shutdown");
+
+ int code;
+ String message;
+
+ Status(int code, String message) {
+ this.code = code;
+ this.message = message;
+ }
+ }
+}
diff --git a/vpro-shared-monitoring/src/test/java/nl/vpro/monitoring/web/HealthControllerTest.java b/vpro-shared-monitoring/src/test/java/nl/vpro/monitoring/web/HealthControllerTest.java
new file mode 100644
index 000000000..a89316846
--- /dev/null
+++ b/vpro-shared-monitoring/src/test/java/nl/vpro/monitoring/web/HealthControllerTest.java
@@ -0,0 +1,76 @@
+package nl.vpro.monitoring.web;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.context.event.ContextStoppedEvent;
+import org.springframework.http.ResponseEntity;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+import nl.vpro.monitoring.domain.Health;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@ExtendWith(SpringExtension.class)
+@WebAppConfiguration
+@ContextConfiguration("/manage-servlet.xml")
+class HealthControllerTest {
+ @Autowired
+ private HealthController healthController;
+
+ @Autowired
+ private WebApplicationContext wac;
+
+ private MockMvc mockMvc;
+
+ @BeforeEach
+ public void setup() {
+ this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
+ }
+
+ @Test
+ void initialValues() {
+ // Pragmatic testing of initial values. Using #wac and #mockMvc is more or less impossible.
+ ResponseEntity response = new HealthController().health();
+ assertThat(response.getStatusCodeValue()).isEqualTo(503);
+ assertThat(response.getBody()).isNotNull();
+ assertThat(response.getBody().getStatus()).isEqualTo(503);
+ assertThat(response.getBody().getMessage()).isEqualTo("Application starting");
+ }
+
+ @Test
+ void statusReady() throws Exception {
+ healthController.onApplicationEvent((ContextRefreshedEvent) null);
+
+ mockMvc.perform(
+ get("/health")
+ .accept(APPLICATION_JSON_VALUE)
+ ).andExpect(status().is(200))
+ .andExpect(jsonPath("$.status", is(200)))
+ .andExpect(jsonPath("$.message", is("Application ready")));
+ }
+
+ @Test
+ void statusStopping() throws Exception {
+ healthController.onApplicationEvent((ContextStoppedEvent) null);
+
+ mockMvc.perform(
+ get("/health")
+ .accept(APPLICATION_JSON_VALUE)
+ ).andExpect(status().is(503))
+ .andExpect(jsonPath("$.status", is(503)))
+ .andExpect(jsonPath("$.message", is("Application shutdown")));
+ }
+}
diff --git a/vpro-shared-monitoring/src/test/resources/manage-servlet.xml b/vpro-shared-monitoring/src/test/resources/manage-servlet.xml
new file mode 100644
index 000000000..769bc79c7
--- /dev/null
+++ b/vpro-shared-monitoring/src/test/resources/manage-servlet.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+