Commit e8085016 authored by Phillip Webb's avatar Phillip Webb

Polish Actuator hypermedia support

parent 67dd164d
...@@ -2,25 +2,24 @@ ...@@ -2,25 +2,24 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>spring-boot-actuator-docs</artifactId>
<packaging>jar</packaging>
<name>spring-boot-actuator-docs</name>
<description>Docs project for Spring Boot</description>
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId> <artifactId>spring-boot-parent</artifactId>
<version>1.3.0.BUILD-SNAPSHOT</version> <version>1.3.0.BUILD-SNAPSHOT</version>
<relativePath>../spring-boot-parent</relativePath>
</parent> </parent>
<artifactId>spring-boot-actuator-docs</artifactId>
<name>Spring Boot Actuator Docs</name>
<description>Spring Boot Actuator Docs</description>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <main.basedir>${basedir}/..</main.basedir>
<java.version>1.7</java.version>
</properties> </properties>
<dependencies> <dependencies>
<!-- Compile -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
...@@ -36,6 +35,7 @@ ...@@ -36,6 +35,7 @@
<artifactId>spring-boot-starter-hateoas</artifactId> <artifactId>spring-boot-starter-hateoas</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<!-- Test -->
<dependency> <dependency>
<groupId>org.springframework.restdocs</groupId> <groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs</artifactId> <artifactId>spring-restdocs</artifactId>
...@@ -53,7 +53,6 @@ ...@@ -53,7 +53,6 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
...@@ -113,5 +112,4 @@ ...@@ -113,5 +112,4 @@
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
</project> </project>
=== /autoconfig === /autoconfig
This endpoint is a report on the Spring Boot auto-configuration 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).
This endpoint is a report on the Spring Boot Autoconfiguration process The report is split into 2 parts, positive matches first, and then negative. If the
that happened when your application started up. It lists all the context is a hierarchy, there is also a separate report on the parent context with the
`@Conditional` annotations that were evaluated as the context started same format (and recursively up to the top of the hierarchy).
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. NOTE: the report is actually about `@Conditional` evaluation not auto-configuration
If the context is a hierarchy, there is also a separate report on the parent per se, but most autoconfiguration features use `@Conditional` heavily, so there is a lot
context with the same format (and recursively up to the top of the hierarchy). of overlap.
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: Example curl request:
include::{generated}/autoconfig/curl-request.adoc[] include::{generated}/autoconfig/curl-request.adoc[]
......
=== /beans === /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.
This endpoint is a report on the Spring Boot `ApplicationContext`. It lists NOTE: some beans are pure configuration (any class that is annotated `@Configuration`).
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: Example curl request:
include::{generated}/beans/curl-request.adoc[] include::{generated}/beans/curl-request.adoc[]
......
=== /configprops === /configprops
This endpoint is a report on the Spring Boot `@ConfigurationProperties` beans. Beans with
This endpoint is a report on the Spring Boot `@ConfigurationProperties` this annotation are bound to the `Environment` on startup, so they reflect the
beans. Beans with this annotation are bound to the `Environment` on externalised configuration of the application. Beans are listed by name. A bean that is
startup, so they reflect the externalised configuration of the application. added using `@EnableConfigurationProperties` will have a conventional name:
Beans are listed by name. `<prefix>.CONFIGURATION_PROPERTIES`, where `<prefix>` is the environment key prefix
A bean that is added using `@EnableConfigurationProperties` will have specified in the `@ConfigurationProperties` annotation.
a conventional name: `<prefix>.CONFIGURATION_PROPERTIES`, where
`<prefix>` is the environment key prefix specified in the
`@ConfigurationProperties` annotation.
Example curl request: Example curl request:
include::{generated}/configprops/curl-request.adoc[] include::{generated}/configprops/curl-request.adoc[]
......
=== /dump === /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).
This endpoint is a thread dump: the result is a list of threads each with NOTE: some `SecurityManager` implementations might prevent this endpoint from working.
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: Example curl request:
include::{generated}/dump/curl-request.adoc[] include::{generated}/dump/curl-request.adoc[]
......
=== /env === /env
This endpoint is a dump of the Spring `Environment`. It lists the active profiles and all
This endpoint is a dump of the Spring `Environment`. It lists the active the `PropertySources` in the `Environment` (the ones that are listed first take precedence
profiles and all the `PropertySources` in the `Environment` (the ones that when binding to `@ConfigurationProperties` or `@Value`). Normally you will see the Java
are listed first take precedence when binding to `@ConfigurationProperties` `System` properties and the OS environment variables in their own `PropertySources` plus
or `@Value`). Normally you will see the Java `System` properties and the any `.properties` or `.yml` files used to configure the application on start up.
OS environment variables in their own `PropertySources` plus any `.properties`
or `.yml` files used to configure the application on start up.
Example curl request: Example curl request:
include::{generated}/env/curl-request.adoc[] include::{generated}/env/curl-request.adoc[]
......
=== /health === /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`).
This endpoint is an indication of the health of the application. The HTTP code in the response reflects the status (e.g. "`UP`"=200,
It has an overall status ("UP", "DOWN" etc.), which is the only thing "`OUT_OF_SERVICE`"=503, "`DOWN`"=503). The mappings can be changed by configuring
you see unless either you are authenticated or the endpoint is marked `endpoints.health.mapping.<STATUS>=XXX`.
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.<STATUS>=XXX`.
Example curl request: Example curl request:
include::{generated}/health/curl-request.adoc[] include::{generated}/health/curl-request.adoc[]
......
...@@ -10,12 +10,14 @@ The way that endpoints are exposed will depend on the type of technology that yo ...@@ -10,12 +10,14 @@ The way that endpoints are exposed will depend on the type of technology that yo
Most applications choose HTTP monitoring, where the ID of the endpoint is mapped 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`. to a URL. For example, by default, the `health` endpoint will be mapped to `/health`.
== List of Endpoints
== List of Endpoints
include::{generated}/endpoints.adoc[] include::{generated}/endpoints.adoc[]
=== /logfile
=== /logfile
This endpoint (if available) contains the plain text logfile configured by the user 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 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). so one of these properties has to be set for this endpoint to be active).
...@@ -29,21 +31,23 @@ include::{generated}/logfile/http-request.adoc[] ...@@ -29,21 +31,23 @@ include::{generated}/logfile/http-request.adoc[]
Example HTTP response: Example HTTP response:
include::{generated}/logfile/http-response.adoc[] include::{generated}/logfile/http-response.adoc[]
=== /docs
=== /docs
This endpoint (if available) contains HTML documemtation for the other endpoints. Its path 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 can be "/docs" (if there is an existing home page) or "/" (otherwise, including if the
HAL browser is not active). 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 == Hypermedia Support
endpoint responses are enhanced with hypermedia in the form of "links". The default If https://projects.spring.io/spring-hateoas[Spring HATEOAS] is enabled (i.e. if it is
media type for responses is http://stateless.co/hal_specification.html[HAL], resulting on the classpath by default) then the Actuator endpoint responses are enhanced with
in each resource having an extra property called "_links". You can change the hypermedia in the form of "links". The default media type for responses is
media type to another one supported by Spring HATEOAS by providing your own http://stateless.co/hal_specification.html[HAL], resulting in each resource having an
`@EnableHypermedia` annotation and custom providers as necessary. 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": Example enhanced "/metrics" endpoint with additional "_links":
...@@ -55,94 +59,103 @@ The easiest way to avoid that is to use a `management.contextPath`, e.g. "/admin ...@@ -55,94 +59,103 @@ 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 TIP: You can disable the hypermedia support in Actuator endpoints by setting
`endpoints.links.enabled=false`. `endpoints.links.enabled=false`.
=== Default home page === Default home page
If the `management.contextPath` is empty, or if the home page provided If the `management.contextPath` is empty, or if the home page provided by the application
by the application happens to be a response body of type `ResourceSupport`, then it will happens to be a response body of type `ResourceSupport`, then it will be enhanced with
be enhanced with links to the actuator endpoints. The latter would happen for instance links to the actuator endpoints. The latter would happen for instance if you use Spring
if you use Spring Data REST to expose `Repository` endpoints. Data REST to expose `Repository` endpoints.
Example vanilla "/" endpoint if the `management.contextPath` is empty (the "/admin" Example vanilla "/" endpoint if the `management.contextPath` is empty (the "/admin" page
page would be the same with different links if `management.contextPath=/admin`): would be the same with different links if `management.contextPath=/admin`):
include::{generated}/admin/http-response.adoc[] 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 === Endpoints with format changes
links, so their contents are inserted as a field named "content". Some endpoints in their "`raw`" form consist of an array (e.g. the `/beans` and the
Example enhanced "/beans" endpoint with additional "_links": `/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[] include::{generated}/beans/hypermedia/http-response.adoc[]
== HAL Browser == 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.
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: For example in Maven:
[source,xml] [source,xml,indent=0]
---- ----
<dependency> <dependency>
<groupId>org.webjars</groupId> <groupId>org.webjars</groupId>
<artifactId>hal-browser</artifactId> <artifactId>hal-browser</artifactId>
</dependency> </dependency>
---- ----
or in Gradle or in Gradle
[source,groovy] [source,groovy,indent=0]
---- ----
dependencies { dependencies {
... ...
compile('org.webjars:hal-browser') compile('org.webjars:hal-browser')
... ...
} }
---- ----
NOTE: if you are using Spring Data REST, then a dependency on the `spring-data-rest-hal-browser` NOTE: if you are using Spring Data REST, then a dependency on the
will have an equivalent effect. `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.
If you do that then a new endpoint will appear at "/" or "/hal" (relative to the `management.contextPath`) Example:
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] image::hal-browser.png[HAL Browser]
TIP: The endpoint path can always, as with all MVC endpoints, be overridden using TIP: The endpoint path can always, as with all MVC endpoints, be overridden using
`endpoints.hal.path=/yourpath` (note the leading slash). `endpoints.hal.path=/yourpath` (note the leading slash).
== Actuator Documentation Browser == 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.
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: For example in Maven:
[source,xml] [source,xml,indent=0]
---- ----
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-hypermedia-docs</artifactId> <artifactId>spring-boot-hypermedia-docs</artifactId>
</dependency> </dependency>
---- ----
or in Gradle or in Gradle
[source,groovy] [source,groovy,indent=0]
---- ----
dependencies { dependencies {
... ...
compile('org.springframework.boot:spring-boot-hypermedia-docs') compile('org.springframework.boot:spring-boot-hypermedia-docs')
... ...
} }
---- ----
If you do that then a new endpoint at "/" or "/docs" (relative to the `management.contextPath`) If you do that then a new endpoint at `/` or `/docs` (relative to the
will serve up a static HTML page with this documentation in it. The default endpoint path depends `management.contextPath`) will serve up a static HTML page with this documentation in it.
on whether or not there is already a static home page 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, ("index.html" or a HAL browser) - if there is not and the `management.contextPath` is
then the docs browser shows up on the home page. empty, then the docs browser shows up on the home page.
\ No newline at end of file
=== /info === /info
This endpoint is empty and marked as `sensitive=false` by default (so it is
This endpoint is empty and marked as `sensitive=false` unauthenticated by default if Spring Security is in use). It reflects the content of the
by default (so it is unauthenticated by default if Spring `info.*` properties in the `Environment`, as well as the properties in `git.properties`
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. if such a file exists in the root of the classpath.
Example curl request: Example curl request:
......
=== /mappings === /mappings
This endpoint lists the Spring MVC request mappings, so users can see the handlers
This endpoint lists the Spring MVC request mappings, so users can registered for requests by path, method, media type, etc.
see the handlers registered for requests by path, method, media type,
etc.
Example curl request: Example curl request:
include::{generated}/mappings/curl-request.adoc[] include::{generated}/mappings/curl-request.adoc[]
......
=== /metrics === /metrics
This endpoint lists the public metrics exposed by the application. By default this
This endpoint lists the public metrics exposed by the application. includes all the counters in the `CounterService` and all the gauges in the
By default this includes all the counters in the `CounterService` `GaugeService`, plus a few JVM metrics about memory and uptime. Users can register
and all the gauges in the `GaugeService`, plus a few JVM metrics about additional sources by creating beans of type `PublicMetrics` and/or by registering
memory and uptime. Users can register additional sources by creating counters and gauges.
beans of type `PublicMetrics` and/or by registering counters and
gauges.
Example curl request: Example curl request:
include::{generated}/metrics/curl-request.adoc[] include::{generated}/metrics/curl-request.adoc[]
......
=== /trace === /trace
This endpoint lists contents of the `TraceRepository` (which users can override by
This endpoint lists contents of the `TraceRepository` (which providing a bean of that type, or by injecting that bean and adding stuff to it). By
users can override by providing a bean of that type, or by default it is the last 100 HTTP requests, including all headers in the request and
injecting that bean and adding stuff to it). By default response, and the path and HTTP status.
it is the last 100 HTTP requests, including all headers in the
request and response, and the path and HTTP status.
Example curl request: Example curl request:
include::{generated}/trace/curl-request.adoc[] include::{generated}/trace/curl-request.adoc[]
......
/*
* Copyright 2012-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.hypermedia.test; 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.Template;
import groovy.text.TemplateEngine; import groovy.text.TemplateEngine;
...@@ -40,6 +52,11 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; ...@@ -40,6 +52,11 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
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;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class) @SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
@WebAppConfiguration @WebAppConfiguration
...@@ -82,8 +99,7 @@ public class EndpointDocumentation { ...@@ -82,8 +99,7 @@ public class EndpointDocumentation {
@Test @Test
public void logfile() throws Exception { public void logfile() throws Exception {
this.mockMvc.perform(get("/logfile").accept(MediaType.TEXT_PLAIN)) this.mockMvc.perform(get("/logfile").accept(MediaType.TEXT_PLAIN))
.andExpect(status().isOk()) .andExpect(status().isOk()).andDo(document("logfile"));
.andDo(document("logfile"));
} }
@Test @Test
...@@ -98,10 +114,10 @@ public class EndpointDocumentation { ...@@ -98,10 +114,10 @@ public class EndpointDocumentation {
final String endpointPath = StringUtils.hasText(endpoint.getPath()) ? endpoint final String endpointPath = StringUtils.hasText(endpoint.getPath()) ? endpoint
.getPath() : "/"; .getPath() : "/";
if (!endpointPath.equals("/docs") && !endpointPath.equals("/logfile")) { if (!endpointPath.equals("/docs") && !endpointPath.equals("/logfile")) {
String output = endpointPath.substring(1); String output = endpointPath.substring(1);
output = output.length() > 0 ? output : "./"; output = output.length() > 0 ? output : "./";
this.mockMvc this.mockMvc
.perform(get(endpointPath).accept(MediaType.APPLICATION_JSON)) .perform(get(endpointPath).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andDo(document(output)) .andExpect(status().isOk()).andDo(document(output))
.andDo(new ResultHandler() { .andDo(new ResultHandler() {
...@@ -111,7 +127,7 @@ public class EndpointDocumentation { ...@@ -111,7 +127,7 @@ public class EndpointDocumentation {
endpoints.add(endpoint); endpoints.add(endpoint);
} }
}); });
} }
} }
File file = new File(this.restdocsOutputDir + "/endpoints.adoc"); File file = new File(this.restdocsOutputDir + "/endpoints.adoc");
file.getParentFile().mkdirs(); file.getParentFile().mkdirs();
......
/*
* Copyright 2012-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.hypermedia.test; 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 groovy.text.TemplateEngine;
import org.junit.Before; import org.junit.Before;
...@@ -22,11 +34,16 @@ import org.springframework.test.web.servlet.MockMvc; ...@@ -22,11 +34,16 @@ import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
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;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class) @SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
@WebAppConfiguration @WebAppConfiguration
@TestPropertySource(properties = { "spring.jackson.serialization.indent_output=true", @TestPropertySource(properties = { "spring.jackson.serialization.indent_output=true",
"endpoints.health.sensitive=false" }) "endpoints.health.sensitive=false" })
@DirtiesContext @DirtiesContext
public class HealthEndpointDocumentation { public class HealthEndpointDocumentation {
...@@ -49,15 +66,13 @@ public class HealthEndpointDocumentation { ...@@ -49,15 +66,13 @@ public class HealthEndpointDocumentation {
System.setProperty("org.springframework.restdocs.outputDir", System.setProperty("org.springframework.restdocs.outputDir",
this.restdocsOutputDir); this.restdocsOutputDir);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration()) .apply(documentationConfiguration()).build();
.build();
} }
@Test @Test
public void health() throws Exception { public void health() throws Exception {
this.mockMvc.perform(get("/health").accept(MediaType.APPLICATION_JSON)) this.mockMvc.perform(get("/health").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk()).andDo(document("health/unsensitive"));
.andDo(document("health/unsensitive"));
} }
} }
/*
* Copyright 2012-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.hypermedia.test; 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 groovy.text.TemplateEngine;
import org.junit.Before; import org.junit.Before;
...@@ -22,6 +34,11 @@ import org.springframework.test.web.servlet.MockMvc; ...@@ -22,6 +34,11 @@ import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
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;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class) @SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
@WebAppConfiguration @WebAppConfiguration
...@@ -48,29 +65,25 @@ public class HypermediaEndpointDocumentation { ...@@ -48,29 +65,25 @@ public class HypermediaEndpointDocumentation {
System.setProperty("org.springframework.restdocs.outputDir", System.setProperty("org.springframework.restdocs.outputDir",
this.restdocsOutputDir); this.restdocsOutputDir);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration()) .apply(documentationConfiguration()).build();
.build();
} }
@Test @Test
public void beans() throws Exception { public void beans() throws Exception {
this.mockMvc.perform(get("/beans").accept(MediaType.APPLICATION_JSON)) this.mockMvc.perform(get("/beans").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk()).andDo(document("beans/hypermedia"));
.andDo(document("beans/hypermedia"));
} }
@Test @Test
public void metrics() throws Exception { public void metrics() throws Exception {
this.mockMvc.perform(get("/metrics").accept(MediaType.APPLICATION_JSON)) this.mockMvc.perform(get("/metrics").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk()).andDo(document("metrics/hypermedia"));
.andDo(document("metrics/hypermedia"));
} }
@Test @Test
public void home() throws Exception { public void home() throws Exception {
this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk()).andDo(document("admin"));
.andDo(document("admin"));
} }
} }
/*
* Copyright 2012-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.hypermedia.test; package org.springframework.boot.actuate.hypermedia.test;
import groovy.text.GStringTemplateEngine; import groovy.text.GStringTemplateEngine;
...@@ -18,4 +34,5 @@ public class SpringBootHypermediaApplication { ...@@ -18,4 +34,5 @@ public class SpringBootHypermediaApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(SpringBootHypermediaApplication.class, args); SpringApplication.run(SpringBootHypermediaApplication.class, args);
} }
} }
...@@ -88,9 +88,9 @@ import org.springframework.web.servlet.DispatcherServlet; ...@@ -88,9 +88,9 @@ import org.springframework.web.servlet.DispatcherServlet;
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class }) @ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
@ConditionalOnWebApplication @ConditionalOnWebApplication
@AutoConfigureAfter({ PropertyPlaceholderAutoConfiguration.class, @AutoConfigureAfter({ PropertyPlaceholderAutoConfiguration.class,
EmbeddedServletContainerAutoConfiguration.class, WebMvcAutoConfiguration.class, EmbeddedServletContainerAutoConfiguration.class, WebMvcAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class, ManagementServerPropertiesAutoConfiguration.class,
HypermediaAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class }) HypermediaAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class })
public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware, public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware,
SmartInitializingSingleton { SmartInitializingSingleton {
......
/* /*
* Copyright 2015 the original author or authors. * Copyright 2012-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -14,48 +14,51 @@ ...@@ -14,48 +14,51 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.actuate.endpoint.mvc; package org.springframework.boot.actuate.autoconfigure;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
import org.springframework.hateoas.ResourceSupport; import org.springframework.hateoas.ResourceSupport;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
/** /**
* @author Dave Syer * Adds enpoints links to {@link ResourceSupport}.
* *
* @author Dave Syer
*/ */
public class LinksEnhancer { class LinksEnhancer {
private MvcEndpoints endpoints; private final String rootPath;
private String rootPath; private final MvcEndpoints endpoints;
public LinksEnhancer(MvcEndpoints endpoints, String rootPath) { public LinksEnhancer(String rootPath, MvcEndpoints endpoints) {
this.endpoints = endpoints;
this.rootPath = rootPath; this.rootPath = rootPath;
this.endpoints = endpoints;
} }
public void addEndpointLinks(ResourceSupport resource, String self) { public void addEndpointLinks(ResourceSupport resource, String self) {
if (!resource.hasLink("self")) { if (!resource.hasLink("self")) {
resource.add(linkTo(LinksEnhancer.class).slash( resource.add(linkTo(LinksEnhancer.class).slash(this.rootPath + self)
this.rootPath + self).withSelfRel()); .withSelfRel());
} }
for (MvcEndpoint endpoint : this.endpoints.getEndpoints()) { for (MvcEndpoint endpoint : this.endpoints.getEndpoints()) {
if (endpoint.getPath().equals(self)) { if (!endpoint.getPath().equals(self)) {
continue; addEndpointLink(resource, endpoint);
}
Class<?> type = endpoint.getEndpointType();
if (type == null) {
type = Object.class;
}
String path = endpoint.getPath();
String rel = path.startsWith("/") ? path.substring(1) : path;
if (StringUtils.hasText(rel)) {
resource.add(linkTo(type).slash(this.rootPath + endpoint.getPath())
.withRel(rel));
} }
} }
} }
private void addEndpointLink(ResourceSupport resource, MvcEndpoint endpoint) {
Class<?> type = endpoint.getEndpointType();
type = (type == null ? Object.class : type);
String path = endpoint.getPath();
String rel = (path.startsWith("/") ? path.substring(1) : path);
if (StringUtils.hasText(rel)) {
String fullPath = this.rootPath + endpoint.getPath();
resource.add(linkTo(type).slash(fullPath).withRel(rel));
}
}
} }
...@@ -103,7 +103,7 @@ public class ManagementSecurityAutoConfiguration { ...@@ -103,7 +103,7 @@ public class ManagementSecurityAutoConfiguration {
@Configuration @Configuration
protected static class ManagementSecurityPropertiesConfiguration implements protected static class ManagementSecurityPropertiesConfiguration implements
SecurityPrerequisite { SecurityPrerequisite {
@Autowired(required = false) @Autowired(required = false)
private SecurityProperties security; private SecurityProperties security;
...@@ -115,7 +115,7 @@ public class ManagementSecurityAutoConfiguration { ...@@ -115,7 +115,7 @@ public class ManagementSecurityAutoConfiguration {
public void init() { public void init() {
if (this.management != null && this.security != null) { if (this.management != null && this.security != null) {
this.security.getUser().getRole() this.security.getUser().getRole()
.add(this.management.getSecurity().getRole()); .add(this.management.getSecurity().getRole());
} }
} }
...@@ -124,7 +124,7 @@ public class ManagementSecurityAutoConfiguration { ...@@ -124,7 +124,7 @@ public class ManagementSecurityAutoConfiguration {
// Get the ignored paths in early // Get the ignored paths in early
@Order(SecurityProperties.IGNORED_ORDER + 1) @Order(SecurityProperties.IGNORED_ORDER + 1)
private static class IgnoredPathsWebSecurityConfigurerAdapter implements private static class IgnoredPathsWebSecurityConfigurerAdapter implements
WebSecurityConfigurer<WebSecurity> { WebSecurityConfigurer<WebSecurity> {
@Autowired(required = false) @Autowired(required = false)
private ErrorController errorController; private ErrorController errorController;
...@@ -209,7 +209,7 @@ public class ManagementSecurityAutoConfiguration { ...@@ -209,7 +209,7 @@ public class ManagementSecurityAutoConfiguration {
@ConditionalOnProperty(prefix = "management.security", name = "enabled", matchIfMissing = true) @ConditionalOnProperty(prefix = "management.security", name = "enabled", matchIfMissing = true)
@Order(ManagementServerProperties.BASIC_AUTH_ORDER) @Order(ManagementServerProperties.BASIC_AUTH_ORDER)
protected static class ManagementWebSecurityConfigurerAdapter extends protected static class ManagementWebSecurityConfigurerAdapter extends
WebSecurityConfigurerAdapter { WebSecurityConfigurerAdapter {
@Autowired @Autowired
private SecurityProperties security; private SecurityProperties security;
...@@ -314,17 +314,15 @@ public class ManagementSecurityAutoConfiguration { ...@@ -314,17 +314,15 @@ public class ManagementSecurityAutoConfiguration {
} }
if (this.delegate == null) { if (this.delegate == null) {
List<RequestMatcher> pathMatchers = new ArrayList<RequestMatcher>(); List<RequestMatcher> pathMatchers = new ArrayList<RequestMatcher>();
String[] paths = !this.sensitive ? getEndpointPaths( String[] paths = !this.sensitive ? getEndpointPaths(endpointMapping,
endpointMapping, false) : getEndpointPaths(endpointMapping);
false) for (String path : paths) {
: getEndpointPaths(endpointMapping); pathMatchers.add(new AntPathRequestMatcher(
for (String path : paths) { ManagementWebSecurityConfigurerAdapter.this.server
pathMatchers.add(new AntPathRequestMatcher(
ManagementWebSecurityConfigurerAdapter.this.server
.getPath(path))); .getPath(path)));
} }
this.delegate = pathMatchers.isEmpty() ? AnyRequestMatcher.INSTANCE this.delegate = pathMatchers.isEmpty() ? AnyRequestMatcher.INSTANCE
: new OrRequestMatcher(pathMatchers); : new OrRequestMatcher(pathMatchers);
} }
return this.delegate.matches(request); return this.delegate.matches(request);
} }
...@@ -354,7 +352,8 @@ public class ManagementSecurityAutoConfiguration { ...@@ -354,7 +352,8 @@ public class ManagementSecurityAutoConfiguration {
paths.add(path + "/**"); paths.add(path + "/**");
// Add Spring MVC-generated additional paths // Add Spring MVC-generated additional paths
paths.add(path + ".*"); paths.add(path + ".*");
} else { }
else {
paths.add("/"); paths.add("/");
} }
} }
......
/* /*
* Copyright 2015 the original author or authors. * Copyright 2012-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -18,7 +18,6 @@ package org.springframework.boot.actuate.endpoint.mvc; ...@@ -18,7 +18,6 @@ package org.springframework.boot.actuate.endpoint.mvc;
import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties; import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties;
import org.springframework.boot.actuate.endpoint.Endpoint; import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
...@@ -26,45 +25,28 @@ import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry ...@@ -26,45 +25,28 @@ import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/** /**
* @author Dave Syer * {@link MvcEndpoint} to expose actuator documentation.
* *
* @author Dave Syer
* @since 1.3.0
*/ */
@ConfigurationProperties("endpoints.docs") @ConfigurationProperties("endpoints.docs")
public class ActuatorDocsEndpoint extends WebMvcConfigurerAdapter implements MvcEndpoint { public class ActuatorDocsEndpoint extends WebMvcConfigurerAdapter implements MvcEndpoint {
private static final String DOCS_LOCATION = "classpath:/META-INF/resources/spring-boot-actuator/docs/";
private String path = "/docs"; private String path = "/docs";
private boolean sensitive; private boolean sensitive;
private ManagementServerProperties management; private ManagementServerProperties management;
private Curies curies = new Curies(); private Curies curies = new Curies();
public Curies getCuries() { public Curies getCuries() {
return this.curies; return this.curies;
} }
/**
* Properties of the default CurieProvider (used for adding docs links). If enabled, all
* unqualified rels will pick up a prefix and a curie template pointing to the docs endpoint.
*
*/
public static class Curies {
/**
* Enable the curie generation (off by default).
*/
private boolean enabled = false;
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
public ActuatorDocsEndpoint(ManagementServerProperties management) { public ActuatorDocsEndpoint(ManagementServerProperties management) {
this.management = management; this.management = management;
} }
...@@ -82,8 +64,7 @@ public class ActuatorDocsEndpoint extends WebMvcConfigurerAdapter implements Mvc ...@@ -82,8 +64,7 @@ public class ActuatorDocsEndpoint extends WebMvcConfigurerAdapter implements Mvc
@Override @Override
public void addResourceHandlers(ResourceHandlerRegistry registry) { public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler(this.management.getContextPath() + this.path + "/**") registry.addResourceHandler(this.management.getContextPath() + this.path + "/**")
.addResourceLocations( .addResourceLocations(DOCS_LOCATION);
"classpath:/META-INF/resources/spring-boot-actuator/docs/");
} }
public void setPath(String path) { public void setPath(String path) {
...@@ -109,4 +90,26 @@ public class ActuatorDocsEndpoint extends WebMvcConfigurerAdapter implements Mvc ...@@ -109,4 +90,26 @@ public class ActuatorDocsEndpoint extends WebMvcConfigurerAdapter implements Mvc
return null; return null;
} }
/**
* Properties of the default CurieProvider (used for adding docs links). If enabled,
* all unqualified rels will pick up a prefix and a curie template pointing to the
* docs endpoint.
*/
public static class Curies {
/**
* Enable the curie generation (off by default).
*/
private boolean enabled = false;
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
} }
...@@ -38,7 +38,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; ...@@ -38,7 +38,7 @@ import org.springframework.web.bind.annotation.ResponseStatus;
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class EnvironmentMvcEndpoint extends EndpointMvcAdapter implements public class EnvironmentMvcEndpoint extends EndpointMvcAdapter implements
EnvironmentAware { EnvironmentAware {
private Environment environment; private Environment environment;
......
/* /*
* Copyright 2015 the original author or authors. * Copyright 2012-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -25,14 +25,19 @@ import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry ...@@ -25,14 +25,19 @@ import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/** /**
* @author Dave Syer * {@link MvcEndpoint} to support the Spring Data HAL browser.
* *
* @author Dave Syer
* @since 1.3.0
*/ */
@ConfigurationProperties("endpoints.hal") @ConfigurationProperties("endpoints.hal")
public class HalBrowserEndpoint extends WebMvcConfigurerAdapter implements MvcEndpoint { public class HalBrowserEndpoint extends WebMvcConfigurerAdapter implements MvcEndpoint {
private static final String HAL_BROWSER_VERSION = "b7669f1-1"; private static final String HAL_BROWSER_VERSION = "b7669f1-1";
private static final String HAL_BROWSER_LOCATION = "classpath:/META-INF/resources/webjars/hal-browser/"
+ HAL_BROWSER_VERSION + "/";
private String path = ""; private String path = "";
private ManagementServerProperties management; private ManagementServerProperties management;
...@@ -58,16 +63,11 @@ public class HalBrowserEndpoint extends WebMvcConfigurerAdapter implements MvcEn ...@@ -58,16 +63,11 @@ public class HalBrowserEndpoint extends WebMvcConfigurerAdapter implements MvcEn
@Override @Override
public void addResourceHandlers(ResourceHandlerRegistry registry) { public void addResourceHandlers(ResourceHandlerRegistry registry) {
// Make sure the root path is not cached otherwise the browser won't come back for // Make sure the root path is not cached so the browser comes back for the JSON
// the JSON
registry.addResourceHandler(this.management.getContextPath() + this.path + "/") registry.addResourceHandler(this.management.getContextPath() + this.path + "/")
.addResourceLocations( .addResourceLocations(HAL_BROWSER_LOCATION).setCachePeriod(0);
"classpath:/META-INF/resources/webjars/hal-browser/"
+ HAL_BROWSER_VERSION + "/").setCachePeriod(0);
registry.addResourceHandler(this.management.getContextPath() + this.path + "/**") registry.addResourceHandler(this.management.getContextPath() + this.path + "/**")
.addResourceLocations( .addResourceLocations(HAL_BROWSER_LOCATION);
"classpath:/META-INF/resources/webjars/hal-browser/"
+ HAL_BROWSER_VERSION + "/");
} }
public void setPath(String path) { public void setPath(String path) {
......
/* /*
* Copyright 2015 the original author or authors. * Copyright 2012-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -22,11 +22,16 @@ import java.lang.annotation.Retention; ...@@ -22,11 +22,16 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
/** /**
* @author Dave Syer * Annotation to that {@link MvcEndpoint} class or {@link RequestMapping} method should't
* generate a hypermedia response.
* *
* @author Dave Syer
* @since 1.3.0
*/ */
@Target({ElementType.TYPE, ElementType.METHOD}) @Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
public @interface HypermediaDisabled { public @interface HypermediaDisabled {
......
...@@ -47,7 +47,7 @@ import org.springframework.web.util.UrlPathHelper; ...@@ -47,7 +47,7 @@ import org.springframework.web.util.UrlPathHelper;
@ConfigurationProperties(prefix = "endpoints.jolokia", ignoreUnknownFields = false) @ConfigurationProperties(prefix = "endpoints.jolokia", ignoreUnknownFields = false)
@HypermediaDisabled @HypermediaDisabled
public class JolokiaMvcEndpoint implements MvcEndpoint, InitializingBean, public class JolokiaMvcEndpoint implements MvcEndpoint, InitializingBean,
ApplicationContextAware, ServletContextAware { ApplicationContextAware, ServletContextAware {
/** /**
* Endpoint URL path. * Endpoint URL path.
......
/* /*
* Copyright 2015 the original author or authors. * Copyright 2013-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -24,8 +24,10 @@ import org.springframework.web.bind.annotation.RequestMapping; ...@@ -24,8 +24,10 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
/** /**
* @author Dave Syer * {@link MvcEndpoint} to add hypermedia links.
* *
* @author Dave Syer
* @since 1.3.0
*/ */
@ConfigurationProperties("endpoints.links") @ConfigurationProperties("endpoints.links")
public class LinksMvcEndpoint implements MvcEndpoint { public class LinksMvcEndpoint implements MvcEndpoint {
...@@ -37,7 +39,7 @@ public class LinksMvcEndpoint implements MvcEndpoint { ...@@ -37,7 +39,7 @@ public class LinksMvcEndpoint implements MvcEndpoint {
this.path = defaultPath; this.path = defaultPath;
} }
@RequestMapping(value = { "/", "" }, produces=MediaType.APPLICATION_JSON_VALUE) @RequestMapping(value = { "/", "" }, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody @ResponseBody
public ResourceSupport links() { public ResourceSupport links() {
ResourceSupport resource = new ResourceSupport(); ResourceSupport resource = new ResourceSupport();
......
package org.springframework.boot.actuate.autoconfigure; /*
* Copyright 2012-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.
*/
import static org.junit.Assert.assertEquals; package org.springframework.boot.actuate.autoconfigure;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
...@@ -24,6 +35,11 @@ import org.springframework.test.web.servlet.MvcResult; ...@@ -24,6 +35,11 @@ import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
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.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class) @SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
@WebAppConfiguration @WebAppConfiguration
...@@ -55,8 +71,8 @@ public class BrowserPathHypermediaIntegrationTests { ...@@ -55,8 +71,8 @@ public class BrowserPathHypermediaIntegrationTests {
@Test @Test
public void redirect() throws Exception { public void redirect() throws Exception {
this.mockMvc.perform(get("/hal").accept(MediaType.TEXT_HTML)) this.mockMvc.perform(get("/hal").accept(MediaType.TEXT_HTML))
.andExpect(status().isFound()) .andExpect(status().isFound())
.andExpect(header().string("location", "/hal/#")); .andExpect(header().string("location", "/hal/#"));
} }
@MinimalActuatorHypermediaApplication @MinimalActuatorHypermediaApplication
...@@ -66,6 +82,7 @@ public class BrowserPathHypermediaIntegrationTests { ...@@ -66,6 +82,7 @@ public class BrowserPathHypermediaIntegrationTests {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(SpringBootHypermediaApplication.class, args); SpringApplication.run(SpringBootHypermediaApplication.class, args);
} }
} }
} }
package org.springframework.boot.actuate.autoconfigure; /*
* Copyright 2012-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.
*/
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; package org.springframework.boot.actuate.autoconfigure;
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.Before;
import org.junit.Test; import org.junit.Test;
...@@ -26,6 +37,11 @@ import org.springframework.web.bind.annotation.RequestMapping; ...@@ -26,6 +37,11 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
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;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class) @SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
@WebAppConfiguration @WebAppConfiguration
...@@ -49,24 +65,24 @@ public class ContextPathHypermediaIntegrationTests { ...@@ -49,24 +65,24 @@ public class ContextPathHypermediaIntegrationTests {
@Test @Test
public void home() throws Exception { public void home() throws Exception {
this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andExpect(jsonPath("$._links").exists()); .andExpect(status().isOk()).andExpect(jsonPath("$._links").exists());
} }
@Test @Test
public void links() throws Exception { public void links() throws Exception {
this.mockMvc.perform(get("/admin").accept(MediaType.APPLICATION_JSON)) this.mockMvc.perform(get("/admin").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andExpect(jsonPath("$._links").exists()); .andExpect(status().isOk()).andExpect(jsonPath("$._links").exists());
} }
@Test @Test
public void trace() throws Exception { public void trace() throws Exception {
this.mockMvc this.mockMvc
.perform(get("/admin/trace").accept(MediaType.APPLICATION_JSON)) .perform(get("/admin/trace").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect( .andExpect(
jsonPath("$._links.self.href").value( jsonPath("$._links.self.href").value(
"http://localhost/admin/trace")) "http://localhost/admin/trace"))
.andExpect(jsonPath("$.content").isArray()); .andExpect(jsonPath("$.content").isArray());
} }
@Test @Test
...@@ -76,11 +92,11 @@ public class ContextPathHypermediaIntegrationTests { ...@@ -76,11 +92,11 @@ public class ContextPathHypermediaIntegrationTests {
path = path.startsWith("/") ? path.substring(1) : path; path = path.startsWith("/") ? path.substring(1) : path;
path = path.length() > 0 ? path : "self"; path = path.length() > 0 ? path : "self";
this.mockMvc this.mockMvc
.perform(get("/admin").accept(MediaType.APPLICATION_JSON)) .perform(get("/admin").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect( .andExpect(
jsonPath("$._links.%s.href", path).value( jsonPath("$._links.%s.href", path).value(
"http://localhost/admin" + endpoint.getPath())); "http://localhost/admin" + endpoint.getPath()));
} }
} }
...@@ -98,8 +114,9 @@ public class ContextPathHypermediaIntegrationTests { ...@@ -98,8 +114,9 @@ public class ContextPathHypermediaIntegrationTests {
public static void main(String[] args) { public static void main(String[] args) {
new SpringApplicationBuilder(SpringBootHypermediaApplication.class) new SpringApplicationBuilder(SpringBootHypermediaApplication.class)
.properties("management.contextPath:/admin").run(args); .properties("management.contextPath:/admin").run(args);
} }
} }
} }
package org.springframework.boot.actuate.autoconfigure; /*
* Copyright 2012-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.
*/
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; package org.springframework.boot.actuate.autoconfigure;
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.Before;
import org.junit.Test; import org.junit.Test;
...@@ -25,6 +36,11 @@ import org.springframework.web.bind.annotation.RequestMapping; ...@@ -25,6 +36,11 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
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;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class) @SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
@WebAppConfiguration @WebAppConfiguration
...@@ -47,7 +63,7 @@ public class CustomHomepageHypermediaIntegrationTests { ...@@ -47,7 +63,7 @@ public class CustomHomepageHypermediaIntegrationTests {
@Test @Test
public void links() throws Exception { public void links() throws Exception {
this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andExpect(jsonPath("$._links").exists()); .andExpect(status().isOk()).andExpect(jsonPath("$._links").exists());
} }
@Test @Test
...@@ -56,8 +72,8 @@ public class CustomHomepageHypermediaIntegrationTests { ...@@ -56,8 +72,8 @@ public class CustomHomepageHypermediaIntegrationTests {
String path = endpoint.getPath(); String path = endpoint.getPath();
path = path.startsWith("/") ? path.substring(1) : path; path = path.startsWith("/") ? path.substring(1) : path;
this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$._links.%s.href", path).exists()); .andExpect(jsonPath("$._links.%s.href", path).exists());
} }
} }
...@@ -68,13 +84,15 @@ public class CustomHomepageHypermediaIntegrationTests { ...@@ -68,13 +84,15 @@ public class CustomHomepageHypermediaIntegrationTests {
@RequestMapping("") @RequestMapping("")
public ResourceSupport home() { public ResourceSupport home() {
ResourceSupport resource = new ResourceSupport(); ResourceSupport resource = new ResourceSupport();
resource.add(linkTo(SpringBootHypermediaApplication.class).slash("/").withSelfRel()); resource.add(linkTo(SpringBootHypermediaApplication.class).slash("/")
.withSelfRel());
return resource; return resource;
} }
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(SpringBootHypermediaApplication.class, args); SpringApplication.run(SpringBootHypermediaApplication.class, args);
} }
} }
} }
/* /*
* Copyright 2012-2014 the original author or authors. * Copyright 2012-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,9 +16,6 @@ ...@@ -16,9 +16,6 @@
package org.springframework.boot.actuate.autoconfigure; package org.springframework.boot.actuate.autoconfigure;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
...@@ -63,6 +60,9 @@ import org.springframework.web.bind.annotation.RestController; ...@@ -63,6 +60,9 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/** /**
* Integration tests for MVC {@link Endpoint}s. * Integration tests for MVC {@link Endpoint}s.
* *
...@@ -94,19 +94,19 @@ public class EndpointMvcIntegrationTests { ...@@ -94,19 +94,19 @@ public class EndpointMvcIntegrationTests {
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@Import({ EmbeddedServletContainerAutoConfiguration.class, @Import({ EmbeddedServletContainerAutoConfiguration.class,
ServerPropertiesAutoConfiguration.class, ServerPropertiesAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
JacksonAutoConfiguration.class, JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
protected static @interface MinimalWebConfiguration { protected static @interface MinimalWebConfiguration {
} }
@Configuration @Configuration
@MinimalWebConfiguration @MinimalWebConfiguration
@Import({ ManagementServerPropertiesAutoConfiguration.class, JacksonAutoConfiguration.class, @Import({ ManagementServerPropertiesAutoConfiguration.class,
EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class }) JacksonAutoConfiguration.class, EndpointAutoConfiguration.class,
EndpointWebMvcAutoConfiguration.class })
@RestController @RestController
protected static class Application { protected static class Application {
......
...@@ -16,18 +16,6 @@ ...@@ -16,18 +16,6 @@
package org.springframework.boot.actuate.autoconfigure; package org.springframework.boot.actuate.autoconfigure;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.net.SocketException; import java.net.SocketException;
import java.net.URI; import java.net.URI;
...@@ -84,6 +72,18 @@ import org.springframework.web.servlet.ModelAndView; ...@@ -84,6 +72,18 @@ import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping; import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/** /**
* Tests for {@link EndpointWebMvcAutoConfiguration}. * Tests for {@link EndpointWebMvcAutoConfiguration}.
* *
...@@ -228,8 +228,7 @@ public class EndpointWebMvcAutoConfigurationTests { ...@@ -228,8 +228,7 @@ public class EndpointWebMvcAutoConfigurationTests {
this.applicationContext.register(RootConfig.class, EndpointConfig.class, this.applicationContext.register(RootConfig.class, EndpointConfig.class,
ServerPortConfig.class, PropertyPlaceholderAutoConfiguration.class, ServerPortConfig.class, PropertyPlaceholderAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class, ManagementServerPropertiesAutoConfiguration.class,
ServerPropertiesAutoConfiguration.class, ServerPropertiesAutoConfiguration.class, JacksonAutoConfiguration.class,
JacksonAutoConfiguration.class,
EmbeddedServletContainerAutoConfiguration.class, EmbeddedServletContainerAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
...@@ -246,7 +245,7 @@ public class EndpointWebMvcAutoConfigurationTests { ...@@ -246,7 +245,7 @@ public class EndpointWebMvcAutoConfigurationTests {
this.applicationContext.register(RootConfig.class, BaseConfiguration.class, this.applicationContext.register(RootConfig.class, BaseConfiguration.class,
ServerPortConfig.class, EndpointWebMvcAutoConfiguration.class); ServerPortConfig.class, EndpointWebMvcAutoConfiguration.class);
new ServerPortInfoApplicationContextInitializer() new ServerPortInfoApplicationContextInitializer()
.initialize(this.applicationContext); .initialize(this.applicationContext);
this.applicationContext.refresh(); this.applicationContext.refresh();
Integer localServerPort = this.applicationContext.getEnvironment().getProperty( Integer localServerPort = this.applicationContext.getEnvironment().getProperty(
"local.server.port", Integer.class); "local.server.port", Integer.class);
...@@ -262,7 +261,7 @@ public class EndpointWebMvcAutoConfigurationTests { ...@@ -262,7 +261,7 @@ public class EndpointWebMvcAutoConfigurationTests {
@Test @Test
public void portPropertiesOnDifferentPort() throws Exception { public void portPropertiesOnDifferentPort() throws Exception {
new ServerPortInfoApplicationContextInitializer() new ServerPortInfoApplicationContextInitializer()
.initialize(this.applicationContext); .initialize(this.applicationContext);
this.applicationContext.register(RootConfig.class, DifferentPortConfig.class, this.applicationContext.register(RootConfig.class, DifferentPortConfig.class,
BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class, BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class,
ErrorMvcAutoConfiguration.class); ErrorMvcAutoConfiguration.class);
...@@ -423,13 +422,12 @@ public class EndpointWebMvcAutoConfigurationTests { ...@@ -423,13 +422,12 @@ public class EndpointWebMvcAutoConfigurationTests {
@Configuration @Configuration
@Import({ PropertyPlaceholderAutoConfiguration.class, @Import({ PropertyPlaceholderAutoConfiguration.class,
EmbeddedServletContainerAutoConfiguration.class, EmbeddedServletContainerAutoConfiguration.class,
JacksonAutoConfiguration.class, JacksonAutoConfiguration.class, EndpointAutoConfiguration.class,
EndpointAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, DispatcherServletAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, ManagementServerPropertiesAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class, ServerPropertiesAutoConfiguration.class, WebMvcAutoConfiguration.class })
ServerPropertiesAutoConfiguration.class, WebMvcAutoConfiguration.class })
protected static class BaseConfiguration { protected static class BaseConfiguration {
} }
...@@ -587,7 +585,7 @@ public class EndpointWebMvcAutoConfigurationTests { ...@@ -587,7 +585,7 @@ public class EndpointWebMvcAutoConfigurationTests {
} }
private static class GrabManagementPort implements private static class GrabManagementPort implements
ApplicationListener<EmbeddedServletContainerInitializedEvent> { ApplicationListener<EmbeddedServletContainerInitializedEvent> {
private ApplicationContext rootContext; private ApplicationContext rootContext;
......
/* /*
* Copyright 2012-2014 the original author or authors. * Copyright 2012-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,8 +16,6 @@ ...@@ -16,8 +16,6 @@
package org.springframework.boot.actuate.autoconfigure; package org.springframework.boot.actuate.autoconfigure;
import static org.junit.Assert.assertEquals;
import org.junit.After; import org.junit.After;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint;
...@@ -33,6 +31,8 @@ import org.springframework.mock.web.MockServletContext; ...@@ -33,6 +31,8 @@ import org.springframework.mock.web.MockServletContext;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import static org.junit.Assert.assertEquals;
/** /**
* Tests for {@link EndpointWebMvcAutoConfiguration} of the {@link HealthMvcEndpoint}. * Tests for {@link EndpointWebMvcAutoConfiguration} of the {@link HealthMvcEndpoint}.
* *
......
...@@ -16,13 +16,6 @@ ...@@ -16,13 +16,6 @@
package org.springframework.boot.actuate.autoconfigure; package org.springframework.boot.actuate.autoconfigure;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import javax.servlet.Filter; import javax.servlet.Filter;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
...@@ -60,6 +53,13 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; ...@@ -60,6 +53,13 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
/** /**
* Tests for {@link ManagementSecurityAutoConfiguration}. * Tests for {@link ManagementSecurityAutoConfiguration}.
* *
...@@ -111,8 +111,7 @@ public class ManagementSecurityAutoConfigurationTests { ...@@ -111,8 +111,7 @@ public class ManagementSecurityAutoConfigurationTests {
this.context = new AnnotationConfigWebApplicationContext(); this.context = new AnnotationConfigWebApplicationContext();
this.context.setServletContext(new MockServletContext()); this.context.setServletContext(new MockServletContext());
this.context.register(EndpointAutoConfiguration.class, this.context.register(EndpointAutoConfiguration.class,
EndpointWebMvcAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, JacksonAutoConfiguration.class,
JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class, ManagementServerPropertiesAutoConfiguration.class,
SecurityAutoConfiguration.class, SecurityAutoConfiguration.class,
...@@ -122,7 +121,7 @@ public class ManagementSecurityAutoConfigurationTests { ...@@ -122,7 +121,7 @@ public class ManagementSecurityAutoConfigurationTests {
UserDetails user = getUser(); UserDetails user = getUser();
assertTrue(user.getAuthorities().containsAll( assertTrue(user.getAuthorities().containsAll(
AuthorityUtils AuthorityUtils
.commaSeparatedStringToAuthorityList("ROLE_USER,ROLE_ADMIN"))); .commaSeparatedStringToAuthorityList("ROLE_USER,ROLE_ADMIN")));
} }
private UserDetails getUser() { private UserDetails getUser() {
...@@ -157,8 +156,8 @@ public class ManagementSecurityAutoConfigurationTests { ...@@ -157,8 +156,8 @@ public class ManagementSecurityAutoConfigurationTests {
this.context = new AnnotationConfigWebApplicationContext(); this.context = new AnnotationConfigWebApplicationContext();
this.context.setServletContext(new MockServletContext()); this.context.setServletContext(new MockServletContext());
this.context.register(HttpMessageConvertersAutoConfiguration.class, this.context.register(HttpMessageConvertersAutoConfiguration.class,
JacksonAutoConfiguration.class, JacksonAutoConfiguration.class, EndpointAutoConfiguration.class,
EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class, ManagementServerPropertiesAutoConfiguration.class,
SecurityAutoConfiguration.class, SecurityAutoConfiguration.class,
ManagementSecurityAutoConfiguration.class, ManagementSecurityAutoConfiguration.class,
...@@ -205,15 +204,16 @@ public class ManagementSecurityAutoConfigurationTests { ...@@ -205,15 +204,16 @@ public class ManagementSecurityAutoConfigurationTests {
public void realmSameForManagement() throws Exception { public void realmSameForManagement() throws Exception {
this.context = new AnnotationConfigWebApplicationContext(); this.context = new AnnotationConfigWebApplicationContext();
this.context.setServletContext(new MockServletContext()); this.context.setServletContext(new MockServletContext());
this.context.register(AuthenticationConfig.class, this.context
SecurityAutoConfiguration.class, .register(AuthenticationConfig.class, SecurityAutoConfiguration.class,
ManagementSecurityAutoConfiguration.class, ManagementSecurityAutoConfiguration.class,
JacksonAutoConfiguration.class, JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, EndpointAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class,
WebMvcAutoConfiguration.class, ManagementServerPropertiesAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class); WebMvcAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh(); this.context.refresh();
Filter filter = this.context.getBean("springSecurityFilterChain", Filter.class); Filter filter = this.context.getBean("springSecurityFilterChain", Filter.class);
...@@ -222,8 +222,8 @@ public class ManagementSecurityAutoConfigurationTests { ...@@ -222,8 +222,8 @@ public class ManagementSecurityAutoConfigurationTests {
// no user (Main) // no user (Main)
mockMvc.perform(MockMvcRequestBuilders.get("/home")) mockMvc.perform(MockMvcRequestBuilders.get("/home"))
.andExpect(MockMvcResultMatchers.status().isUnauthorized()) .andExpect(MockMvcResultMatchers.status().isUnauthorized())
.andExpect(springAuthenticateRealmHeader()); .andExpect(springAuthenticateRealmHeader());
// invalid user (Main) // invalid user (Main)
mockMvc.perform( mockMvc.perform(
...@@ -233,8 +233,8 @@ public class ManagementSecurityAutoConfigurationTests { ...@@ -233,8 +233,8 @@ public class ManagementSecurityAutoConfigurationTests {
// no user (Management) // no user (Management)
mockMvc.perform(MockMvcRequestBuilders.get("/beans")) mockMvc.perform(MockMvcRequestBuilders.get("/beans"))
.andExpect(MockMvcResultMatchers.status().isUnauthorized()) .andExpect(MockMvcResultMatchers.status().isUnauthorized())
.andExpect(springAuthenticateRealmHeader()); .andExpect(springAuthenticateRealmHeader());
// invalid user (Management) // invalid user (Management)
mockMvc.perform( mockMvc.perform(
...@@ -254,7 +254,7 @@ public class ManagementSecurityAutoConfigurationTests { ...@@ -254,7 +254,7 @@ public class ManagementSecurityAutoConfigurationTests {
@Autowired @Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password") auth.inMemoryAuthentication().withUser("user").password("password")
.roles("USER"); .roles("USER");
} }
} }
......
/* /*
* Copyright 2015 the original author or authors. * Copyright 2012-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -36,20 +36,19 @@ import org.springframework.context.annotation.Import; ...@@ -36,20 +36,19 @@ import org.springframework.context.annotation.Import;
/** /**
* @author Dave Syer * @author Dave Syer
*
*/ */
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@Configuration @Configuration
@Import({ ServerPropertiesAutoConfiguration.class, @Import({ ServerPropertiesAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class, ManagementServerPropertiesAutoConfiguration.class,
EmbeddedServletContainerAutoConfiguration.class, EmbeddedServletContainerAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, JacksonAutoConfiguration.class, DispatcherServletAutoConfiguration.class, JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, WebMvcAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, WebMvcAutoConfiguration.class,
HypermediaAutoConfiguration.class, EndpointAutoConfiguration.class, HypermediaAutoConfiguration.class, EndpointAutoConfiguration.class,
EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class }) PropertyPlaceholderAutoConfiguration.class })
public @interface MinimalActuatorHypermediaApplication { public @interface MinimalActuatorHypermediaApplication {
} }
package org.springframework.boot.actuate.autoconfigure; /*
* Copyright 2012-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.
*/
import static org.junit.Assert.assertEquals; package org.springframework.boot.actuate.autoconfigure;
import static org.junit.Assert.assertTrue;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import java.util.Arrays; import java.util.Arrays;
...@@ -27,6 +39,10 @@ import org.springframework.test.context.web.WebAppConfiguration; ...@@ -27,6 +39,10 @@ import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class) @SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
@WebAppConfiguration @WebAppConfiguration
...@@ -57,8 +73,7 @@ public class ServerContextPathHypermediaIntegrationTests { ...@@ -57,8 +73,7 @@ public class ServerContextPathHypermediaIntegrationTests {
"http://localhost:" + this.port + "/spring/", HttpMethod.GET, "http://localhost:" + this.port + "/spring/", HttpMethod.GET,
new HttpEntity<Void>(null, headers), String.class); new HttpEntity<Void>(null, headers), String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode()); assertEquals(HttpStatus.OK, entity.getStatusCode());
assertTrue("Wrong body: " + entity.getBody(), assertTrue("Wrong body: " + entity.getBody(), entity.getBody().contains("<title"));
entity.getBody().contains("<title"));
} }
@MinimalActuatorHypermediaApplication @MinimalActuatorHypermediaApplication
...@@ -75,8 +90,9 @@ public class ServerContextPathHypermediaIntegrationTests { ...@@ -75,8 +90,9 @@ public class ServerContextPathHypermediaIntegrationTests {
public static void main(String[] args) { public static void main(String[] args) {
new SpringApplicationBuilder(SpringBootHypermediaApplication.class) new SpringApplicationBuilder(SpringBootHypermediaApplication.class)
.properties("server.contextPath=/spring").run(args); .properties("server.contextPath=/spring").run(args);
} }
} }
} }
package org.springframework.boot.actuate.autoconfigure; /*
* Copyright 2012-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.
*/
import static org.junit.Assert.assertEquals; package org.springframework.boot.actuate.autoconfigure;
import static org.junit.Assert.assertTrue;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import java.util.Arrays; import java.util.Arrays;
...@@ -27,6 +39,10 @@ import org.springframework.test.context.web.WebAppConfiguration; ...@@ -27,6 +39,10 @@ import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class) @SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
@WebAppConfiguration @WebAppConfiguration
...@@ -74,8 +90,9 @@ public class ServerPortHypermediaIntegrationTests { ...@@ -74,8 +90,9 @@ public class ServerPortHypermediaIntegrationTests {
public static void main(String[] args) { public static void main(String[] args) {
new SpringApplicationBuilder(SpringBootHypermediaApplication.class) new SpringApplicationBuilder(SpringBootHypermediaApplication.class)
.properties("management.port:9000").run(args); .properties("management.port:9000").run(args);
} }
} }
} }
package org.springframework.boot.actuate.autoconfigure; /*
* Copyright 2012-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.
*/
import static org.junit.Assert.assertEquals; package org.springframework.boot.actuate.autoconfigure;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
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.Before;
import org.junit.Test; import org.junit.Test;
...@@ -25,6 +35,12 @@ import org.springframework.test.web.servlet.MvcResult; ...@@ -25,6 +35,12 @@ import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
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.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class) @SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
@WebAppConfiguration @WebAppConfiguration
...@@ -47,8 +63,8 @@ public class VanillaHypermediaIntegrationTests { ...@@ -47,8 +63,8 @@ public class VanillaHypermediaIntegrationTests {
@Test @Test
public void links() throws Exception { public void links() throws Exception {
this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andExpect(jsonPath("$._links").exists()) .andExpect(status().isOk()).andExpect(jsonPath("$._links").exists())
.andExpect(header().doesNotExist("cache-control")); .andExpect(header().doesNotExist("cache-control"));
} }
@Test @Test
...@@ -61,17 +77,17 @@ public class VanillaHypermediaIntegrationTests { ...@@ -61,17 +77,17 @@ public class VanillaHypermediaIntegrationTests {
@Test @Test
public void trace() throws Exception { public void trace() throws Exception {
this.mockMvc this.mockMvc
.perform(get("/trace").accept(MediaType.APPLICATION_JSON)) .perform(get("/trace").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$._links.self.href").value("http://localhost/trace")) .andExpect(jsonPath("$._links.self.href").value("http://localhost/trace"))
.andExpect(jsonPath("$.content").isArray()); .andExpect(jsonPath("$.content").isArray());
} }
@Test @Test
public void envValue() throws Exception { public void envValue() throws Exception {
this.mockMvc.perform(get("/env/user.home").accept(MediaType.APPLICATION_JSON)) this.mockMvc.perform(get("/env/user.home").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$._links").doesNotExist()); .andExpect(jsonPath("$._links").doesNotExist());
} }
@Test @Test
...@@ -80,8 +96,8 @@ public class VanillaHypermediaIntegrationTests { ...@@ -80,8 +96,8 @@ public class VanillaHypermediaIntegrationTests {
String path = endpoint.getPath(); String path = endpoint.getPath();
path = path.startsWith("/") ? path.substring(1) : path; path = path.startsWith("/") ? path.substring(1) : path;
this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$._links.%s.href", path).exists()); .andExpect(jsonPath("$._links.%s.href", path).exists());
} }
} }
...@@ -95,11 +111,11 @@ public class VanillaHypermediaIntegrationTests { ...@@ -95,11 +111,11 @@ public class VanillaHypermediaIntegrationTests {
} }
path = path.length() > 0 ? path : "/"; path = path.length() > 0 ? path : "/";
this.mockMvc this.mockMvc
.perform(get(path).accept(MediaType.APPLICATION_JSON)) .perform(get(path).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect( .andExpect(
jsonPath("$._links.self.href").value( jsonPath("$._links.self.href").value(
"http://localhost" + endpoint.getPath())); "http://localhost" + endpoint.getPath()));
} }
} }
...@@ -110,6 +126,7 @@ public class VanillaHypermediaIntegrationTests { ...@@ -110,6 +126,7 @@ public class VanillaHypermediaIntegrationTests {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(SpringBootHypermediaApplication.class, args); SpringApplication.run(SpringBootHypermediaApplication.class, args);
} }
} }
} }
...@@ -16,12 +16,6 @@ ...@@ -16,12 +16,6 @@
package org.springframework.boot.actuate.endpoint.mvc; package org.springframework.boot.actuate.endpoint.mvc;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalToIgnoringCase;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
...@@ -45,6 +39,12 @@ import org.springframework.test.web.servlet.MockMvc; ...@@ -45,6 +39,12 @@ import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalToIgnoringCase;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/** /**
* Tests for {@link EnvironmentMvcEndpoint} * Tests for {@link EnvironmentMvcEndpoint}
* *
...@@ -72,13 +72,13 @@ public class EnvironmentMvcEndpointTests { ...@@ -72,13 +72,13 @@ public class EnvironmentMvcEndpointTests {
@Test @Test
public void home() throws Exception { public void home() throws Exception {
this.mvc.perform(get("/env")).andExpect(status().isOk()) this.mvc.perform(get("/env")).andExpect(status().isOk())
.andExpect(content().string(containsString("systemProperties"))); .andExpect(content().string(containsString("systemProperties")));
} }
@Test @Test
public void sub() throws Exception { public void sub() throws Exception {
this.mvc.perform(get("/env/foo")).andExpect(status().isOk()) this.mvc.perform(get("/env/foo")).andExpect(status().isOk())
.andExpect(content().string(equalToIgnoringCase("bar"))); .andExpect(content().string(equalToIgnoringCase("bar")));
} }
@Test @Test
...@@ -90,15 +90,14 @@ public class EnvironmentMvcEndpointTests { ...@@ -90,15 +90,14 @@ public class EnvironmentMvcEndpointTests {
@Test @Test
public void regex() throws Exception { public void regex() throws Exception {
this.mvc.perform(get("/env/foo.*")).andExpect(status().isOk()) this.mvc.perform(get("/env/foo.*")).andExpect(status().isOk())
.andExpect(content().string(containsString("\"foo\":\"bar\""))) .andExpect(content().string(containsString("\"foo\":\"bar\"")))
.andExpect(content().string(containsString("\"fool\":\"baz\""))); .andExpect(content().string(containsString("\"fool\":\"baz\"")));
} }
@Import({ JacksonAutoConfiguration.class, @Import({ JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, WebMvcAutoConfiguration.class,
WebMvcAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class,
EndpointWebMvcAutoConfiguration.class, ManagementServerPropertiesAutoConfiguration.class })
ManagementServerPropertiesAutoConfiguration.class })
@Configuration @Configuration
public static class TestConfiguration { public static class TestConfiguration {
......
...@@ -16,11 +16,6 @@ ...@@ -16,11 +16,6 @@
package org.springframework.boot.actuate.endpoint.mvc; package org.springframework.boot.actuate.endpoint.mvc;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
...@@ -46,6 +41,11 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; ...@@ -46,6 +41,11 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/** /**
* @author Christian Dupuis * @author Christian Dupuis
* @author Dave Syer * @author Dave Syer
...@@ -73,22 +73,22 @@ public class JolokiaMvcEndpointContextPathTests { ...@@ -73,22 +73,22 @@ public class JolokiaMvcEndpointContextPathTests {
@Test @Test
public void read() throws Exception { public void read() throws Exception {
this.mvc.perform(get("/admin/jolokia/read/java.lang:type=Memory")) this.mvc.perform(get("/admin/jolokia/read/java.lang:type=Memory"))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().string(containsString("NonHeapMemoryUsage"))); .andExpect(content().string(containsString("NonHeapMemoryUsage")));
} }
@Configuration @Configuration
@EnableConfigurationProperties @EnableConfigurationProperties
@EnableWebMvc @EnableWebMvc
@Import({ JacksonAutoConfiguration.class, @Import({ JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
EndpointWebMvcAutoConfiguration.class, JolokiaAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, JolokiaAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class }) ManagementServerPropertiesAutoConfiguration.class })
public static class Config { public static class Config {
} }
public static class ContextPathListener implements public static class ContextPathListener implements
ApplicationContextInitializer<ConfigurableApplicationContext> { ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override @Override
public void initialize(ConfigurableApplicationContext context) { public void initialize(ConfigurableApplicationContext context) {
EnvironmentTestUtils.addEnvironment(context, "management.contextPath:/admin"); EnvironmentTestUtils.addEnvironment(context, "management.contextPath:/admin");
......
...@@ -16,14 +16,6 @@ ...@@ -16,14 +16,6 @@
package org.springframework.boot.actuate.endpoint.mvc; package org.springframework.boot.actuate.endpoint.mvc;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.Set; import java.util.Set;
import org.hamcrest.Matcher; import org.hamcrest.Matcher;
...@@ -50,6 +42,14 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; ...@@ -50,6 +42,14 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/** /**
* Tests for {@link JolokiaMvcEndpoint} * Tests for {@link JolokiaMvcEndpoint}
* *
...@@ -86,30 +86,30 @@ public class JolokiaMvcEndpointTests { ...@@ -86,30 +86,30 @@ public class JolokiaMvcEndpointTests {
@Test @Test
public void search() throws Exception { public void search() throws Exception {
this.mvc.perform(get("/jolokia/search/java.lang:*")).andExpect(status().isOk()) this.mvc.perform(get("/jolokia/search/java.lang:*")).andExpect(status().isOk())
.andExpect(content().string(containsString("GarbageCollector"))); .andExpect(content().string(containsString("GarbageCollector")));
} }
@Test @Test
public void read() throws Exception { public void read() throws Exception {
this.mvc.perform(get("/jolokia/read/java.lang:type=Memory")) this.mvc.perform(get("/jolokia/read/java.lang:type=Memory"))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().string(containsString("NonHeapMemoryUsage"))); .andExpect(content().string(containsString("NonHeapMemoryUsage")));
} }
@Test @Test
public void list() throws Exception { public void list() throws Exception {
this.mvc.perform(get("/jolokia/list/java.lang/type=Memory/attr")) this.mvc.perform(get("/jolokia/list/java.lang/type=Memory/attr"))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().string(containsString("NonHeapMemoryUsage"))); .andExpect(content().string(containsString("NonHeapMemoryUsage")));
} }
@Configuration @Configuration
@EnableConfigurationProperties @EnableConfigurationProperties
@EnableWebMvc @EnableWebMvc
@Import({ JacksonAutoConfiguration.class, @Import({ JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
EndpointWebMvcAutoConfiguration.class, JolokiaAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, JolokiaAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class }) ManagementServerPropertiesAutoConfiguration.class })
public static class Config { public static class Config {
} }
......
...@@ -16,12 +16,6 @@ ...@@ -16,12 +16,6 @@
package org.springframework.boot.actuate.endpoint.mvc; package org.springframework.boot.actuate.endpoint.mvc;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
...@@ -49,6 +43,12 @@ import org.springframework.test.web.servlet.MockMvc; ...@@ -49,6 +43,12 @@ import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/** /**
* Tests for {@link MetricsMvcEndpoint} * Tests for {@link MetricsMvcEndpoint}
* *
...@@ -74,7 +74,7 @@ public class MetricsMvcEndpointTests { ...@@ -74,7 +74,7 @@ public class MetricsMvcEndpointTests {
@Test @Test
public void home() throws Exception { public void home() throws Exception {
this.mvc.perform(get("/metrics")).andExpect(status().isOk()) this.mvc.perform(get("/metrics")).andExpect(status().isOk())
.andExpect(content().string(containsString("\"foo\":1"))); .andExpect(content().string(containsString("\"foo\":1")));
} }
@Test @Test
...@@ -86,7 +86,7 @@ public class MetricsMvcEndpointTests { ...@@ -86,7 +86,7 @@ public class MetricsMvcEndpointTests {
@Test @Test
public void specificMetric() throws Exception { public void specificMetric() throws Exception {
this.mvc.perform(get("/metrics/foo")).andExpect(status().isOk()) this.mvc.perform(get("/metrics/foo")).andExpect(status().isOk())
.andExpect(content().string(equalTo("1"))); .andExpect(content().string(equalTo("1")));
} }
@Test @Test
...@@ -104,35 +104,34 @@ public class MetricsMvcEndpointTests { ...@@ -104,35 +104,34 @@ public class MetricsMvcEndpointTests {
public void regexAll() throws Exception { public void regexAll() throws Exception {
String expected = "\"foo\":1,\"group1.a\":1,\"group1.b\":1,\"group2.a\":1,\"group2_a\":1"; String expected = "\"foo\":1,\"group1.a\":1,\"group1.b\":1,\"group2.a\":1,\"group2_a\":1";
this.mvc.perform(get("/metrics/.*")).andExpect(status().isOk()) this.mvc.perform(get("/metrics/.*")).andExpect(status().isOk())
.andExpect(content().string(containsString(expected))); .andExpect(content().string(containsString(expected)));
} }
@Test @Test
public void regexGroupDot() throws Exception { public void regexGroupDot() throws Exception {
String expected = "\"group1.a\":1,\"group1.b\":1,\"group2.a\":1"; String expected = "\"group1.a\":1,\"group1.b\":1,\"group2.a\":1";
this.mvc.perform(get("/metrics/group[0-9]+\\..*")).andExpect(status().isOk()) this.mvc.perform(get("/metrics/group[0-9]+\\..*")).andExpect(status().isOk())
.andExpect(content().string(containsString(expected))); .andExpect(content().string(containsString(expected)));
} }
@Test @Test
public void regexGroup1() throws Exception { public void regexGroup1() throws Exception {
String expected = "\"group1.a\":1,\"group1.b\":1"; String expected = "\"group1.a\":1,\"group1.b\":1";
this.mvc.perform(get("/metrics/group1\\..*")).andExpect(status().isOk()) this.mvc.perform(get("/metrics/group1\\..*")).andExpect(status().isOk())
.andExpect(content().string(containsString(expected))); .andExpect(content().string(containsString(expected)));
} }
@Test @Test
public void specificMetricWithDot() throws Exception { public void specificMetricWithDot() throws Exception {
this.mvc.perform(get("/metrics/group2.a")).andExpect(status().isOk()) this.mvc.perform(get("/metrics/group2.a")).andExpect(status().isOk())
.andExpect(content().string(containsString("1"))); .andExpect(content().string(containsString("1")));
} }
@Import({ JacksonAutoConfiguration.class, @Import({ JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
EndpointWebMvcAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class,
WebMvcAutoConfiguration.class, ManagementServerPropertiesAutoConfiguration.class })
ManagementServerPropertiesAutoConfiguration.class })
@Configuration @Configuration
public static class TestConfiguration { public static class TestConfiguration {
......
...@@ -16,10 +16,6 @@ ...@@ -16,10 +16,6 @@
package org.springframework.boot.actuate.endpoint.mvc; package org.springframework.boot.actuate.endpoint.mvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration;
...@@ -38,6 +34,10 @@ import org.springframework.test.web.servlet.ResultActions; ...@@ -38,6 +34,10 @@ import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/** /**
* Integration tests for the actuator endpoints' CORS support * Integration tests for the actuator endpoints' CORS support
* *
...@@ -51,7 +51,8 @@ public class MvcEndpointCorsIntegrationTests { ...@@ -51,7 +51,8 @@ public class MvcEndpointCorsIntegrationTests {
public void createContext() { public void createContext() {
this.context = new AnnotationConfigWebApplicationContext(); this.context = new AnnotationConfigWebApplicationContext();
this.context.setServletContext(new MockServletContext()); this.context.setServletContext(new MockServletContext());
this.context.register(JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, this.context.register(JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class, ManagementServerPropertiesAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class,
...@@ -63,7 +64,7 @@ public class MvcEndpointCorsIntegrationTests { ...@@ -63,7 +64,7 @@ public class MvcEndpointCorsIntegrationTests {
createMockMvc().perform( createMockMvc().perform(
options("/beans").header("Origin", "foo.example.com").header( options("/beans").header("Origin", "foo.example.com").header(
HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")).andExpect( HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")).andExpect(
header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
} }
@Test @Test
...@@ -73,7 +74,7 @@ public class MvcEndpointCorsIntegrationTests { ...@@ -73,7 +74,7 @@ public class MvcEndpointCorsIntegrationTests {
createMockMvc().perform( createMockMvc().perform(
options("/beans").header("Origin", "bar.example.com").header( options("/beans").header("Origin", "bar.example.com").header(
HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")).andExpect( HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")).andExpect(
status().isForbidden()); status().isForbidden());
performAcceptedCorsRequest(); performAcceptedCorsRequest();
} }
...@@ -100,8 +101,8 @@ public class MvcEndpointCorsIntegrationTests { ...@@ -100,8 +101,8 @@ public class MvcEndpointCorsIntegrationTests {
"endpoints.cors.allowed-origins:foo.example.com"); "endpoints.cors.allowed-origins:foo.example.com");
createMockMvc().perform( createMockMvc().perform(
options("/beans").header("Origin", "foo.example.com") options("/beans").header("Origin", "foo.example.com")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET") .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Alpha")) .header(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Alpha"))
.andExpect(status().isForbidden()); .andExpect(status().isForbidden());
} }
...@@ -111,15 +112,15 @@ public class MvcEndpointCorsIntegrationTests { ...@@ -111,15 +112,15 @@ public class MvcEndpointCorsIntegrationTests {
"endpoints.cors.allowed-origins:foo.example.com", "endpoints.cors.allowed-origins:foo.example.com",
"endpoints.cors.allowed-headers:Alpha,Bravo"); "endpoints.cors.allowed-headers:Alpha,Bravo");
createMockMvc() createMockMvc()
.perform( .perform(
options("/beans") options("/beans")
.header("Origin", "foo.example.com") .header("Origin", "foo.example.com")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET") .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, .header(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS,
"Alpha")) "Alpha"))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect( .andExpect(
header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Alpha")); header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Alpha"));
} }
@Test @Test
...@@ -129,7 +130,7 @@ public class MvcEndpointCorsIntegrationTests { ...@@ -129,7 +130,7 @@ public class MvcEndpointCorsIntegrationTests {
createMockMvc().perform( createMockMvc().perform(
options("/health").header(HttpHeaders.ORIGIN, "foo.example.com").header( options("/health").header(HttpHeaders.ORIGIN, "foo.example.com").header(
HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "HEAD")).andExpect( HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "HEAD")).andExpect(
status().isForbidden()); status().isForbidden());
} }
@Test @Test
...@@ -138,10 +139,10 @@ public class MvcEndpointCorsIntegrationTests { ...@@ -138,10 +139,10 @@ public class MvcEndpointCorsIntegrationTests {
"endpoints.cors.allowed-origins:foo.example.com", "endpoints.cors.allowed-origins:foo.example.com",
"endpoints.cors.allowed-methods:GET,HEAD"); "endpoints.cors.allowed-methods:GET,HEAD");
createMockMvc() createMockMvc()
.perform( .perform(
options("/health") options("/health")
.header(HttpHeaders.ORIGIN, "foo.example.com") .header(HttpHeaders.ORIGIN, "foo.example.com")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "HEAD")) .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "HEAD"))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect( .andExpect(
header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS,
...@@ -173,7 +174,7 @@ public class MvcEndpointCorsIntegrationTests { ...@@ -173,7 +174,7 @@ public class MvcEndpointCorsIntegrationTests {
createMockMvc().perform( createMockMvc().perform(
options("/jolokia").header("Origin", "bar.example.com").header( options("/jolokia").header("Origin", "bar.example.com").header(
HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")).andExpect( HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")).andExpect(
status().isForbidden()); status().isForbidden());
performAcceptedCorsRequest("/jolokia"); performAcceptedCorsRequest("/jolokia");
} }
...@@ -190,10 +191,10 @@ public class MvcEndpointCorsIntegrationTests { ...@@ -190,10 +191,10 @@ public class MvcEndpointCorsIntegrationTests {
return createMockMvc() return createMockMvc()
.perform( .perform(
options(url).header(HttpHeaders.ORIGIN, "foo.example.com") options(url).header(HttpHeaders.ORIGIN, "foo.example.com")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")) .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"))
.andExpect( .andExpect(
header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN,
"foo.example.com")).andExpect(status().isOk()); "foo.example.com")).andExpect(status().isOk());
} }
} }
...@@ -150,30 +150,33 @@ For example, the following will disable _all_ endpoints except for `info`: ...@@ -150,30 +150,33 @@ For example, the following will disable _all_ endpoints except for `info`:
endpoints.info.enabled=true endpoints.info.enabled=true
---- ----
[[production-ready-endpoint-hypermedia]] [[production-ready-endpoint-hypermedia]]
=== Hypermedia for MVC Endpoints === Hypermedia for MVC Endpoints
If http://projects.spring.io/spring-hateoas[Spring HATEOAS] is on the classpath (e.g. 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 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 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 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, 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 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 `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 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). 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 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 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 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`). links will show only if the client accepts `application/json`).
If the https://github.com/mikekelly/hal-browser[HAL Browser] is on the classpath 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 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 the default home page for HTML clients will be the HAL Browser. This is also exposed via
endpoint ("hal") so it can be disabled and have its path explicitly configured like an endpoint ("`hal`") so it can be disabled and have its path explicitly configured like
the other endpoints. the other endpoints.
[[production-ready-customizing-endpoints-programmatically]] [[production-ready-customizing-endpoints-programmatically]]
=== Adding Custom Endpoints === Adding Custom Endpoints
If you add a `@Bean` of type `Endpoint` then it will automatically be exposed over JMX and If you add a `@Bean` of type `Endpoint` then it will automatically be exposed over JMX and
......
...@@ -2,24 +2,19 @@ ...@@ -2,24 +2,19 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>spring-boot-sample-hypermedia-gson</artifactId>
<packaging>jar</packaging>
<name>spring-boot-sample-hypermedia-gson</name>
<description>Demo project for Spring Boot</description>
<parent> <parent>
<!-- Your own application should inherit from spring-boot-starter-parent -->
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-samples</artifactId> <artifactId>spring-boot-samples</artifactId>
<version>1.3.0.BUILD-SNAPSHOT</version> <version>1.3.0.BUILD-SNAPSHOT</version>
</parent> </parent>
<artifactId>spring-boot-sample-hypermedia-gson</artifactId>
<name>Spring Boot Hypermedia GSON Sample</name>
<description>Spring Boot Hypermedia GSON Sample</description>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <main.basedir>${basedir}/../..</main.basedir>
<java.version>1.7</java.version> <java.version>1.7</java.version>
</properties> </properties>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
...@@ -37,11 +32,6 @@ ...@@ -37,11 +32,6 @@
<groupId>com.google.code.gson</groupId> <groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId> <artifactId>gson</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
...@@ -53,7 +43,6 @@ ...@@ -53,7 +43,6 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
...@@ -62,5 +51,4 @@ ...@@ -62,5 +51,4 @@
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
</project> </project>
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);
}
}
/*
* Copyright 2012-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 sample.hypermedia.gson;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SampleHypermediaGsonApplication {
public static void main(String[] args) {
SpringApplication.run(SampleHypermediaGsonApplication.class, args);
}
}
package demo; /*
* Copyright 2012-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.
*/
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; package sample.hypermedia.gson;
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.Before;
import org.junit.Test; import org.junit.Test;
...@@ -17,11 +29,16 @@ import org.springframework.test.web.servlet.MockMvc; ...@@ -17,11 +29,16 @@ import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import sample.hypermedia.gson.SampleHypermediaGsonApplication;
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;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class) @SpringApplicationConfiguration(classes = SampleHypermediaGsonApplication.class)
@WebAppConfiguration @WebAppConfiguration
@TestPropertySource(properties="endpoints.health.sensitive: false") @TestPropertySource(properties = "endpoints.health.sensitive: false")
public class SpringBootHypermediaApplicationTests { public class SampleHypermediaGsonApplicationTests {
@Autowired @Autowired
private WebApplicationContext context; private WebApplicationContext context;
...@@ -35,27 +52,25 @@ public class SpringBootHypermediaApplicationTests { ...@@ -35,27 +52,25 @@ public class SpringBootHypermediaApplicationTests {
@Test @Test
public void health() throws Exception { public void health() throws Exception {
this.mockMvc this.mockMvc.perform(get("/health").accept(MediaType.APPLICATION_JSON))
.perform(get("/health").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk())
.andExpect(status().isOk()) .andExpect(jsonPath("$.links[0].href").value("http://localhost/health"))
.andExpect(jsonPath("$.links[0].href").value("http://localhost/health")) .andExpect(jsonPath("$.content.status").exists());
.andExpect(jsonPath("$.content.status").exists());
} }
@Test @Test
public void trace() throws Exception { public void trace() throws Exception {
this.mockMvc this.mockMvc.perform(get("/trace").accept(MediaType.APPLICATION_JSON))
.perform(get("/trace").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk())
.andExpect(status().isOk()) .andExpect(jsonPath("$.links[0].href").value("http://localhost/trace"))
.andExpect(jsonPath("$.links[0].href").value("http://localhost/trace")) .andExpect(jsonPath("$.content").isArray());
.andExpect(jsonPath("$.content").isArray());
} }
@Test @Test
public void envValue() throws Exception { public void envValue() throws Exception {
this.mockMvc.perform(get("/env/user.home").accept(MediaType.APPLICATION_JSON)) this.mockMvc.perform(get("/env/user.home").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$._links").doesNotExist()); .andExpect(jsonPath("$._links").doesNotExist());
} }
} }
...@@ -2,24 +2,19 @@ ...@@ -2,24 +2,19 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>spring-boot-sample-hypermedia-jpa</artifactId>
<packaging>jar</packaging>
<name>spring-boot-sample-hypermedia-jpa</name>
<description>Demo project for Spring Boot</description>
<parent> <parent>
<!-- Your own application should inherit from spring-boot-starter-parent -->
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-samples</artifactId> <artifactId>spring-boot-samples</artifactId>
<version>1.3.0.BUILD-SNAPSHOT</version> <version>1.3.0.BUILD-SNAPSHOT</version>
</parent> </parent>
<artifactId>spring-boot-sample-hypermedia-jpa</artifactId>
<name>Spring Boot Hypermedia JPA Sample</name>
<description>Spring Boot Hypermedia JPA Sample</description>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <main.basedir>${basedir}/../..</main.basedir>
<java.version>1.7</java.version> <java.version>1.7</java.version>
</properties> </properties>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
...@@ -53,11 +48,6 @@ ...@@ -53,11 +48,6 @@
<groupId>com.h2database</groupId> <groupId>com.h2database</groupId>
<artifactId>h2</artifactId> <artifactId>h2</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
...@@ -69,7 +59,6 @@ ...@@ -69,7 +59,6 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
...@@ -78,5 +67,4 @@ ...@@ -78,5 +67,4 @@
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
</project> </project>
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);
}
}
/* /*
* Copyright 2015 the original author or authors. * Copyright 2012-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package demo; package sample.hypermedia.jpa;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.GeneratedValue; import javax.persistence.GeneratedValue;
...@@ -22,14 +22,19 @@ import javax.persistence.Id; ...@@ -22,14 +22,19 @@ import javax.persistence.Id;
@Entity @Entity
public class Book { public class Book {
@Id @Id
@GeneratedValue @GeneratedValue
private Long id; private Long id;
private String title; private String title;
public String getTitle() { public String getTitle() {
return this.title; return this.title;
} }
public void setTitle(String title) { public void setTitle(String title) {
this.title = title; this.title = title;
} }
}
\ No newline at end of file }
/* /*
* Copyright 2015 the original author or authors. * Copyright 2012-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -14,9 +14,10 @@ ...@@ -14,9 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
package demo; package sample.hypermedia.jpa;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
public interface BookRepository extends JpaRepository<Book, Long> { public interface BookRepository extends JpaRepository<Book, Long> {
}
\ No newline at end of file }
/*
* Copyright 2012-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 sample.hypermedia.jpa;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SampleHypermediaJpaApplication {
public static void main(String[] args) {
SpringApplication.run(SampleHypermediaJpaApplication.class, args);
}
}
package demo; /*
* Copyright 2012-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 sample.hypermedia.jpa;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import sample.hypermedia.jpa.SampleHypermediaJpaApplication;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = JpaHypermediaApplication.class) @SpringApplicationConfiguration(classes = SampleHypermediaJpaApplication.class)
@WebAppConfiguration @WebAppConfiguration
public class JpaHypermediaApplicationTests { public class SampleHypermediaJpaApplicationTests {
@Test @Test
public void contextLoads() { public void contextLoads() {
......
package demo; /*
* Copyright 2012-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.
*/
import static org.hamcrest.Matchers.containsString; package sample.hypermedia.jpa;
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.Before;
import org.junit.Test; import org.junit.Test;
...@@ -20,11 +31,17 @@ import org.springframework.test.web.servlet.MvcResult; ...@@ -20,11 +31,17 @@ import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import sample.hypermedia.jpa.SampleHypermediaJpaApplication;
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.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = JpaHypermediaApplication.class) @SpringApplicationConfiguration(classes = SampleHypermediaJpaApplication.class)
@WebAppConfiguration @WebAppConfiguration
@DirtiesContext @DirtiesContext
public class VanillaHypermediaIntegrationTests { public class SampleHypermediaJpaApplicationVanillaIntegrationTests {
@Autowired @Autowired
private WebApplicationContext context; private WebApplicationContext context;
...@@ -59,7 +76,8 @@ public class VanillaHypermediaIntegrationTests { ...@@ -59,7 +76,8 @@ public class VanillaHypermediaIntegrationTests {
@Test @Test
public void docs() throws Exception { public void docs() throws Exception {
MvcResult response = this.mockMvc.perform(get("/admin/docs/").accept(MediaType.TEXT_HTML)) MvcResult response = this.mockMvc
.perform(get("/admin/docs/").accept(MediaType.TEXT_HTML))
.andExpect(status().isOk()).andReturn(); .andExpect(status().isOk()).andReturn();
System.err.println(response.getResponse().getContentAsString()); System.err.println(response.getResponse().getContentAsString());
} }
...@@ -68,7 +86,8 @@ public class VanillaHypermediaIntegrationTests { ...@@ -68,7 +86,8 @@ public class VanillaHypermediaIntegrationTests {
public void browser() throws Exception { public void browser() throws Exception {
MvcResult response = this.mockMvc.perform(get("/").accept(MediaType.TEXT_HTML)) MvcResult response = this.mockMvc.perform(get("/").accept(MediaType.TEXT_HTML))
.andExpect(status().isFound()).andReturn(); .andExpect(status().isFound()).andReturn();
assertEquals("/browser/index.html#", response.getResponse().getHeaders("location").get(0)); assertEquals("/browser/index.html#", response.getResponse()
.getHeaders("location").get(0));
} }
} }
...@@ -2,24 +2,19 @@ ...@@ -2,24 +2,19 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>spring-boot-sample-hypermedia-ui</artifactId>
<packaging>jar</packaging>
<name>spring-boot-sample-hypermedia-ui</name>
<description>Demo project for Spring Boot</description>
<parent> <parent>
<!-- Your own application should inherit from spring-boot-starter-parent -->
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-samples</artifactId> <artifactId>spring-boot-samples</artifactId>
<version>1.3.0.BUILD-SNAPSHOT</version> <version>1.3.0.BUILD-SNAPSHOT</version>
</parent> </parent>
<artifactId>spring-boot-sample-hypermedia-ui</artifactId>
<name>Spring Boot Hypermedia UI Sample</name>
<description>Spring Boot Hypermedia UI Sample</description>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <main.basedir>${basedir}/../..</main.basedir>
<java.version>1.7</java.version> <java.version>1.7</java.version>
</properties> </properties>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
...@@ -37,18 +32,12 @@ ...@@ -37,18 +32,12 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator-docs</artifactId> <artifactId>spring-boot-actuator-docs</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
...@@ -57,5 +46,4 @@ ...@@ -57,5 +46,4 @@
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
</project> </project>
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);
}
}
/*
* Copyright 2012-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 sample.hypermedia.ui;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SampleHypermediaUiApplication {
public static void main(String[] args) {
SpringApplication.run(SampleHypermediaUiApplication.class, args);
}
}
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 = SpringBootHypermediaApplication.class)
@WebAppConfiguration
public class SpringBootHypermediaApplicationTests {
@Test
public void contextLoads() {
}
}
package demo; /*
* Copyright 2012-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.
*/
import static org.junit.Assert.assertTrue; package sample.hypermedia.ui;
import java.net.URI; import java.net.URI;
import java.util.Arrays; import java.util.Arrays;
...@@ -19,26 +33,29 @@ import org.springframework.http.ResponseEntity; ...@@ -19,26 +33,29 @@ import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.context.web.WebAppConfiguration;
import sample.hypermedia.ui.SampleHypermediaUiApplication;
import static org.junit.Assert.assertTrue;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class) @SpringApplicationConfiguration(classes = SampleHypermediaUiApplication.class)
@WebAppConfiguration @WebAppConfiguration
@IntegrationTest({ "management.contextPath=", "server.port=0" }) @IntegrationTest({ "management.contextPath=", "server.port=0" })
public class HomePageHypermediaApplicationTests { public class SampleHypermediaUiApplicationTests {
@Value("${local.server.port}") @Value("${local.server.port}")
private int port; private int port;
@Test @Test
public void home() { public void home() {
String response = new TestRestTemplate().getForObject("http://localhost:" + port, String response = new TestRestTemplate().getForObject("http://localhost:"
String.class); + this.port, String.class);
assertTrue("Wrong body: " + response, response.contains("Hello World")); assertTrue("Wrong body: " + response, response.contains("Hello World"));
} }
@Test @Test
public void links() { public void links() {
String response = new TestRestTemplate().getForObject("http://localhost:" + port + "/links", String response = new TestRestTemplate().getForObject("http://localhost:"
String.class); + this.port + "/links", String.class);
assertTrue("Wrong body: " + response, response.contains("\"_links\":")); assertTrue("Wrong body: " + response, response.contains("\"_links\":"));
} }
...@@ -47,8 +64,8 @@ public class HomePageHypermediaApplicationTests { ...@@ -47,8 +64,8 @@ public class HomePageHypermediaApplicationTests {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
ResponseEntity<String> response = new TestRestTemplate().exchange( ResponseEntity<String> response = new TestRestTemplate().exchange(
new RequestEntity<Void>(headers , HttpMethod.GET, new URI("http://localhost:" new RequestEntity<Void>(headers, HttpMethod.GET, new URI(
+ port + "/links")), String.class); "http://localhost:" + this.port + "/links")), String.class);
assertTrue("Wrong body: " + response, response.getBody().contains("\"_links\":")); assertTrue("Wrong body: " + response, response.getBody().contains("\"_links\":"));
} }
...@@ -57,8 +74,8 @@ public class HomePageHypermediaApplicationTests { ...@@ -57,8 +74,8 @@ public class HomePageHypermediaApplicationTests {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML)); headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
ResponseEntity<String> response = new TestRestTemplate().exchange( ResponseEntity<String> response = new TestRestTemplate().exchange(
new RequestEntity<Void>(headers , HttpMethod.GET, new URI("http://localhost:" new RequestEntity<Void>(headers, HttpMethod.GET, new URI(
+ port)), String.class); "http://localhost:" + this.port)), String.class);
assertTrue("Wrong body: " + response, response.getBody().contains("Hello World")); assertTrue("Wrong body: " + response, response.getBody().contains("Hello World"));
} }
......
...@@ -2,24 +2,20 @@ ...@@ -2,24 +2,20 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>spring-boot-sample-hypermedia</artifactId>
<packaging>jar</packaging>
<name>spring-boot-sample-hypermedia</name>
<description>Demo project for Spring Boot</description>
<parent> <parent>
<!-- Your own application should inherit from spring-boot-starter-parent -->
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-samples</artifactId> <artifactId>spring-boot-samples</artifactId>
<version>1.3.0.BUILD-SNAPSHOT</version> <version>1.3.0.BUILD-SNAPSHOT</version>
</parent> </parent>
<artifactId>spring-boot-sample-hypermedia</artifactId>
<name>Spring Boot Hypermedia Sample</name>
<description>Spring Boot Hypermedia Sample</description>
<properties> <properties>
<main.basedir>${basedir}/../..</main.basedir>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version> <java.version>1.7</java.version>
</properties> </properties>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
...@@ -41,18 +37,12 @@ ...@@ -41,18 +37,12 @@
<groupId>org.webjars</groupId> <groupId>org.webjars</groupId>
<artifactId>hal-browser</artifactId> <artifactId>hal-browser</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
...@@ -61,5 +51,4 @@ ...@@ -61,5 +51,4 @@
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
</project> </project>
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);
}
}
/*
* Copyright 2012-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 sample.hypermedia;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SampleHypermediaApplication {
public static void main(String[] args) {
SpringApplication.run(SampleHypermediaApplication.class, args);
}
}
package demo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
@WebAppConfiguration
public class SpringBootHypermediaApplicationTests {
@Test
public void contextLoads() {
}
}
package demo; /*
* Copyright 2012-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.
*/
import static org.junit.Assert.assertTrue; package sample.hypermedia;
import java.net.URI; import java.net.URI;
import java.util.Arrays; import java.util.Arrays;
...@@ -19,19 +33,21 @@ import org.springframework.http.ResponseEntity; ...@@ -19,19 +33,21 @@ import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.context.web.WebAppConfiguration;
import static org.junit.Assert.assertTrue;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class) @SpringApplicationConfiguration(classes = SampleHypermediaApplication.class)
@WebAppConfiguration @WebAppConfiguration
@IntegrationTest("server.port=0") @IntegrationTest("server.port=0")
public class HomePageHypermediaApplicationTests { public class SampleHypermediaApplicationHomePageTests {
@Value("${local.server.port}") @Value("${local.server.port}")
private int port; private int port;
@Test @Test
public void home() { public void home() {
String response = new TestRestTemplate().getForObject("http://localhost:" + port, String response = new TestRestTemplate().getForObject("http://localhost:"
String.class); + this.port, String.class);
assertTrue("Wrong body: " + response, response.contains("\"_links\":")); assertTrue("Wrong body: " + response, response.contains("\"_links\":"));
assertTrue("Wrong body: " + response, response.contains("\"curies\":")); assertTrue("Wrong body: " + response, response.contains("\"curies\":"));
} }
...@@ -41,8 +57,8 @@ public class HomePageHypermediaApplicationTests { ...@@ -41,8 +57,8 @@ public class HomePageHypermediaApplicationTests {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
ResponseEntity<String> response = new TestRestTemplate().exchange( ResponseEntity<String> response = new TestRestTemplate().exchange(
new RequestEntity<Void>(headers , HttpMethod.GET, new URI("http://localhost:" new RequestEntity<Void>(headers, HttpMethod.GET, new URI(
+ port + "/")), String.class); "http://localhost:" + this.port + "/")), String.class);
assertTrue("Wrong body: " + response, response.getBody().contains("\"_links\":")); assertTrue("Wrong body: " + response, response.getBody().contains("\"_links\":"));
} }
...@@ -51,8 +67,8 @@ public class HomePageHypermediaApplicationTests { ...@@ -51,8 +67,8 @@ public class HomePageHypermediaApplicationTests {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML)); headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
ResponseEntity<String> response = new TestRestTemplate().exchange( ResponseEntity<String> response = new TestRestTemplate().exchange(
new RequestEntity<Void>(headers , HttpMethod.GET, new URI("http://localhost:" new RequestEntity<Void>(headers, HttpMethod.GET, new URI(
+ port)), String.class); "http://localhost:" + this.port)), String.class);
assertTrue("Wrong body: " + response, response.getBody().contains("HAL Browser")); assertTrue("Wrong body: " + response, response.getBody().contains("HAL Browser"));
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment