diff --git a/pom.xml b/pom.xml
index 0e9f48599c..50d046ea78 100644
--- a/pom.xml
+++ b/pom.xml
@@ -87,6 +87,7 @@
spring-boot-devtoolsspring-boot-docsspring-boot-starters
+ spring-boot-actuator-docsspring-boot-cli
diff --git a/spring-boot-actuator-docs/pom.xml b/spring-boot-actuator-docs/pom.xml
new file mode 100644
index 0000000000..76e893ccb3
--- /dev/null
+++ b/spring-boot-actuator-docs/pom.xml
@@ -0,0 +1,117 @@
+
+
+ 4.0.0
+
+ spring-boot-actuator-docs
+ jar
+
+ spring-boot-actuator-docs
+ Docs project for Spring Boot
+
+
+ org.springframework.boot
+ spring-boot-parent
+ 1.3.0.BUILD-SNAPSHOT
+
+
+
+ UTF-8
+ 1.7
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-hateoas
+ true
+
+
+ org.springframework.restdocs
+ spring-restdocs
+ 1.0.0.M1
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-groovy-templates
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ **/*Documentation.java
+
+
+ ${project.build.directory}/generated-snippets
+
+
+
+
+ org.asciidoctor
+ asciidoctor-maven-plugin
+ 1.5.2
+
+
+ generate-docs
+ prepare-package
+
+ process-asciidoc
+
+
+ html
+ book
+ index.adoc
+
+ ${project.build.directory}/generated-snippets
+ ${project.build.directory}/../src/main/asciidoc
+
+
+
+
+
+
+ maven-resources-plugin
+
+
+ copy-resources
+ prepare-package
+
+ copy-resources
+
+
+ ${project.build.outputDirectory}/META-INF/resources/spring-boot-actuator/docs
+
+
+ ${project.build.directory}/generated-docs
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-boot-actuator-docs/src/main/asciidoc/autoconfig.adoc b/spring-boot-actuator-docs/src/main/asciidoc/autoconfig.adoc
new file mode 100644
index 0000000000..5208f8fd70
--- /dev/null
+++ b/spring-boot-actuator-docs/src/main/asciidoc/autoconfig.adoc
@@ -0,0 +1,26 @@
+=== /autoconfig
+
+This endpoint is a report on the Spring Boot Autoconfiguration process
+that happened when your application started up. It lists all the
+`@Conditional` annotations that were evaluated as the context started
+and in each case it gives an indication of if (and why) the condition
+matched. A positive match results in a bean being included in the context,
+and a negative result means the opposite (the beans's class may not even
+be loaded).
+
+The report is split into 2 parts, positive matches first, and then negative.
+If the context is a hierarchy, there is also a separate report on the parent
+context with the same format (and recursively up to the top of the hierarchy).
+
+NOTE: the report is actually about `@Conditional` evaluation not autoconfiguration
+per se, but most autoconfiguration features use `@Conditional` heavily, so there is
+a lot of overlap.
+
+Example curl request:
+include::{generated}/autoconfig/curl-request.adoc[]
+
+Example HTTP request:
+include::{generated}/autoconfig/http-request.adoc[]
+
+Example HTTP response:
+include::{generated}/autoconfig/http-response.adoc[]
diff --git a/spring-boot-actuator-docs/src/main/asciidoc/beans.adoc b/spring-boot-actuator-docs/src/main/asciidoc/beans.adoc
new file mode 100644
index 0000000000..e725f3f21d
--- /dev/null
+++ b/spring-boot-actuator-docs/src/main/asciidoc/beans.adoc
@@ -0,0 +1,17 @@
+=== /beans
+
+This endpoint is a report on the Spring Boot `ApplicationContext`. It lists
+the beans in the context and their dependencies, detailing the names and
+concrete classes of each bean.
+
+NOTE: some beans are pure configuration (any class that is annotated
+`@Configuration`).
+
+Example curl request:
+include::{generated}/beans/curl-request.adoc[]
+
+Example HTTP request:
+include::{generated}/beans/http-request.adoc[]
+
+Example HTTP response:
+include::{generated}/beans/http-response.adoc[]
diff --git a/spring-boot-actuator-docs/src/main/asciidoc/configprops.adoc b/spring-boot-actuator-docs/src/main/asciidoc/configprops.adoc
new file mode 100644
index 0000000000..32b3a5b990
--- /dev/null
+++ b/spring-boot-actuator-docs/src/main/asciidoc/configprops.adoc
@@ -0,0 +1,19 @@
+=== /configprops
+
+This endpoint is a report on the Spring Boot `@ConfigurationProperties`
+beans. Beans with this annotation are bound to the `Environment` on
+startup, so they reflect the externalised configuration of the application.
+Beans are listed by name.
+A bean that is added using `@EnableConfigurationProperties` will have
+a conventional name: `.CONFIGURATION_PROPERTIES`, where
+`` is the environment key prefix specified in the
+`@ConfigurationProperties` annotation.
+
+Example curl request:
+include::{generated}/configprops/curl-request.adoc[]
+
+Example HTTP request:
+include::{generated}/configprops/http-request.adoc[]
+
+Example HTTP response:
+include::{generated}/configprops/http-response.adoc[]
diff --git a/spring-boot-actuator-docs/src/main/asciidoc/dump.adoc b/spring-boot-actuator-docs/src/main/asciidoc/dump.adoc
new file mode 100644
index 0000000000..4a5ac589e4
--- /dev/null
+++ b/spring-boot-actuator-docs/src/main/asciidoc/dump.adoc
@@ -0,0 +1,19 @@
+=== /dump
+
+This endpoint is a thread dump: the result is a list of threads each with
+their name, monitor state and stack. It is the same information as you would
+get from `kill -3` of a running Java process. Can be very useful for detecting
+issues at runtime, especially sluggish behaviour caused by threads blocked
+by slow or unavailable I/O (e.g. if a connection pool is exhausted).
+
+NOTE: some `SecurityManager` implementations might prevent this endpoint
+from working.
+
+Example curl request:
+include::{generated}/dump/curl-request.adoc[]
+
+Example HTTP request:
+include::{generated}/dump/http-request.adoc[]
+
+Example HTTP response:
+include::{generated}/dump/http-response.adoc[]
diff --git a/spring-boot-actuator-docs/src/main/asciidoc/env.adoc b/spring-boot-actuator-docs/src/main/asciidoc/env.adoc
new file mode 100644
index 0000000000..c0266a0c1b
--- /dev/null
+++ b/spring-boot-actuator-docs/src/main/asciidoc/env.adoc
@@ -0,0 +1,17 @@
+=== /env
+
+This endpoint is a dump of the Spring `Environment`. It lists the active
+profiles and all the `PropertySources` in the `Environment` (the ones that
+are listed first take precedence when binding to `@ConfigurationProperties`
+or `@Value`). Normally you will see the Java `System` properties and the
+OS environment variables in their own `PropertySources` plus any `.properties`
+or `.yml` files used to configure the application on start up.
+
+Example curl request:
+include::{generated}/env/curl-request.adoc[]
+
+Example HTTP request:
+include::{generated}/env/http-request.adoc[]
+
+Example HTTP response:
+include::{generated}/env/http-response.adoc[]
diff --git a/spring-boot-actuator-docs/src/main/asciidoc/health.adoc b/spring-boot-actuator-docs/src/main/asciidoc/health.adoc
new file mode 100644
index 0000000000..54a292378d
--- /dev/null
+++ b/spring-boot-actuator-docs/src/main/asciidoc/health.adoc
@@ -0,0 +1,22 @@
+=== /health
+
+This endpoint is an indication of the health of the application.
+It has an overall status ("UP", "DOWN" etc.), which is the only thing
+you see unless either you are authenticated or the endpoint is marked
+as `sensitive=false` (`endpoints.health.sensitive=false`).
+
+The HTTP code in the response reflects the status (e.g. "UP" = 200,
+"OUT_OF_SERVICE"=503, "DOWN"=503). The mappings can be changed by
+configuring `endpoints.health.mapping.=XXX`.
+
+Example curl request:
+include::{generated}/health/curl-request.adoc[]
+
+Example HTTP request:
+include::{generated}/health/http-request.adoc[]
+
+Example HTTP response:
+include::{generated}/health/http-response.adoc[]
+
+Example HTTP response with `endpoints.health.sensitive=false`:
+include::{generated}/health/unsensitive/http-response.adoc[]
diff --git a/spring-boot-actuator-docs/src/main/asciidoc/images/hal-browser.png b/spring-boot-actuator-docs/src/main/asciidoc/images/hal-browser.png
new file mode 100644
index 0000000000..2cab51e51d
Binary files /dev/null and b/spring-boot-actuator-docs/src/main/asciidoc/images/hal-browser.png differ
diff --git a/spring-boot-actuator-docs/src/main/asciidoc/index.adoc b/spring-boot-actuator-docs/src/main/asciidoc/index.adoc
new file mode 100644
index 0000000000..b56959055b
--- /dev/null
+++ b/spring-boot-actuator-docs/src/main/asciidoc/index.adoc
@@ -0,0 +1,148 @@
+= Spring Boot Actuator Endpoints
+:toc: left
+:idprefix: spring_boot_actuator_
+
+Actuator endpoints allow you to monitor and interact with your application. Spring Boot
+includes a number of built-in endpoints and you can also add your own. For example the
+`health` endpoint provides basic application health information.
+
+The way that endpoints are exposed will depend on the type of technology that you choose.
+Most applications choose HTTP monitoring, where the ID of the endpoint is mapped
+to a URL. For example, by default, the `health` endpoint will be mapped to `/health`.
+
+== List of Endpoints
+
+include::{generated}/endpoints.adoc[]
+
+=== /logfile
+
+This endpoint (if available) contains the plain text logfile configured by the user
+using `logging.file` or `logging.path` (by default logs are only emitted on stdout
+so one of these properties has to be set for this endpoint to be active).
+
+Example curl request:
+include::{generated}/logfile/curl-request.adoc[]
+
+Example HTTP request:
+include::{generated}/logfile/http-request.adoc[]
+
+Example HTTP response:
+include::{generated}/logfile/http-response.adoc[]
+
+=== /docs
+
+This endpoint (if available) contains HTML documemtation for the other endpoints. Its path
+can be "/docs" (if there is an existing home page) or "/" (otherwise, including if the
+HAL browser is not active).
+
+== Hypermedia Support
+
+If https://projects.spring.io/spring-hateoas[Spring HATEOAS] is enabled
+(i.e. if it is on the classpath by default) then the Actuator
+endpoint responses are enhanced with hypermedia in the form of "links". The default
+media type for responses is http://stateless.co/hal_specification.html[HAL], resulting
+in each resource having an extra property called "_links". You can change the
+media type to another one supported by Spring HATEOAS by providing your own
+`@EnableHypermedia` annotation and custom providers as necessary.
+
+Example enhanced "/metrics" endpoint with additional "_links":
+
+include::{generated}/metrics/hypermedia/http-response.adoc[]
+
+WARNING: Beware of Actuator endpoint paths clashing with application endpoints.
+The easiest way to avoid that is to use a `management.contextPath`, e.g. "/admin".
+
+TIP: You can disable the hypermedia support in Actuator endpoints by setting
+`endpoints.links.enabled=false`.
+
+=== Default home page
+If the `management.contextPath` is empty, or if the home page provided
+by the application happens to be a response body of type `ResourceSupport`, then it will
+be enhanced with links to the actuator endpoints. The latter would happen for instance
+if you use Spring Data REST to expose `Repository` endpoints.
+
+Example vanilla "/" endpoint if the `management.contextPath` is empty (the "/admin"
+page would be the same with different links if `management.contextPath=/admin`):
+
+include::{generated}/admin/http-response.adoc[]
+
+=== Endpoints with format changes
+Some endpoints in their "raw" form consist of an array (e.g. the "/beans" and the "/trace" endpoints).
+These need to be converted to objects (maps) before they can be enhanced with
+links, so their contents are inserted as a field named "content".
+Example enhanced "/beans" endpoint with additional "_links":
+
+include::{generated}/beans/hypermedia/http-response.adoc[]
+
+== HAL Browser
+
+If Hypermedia is enabled and the HAL format is in use (which is the default), then
+you can provide a browser for the resources by including a dependency
+on the https://github.com/mikekelly/hal-browser[HAL browser] webjar.
+For example in Maven:
+
+[source,xml]
+----
+
+ org.webjars
+ hal-browser
+
+----
+
+
+or in Gradle
+
+[source,groovy]
+----
+dependencies {
+ ...
+ compile('org.webjars:hal-browser')
+ ...
+}
+----
+
+NOTE: if you are using Spring Data REST, then a dependency on the `spring-data-rest-hal-browser`
+will have an equivalent effect.
+
+If you do that then a new endpoint will appear at "/" or "/hal" (relative to the `management.contextPath`)
+serving up a static HTML page with some JavaScript that lets you browse the available
+resources. The default endpoint path depends on whether or not there is already a static home page
+("index.html") - if there is not and the `management.contextPath` is empty, then the HAL browser
+shows up on the home page. Example:
+
+image::hal-browser.png[HAL Browser]
+
+TIP: The endpoint path can always, as with all MVC endpoints, be overridden using
+`endpoints.hal.path=/yourpath` (note the leading slash).
+
+== Actuator Documentation Browser
+
+You can also provide a browser for the standard generated documentation
+for the Actuator endpoints by including a dependency on the documentation jar.
+For example in Maven:
+
+[source,xml]
+----
+
+ org.springframework.boot
+ spring-boot-hypermedia-docs
+
+----
+
+
+or in Gradle
+
+[source,groovy]
+----
+dependencies {
+ ...
+ compile('org.springframework.boot:spring-boot-hypermedia-docs')
+ ...
+}
+----
+
+If you do that then a new endpoint at "/" or "/docs" (relative to the `management.contextPath`)
+will serve up a static HTML page with this documentation in it. The default endpoint path depends
+on whether or not there is already a static home page
+("index.html" or a HAL browser) - if there is not and the `management.contextPath` is empty,
+then the docs browser shows up on the home page.
\ No newline at end of file
diff --git a/spring-boot-actuator-docs/src/main/asciidoc/info.adoc b/spring-boot-actuator-docs/src/main/asciidoc/info.adoc
new file mode 100644
index 0000000000..af1c191462
--- /dev/null
+++ b/spring-boot-actuator-docs/src/main/asciidoc/info.adoc
@@ -0,0 +1,16 @@
+=== /info
+
+This endpoint is empty and marked as `sensitive=false`
+by default (so it is unauthenticated by default if Spring
+Security is in use). It reflects the content of the `info.*` properties
+in the `Environment`, as well as the properties in `git.properties`
+if such a file exists in the root of the classpath.
+
+Example curl request:
+include::{generated}/info/curl-request.adoc[]
+
+Example HTTP request:
+include::{generated}/info/http-request.adoc[]
+
+Example HTTP response:
+include::{generated}/info/http-response.adoc[]
diff --git a/spring-boot-actuator-docs/src/main/asciidoc/mappings.adoc b/spring-boot-actuator-docs/src/main/asciidoc/mappings.adoc
new file mode 100644
index 0000000000..685d227231
--- /dev/null
+++ b/spring-boot-actuator-docs/src/main/asciidoc/mappings.adoc
@@ -0,0 +1,14 @@
+=== /mappings
+
+This endpoint lists the Spring MVC request mappings, so users can
+see the handlers registered for requests by path, method, media type,
+etc.
+
+Example curl request:
+include::{generated}/mappings/curl-request.adoc[]
+
+Example HTTP request:
+include::{generated}/mappings/http-request.adoc[]
+
+Example HTTP response:
+include::{generated}/mappings/http-response.adoc[]
diff --git a/spring-boot-actuator-docs/src/main/asciidoc/metrics.adoc b/spring-boot-actuator-docs/src/main/asciidoc/metrics.adoc
new file mode 100644
index 0000000000..42c61f6c34
--- /dev/null
+++ b/spring-boot-actuator-docs/src/main/asciidoc/metrics.adoc
@@ -0,0 +1,17 @@
+=== /metrics
+
+This endpoint lists the public metrics exposed by the application.
+By default this includes all the counters in the `CounterService`
+and all the gauges in the `GaugeService`, plus a few JVM metrics about
+memory and uptime. Users can register additional sources by creating
+beans of type `PublicMetrics` and/or by registering counters and
+gauges.
+
+Example curl request:
+include::{generated}/metrics/curl-request.adoc[]
+
+Example HTTP request:
+include::{generated}/metrics/http-request.adoc[]
+
+Example HTTP response:
+include::{generated}/metrics/http-response.adoc[]
diff --git a/spring-boot-actuator-docs/src/main/asciidoc/trace.adoc b/spring-boot-actuator-docs/src/main/asciidoc/trace.adoc
new file mode 100644
index 0000000000..c35b66a829
--- /dev/null
+++ b/spring-boot-actuator-docs/src/main/asciidoc/trace.adoc
@@ -0,0 +1,16 @@
+=== /trace
+
+This endpoint lists contents of the `TraceRepository` (which
+users can override by providing a bean of that type, or by
+injecting that bean and adding stuff to it). By default
+it is the last 100 HTTP requests, including all headers in the
+request and response, and the path and HTTP status.
+
+Example curl request:
+include::{generated}/trace/curl-request.adoc[]
+
+Example HTTP request:
+include::{generated}/trace/http-request.adoc[]
+
+Example HTTP response:
+include::{generated}/trace/http-response.adoc[]
diff --git a/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/EndpointDocumentation.java b/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/EndpointDocumentation.java
new file mode 100644
index 0000000000..1b02d66ea4
--- /dev/null
+++ b/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/EndpointDocumentation.java
@@ -0,0 +1,170 @@
+package org.springframework.boot.actuate.hypermedia.test;
+
+import static org.springframework.restdocs.RestDocumentation.document;
+import static org.springframework.restdocs.RestDocumentation.documentationConfiguration;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import groovy.text.Template;
+import groovy.text.TemplateEngine;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.Filter;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
+import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
+import org.springframework.boot.test.SpringApplicationConfiguration;
+import org.springframework.http.MediaType;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.ResultHandler;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.util.StringUtils;
+import org.springframework.web.context.WebApplicationContext;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
+@WebAppConfiguration
+@TestPropertySource(properties = { "spring.jackson.serialization.indent_output=true",
+ "endpoints.health.sensitive=true", "endpoints.links.enabled=false" })
+@DirtiesContext
+public class EndpointDocumentation {
+
+ @Autowired
+ private WebApplicationContext context;
+
+ @Autowired
+ private MvcEndpoints mvcEndpoints;
+
+ @Autowired
+ @Qualifier("metricFilter")
+ private Filter metricFilter;
+
+ @Autowired
+ @Qualifier("webRequestLoggingFilter")
+ private Filter traceFilter;
+
+ @Autowired
+ private TemplateEngine templates;
+
+ @Value("${org.springframework.restdocs.outputDir:${user.dir}/target/generated-snippets}")
+ private String restdocsOutputDir;
+
+ private MockMvc mockMvc;
+
+ @Before
+ public void setUp() {
+ System.setProperty("org.springframework.restdocs.outputDir",
+ this.restdocsOutputDir);
+ this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
+ .addFilters(this.metricFilter, this.traceFilter)
+ .apply(documentationConfiguration()).build();
+ }
+
+ @Test
+ public void logfile() throws Exception {
+ this.mockMvc.perform(get("/logfile").accept(MediaType.TEXT_PLAIN))
+ .andExpect(status().isOk())
+ .andDo(document("logfile"));
+ }
+
+ @Test
+ public void endpoints() throws Exception {
+
+ final File docs = new File("src/main/asciidoc");
+
+ final Map model = new LinkedHashMap();
+ final List endpoints = new ArrayList();
+ model.put("endpoints", endpoints);
+ for (MvcEndpoint endpoint : getEndpoints()) {
+ final String endpointPath = StringUtils.hasText(endpoint.getPath()) ? endpoint
+ .getPath() : "/";
+
+ if (!endpointPath.equals("/docs") && !endpointPath.equals("/logfile")) {
+ String output = endpointPath.substring(1);
+ output = output.length() > 0 ? output : "./";
+ this.mockMvc
+ .perform(get(endpointPath).accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk()).andDo(document(output))
+ .andDo(new ResultHandler() {
+ @Override
+ public void handle(MvcResult mvcResult) throws Exception {
+ EndpointDoc endpoint = new EndpointDoc(docs, endpointPath);
+ endpoints.add(endpoint);
+ }
+ });
+ }
+ }
+ File file = new File(this.restdocsOutputDir + "/endpoints.adoc");
+ file.getParentFile().mkdirs();
+ PrintWriter writer = new PrintWriter(file, "UTF-8");
+ try {
+ Template template = this.templates.createTemplate(new File(
+ "src/test/resources/templates/endpoints.adoc.tpl"));
+ template.make(model).writeTo(writer);
+ }
+ finally {
+ writer.close();
+ }
+ }
+
+ private Collection extends MvcEndpoint> getEndpoints() {
+ List extends MvcEndpoint> endpoints = new ArrayList(
+ this.mvcEndpoints.getEndpoints());
+ Collections.sort(endpoints, new Comparator() {
+ @Override
+ public int compare(MvcEndpoint o1, MvcEndpoint o2) {
+ return o1.getPath().compareTo(o2.getPath());
+ }
+ });
+ return endpoints;
+ }
+
+ public static class EndpointDoc {
+
+ private String path;
+ private String custom;
+ private String title;
+
+ public EndpointDoc(File rootDir, String path) {
+ this.title = path;
+ this.path = path.equals("/") ? "" : path;
+ String custom = path.substring(1) + ".adoc";
+ if (new File(rootDir, custom).exists()) {
+ this.custom = custom;
+ }
+ }
+
+ public String getTitle() {
+ return this.title;
+ }
+
+ public String getPath() {
+ return this.path;
+ }
+
+ public String getCustom() {
+ return this.custom;
+ }
+
+ }
+
+}
diff --git a/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/HealthEndpointDocumentation.java b/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/HealthEndpointDocumentation.java
new file mode 100644
index 0000000000..d9f6ba26cf
--- /dev/null
+++ b/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/HealthEndpointDocumentation.java
@@ -0,0 +1,63 @@
+package org.springframework.boot.actuate.hypermedia.test;
+
+import static org.springframework.restdocs.RestDocumentation.document;
+import static org.springframework.restdocs.RestDocumentation.documentationConfiguration;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import groovy.text.TemplateEngine;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
+import org.springframework.boot.test.SpringApplicationConfiguration;
+import org.springframework.http.MediaType;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+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;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
+@WebAppConfiguration
+@TestPropertySource(properties = { "spring.jackson.serialization.indent_output=true",
+"endpoints.health.sensitive=false" })
+@DirtiesContext
+public class HealthEndpointDocumentation {
+
+ @Autowired
+ private WebApplicationContext context;
+
+ @Autowired
+ private MvcEndpoints mvcEndpoints;
+
+ @Autowired
+ private TemplateEngine templates;
+
+ @Value("${org.springframework.restdocs.outputDir:target/generated-snippets}")
+ private String restdocsOutputDir;
+
+ private MockMvc mockMvc;
+
+ @Before
+ public void setUp() {
+ System.setProperty("org.springframework.restdocs.outputDir",
+ this.restdocsOutputDir);
+ this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
+ .apply(documentationConfiguration())
+ .build();
+ }
+
+ @Test
+ public void health() throws Exception {
+ this.mockMvc.perform(get("/health").accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andDo(document("health/unsensitive"));
+ }
+
+}
diff --git a/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/HypermediaEndpointDocumentation.java b/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/HypermediaEndpointDocumentation.java
new file mode 100644
index 0000000000..1ca6a2e2ae
--- /dev/null
+++ b/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/HypermediaEndpointDocumentation.java
@@ -0,0 +1,76 @@
+package org.springframework.boot.actuate.hypermedia.test;
+
+import static org.springframework.restdocs.RestDocumentation.document;
+import static org.springframework.restdocs.RestDocumentation.documentationConfiguration;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import groovy.text.TemplateEngine;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
+import org.springframework.boot.test.SpringApplicationConfiguration;
+import org.springframework.http.MediaType;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+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;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
+@WebAppConfiguration
+@TestPropertySource(properties = "spring.jackson.serialization.indent_output=true")
+@DirtiesContext
+public class HypermediaEndpointDocumentation {
+
+ @Autowired
+ private WebApplicationContext context;
+
+ @Autowired
+ private MvcEndpoints mvcEndpoints;
+
+ @Autowired
+ private TemplateEngine templates;
+
+ @Value("${org.springframework.restdocs.outputDir:target/generated-snippets}")
+ private String restdocsOutputDir;
+
+ private MockMvc mockMvc;
+
+ @Before
+ public void setUp() {
+ System.setProperty("org.springframework.restdocs.outputDir",
+ this.restdocsOutputDir);
+ this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
+ .apply(documentationConfiguration())
+ .build();
+ }
+
+ @Test
+ public void beans() throws Exception {
+ this.mockMvc.perform(get("/beans").accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andDo(document("beans/hypermedia"));
+ }
+
+ @Test
+ public void metrics() throws Exception {
+ this.mockMvc.perform(get("/metrics").accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andDo(document("metrics/hypermedia"));
+ }
+
+ @Test
+ public void home() throws Exception {
+ this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andDo(document("admin"));
+ }
+
+}
diff --git a/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/SpringBootHypermediaApplication.java b/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/SpringBootHypermediaApplication.java
new file mode 100644
index 0000000000..5a9ca58fe6
--- /dev/null
+++ b/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/SpringBootHypermediaApplication.java
@@ -0,0 +1,21 @@
+package org.springframework.boot.actuate.hypermedia.test;
+
+import groovy.text.GStringTemplateEngine;
+import groovy.text.TemplateEngine;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+
+@SpringBootApplication
+public class SpringBootHypermediaApplication {
+
+ @Bean
+ public TemplateEngine groovyTemplateEngine() {
+ return new GStringTemplateEngine();
+ }
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringBootHypermediaApplication.class, args);
+ }
+}
diff --git a/spring-boot-actuator-docs/src/test/resources/application.properties b/spring-boot-actuator-docs/src/test/resources/application.properties
new file mode 100644
index 0000000000..6cd849878a
--- /dev/null
+++ b/spring-boot-actuator-docs/src/test/resources/application.properties
@@ -0,0 +1,2 @@
+# management.contextPath=/admin
+logging.path: target/logs
\ No newline at end of file
diff --git a/spring-boot-actuator-docs/src/test/resources/templates/endpoints.adoc.tpl b/spring-boot-actuator-docs/src/test/resources/templates/endpoints.adoc.tpl
new file mode 100644
index 0000000000..a7c0617c55
--- /dev/null
+++ b/spring-boot-actuator-docs/src/test/resources/templates/endpoints.adoc.tpl
@@ -0,0 +1,16 @@
+<% endpoints.each { endpoint ->
+ if (endpoint.custom) { %>
+include::{docs}/${endpoint.custom}[]
+<% } else { %>
+=== ${endpoint.title}
+
+Example curl request:
+include::{generated}${endpoint.path}/curl-request.adoc[]
+
+Example HTTP request:
+include::{generated}${endpoint.path}/http-request.adoc[]
+
+Example HTTP response:
+include::{generated}${endpoint.path}/http-response.adoc[]
+<% }
+} %>
\ No newline at end of file
diff --git a/spring-boot-actuator/pom.xml b/spring-boot-actuator/pom.xml
index a61dbf5925..10b7554350 100644
--- a/spring-boot-actuator/pom.xml
+++ b/spring-boot-actuator/pom.xml
@@ -42,6 +42,26 @@
spring-context
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-xml
+ true
+
+
+ org.springframework.hateoas
+ spring-hateoas
+ true
+
+
+ org.springframework.plugin
+ spring-plugin-core
+ true
+
+
+ org.webjars
+ hal-browser
+ true
+ com.google.guavaguava
@@ -219,6 +239,11 @@
true
+
+ com.jayway.jsonpath
+ json-path
+ test
+ org.springframework.bootspring-boot
diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java
index 227427682d..bf7b3f9e3d 100644
--- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java
+++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java
@@ -44,8 +44,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
+import org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration;
import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration;
+import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.boot.bind.RelaxedPropertyResolver;
@@ -86,8 +88,9 @@ import org.springframework.web.servlet.DispatcherServlet;
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
@ConditionalOnWebApplication
@AutoConfigureAfter({ PropertyPlaceholderAutoConfiguration.class,
- EmbeddedServletContainerAutoConfiguration.class, WebMvcAutoConfiguration.class,
- ManagementServerPropertiesAutoConfiguration.class })
+ EmbeddedServletContainerAutoConfiguration.class, WebMvcAutoConfiguration.class,
+ ManagementServerPropertiesAutoConfiguration.class,
+ HypermediaAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class })
public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware,
SmartInitializingSingleton {
diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcHypermediaConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcHypermediaConfiguration.java
new file mode 100644
index 0000000000..a387675fe9
--- /dev/null
+++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcHypermediaConfiguration.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed 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.springframework.boot.actuate.autoconfigure;
+
+import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.annotation.PostConstruct;
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.actuate.endpoint.mvc.ActuatorDocsEndpoint;
+import org.springframework.boot.actuate.endpoint.mvc.HalBrowserEndpoint;
+import org.springframework.boot.actuate.endpoint.mvc.HypermediaDisabled;
+import org.springframework.boot.actuate.endpoint.mvc.LinksEnhancer;
+import org.springframework.boot.actuate.endpoint.mvc.LinksMvcEndpoint;
+import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
+import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
+import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnResource;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
+import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
+import org.springframework.boot.autoconfigure.web.ResourceProperties;
+import org.springframework.boot.autoconfigure.web.ServerProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ConditionContext;
+import org.springframework.context.annotation.Conditional;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.type.AnnotatedTypeMetadata;
+import org.springframework.hateoas.Link;
+import org.springframework.hateoas.Resource;
+import org.springframework.hateoas.ResourceSupport;
+import org.springframework.hateoas.UriTemplate;
+import org.springframework.hateoas.hal.CurieProvider;
+import org.springframework.hateoas.hal.DefaultCurieProvider;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.HttpMessageNotWritableException;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.http.server.ServletServerHttpRequest;
+import org.springframework.util.StringUtils;
+import org.springframework.util.TypeUtils;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.servlet.HandlerMapping;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonUnwrapped;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+
+/**
+ * Autoconfiguration for hypermedia in HTTP endpoints.
+ *
+ * @author Dave Syer
+ *
+ */
+@Configuration
+@ConditionalOnClass(Link.class)
+@ConditionalOnWebApplication
+@ConditionalOnBean(HttpMessageConverters.class)
+@ConditionalOnProperty(value = "endpoints.enabled", matchIfMissing = true)
+@EnableConfigurationProperties(ResourceProperties.class)
+public class EndpointWebMvcHypermediaConfiguration {
+
+ @Bean
+ @ConditionalOnProperty(value = "endpoints.hal.enabled", matchIfMissing = true)
+ @ConditionalOnResource(resources = "classpath:/META-INF/resources/webjars/hal-browser/b7669f1-1")
+ @Conditional(MissingSpringDataRestResourceCondition.class)
+ public HalBrowserEndpoint halBrowserMvcEndpoint(
+ ManagementServerProperties management, ResourceProperties resources) {
+ return new HalBrowserEndpoint(management,
+ resources.getWelcomePage() != null ? "/hal" : "");
+ }
+
+ @Bean
+ @ConditionalOnProperty(value = "endpoints.docs.enabled", matchIfMissing = true)
+ @ConditionalOnResource(resources = "classpath:/META-INF/resources/spring-boot-actuator/docs/index.html")
+ public ActuatorDocsEndpoint actuatorDocsEndpoint(ManagementServerProperties management) {
+ return new ActuatorDocsEndpoint(management);
+ }
+
+ @Bean
+ @ConditionalOnBean(ActuatorDocsEndpoint.class)
+ @ConditionalOnMissingBean(CurieProvider.class)
+ @ConditionalOnProperty(value = "endpoints.docs.curies.enabled", matchIfMissing = false)
+ public DefaultCurieProvider curieProvider(ServerProperties server,
+ ManagementServerProperties management, ActuatorDocsEndpoint endpoint) {
+ String path = management.getContextPath() + endpoint.getPath()
+ + "/#spring_boot_actuator__{rel}";
+ if (server.getPort() == management.getPort() && management.getPort() != null
+ && management.getPort() != 0) {
+ path = server.getPath(path);
+ }
+ return new DefaultCurieProvider("boot", new UriTemplate(path));
+ }
+
+ @Configuration("EndpointHypermediaAutoConfiguration.MissingResourceCondition")
+ @ConditionalOnResource(resources = "classpath:/META-INF/spring-data-rest/hal-browser/index.html")
+ protected static class MissingSpringDataRestResourceCondition extends
+ SpringBootCondition {
+ @Override
+ public ConditionOutcome getMatchOutcome(ConditionContext context,
+ AnnotatedTypeMetadata metadata) {
+ if (context.getRegistry().containsBeanDefinition(
+ "EndpointHypermediaAutoConfiguration.MissingResourceCondition")) {
+ return ConditionOutcome.noMatch("Spring Data REST HAL browser found");
+ }
+ return ConditionOutcome.match("Spring Data REST HAL browser not found");
+ }
+ }
+
+ @ConditionalOnProperty(value = "endpoints.links.enabled", matchIfMissing = true)
+ public static class LinksConfiguration {
+
+ @Bean
+ public LinksMvcEndpoint linksMvcEndpoint(ResourceProperties resources) {
+ return new LinksMvcEndpoint(resources.getWelcomePage() != null ? "/links"
+ : "");
+ }
+
+ /**
+ * Controller advice that adds links to the home page and/or the management
+ * context path. The home page is enhanced if it is composed already of a
+ * {@link ResourceSupport} (e.g. when using Spring Data REST).
+ *
+ * @author Dave Syer
+ *
+ */
+ @ControllerAdvice
+ public static class HomePageLinksAdvice implements ResponseBodyAdvice
+
+ org.springframework.boot
+ spring-boot-actuator-docs
+ 1.3.0.BUILD-SNAPSHOT
+ org.springframework.bootspring-boot-autoconfigure
@@ -1842,6 +1847,11 @@
thymeleaf-extras-springsecurity4${thymeleaf-extras-springsecurity4.version}
+
+ org.webjars
+ hal-browser
+ b7669f1-1
+ org.yamlsnakeyaml
diff --git a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc
index 43e8754c3a..138bbf292b 100644
--- a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc
+++ b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc
@@ -150,6 +150,28 @@ For example, the following will disable _all_ endpoints except for `info`:
endpoints.info.enabled=true
----
+[[production-ready-endpoint-hypermedia]]
+=== Hypermedia for MVC Endpoints
+If http://projects.spring.io/spring-hateoas[Spring HATEOAS] is on the classpath (e.g.
+through the `spring-boot-starter-hateoas` or if you are using
+http://projects.spring.io/spring-data-rest[Spring Data REST]) then the HTTP endpoints
+from the Actuator are enhanced with hypermedia links, and a "discovery page" is added
+with links to all the endpoints. The "discovery page" is actually an endpoint itself,
+so it can be disabled along with the rest of the hypermedia by setting
+`endpoints.links.enabled=false`. If it is not explicitly disabled the links
+endpoint renders a JSON object with a link for each other endpoint, and the default
+path is the same as the `management.contentPath` (so "/" by default).
+
+NOTE: if there is a static home page ("index.html") in your application and the links
+endpoint is registered with its default path ("/") then content negotiation will kick in
+to determine which content is shown to a client that requests the home page (the
+links will show only if the client accepts `application/json`).
+
+If the https://github.com/mikekelly/hal-browser[HAL Browser] is on the classpath
+via its webjar (`org.webjars:hal-browser`), or via the `spring-data-hal-browser` then
+the default home page for HTML clients will be the HAL Browser. This is also exposed via an
+endpoint ("hal") so it can be disabled and have its path explicitly configured like
+the other endpoints.
[[production-ready-customizing-endpoints-programmatically]]
diff --git a/spring-boot-samples/pom.xml b/spring-boot-samples/pom.xml
index 0b4be403c9..00ed00afbd 100644
--- a/spring-boot-samples/pom.xml
+++ b/spring-boot-samples/pom.xml
@@ -43,6 +43,10 @@
spring-boot-sample-flywayspring-boot-sample-hateoasspring-boot-sample-hornetq
+ spring-boot-sample-hypermedia
+ spring-boot-sample-hypermedia-gson
+ spring-boot-sample-hypermedia-jpa
+ spring-boot-sample-hypermedia-uispring-boot-sample-integrationspring-boot-sample-jerseyspring-boot-sample-jersey1
diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-gson/pom.xml b/spring-boot-samples/spring-boot-sample-hypermedia-gson/pom.xml
new file mode 100644
index 0000000000..e8f6fc2c20
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-hypermedia-gson/pom.xml
@@ -0,0 +1,66 @@
+
+
+ 4.0.0
+
+ spring-boot-sample-hypermedia-gson
+ jar
+
+ spring-boot-sample-hypermedia-gson
+ Demo project for Spring Boot
+
+
+ org.springframework.boot
+ spring-boot-samples
+ 1.3.0.BUILD-SNAPSHOT
+
+
+
+ UTF-8
+ 1.7
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springframework.boot
+ spring-boot-starter-hateoas
+
+
+ com.google.code.gson
+ gson
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ com.jayway.jsonpath
+ json-path
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-gson/src/main/java/demo/SpringBootHypermediaApplication.java b/spring-boot-samples/spring-boot-sample-hypermedia-gson/src/main/java/demo/SpringBootHypermediaApplication.java
new file mode 100644
index 0000000000..954faed6f9
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-hypermedia-gson/src/main/java/demo/SpringBootHypermediaApplication.java
@@ -0,0 +1,12 @@
+package demo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SpringBootHypermediaApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringBootHypermediaApplication.class, args);
+ }
+}
diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-gson/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-hypermedia-gson/src/main/resources/application.properties
new file mode 100644
index 0000000000..ef0f024d90
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-hypermedia-gson/src/main/resources/application.properties
@@ -0,0 +1,2 @@
+# management.contextPath=/admin
+spring.http.converters.preferred-json-mapper: gson
\ No newline at end of file
diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-gson/src/test/java/demo/SpringBootHypermediaApplicationTests.java b/spring-boot-samples/spring-boot-sample-hypermedia-gson/src/test/java/demo/SpringBootHypermediaApplicationTests.java
new file mode 100644
index 0000000000..54e46efb31
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-hypermedia-gson/src/test/java/demo/SpringBootHypermediaApplicationTests.java
@@ -0,0 +1,61 @@
+package demo;
+
+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;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.SpringApplicationConfiguration;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+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;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
+@WebAppConfiguration
+@TestPropertySource(properties="endpoints.health.sensitive: false")
+public class SpringBootHypermediaApplicationTests {
+
+ @Autowired
+ private WebApplicationContext context;
+
+ private MockMvc mockMvc;
+
+ @Before
+ public void setUp() {
+ this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
+ }
+
+ @Test
+ public void health() throws Exception {
+ this.mockMvc
+ .perform(get("/health").accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.links[0].href").value("http://localhost/health"))
+ .andExpect(jsonPath("$.content.status").exists());
+ }
+
+ @Test
+ public void trace() throws Exception {
+ this.mockMvc
+ .perform(get("/trace").accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.links[0].href").value("http://localhost/trace"))
+ .andExpect(jsonPath("$.content").isArray());
+ }
+
+ @Test
+ public void envValue() throws Exception {
+ this.mockMvc.perform(get("/env/user.home").accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$._links").doesNotExist());
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-jpa/pom.xml b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/pom.xml
new file mode 100644
index 0000000000..b8ead35e0c
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/pom.xml
@@ -0,0 +1,82 @@
+
+
+ 4.0.0
+
+ spring-boot-sample-hypermedia-jpa
+ jar
+
+ spring-boot-sample-hypermedia-jpa
+ Demo project for Spring Boot
+
+
+ org.springframework.boot
+ spring-boot-samples
+ 1.3.0.BUILD-SNAPSHOT
+
+
+
+ UTF-8
+ 1.7
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springframework.boot
+ spring-boot-starter-hateoas
+
+
+ org.springframework.boot
+ spring-boot-actuator-docs
+
+
+ org.springframework.boot
+ spring-boot-starter-data-rest
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.data
+ spring-data-rest-hal-browser
+
+
+ com.h2database
+ h2
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ com.jayway.jsonpath
+ json-path
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/java/demo/Book.java b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/java/demo/Book.java
new file mode 100644
index 0000000000..838c1901bd
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/java/demo/Book.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed 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 demo;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+
+@Entity
+public class Book {
+ @Id
+ @GeneratedValue
+ private Long id;
+ private String title;
+ public String getTitle() {
+ return this.title;
+ }
+ public void setTitle(String title) {
+ this.title = title;
+ }
+}
\ No newline at end of file
diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/java/demo/BookRepository.java b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/java/demo/BookRepository.java
new file mode 100644
index 0000000000..6ac159bc81
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/java/demo/BookRepository.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed 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 demo;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface BookRepository extends JpaRepository {
+}
\ No newline at end of file
diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/java/demo/JpaHypermediaApplication.java b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/java/demo/JpaHypermediaApplication.java
new file mode 100644
index 0000000000..baae2f2a8c
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/java/demo/JpaHypermediaApplication.java
@@ -0,0 +1,13 @@
+package demo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class JpaHypermediaApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(JpaHypermediaApplication.class, args);
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/resources/application.properties
new file mode 100644
index 0000000000..31e1ded85d
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/resources/application.properties
@@ -0,0 +1,2 @@
+management.contextPath=/admin
+endpoints.docs.curies.enabled=true
\ No newline at end of file
diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/test/java/demo/JpaHypermediaApplicationTests.java b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/test/java/demo/JpaHypermediaApplicationTests.java
new file mode 100644
index 0000000000..46ac33e2d9
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/test/java/demo/JpaHypermediaApplicationTests.java
@@ -0,0 +1,18 @@
+package demo;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.boot.test.SpringApplicationConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringApplicationConfiguration(classes = JpaHypermediaApplication.class)
+@WebAppConfiguration
+public class JpaHypermediaApplicationTests {
+
+ @Test
+ public void contextLoads() {
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/test/java/demo/VanillaHypermediaIntegrationTests.java b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/test/java/demo/VanillaHypermediaIntegrationTests.java
new file mode 100644
index 0000000000..e20d8be0f0
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/test/java/demo/VanillaHypermediaIntegrationTests.java
@@ -0,0 +1,74 @@
+package demo;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
+import org.springframework.boot.test.SpringApplicationConfiguration;
+import org.springframework.http.MediaType;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringApplicationConfiguration(classes = JpaHypermediaApplication.class)
+@WebAppConfiguration
+@DirtiesContext
+public class VanillaHypermediaIntegrationTests {
+
+ @Autowired
+ private WebApplicationContext context;
+
+ @Autowired
+ private MvcEndpoints mvcEndpoints;
+
+ private MockMvc mockMvc;
+
+ @Before
+ public void setUp() {
+ this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
+ }
+
+ @Test
+ public void links() throws Exception {
+ this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk()).andExpect(jsonPath("$._links").exists());
+ }
+
+ @Test
+ public void health() throws Exception {
+ this.mockMvc.perform(get("/admin/health").accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk()).andExpect(jsonPath("$._links").exists());
+ }
+
+ @Test
+ public void adminLinks() throws Exception {
+ this.mockMvc.perform(get("/admin").accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk()).andExpect(jsonPath("$._links").exists());
+ }
+
+ @Test
+ public void docs() throws Exception {
+ MvcResult response = this.mockMvc.perform(get("/admin/docs/").accept(MediaType.TEXT_HTML))
+ .andExpect(status().isOk()).andReturn();
+ System.err.println(response.getResponse().getContentAsString());
+ }
+
+ @Test
+ public void browser() throws Exception {
+ MvcResult response = this.mockMvc.perform(get("/").accept(MediaType.TEXT_HTML))
+ .andExpect(status().isFound()).andReturn();
+ assertEquals("/browser/index.html#", response.getResponse().getHeaders("location").get(0));
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-ui/pom.xml b/spring-boot-samples/spring-boot-sample-hypermedia-ui/pom.xml
new file mode 100644
index 0000000000..2ed2b746cf
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-hypermedia-ui/pom.xml
@@ -0,0 +1,61 @@
+
+
+ 4.0.0
+
+ spring-boot-sample-hypermedia-ui
+ jar
+
+ spring-boot-sample-hypermedia-ui
+ Demo project for Spring Boot
+
+
+ org.springframework.boot
+ spring-boot-samples
+ 1.3.0.BUILD-SNAPSHOT
+
+
+
+ UTF-8
+ 1.7
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springframework.boot
+ spring-boot-starter-hateoas
+
+
+ org.springframework.boot
+ spring-boot-actuator-docs
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-ui/src/main/java/demo/SpringBootHypermediaApplication.java b/spring-boot-samples/spring-boot-sample-hypermedia-ui/src/main/java/demo/SpringBootHypermediaApplication.java
new file mode 100644
index 0000000000..954faed6f9
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-hypermedia-ui/src/main/java/demo/SpringBootHypermediaApplication.java
@@ -0,0 +1,12 @@
+package demo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SpringBootHypermediaApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringBootHypermediaApplication.class, args);
+ }
+}
diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-ui/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-hypermedia-ui/src/main/resources/application.properties
new file mode 100644
index 0000000000..560a46ccde
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-hypermedia-ui/src/main/resources/application.properties
@@ -0,0 +1 @@
+management.contextPath=/admin
\ No newline at end of file
diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-ui/src/main/resources/static/index.html b/spring-boot-samples/spring-boot-sample-hypermedia-ui/src/main/resources/static/index.html
new file mode 100644
index 0000000000..66a41f708c
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-hypermedia-ui/src/main/resources/static/index.html
@@ -0,0 +1,5 @@
+
+
+