From 74e9e0749bcd54053a6a4afc534209060a3ae294 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 1 Jul 2015 18:36:21 +0100 Subject: [PATCH] Add support for Spring HATEOAS hypermedia in Actuator endpoints If spring-hateoas is on the classpath and an MvcEndpoint returns a @ResponseBody it will be extended and wrapped into a Resource with links. All the existing endpoints that return sensible JSON data can be extended this way (i.e. not /logfile). The HAL browser will also be added as an endpoint if available on the classpath. Finally, asciidocs for the Actuator endpoints are available as a separate jar file, which if included in an app will also generate a new (HTTP) endpoint. Fixes gh-1390 --- pom.xml | 1 + spring-boot-actuator-docs/pom.xml | 117 ++++++ .../src/main/asciidoc/autoconfig.adoc | 26 ++ .../src/main/asciidoc/beans.adoc | 17 + .../src/main/asciidoc/configprops.adoc | 19 + .../src/main/asciidoc/dump.adoc | 19 + .../src/main/asciidoc/env.adoc | 17 + .../src/main/asciidoc/health.adoc | 22 + .../src/main/asciidoc/images/hal-browser.png | Bin 0 -> 208251 bytes .../src/main/asciidoc/index.adoc | 148 +++++++ .../src/main/asciidoc/info.adoc | 16 + .../src/main/asciidoc/mappings.adoc | 14 + .../src/main/asciidoc/metrics.adoc | 17 + .../src/main/asciidoc/trace.adoc | 16 + .../test/EndpointDocumentation.java | 170 ++++++++ .../test/HealthEndpointDocumentation.java | 63 +++ .../test/HypermediaEndpointDocumentation.java | 76 ++++ .../test/SpringBootHypermediaApplication.java | 21 + .../src/test/resources/application.properties | 2 + .../resources/templates/endpoints.adoc.tpl | 16 + spring-boot-actuator/pom.xml | 25 ++ .../EndpointWebMvcAutoConfiguration.java | 7 +- ...EndpointWebMvcHypermediaConfiguration.java | 380 ++++++++++++++++++ .../ManagementSecurityAutoConfiguration.java | 35 +- .../endpoint/mvc/ActuatorDocsEndpoint.java | 112 ++++++ .../endpoint/mvc/EnvironmentMvcEndpoint.java | 3 +- .../endpoint/mvc/HalBrowserEndpoint.java | 96 +++++ .../endpoint/mvc/HypermediaDisabled.java | 34 ++ .../endpoint/mvc/JolokiaMvcEndpoint.java | 3 +- .../actuate/endpoint/mvc/LinksEnhancer.java | 61 +++ .../endpoint/mvc/LinksMvcEndpoint.java | 70 ++++ .../endpoint/mvc/MetricsMvcEndpoint.java | 1 + .../main/resources/META-INF/spring.factories | 3 +- ...BrowserPathHypermediaIntegrationTests.java | 71 ++++ ...ContextPathHypermediaIntegrationTests.java | 105 +++++ ...tomHomepageHypermediaIntegrationTests.java | 80 ++++ .../EndpointMvcIntegrationTests.java | 20 +- .../EndpointWebMvcAutoConfigurationTests.java | 47 ++- ...althMvcEndpointAutoConfigurationTests.java | 10 +- ...agementSecurityAutoConfigurationTests.java | 37 +- .../MinimalActuatorHypermediaApplication.java | 55 +++ ...ContextPathHypermediaIntegrationTests.java | 82 ++++ .../ServerPortHypermediaIntegrationTests.java | 81 ++++ .../VanillaHypermediaIntegrationTests.java | 115 ++++++ .../mvc/EnvironmentMvcEndpointTests.java | 32 +- .../JolokiaMvcEndpointContextPathTests.java | 24 +- .../endpoint/mvc/JolokiaMvcEndpointTests.java | 34 +- .../endpoint/mvc/MetricsMvcEndpointTests.java | 42 +- .../mvc/MvcEndpointCorsIntegrationTests.java | 57 +-- spring-boot-dependencies/pom.xml | 10 + .../asciidoc/production-ready-features.adoc | 22 + spring-boot-samples/pom.xml | 4 + .../pom.xml | 66 +++ .../demo/SpringBootHypermediaApplication.java | 12 + .../src/main/resources/application.properties | 2 + .../SpringBootHypermediaApplicationTests.java | 61 +++ .../spring-boot-sample-hypermedia-jpa/pom.xml | 82 ++++ .../src/main/java/demo/Book.java | 35 ++ .../src/main/java/demo/BookRepository.java | 22 + .../java/demo/JpaHypermediaApplication.java | 13 + .../src/main/resources/application.properties | 2 + .../demo/JpaHypermediaApplicationTests.java | 18 + .../VanillaHypermediaIntegrationTests.java | 74 ++++ .../spring-boot-sample-hypermedia-ui/pom.xml | 61 +++ .../demo/SpringBootHypermediaApplication.java | 12 + .../src/main/resources/application.properties | 1 + .../src/main/resources/static/index.html | 5 + .../HomePageHypermediaApplicationTests.java | 65 +++ .../SpringBootHypermediaApplicationTests.java | 18 + .../spring-boot-sample-hypermedia/pom.xml | 65 +++ .../demo/SpringBootHypermediaApplication.java | 12 + .../src/main/resources/application.properties | 2 + .../HomePageHypermediaApplicationTests.java | 59 +++ .../SpringBootHypermediaApplicationTests.java | 18 + 74 files changed, 3106 insertions(+), 154 deletions(-) create mode 100644 spring-boot-actuator-docs/pom.xml create mode 100644 spring-boot-actuator-docs/src/main/asciidoc/autoconfig.adoc create mode 100644 spring-boot-actuator-docs/src/main/asciidoc/beans.adoc create mode 100644 spring-boot-actuator-docs/src/main/asciidoc/configprops.adoc create mode 100644 spring-boot-actuator-docs/src/main/asciidoc/dump.adoc create mode 100644 spring-boot-actuator-docs/src/main/asciidoc/env.adoc create mode 100644 spring-boot-actuator-docs/src/main/asciidoc/health.adoc create mode 100644 spring-boot-actuator-docs/src/main/asciidoc/images/hal-browser.png create mode 100644 spring-boot-actuator-docs/src/main/asciidoc/index.adoc create mode 100644 spring-boot-actuator-docs/src/main/asciidoc/info.adoc create mode 100644 spring-boot-actuator-docs/src/main/asciidoc/mappings.adoc create mode 100644 spring-boot-actuator-docs/src/main/asciidoc/metrics.adoc create mode 100644 spring-boot-actuator-docs/src/main/asciidoc/trace.adoc create mode 100644 spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/EndpointDocumentation.java create mode 100644 spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/HealthEndpointDocumentation.java create mode 100644 spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/HypermediaEndpointDocumentation.java create mode 100644 spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/SpringBootHypermediaApplication.java create mode 100644 spring-boot-actuator-docs/src/test/resources/application.properties create mode 100644 spring-boot-actuator-docs/src/test/resources/templates/endpoints.adoc.tpl create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcHypermediaConfiguration.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ActuatorDocsEndpoint.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HalBrowserEndpoint.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HypermediaDisabled.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LinksEnhancer.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LinksMvcEndpoint.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/BrowserPathHypermediaIntegrationTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ContextPathHypermediaIntegrationTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/CustomHomepageHypermediaIntegrationTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MinimalActuatorHypermediaApplication.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ServerContextPathHypermediaIntegrationTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ServerPortHypermediaIntegrationTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/VanillaHypermediaIntegrationTests.java create mode 100644 spring-boot-samples/spring-boot-sample-hypermedia-gson/pom.xml create mode 100644 spring-boot-samples/spring-boot-sample-hypermedia-gson/src/main/java/demo/SpringBootHypermediaApplication.java create mode 100644 spring-boot-samples/spring-boot-sample-hypermedia-gson/src/main/resources/application.properties create mode 100644 spring-boot-samples/spring-boot-sample-hypermedia-gson/src/test/java/demo/SpringBootHypermediaApplicationTests.java create mode 100644 spring-boot-samples/spring-boot-sample-hypermedia-jpa/pom.xml create mode 100644 spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/java/demo/Book.java create mode 100644 spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/java/demo/BookRepository.java create mode 100644 spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/java/demo/JpaHypermediaApplication.java create mode 100644 spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/resources/application.properties create mode 100644 spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/test/java/demo/JpaHypermediaApplicationTests.java create mode 100644 spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/test/java/demo/VanillaHypermediaIntegrationTests.java create mode 100644 spring-boot-samples/spring-boot-sample-hypermedia-ui/pom.xml create mode 100644 spring-boot-samples/spring-boot-sample-hypermedia-ui/src/main/java/demo/SpringBootHypermediaApplication.java create mode 100644 spring-boot-samples/spring-boot-sample-hypermedia-ui/src/main/resources/application.properties create mode 100644 spring-boot-samples/spring-boot-sample-hypermedia-ui/src/main/resources/static/index.html create mode 100644 spring-boot-samples/spring-boot-sample-hypermedia-ui/src/test/java/demo/HomePageHypermediaApplicationTests.java create mode 100644 spring-boot-samples/spring-boot-sample-hypermedia-ui/src/test/java/demo/SpringBootHypermediaApplicationTests.java create mode 100644 spring-boot-samples/spring-boot-sample-hypermedia/pom.xml create mode 100644 spring-boot-samples/spring-boot-sample-hypermedia/src/main/java/demo/SpringBootHypermediaApplication.java create mode 100644 spring-boot-samples/spring-boot-sample-hypermedia/src/main/resources/application.properties create mode 100644 spring-boot-samples/spring-boot-sample-hypermedia/src/test/java/demo/HomePageHypermediaApplicationTests.java create mode 100644 spring-boot-samples/spring-boot-sample-hypermedia/src/test/java/demo/SpringBootHypermediaApplicationTests.java diff --git a/pom.xml b/pom.xml index 0e9f48599c..50d046ea78 100644 --- a/pom.xml +++ b/pom.xml @@ -87,6 +87,7 @@ spring-boot-devtools spring-boot-docs spring-boot-starters + spring-boot-actuator-docs spring-boot-cli diff --git a/spring-boot-actuator-docs/pom.xml b/spring-boot-actuator-docs/pom.xml new file mode 100644 index 0000000000..76e893ccb3 --- /dev/null +++ b/spring-boot-actuator-docs/pom.xml @@ -0,0 +1,117 @@ + + + 4.0.0 + + spring-boot-actuator-docs + jar + + spring-boot-actuator-docs + Docs project for Spring Boot + + + org.springframework.boot + spring-boot-parent + 1.3.0.BUILD-SNAPSHOT + + + + UTF-8 + 1.7 + + + + + org.springframework.boot + spring-boot-starter-web + true + + + org.springframework.boot + spring-boot-starter-actuator + true + + + org.springframework.boot + spring-boot-starter-hateoas + true + + + org.springframework.restdocs + spring-restdocs + 1.0.0.M1 + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-groovy-templates + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*Documentation.java + + + ${project.build.directory}/generated-snippets + + + + + org.asciidoctor + asciidoctor-maven-plugin + 1.5.2 + + + generate-docs + prepare-package + + process-asciidoc + + + html + book + index.adoc + + ${project.build.directory}/generated-snippets + ${project.build.directory}/../src/main/asciidoc + + + + + + + maven-resources-plugin + + + copy-resources + prepare-package + + copy-resources + + + ${project.build.outputDirectory}/META-INF/resources/spring-boot-actuator/docs + + + ${project.build.directory}/generated-docs + + + + + + + + + + diff --git a/spring-boot-actuator-docs/src/main/asciidoc/autoconfig.adoc b/spring-boot-actuator-docs/src/main/asciidoc/autoconfig.adoc new file mode 100644 index 0000000000..5208f8fd70 --- /dev/null +++ b/spring-boot-actuator-docs/src/main/asciidoc/autoconfig.adoc @@ -0,0 +1,26 @@ +=== /autoconfig + +This endpoint is a report on the Spring Boot Autoconfiguration process +that happened when your application started up. It lists all the +`@Conditional` annotations that were evaluated as the context started +and in each case it gives an indication of if (and why) the condition +matched. A positive match results in a bean being included in the context, +and a negative result means the opposite (the beans's class may not even +be loaded). + +The report is split into 2 parts, positive matches first, and then negative. +If the context is a hierarchy, there is also a separate report on the parent +context with the same format (and recursively up to the top of the hierarchy). + +NOTE: the report is actually about `@Conditional` evaluation not autoconfiguration +per se, but most autoconfiguration features use `@Conditional` heavily, so there is +a lot of overlap. + +Example curl request: +include::{generated}/autoconfig/curl-request.adoc[] + +Example HTTP request: +include::{generated}/autoconfig/http-request.adoc[] + +Example HTTP response: +include::{generated}/autoconfig/http-response.adoc[] diff --git a/spring-boot-actuator-docs/src/main/asciidoc/beans.adoc b/spring-boot-actuator-docs/src/main/asciidoc/beans.adoc new file mode 100644 index 0000000000..e725f3f21d --- /dev/null +++ b/spring-boot-actuator-docs/src/main/asciidoc/beans.adoc @@ -0,0 +1,17 @@ +=== /beans + +This endpoint is a report on the Spring Boot `ApplicationContext`. It lists +the beans in the context and their dependencies, detailing the names and +concrete classes of each bean. + +NOTE: some beans are pure configuration (any class that is annotated +`@Configuration`). + +Example curl request: +include::{generated}/beans/curl-request.adoc[] + +Example HTTP request: +include::{generated}/beans/http-request.adoc[] + +Example HTTP response: +include::{generated}/beans/http-response.adoc[] diff --git a/spring-boot-actuator-docs/src/main/asciidoc/configprops.adoc b/spring-boot-actuator-docs/src/main/asciidoc/configprops.adoc new file mode 100644 index 0000000000..32b3a5b990 --- /dev/null +++ b/spring-boot-actuator-docs/src/main/asciidoc/configprops.adoc @@ -0,0 +1,19 @@ +=== /configprops + +This endpoint is a report on the Spring Boot `@ConfigurationProperties` +beans. Beans with this annotation are bound to the `Environment` on +startup, so they reflect the externalised configuration of the application. +Beans are listed by name. +A bean that is added using `@EnableConfigurationProperties` will have +a conventional name: `.CONFIGURATION_PROPERTIES`, where +`` is the environment key prefix specified in the +`@ConfigurationProperties` annotation. + +Example curl request: +include::{generated}/configprops/curl-request.adoc[] + +Example HTTP request: +include::{generated}/configprops/http-request.adoc[] + +Example HTTP response: +include::{generated}/configprops/http-response.adoc[] diff --git a/spring-boot-actuator-docs/src/main/asciidoc/dump.adoc b/spring-boot-actuator-docs/src/main/asciidoc/dump.adoc new file mode 100644 index 0000000000..4a5ac589e4 --- /dev/null +++ b/spring-boot-actuator-docs/src/main/asciidoc/dump.adoc @@ -0,0 +1,19 @@ +=== /dump + +This endpoint is a thread dump: the result is a list of threads each with +their name, monitor state and stack. It is the same information as you would +get from `kill -3` of a running Java process. Can be very useful for detecting +issues at runtime, especially sluggish behaviour caused by threads blocked +by slow or unavailable I/O (e.g. if a connection pool is exhausted). + +NOTE: some `SecurityManager` implementations might prevent this endpoint +from working. + +Example curl request: +include::{generated}/dump/curl-request.adoc[] + +Example HTTP request: +include::{generated}/dump/http-request.adoc[] + +Example HTTP response: +include::{generated}/dump/http-response.adoc[] diff --git a/spring-boot-actuator-docs/src/main/asciidoc/env.adoc b/spring-boot-actuator-docs/src/main/asciidoc/env.adoc new file mode 100644 index 0000000000..c0266a0c1b --- /dev/null +++ b/spring-boot-actuator-docs/src/main/asciidoc/env.adoc @@ -0,0 +1,17 @@ +=== /env + +This endpoint is a dump of the Spring `Environment`. It lists the active +profiles and all the `PropertySources` in the `Environment` (the ones that +are listed first take precedence when binding to `@ConfigurationProperties` +or `@Value`). Normally you will see the Java `System` properties and the +OS environment variables in their own `PropertySources` plus any `.properties` +or `.yml` files used to configure the application on start up. + +Example curl request: +include::{generated}/env/curl-request.adoc[] + +Example HTTP request: +include::{generated}/env/http-request.adoc[] + +Example HTTP response: +include::{generated}/env/http-response.adoc[] diff --git a/spring-boot-actuator-docs/src/main/asciidoc/health.adoc b/spring-boot-actuator-docs/src/main/asciidoc/health.adoc new file mode 100644 index 0000000000..54a292378d --- /dev/null +++ b/spring-boot-actuator-docs/src/main/asciidoc/health.adoc @@ -0,0 +1,22 @@ +=== /health + +This endpoint is an indication of the health of the application. +It has an overall status ("UP", "DOWN" etc.), which is the only thing +you see unless either you are authenticated or the endpoint is marked +as `sensitive=false` (`endpoints.health.sensitive=false`). + +The HTTP code in the response reflects the status (e.g. "UP" = 200, +"OUT_OF_SERVICE"=503, "DOWN"=503). The mappings can be changed by +configuring `endpoints.health.mapping.=XXX`. + +Example curl request: +include::{generated}/health/curl-request.adoc[] + +Example HTTP request: +include::{generated}/health/http-request.adoc[] + +Example HTTP response: +include::{generated}/health/http-response.adoc[] + +Example HTTP response with `endpoints.health.sensitive=false`: +include::{generated}/health/unsensitive/http-response.adoc[] diff --git a/spring-boot-actuator-docs/src/main/asciidoc/images/hal-browser.png b/spring-boot-actuator-docs/src/main/asciidoc/images/hal-browser.png new file mode 100644 index 0000000000000000000000000000000000000000..2cab51e51d2d77373a38bf6ed9f732be28d14747 GIT binary patch literal 208251 zcmZU)1yoyM(>00}w-znl7A>wpf)t7tr^SQ2ySo-I?p6wv;ts(zNFi8pPjR>4aP$7_ z|L*Pk-K@w;)>%2vdCtu2nLT@=RF!4%uqm*SkdW}?q9cC6(EQMd z_&{@&l>dy0c=%zOeMNkK?Ix}5rr~Je=4tF=j%4ZJXm8H$YU*Nc?%-at@y(gya zG<~-v?fqACy_H6IeV?K!B(`#QkJfC?k>GTV^C{Yty}ED-i%$ua{L%ZJS6<`+*b1y1 z5Fqxh&)My9&_03igzUwOaId^iJel;l%0&kh5yk}vcPSUlş)2BcJh%+WyNJ?6& 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

F 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 Wzbn_A?o0t804m6;$Ye;Fdj22RJeofM literal 0 HcmV?d00001 diff --git a/spring-boot-actuator-docs/src/main/asciidoc/index.adoc b/spring-boot-actuator-docs/src/main/asciidoc/index.adoc new file mode 100644 index 0000000000..b56959055b --- /dev/null +++ b/spring-boot-actuator-docs/src/main/asciidoc/index.adoc @@ -0,0 +1,148 @@ += Spring Boot Actuator Endpoints +:toc: left +:idprefix: spring_boot_actuator_ + +Actuator endpoints allow you to monitor and interact with your application. Spring Boot +includes a number of built-in endpoints and you can also add your own. For example the +`health` endpoint provides basic application health information. + +The way that endpoints are exposed will depend on the type of technology that you choose. +Most applications choose HTTP monitoring, where the ID of the endpoint is mapped +to a URL. For example, by default, the `health` endpoint will be mapped to `/health`. + +== List of Endpoints + +include::{generated}/endpoints.adoc[] + +=== /logfile + +This endpoint (if available) contains the plain text logfile configured by the user +using `logging.file` or `logging.path` (by default logs are only emitted on stdout +so one of these properties has to be set for this endpoint to be active). + +Example curl request: +include::{generated}/logfile/curl-request.adoc[] + +Example HTTP request: +include::{generated}/logfile/http-request.adoc[] + +Example HTTP response: +include::{generated}/logfile/http-response.adoc[] + +=== /docs + +This endpoint (if available) contains HTML documemtation for the other endpoints. Its path +can be "/docs" (if there is an existing home page) or "/" (otherwise, including if the +HAL browser is not active). + +== Hypermedia Support + +If https://projects.spring.io/spring-hateoas[Spring HATEOAS] is enabled +(i.e. if it is on the classpath by default) then the Actuator +endpoint responses are enhanced with hypermedia in the form of "links". The default +media type for responses is http://stateless.co/hal_specification.html[HAL], resulting +in each resource having an extra property called "_links". You can change the +media type to another one supported by Spring HATEOAS by providing your own +`@EnableHypermedia` annotation and custom providers as necessary. + +Example enhanced "/metrics" endpoint with additional "_links": + +include::{generated}/metrics/hypermedia/http-response.adoc[] + +WARNING: Beware of Actuator endpoint paths clashing with application endpoints. +The easiest way to avoid that is to use a `management.contextPath`, e.g. "/admin". + +TIP: You can disable the hypermedia support in Actuator endpoints by setting +`endpoints.links.enabled=false`. + +=== Default home page +If the `management.contextPath` is empty, or if the home page provided +by the application happens to be a response body of type `ResourceSupport`, then it will +be enhanced with links to the actuator endpoints. The latter would happen for instance +if you use Spring Data REST to expose `Repository` endpoints. + +Example vanilla "/" endpoint if the `management.contextPath` is empty (the "/admin" +page would be the same with different links if `management.contextPath=/admin`): + +include::{generated}/admin/http-response.adoc[] + +=== Endpoints with format changes +Some endpoints in their "raw" form consist of an array (e.g. the "/beans" and the "/trace" endpoints). +These need to be converted to objects (maps) before they can be enhanced with +links, so their contents are inserted as a field named "content". +Example enhanced "/beans" endpoint with additional "_links": + +include::{generated}/beans/hypermedia/http-response.adoc[] + +== HAL Browser + +If Hypermedia is enabled and the HAL format is in use (which is the default), then +you can provide a browser for the resources by including a dependency +on the https://github.com/mikekelly/hal-browser[HAL browser] webjar. +For example in Maven: + +[source,xml] +---- + + org.webjars + hal-browser + +---- + + +or in Gradle + +[source,groovy] +---- +dependencies { + ... + compile('org.webjars:hal-browser') + ... +} +---- + +NOTE: if you are using Spring Data REST, then a dependency on the `spring-data-rest-hal-browser` +will have an equivalent effect. + +If you do that then a new endpoint will appear at "/" or "/hal" (relative to the `management.contextPath`) +serving up a static HTML page with some JavaScript that lets you browse the available +resources. The default endpoint path depends on whether or not there is already a static home page +("index.html") - if there is not and the `management.contextPath` is empty, then the HAL browser +shows up on the home page. Example: + +image::hal-browser.png[HAL Browser] + +TIP: The endpoint path can always, as with all MVC endpoints, be overridden using +`endpoints.hal.path=/yourpath` (note the leading slash). + +== Actuator Documentation Browser + +You can also provide a browser for the standard generated documentation +for the Actuator endpoints by including a dependency on the documentation jar. +For example in Maven: + +[source,xml] +---- + + org.springframework.boot + spring-boot-hypermedia-docs + +---- + + +or in Gradle + +[source,groovy] +---- +dependencies { + ... + compile('org.springframework.boot:spring-boot-hypermedia-docs') + ... +} +---- + +If you do that then a new endpoint at "/" or "/docs" (relative to the `management.contextPath`) +will serve up a static HTML page with this documentation in it. The default endpoint path depends +on whether or not there is already a static home page +("index.html" or a HAL browser) - if there is not and the `management.contextPath` is empty, +then the docs browser shows up on the home page. \ No newline at end of file diff --git a/spring-boot-actuator-docs/src/main/asciidoc/info.adoc b/spring-boot-actuator-docs/src/main/asciidoc/info.adoc new file mode 100644 index 0000000000..af1c191462 --- /dev/null +++ b/spring-boot-actuator-docs/src/main/asciidoc/info.adoc @@ -0,0 +1,16 @@ +=== /info + +This endpoint is empty and marked as `sensitive=false` +by default (so it is unauthenticated by default if Spring +Security is in use). It reflects the content of the `info.*` properties +in the `Environment`, as well as the properties in `git.properties` +if such a file exists in the root of the classpath. + +Example curl request: +include::{generated}/info/curl-request.adoc[] + +Example HTTP request: +include::{generated}/info/http-request.adoc[] + +Example HTTP response: +include::{generated}/info/http-response.adoc[] diff --git a/spring-boot-actuator-docs/src/main/asciidoc/mappings.adoc b/spring-boot-actuator-docs/src/main/asciidoc/mappings.adoc new file mode 100644 index 0000000000..685d227231 --- /dev/null +++ b/spring-boot-actuator-docs/src/main/asciidoc/mappings.adoc @@ -0,0 +1,14 @@ +=== /mappings + +This endpoint lists the Spring MVC request mappings, so users can +see the handlers registered for requests by path, method, media type, +etc. + +Example curl request: +include::{generated}/mappings/curl-request.adoc[] + +Example HTTP request: +include::{generated}/mappings/http-request.adoc[] + +Example HTTP response: +include::{generated}/mappings/http-response.adoc[] diff --git a/spring-boot-actuator-docs/src/main/asciidoc/metrics.adoc b/spring-boot-actuator-docs/src/main/asciidoc/metrics.adoc new file mode 100644 index 0000000000..42c61f6c34 --- /dev/null +++ b/spring-boot-actuator-docs/src/main/asciidoc/metrics.adoc @@ -0,0 +1,17 @@ +=== /metrics + +This endpoint lists the public metrics exposed by the application. +By default this includes all the counters in the `CounterService` +and all the gauges in the `GaugeService`, plus a few JVM metrics about +memory and uptime. Users can register additional sources by creating +beans of type `PublicMetrics` and/or by registering counters and +gauges. + +Example curl request: +include::{generated}/metrics/curl-request.adoc[] + +Example HTTP request: +include::{generated}/metrics/http-request.adoc[] + +Example HTTP response: +include::{generated}/metrics/http-response.adoc[] diff --git a/spring-boot-actuator-docs/src/main/asciidoc/trace.adoc b/spring-boot-actuator-docs/src/main/asciidoc/trace.adoc new file mode 100644 index 0000000000..c35b66a829 --- /dev/null +++ b/spring-boot-actuator-docs/src/main/asciidoc/trace.adoc @@ -0,0 +1,16 @@ +=== /trace + +This endpoint lists contents of the `TraceRepository` (which +users can override by providing a bean of that type, or by +injecting that bean and adding stuff to it). By default +it is the last 100 HTTP requests, including all headers in the +request and response, and the path and HTTP status. + +Example curl request: +include::{generated}/trace/curl-request.adoc[] + +Example HTTP request: +include::{generated}/trace/http-request.adoc[] + +Example HTTP response: +include::{generated}/trace/http-response.adoc[] diff --git a/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/EndpointDocumentation.java b/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/EndpointDocumentation.java new file mode 100644 index 0000000000..1b02d66ea4 --- /dev/null +++ b/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/EndpointDocumentation.java @@ -0,0 +1,170 @@ +package org.springframework.boot.actuate.hypermedia.test; + +import static org.springframework.restdocs.RestDocumentation.document; +import static org.springframework.restdocs.RestDocumentation.documentationConfiguration; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import groovy.text.Template; +import groovy.text.TemplateEngine; + +import java.io.File; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.Filter; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; +import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.ResultHandler; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.util.StringUtils; +import org.springframework.web.context.WebApplicationContext; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class) +@WebAppConfiguration +@TestPropertySource(properties = { "spring.jackson.serialization.indent_output=true", + "endpoints.health.sensitive=true", "endpoints.links.enabled=false" }) +@DirtiesContext +public class EndpointDocumentation { + + @Autowired + private WebApplicationContext context; + + @Autowired + private MvcEndpoints mvcEndpoints; + + @Autowired + @Qualifier("metricFilter") + private Filter metricFilter; + + @Autowired + @Qualifier("webRequestLoggingFilter") + private Filter traceFilter; + + @Autowired + private TemplateEngine templates; + + @Value("${org.springframework.restdocs.outputDir:${user.dir}/target/generated-snippets}") + private String restdocsOutputDir; + + private MockMvc mockMvc; + + @Before + public void setUp() { + System.setProperty("org.springframework.restdocs.outputDir", + this.restdocsOutputDir); + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) + .addFilters(this.metricFilter, this.traceFilter) + .apply(documentationConfiguration()).build(); + } + + @Test + public void logfile() throws Exception { + this.mockMvc.perform(get("/logfile").accept(MediaType.TEXT_PLAIN)) + .andExpect(status().isOk()) + .andDo(document("logfile")); + } + + @Test + public void endpoints() throws Exception { + + final File docs = new File("src/main/asciidoc"); + + final Map model = new LinkedHashMap(); + final List endpoints = new ArrayList(); + model.put("endpoints", endpoints); + for (MvcEndpoint endpoint : getEndpoints()) { + final String endpointPath = StringUtils.hasText(endpoint.getPath()) ? endpoint + .getPath() : "/"; + + if (!endpointPath.equals("/docs") && !endpointPath.equals("/logfile")) { + String output = endpointPath.substring(1); + output = output.length() > 0 ? output : "./"; + this.mockMvc + .perform(get(endpointPath).accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andDo(document(output)) + .andDo(new ResultHandler() { + @Override + public void handle(MvcResult mvcResult) throws Exception { + EndpointDoc endpoint = new EndpointDoc(docs, endpointPath); + endpoints.add(endpoint); + } + }); + } + } + File file = new File(this.restdocsOutputDir + "/endpoints.adoc"); + file.getParentFile().mkdirs(); + PrintWriter writer = new PrintWriter(file, "UTF-8"); + try { + Template template = this.templates.createTemplate(new File( + "src/test/resources/templates/endpoints.adoc.tpl")); + template.make(model).writeTo(writer); + } + finally { + writer.close(); + } + } + + private Collection getEndpoints() { + List endpoints = new ArrayList( + this.mvcEndpoints.getEndpoints()); + Collections.sort(endpoints, new Comparator() { + @Override + public int compare(MvcEndpoint o1, MvcEndpoint o2) { + return o1.getPath().compareTo(o2.getPath()); + } + }); + return endpoints; + } + + public static class EndpointDoc { + + private String path; + private String custom; + private String title; + + public EndpointDoc(File rootDir, String path) { + this.title = path; + this.path = path.equals("/") ? "" : path; + String custom = path.substring(1) + ".adoc"; + if (new File(rootDir, custom).exists()) { + this.custom = custom; + } + } + + public String getTitle() { + return this.title; + } + + public String getPath() { + return this.path; + } + + public String getCustom() { + return this.custom; + } + + } + +} diff --git a/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/HealthEndpointDocumentation.java b/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/HealthEndpointDocumentation.java new file mode 100644 index 0000000000..d9f6ba26cf --- /dev/null +++ b/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/HealthEndpointDocumentation.java @@ -0,0 +1,63 @@ +package org.springframework.boot.actuate.hypermedia.test; + +import static org.springframework.restdocs.RestDocumentation.document; +import static org.springframework.restdocs.RestDocumentation.documentationConfiguration; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import groovy.text.TemplateEngine; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class) +@WebAppConfiguration +@TestPropertySource(properties = { "spring.jackson.serialization.indent_output=true", +"endpoints.health.sensitive=false" }) +@DirtiesContext +public class HealthEndpointDocumentation { + + @Autowired + private WebApplicationContext context; + + @Autowired + private MvcEndpoints mvcEndpoints; + + @Autowired + private TemplateEngine templates; + + @Value("${org.springframework.restdocs.outputDir:target/generated-snippets}") + private String restdocsOutputDir; + + private MockMvc mockMvc; + + @Before + public void setUp() { + System.setProperty("org.springframework.restdocs.outputDir", + this.restdocsOutputDir); + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) + .apply(documentationConfiguration()) + .build(); + } + + @Test + public void health() throws Exception { + this.mockMvc.perform(get("/health").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(document("health/unsensitive")); + } + +} diff --git a/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/HypermediaEndpointDocumentation.java b/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/HypermediaEndpointDocumentation.java new file mode 100644 index 0000000000..1ca6a2e2ae --- /dev/null +++ b/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/HypermediaEndpointDocumentation.java @@ -0,0 +1,76 @@ +package org.springframework.boot.actuate.hypermedia.test; + +import static org.springframework.restdocs.RestDocumentation.document; +import static org.springframework.restdocs.RestDocumentation.documentationConfiguration; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import groovy.text.TemplateEngine; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class) +@WebAppConfiguration +@TestPropertySource(properties = "spring.jackson.serialization.indent_output=true") +@DirtiesContext +public class HypermediaEndpointDocumentation { + + @Autowired + private WebApplicationContext context; + + @Autowired + private MvcEndpoints mvcEndpoints; + + @Autowired + private TemplateEngine templates; + + @Value("${org.springframework.restdocs.outputDir:target/generated-snippets}") + private String restdocsOutputDir; + + private MockMvc mockMvc; + + @Before + public void setUp() { + System.setProperty("org.springframework.restdocs.outputDir", + this.restdocsOutputDir); + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) + .apply(documentationConfiguration()) + .build(); + } + + @Test + public void beans() throws Exception { + this.mockMvc.perform(get("/beans").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(document("beans/hypermedia")); + } + + @Test + public void metrics() throws Exception { + this.mockMvc.perform(get("/metrics").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(document("metrics/hypermedia")); + } + + @Test + public void home() throws Exception { + this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(document("admin")); + } + +} diff --git a/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/SpringBootHypermediaApplication.java b/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/SpringBootHypermediaApplication.java new file mode 100644 index 0000000000..5a9ca58fe6 --- /dev/null +++ b/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/hypermedia/test/SpringBootHypermediaApplication.java @@ -0,0 +1,21 @@ +package org.springframework.boot.actuate.hypermedia.test; + +import groovy.text.GStringTemplateEngine; +import groovy.text.TemplateEngine; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +@SpringBootApplication +public class SpringBootHypermediaApplication { + + @Bean + public TemplateEngine groovyTemplateEngine() { + return new GStringTemplateEngine(); + } + + public static void main(String[] args) { + SpringApplication.run(SpringBootHypermediaApplication.class, args); + } +} diff --git a/spring-boot-actuator-docs/src/test/resources/application.properties b/spring-boot-actuator-docs/src/test/resources/application.properties new file mode 100644 index 0000000000..6cd849878a --- /dev/null +++ b/spring-boot-actuator-docs/src/test/resources/application.properties @@ -0,0 +1,2 @@ +# management.contextPath=/admin +logging.path: target/logs \ No newline at end of file diff --git a/spring-boot-actuator-docs/src/test/resources/templates/endpoints.adoc.tpl b/spring-boot-actuator-docs/src/test/resources/templates/endpoints.adoc.tpl new file mode 100644 index 0000000000..a7c0617c55 --- /dev/null +++ b/spring-boot-actuator-docs/src/test/resources/templates/endpoints.adoc.tpl @@ -0,0 +1,16 @@ +<% endpoints.each { endpoint -> + if (endpoint.custom) { %> +include::{docs}/${endpoint.custom}[] +<% } else { %> +=== ${endpoint.title} + +Example curl request: +include::{generated}${endpoint.path}/curl-request.adoc[] + +Example HTTP request: +include::{generated}${endpoint.path}/http-request.adoc[] + +Example HTTP response: +include::{generated}${endpoint.path}/http-response.adoc[] +<% } +} %> \ No newline at end of file diff --git a/spring-boot-actuator/pom.xml b/spring-boot-actuator/pom.xml index a61dbf5925..10b7554350 100644 --- a/spring-boot-actuator/pom.xml +++ b/spring-boot-actuator/pom.xml @@ -42,6 +42,26 @@ spring-context + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + true + + + org.springframework.hateoas + spring-hateoas + true + + + org.springframework.plugin + spring-plugin-core + true + + + org.webjars + hal-browser + true + com.google.guava guava @@ -219,6 +239,11 @@ true + + com.jayway.jsonpath + json-path + test + org.springframework.boot spring-boot diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java index 227427682d..bf7b3f9e3d 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java @@ -44,8 +44,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration; import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration; +import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; import org.springframework.boot.bind.RelaxedPropertyResolver; @@ -86,8 +88,9 @@ import org.springframework.web.servlet.DispatcherServlet; @ConditionalOnClass({ Servlet.class, DispatcherServlet.class }) @ConditionalOnWebApplication @AutoConfigureAfter({ PropertyPlaceholderAutoConfiguration.class, - EmbeddedServletContainerAutoConfiguration.class, WebMvcAutoConfiguration.class, - ManagementServerPropertiesAutoConfiguration.class }) + EmbeddedServletContainerAutoConfiguration.class, WebMvcAutoConfiguration.class, + ManagementServerPropertiesAutoConfiguration.class, + HypermediaAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class }) public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware, SmartInitializingSingleton { diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcHypermediaConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcHypermediaConfiguration.java new file mode 100644 index 0000000000..a387675fe9 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcHypermediaConfiguration.java @@ -0,0 +1,380 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.endpoint.mvc.ActuatorDocsEndpoint; +import org.springframework.boot.actuate.endpoint.mvc.HalBrowserEndpoint; +import org.springframework.boot.actuate.endpoint.mvc.HypermediaDisabled; +import org.springframework.boot.actuate.endpoint.mvc.LinksEnhancer; +import org.springframework.boot.actuate.endpoint.mvc.LinksMvcEndpoint; +import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; +import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnResource; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.boot.autoconfigure.web.HttpMessageConverters; +import org.springframework.boot.autoconfigure.web.ResourceProperties; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.MethodParameter; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.Resource; +import org.springframework.hateoas.ResourceSupport; +import org.springframework.hateoas.UriTemplate; +import org.springframework.hateoas.hal.CurieProvider; +import org.springframework.hateoas.hal.DefaultCurieProvider; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.util.StringUtils; +import org.springframework.util.TypeUtils; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +/** + * Autoconfiguration for hypermedia in HTTP endpoints. + * + * @author Dave Syer + * + */ +@Configuration +@ConditionalOnClass(Link.class) +@ConditionalOnWebApplication +@ConditionalOnBean(HttpMessageConverters.class) +@ConditionalOnProperty(value = "endpoints.enabled", matchIfMissing = true) +@EnableConfigurationProperties(ResourceProperties.class) +public class EndpointWebMvcHypermediaConfiguration { + + @Bean + @ConditionalOnProperty(value = "endpoints.hal.enabled", matchIfMissing = true) + @ConditionalOnResource(resources = "classpath:/META-INF/resources/webjars/hal-browser/b7669f1-1") + @Conditional(MissingSpringDataRestResourceCondition.class) + public HalBrowserEndpoint halBrowserMvcEndpoint( + ManagementServerProperties management, ResourceProperties resources) { + return new HalBrowserEndpoint(management, + resources.getWelcomePage() != null ? "/hal" : ""); + } + + @Bean + @ConditionalOnProperty(value = "endpoints.docs.enabled", matchIfMissing = true) + @ConditionalOnResource(resources = "classpath:/META-INF/resources/spring-boot-actuator/docs/index.html") + public ActuatorDocsEndpoint actuatorDocsEndpoint(ManagementServerProperties management) { + return new ActuatorDocsEndpoint(management); + } + + @Bean + @ConditionalOnBean(ActuatorDocsEndpoint.class) + @ConditionalOnMissingBean(CurieProvider.class) + @ConditionalOnProperty(value = "endpoints.docs.curies.enabled", matchIfMissing = false) + public DefaultCurieProvider curieProvider(ServerProperties server, + ManagementServerProperties management, ActuatorDocsEndpoint endpoint) { + String path = management.getContextPath() + endpoint.getPath() + + "/#spring_boot_actuator__{rel}"; + if (server.getPort() == management.getPort() && management.getPort() != null + && management.getPort() != 0) { + path = server.getPath(path); + } + return new DefaultCurieProvider("boot", new UriTemplate(path)); + } + + @Configuration("EndpointHypermediaAutoConfiguration.MissingResourceCondition") + @ConditionalOnResource(resources = "classpath:/META-INF/spring-data-rest/hal-browser/index.html") + protected static class MissingSpringDataRestResourceCondition extends + SpringBootCondition { + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, + AnnotatedTypeMetadata metadata) { + if (context.getRegistry().containsBeanDefinition( + "EndpointHypermediaAutoConfiguration.MissingResourceCondition")) { + return ConditionOutcome.noMatch("Spring Data REST HAL browser found"); + } + return ConditionOutcome.match("Spring Data REST HAL browser not found"); + } + } + + @ConditionalOnProperty(value = "endpoints.links.enabled", matchIfMissing = true) + public static class LinksConfiguration { + + @Bean + public LinksMvcEndpoint linksMvcEndpoint(ResourceProperties resources) { + return new LinksMvcEndpoint(resources.getWelcomePage() != null ? "/links" + : ""); + } + + /** + * Controller advice that adds links to the home page and/or the management + * context path. The home page is enhanced if it is composed already of a + * {@link ResourceSupport} (e.g. when using Spring Data REST). + * + * @author Dave Syer + * + */ + @ControllerAdvice + public static class HomePageLinksAdvice implements ResponseBodyAdvice { + + @Autowired + MvcEndpoints endpoints; + + @Autowired + LinksMvcEndpoint linksEndpoint; + + @Autowired + ManagementServerProperties management; + + private LinksEnhancer linksEnhancer; + + @PostConstruct + public void init() { + this.linksEnhancer = new LinksEnhancer(this.endpoints, + this.management.getContextPath()); + } + + @Override + public boolean supports(MethodParameter returnType, + Class> converterType) { + Class controllerType = returnType.getDeclaringClass(); + if (!LinksMvcEndpoint.class.isAssignableFrom(controllerType) + && MvcEndpoint.class.isAssignableFrom(controllerType)) { + return false; + } + returnType.increaseNestingLevel(); + Type nestedType = returnType.getNestedGenericParameterType(); + returnType.decreaseNestingLevel(); + return ResourceSupport.class.isAssignableFrom(returnType + .getParameterType()) + || TypeUtils.isAssignable(ResourceSupport.class, nestedType); + } + + @Override + public Object beforeBodyWrite(Object body, MethodParameter returnType, + MediaType selectedContentType, + Class> selectedConverterType, + ServerHttpRequest request, ServerHttpResponse response) { + HttpServletRequest servletRequest = null; + if (request instanceof ServletServerHttpRequest) { + servletRequest = ((ServletServerHttpRequest) request) + .getServletRequest(); + Object pattern = servletRequest + .getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE); + if (pattern != null) { + String path = pattern.toString(); + if (isHomePage(path) || isManagementPath(path) + || isLinksPath(path)) { + ResourceSupport resource = (ResourceSupport) body; + if (isHomePage(path) && hasManagementPath()) { + String rel = this.management.getContextPath() + .substring(1); + resource.add(linkTo( + EndpointWebMvcHypermediaConfiguration.class) + .slash(this.management.getContextPath()).withRel( + rel)); + } + else { + this.linksEnhancer.addEndpointLinks(resource, ""); + } + } + } + } + return body; + } + + private boolean hasManagementPath() { + return StringUtils.hasText(this.management.getContextPath()); + } + + private boolean isManagementPath(String path) { + return this.management.getContextPath().equals(path); + } + + private boolean isLinksPath(String path) { + return (this.management.getContextPath() + this.linksEndpoint.getPath()) + .equals(path); + } + + private boolean isHomePage(String path) { + return "".equals(path) || "/".equals(path); + } + + } + + /** + * Controller advice that adds links to the existing Actuator endpoints. By default + * all the top-level resources are enhanced with a "self" link. Those resources that + * could not be enhanced (e.g. "/env/{name}") because their values are "primitive" are + * ignored. Those that have values of type Collection (e.g. /trace) are transformed in + * to maps, and the original collection value is added with a key equal to the + * endpoint name. + * + * @author Dave Syer + * + */ + @ControllerAdvice(assignableTypes = MvcEndpoint.class) + public static class MvcEndpointAdvice implements ResponseBodyAdvice { + + @Autowired + ManagementServerProperties management; + + @Autowired + HttpMessageConverters converters; + + private Map> converterCache = new ConcurrentHashMap>(); + + @Autowired + ObjectMapper mapper; + + @Override + public boolean supports(MethodParameter returnType, + Class> converterType) { + Class controllerType = returnType.getDeclaringClass(); + return !LinksMvcEndpoint.class.isAssignableFrom(controllerType) + && !HalBrowserEndpoint.class.isAssignableFrom(controllerType); + } + + @Override + public Object beforeBodyWrite(Object body, MethodParameter returnType, + MediaType selectedContentType, + Class> selectedConverterType, + ServerHttpRequest request, ServerHttpResponse response) { + + if (body == null) { + // Assume it already was handled + return body; + } + + if (body instanceof Resource) { + // Assume it already has its links + return body; + } + + @SuppressWarnings("unchecked") + HttpMessageConverter converter = (HttpMessageConverter) findConverter( + selectedConverterType, selectedContentType); + if (converter == null) { + // Not a resource that can be enhanced with a link + return body; + } + if (AnnotationUtils.findAnnotation(returnType.getMethod(), + HypermediaDisabled.class) != null + || AnnotationUtils.findAnnotation(returnType.getMethod() + .getDeclaringClass(), HypermediaDisabled.class) != null) { + return body; + } + + HttpServletRequest servletRequest = null; + if (request instanceof ServletServerHttpRequest) { + servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); + String path = (String) servletRequest + .getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE); + if (path == null) { + path = ""; + } + try { + converter.write(new EndpointResource(body, path), + selectedContentType, response); + } + catch (IOException e) { + throw new HttpMessageNotWritableException("Cannot write response", e); + } + return null; + } + else { + return body; + } + + } + + private HttpMessageConverter findConverter( + Class> selectedConverterType, + MediaType mediaType) { + if (this.converterCache.containsKey(mediaType)) { + return this.converterCache.get(mediaType); + } + for (HttpMessageConverter converter : this.converters) { + if (selectedConverterType.isAssignableFrom(converter.getClass()) + && converter.canWrite(EndpointResource.class, mediaType)) { + this.converterCache.put(mediaType, converter); + return converter; + } + } + return null; + } + + } + + } + + @JsonInclude(content = Include.NON_NULL) + @JacksonXmlRootElement(localName = "resource") + private static class EndpointResource extends ResourceSupport { + + private Object content; + + private Map embedded; + + @SuppressWarnings("unchecked") + public EndpointResource(Object content, String path) { + this.content = content instanceof Map ? null : content; + this.embedded = (Map) (this.content == null ? content : null); + add(linkTo(Object.class).slash(path).withSelfRel()); + } + + @JsonUnwrapped + public Object getContent() { + return this.content; + } + + @JsonAnyGetter + public Map getEmbedded() { + return this.embedded; + } + + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementSecurityAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementSecurityAutoConfiguration.java index 534e834054..85b5698b8b 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementSecurityAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementSecurityAutoConfiguration.java @@ -19,6 +19,7 @@ package org.springframework.boot.actuate.autoconfigure; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -102,7 +103,7 @@ public class ManagementSecurityAutoConfiguration { @Configuration protected static class ManagementSecurityPropertiesConfiguration implements - SecurityPrerequisite { + SecurityPrerequisite { @Autowired(required = false) private SecurityProperties security; @@ -114,7 +115,7 @@ public class ManagementSecurityAutoConfiguration { public void init() { if (this.management != null && this.security != null) { this.security.getUser().getRole() - .add(this.management.getSecurity().getRole()); + .add(this.management.getSecurity().getRole()); } } @@ -123,7 +124,7 @@ public class ManagementSecurityAutoConfiguration { // Get the ignored paths in early @Order(SecurityProperties.IGNORED_ORDER + 1) private static class IgnoredPathsWebSecurityConfigurerAdapter implements - WebSecurityConfigurer { + WebSecurityConfigurer { @Autowired(required = false) private ErrorController errorController; @@ -208,7 +209,7 @@ public class ManagementSecurityAutoConfiguration { @ConditionalOnProperty(prefix = "management.security", name = "enabled", matchIfMissing = true) @Order(ManagementServerProperties.BASIC_AUTH_ORDER) protected static class ManagementWebSecurityConfigurerAdapter extends - WebSecurityConfigurerAdapter { + WebSecurityConfigurerAdapter { @Autowired private SecurityProperties security; @@ -317,13 +318,13 @@ public class ManagementSecurityAutoConfiguration { endpointMapping, false) : getEndpointPaths(endpointMapping); - for (String path : paths) { - pathMatchers.add(new AntPathRequestMatcher( - ManagementWebSecurityConfigurerAdapter.this.server + for (String path : paths) { + pathMatchers.add(new AntPathRequestMatcher( + ManagementWebSecurityConfigurerAdapter.this.server .getPath(path))); - } - this.delegate = pathMatchers.isEmpty() ? AnyRequestMatcher.INSTANCE - : new OrRequestMatcher(pathMatchers); + } + this.delegate = pathMatchers.isEmpty() ? AnyRequestMatcher.INSTANCE + : new OrRequestMatcher(pathMatchers); } return this.delegate.matches(request); } @@ -343,15 +344,19 @@ public class ManagementSecurityAutoConfiguration { return NO_PATHS; } Set endpoints = endpointHandlerMapping.getEndpoints(); - List paths = new ArrayList(endpoints.size()); + Set paths = new LinkedHashSet(endpoints.size()); for (MvcEndpoint endpoint : endpoints) { if (endpoint.isSensitive() == secure) { String path = endpointHandlerMapping.getPath(endpoint.getPath()); paths.add(path); - // Ensure that nested paths are secured - paths.add(path + "/**"); - // Add Spring MVC-generated additional paths - paths.add(path + ".*"); + if (!path.equals("")) { + // Ensure that nested paths are secured + paths.add(path + "/**"); + // Add Spring MVC-generated additional paths + paths.add(path + ".*"); + } else { + paths.add("/"); + } } } return paths.toArray(new String[paths.size()]); diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ActuatorDocsEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ActuatorDocsEndpoint.java new file mode 100644 index 0000000000..568c6d1692 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ActuatorDocsEndpoint.java @@ -0,0 +1,112 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.endpoint.mvc; + +import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties; +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.http.MediaType; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +/** + * @author Dave Syer + * + */ +@ConfigurationProperties("endpoints.docs") +public class ActuatorDocsEndpoint extends WebMvcConfigurerAdapter implements MvcEndpoint { + + private String path = "/docs"; + + private boolean sensitive; + + private ManagementServerProperties management; + + + private Curies curies = new Curies(); + + public Curies getCuries() { + 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) { + this.management = management; + } + + @RequestMapping(value = "/", produces = MediaType.TEXT_HTML_VALUE) + public String browse() { + return "forward:" + this.management.getContextPath() + this.path + "/index.html"; + } + + @RequestMapping(value = "", produces = MediaType.TEXT_HTML_VALUE) + public String redirect() { + return "redirect:" + this.management.getContextPath() + this.path + "/"; + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler(this.management.getContextPath() + this.path + "/**") + .addResourceLocations( + "classpath:/META-INF/resources/spring-boot-actuator/docs/"); + } + + public void setPath(String path) { + this.path = path; + } + + @Override + public String getPath() { + return this.path; + } + + public void setSensitive(boolean sensitive) { + this.sensitive = sensitive; + } + + @Override + public boolean isSensitive() { + return this.sensitive; + } + + @Override + public Class> getEndpointType() { + return null; + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpoint.java index 1aec58d6cd..655059cf5b 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpoint.java @@ -38,7 +38,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; * @author Andy Wilkinson */ public class EnvironmentMvcEndpoint extends EndpointMvcAdapter implements - EnvironmentAware { +EnvironmentAware { private Environment environment; @@ -48,6 +48,7 @@ public class EnvironmentMvcEndpoint extends EndpointMvcAdapter implements @RequestMapping(value = "/{name:.*}", method = RequestMethod.GET) @ResponseBody + @HypermediaDisabled public Object value(@PathVariable String name) { if (!getDelegate().isEnabled()) { // Shouldn't happen - MVC endpoint shouldn't be registered when delegate's diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HalBrowserEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HalBrowserEndpoint.java new file mode 100644 index 0000000000..96aafbb823 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HalBrowserEndpoint.java @@ -0,0 +1,96 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.endpoint.mvc; + +import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties; +import org.springframework.boot.actuate.endpoint.Endpoint; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +/** + * @author Dave Syer + * + */ +@ConfigurationProperties("endpoints.hal") +public class HalBrowserEndpoint extends WebMvcConfigurerAdapter implements MvcEndpoint { + + private static final String HAL_BROWSER_VERSION = "b7669f1-1"; + + private String path = ""; + + private ManagementServerProperties management; + + private boolean sensitive = false; + + public HalBrowserEndpoint(ManagementServerProperties management, String defaultPath) { + this.management = management; + this.path = defaultPath; + } + + @RequestMapping(value = "/", produces = MediaType.TEXT_HTML_VALUE) + public String browse() { + return "forward:" + this.management.getContextPath() + this.path + + "/browser.html"; + } + + @RequestMapping(value = "", produces = MediaType.TEXT_HTML_VALUE) + public String redirect() { + return "redirect:" + this.management.getContextPath() + this.path + "/#" + + this.management.getContextPath(); + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + // Make sure the root path is not cached otherwise the browser won't come back for + // the JSON + registry.addResourceHandler(this.management.getContextPath() + this.path + "/") + .addResourceLocations( + "classpath:/META-INF/resources/webjars/hal-browser/" + + HAL_BROWSER_VERSION + "/").setCachePeriod(0); + registry.addResourceHandler(this.management.getContextPath() + this.path + "/**") + .addResourceLocations( + "classpath:/META-INF/resources/webjars/hal-browser/" + + HAL_BROWSER_VERSION + "/"); + } + + public void setPath(String path) { + this.path = path; + } + + @Override + public String getPath() { + return this.path; + } + + public void setSensitive(boolean sensitive) { + this.sensitive = sensitive; + } + + @Override + public boolean isSensitive() { + return this.sensitive; + } + + @Override + public Class> getEndpointType() { + return null; + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HypermediaDisabled.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HypermediaDisabled.java new file mode 100644 index 0000000000..100481d86a --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HypermediaDisabled.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.endpoint.mvc; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Dave Syer + * + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface HypermediaDisabled { + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/JolokiaMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/JolokiaMvcEndpoint.java index 71152270bf..fa16def8a5 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/JolokiaMvcEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/JolokiaMvcEndpoint.java @@ -45,8 +45,9 @@ import org.springframework.web.util.UrlPathHelper; * @author Andy Wilkinson */ @ConfigurationProperties(prefix = "endpoints.jolokia", ignoreUnknownFields = false) +@HypermediaDisabled public class JolokiaMvcEndpoint implements MvcEndpoint, InitializingBean, - ApplicationContextAware, ServletContextAware { +ApplicationContextAware, ServletContextAware { /** * Endpoint URL path. diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LinksEnhancer.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LinksEnhancer.java new file mode 100644 index 0000000000..6e96af9756 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LinksEnhancer.java @@ -0,0 +1,61 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.endpoint.mvc; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; + +import org.springframework.hateoas.ResourceSupport; +import org.springframework.util.StringUtils; + +/** + * @author Dave Syer + * + */ +public class LinksEnhancer { + + private MvcEndpoints endpoints; + + private String rootPath; + + public LinksEnhancer(MvcEndpoints endpoints, String rootPath) { + this.endpoints = endpoints; + this.rootPath = rootPath; + } + + public void addEndpointLinks(ResourceSupport resource, String self) { + if (!resource.hasLink("self")) { + resource.add(linkTo(LinksEnhancer.class).slash( + this.rootPath + self).withSelfRel()); + } + for (MvcEndpoint endpoint : this.endpoints.getEndpoints()) { + if (endpoint.getPath().equals(self)) { + continue; + } + 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)); + } + } + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LinksMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LinksMvcEndpoint.java new file mode 100644 index 0000000000..7466a9ee2f --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LinksMvcEndpoint.java @@ -0,0 +1,70 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.endpoint.mvc; + +import org.springframework.boot.actuate.endpoint.Endpoint; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.hateoas.ResourceSupport; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * @author Dave Syer + * + */ +@ConfigurationProperties("endpoints.links") +public class LinksMvcEndpoint implements MvcEndpoint { + + private String path = ""; + private boolean sensitive = false; + + public LinksMvcEndpoint(String defaultPath) { + this.path = defaultPath; + } + + @RequestMapping(value = { "/", "" }, produces=MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public ResourceSupport links() { + ResourceSupport resource = new ResourceSupport(); + return resource; + } + + public void setPath(String path) { + this.path = path; + } + + @Override + public String getPath() { + return this.path; + } + + @Override + public boolean isSensitive() { + return this.sensitive; + } + + public void setSensitive(boolean sensitive) { + this.sensitive = sensitive; + } + + @Override + public Class> getEndpointType() { + return null; + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpoint.java index ae54c50ca9..0fbf96fbbd 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpoint.java @@ -44,6 +44,7 @@ public class MetricsMvcEndpoint extends EndpointMvcAdapter { @RequestMapping(value = "/{name:.*}", method = RequestMethod.GET) @ResponseBody + @HypermediaDisabled public Object value(@PathVariable String name) { if (!this.delegate.isEnabled()) { // Shouldn't happen - MVC endpoint shouldn't be registered when delegate's diff --git a/spring-boot-actuator/src/main/resources/META-INF/spring.factories b/spring-boot-actuator/src/main/resources/META-INF/spring.factories index 70773f6d40..3d76679570 100644 --- a/spring-boot-actuator/src/main/resources/META-INF/spring.factories +++ b/spring-boot-actuator/src/main/resources/META-INF/spring.factories @@ -19,4 +19,5 @@ org.springframework.boot.actuate.autoconfigure.TraceRepositoryAutoConfiguration, org.springframework.boot.actuate.autoconfigure.TraceWebFilterAutoConfiguration org.springframework.boot.actuate.autoconfigure.EndpointWebMvcConfiguration=\ -org.springframework.boot.actuate.autoconfigure.EndpointWebMvcConfiguration \ No newline at end of file +org.springframework.boot.actuate.autoconfigure.EndpointWebMvcConfiguration,\ +org.springframework.boot.actuate.autoconfigure.EndpointWebMvcHypermediaConfiguration \ No newline at end of file diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/BrowserPathHypermediaIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/BrowserPathHypermediaIntegrationTests.java new file mode 100644 index 0000000000..668886bf6b --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/BrowserPathHypermediaIntegrationTests.java @@ -0,0 +1,71 @@ +package org.springframework.boot.actuate.autoconfigure; + +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; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.actuate.autoconfigure.BrowserPathHypermediaIntegrationTests.SpringBootHypermediaApplication; +import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class) +@WebAppConfiguration +@TestPropertySource(properties = "endpoints.hal.path=/hal") +@DirtiesContext +public class BrowserPathHypermediaIntegrationTests { + + @Autowired + private WebApplicationContext context; + + @Autowired + private MvcEndpoints mvcEndpoints; + + private MockMvc mockMvc; + + @Before + public void setUp() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + } + + @Test + public void browser() throws Exception { + MvcResult response = this.mockMvc + .perform(get("/hal/").accept(MediaType.TEXT_HTML)) + .andExpect(status().isOk()).andReturn(); + assertEquals("/hal/browser.html", response.getResponse().getForwardedUrl()); + } + + @Test + public void redirect() throws Exception { + this.mockMvc.perform(get("/hal").accept(MediaType.TEXT_HTML)) + .andExpect(status().isFound()) + .andExpect(header().string("location", "/hal/#")); + } + + @MinimalActuatorHypermediaApplication + @Configuration + public static class SpringBootHypermediaApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootHypermediaApplication.class, args); + } + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ContextPathHypermediaIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ContextPathHypermediaIntegrationTests.java new file mode 100644 index 0000000000..78eae21400 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ContextPathHypermediaIntegrationTests.java @@ -0,0 +1,105 @@ +package org.springframework.boot.actuate.autoconfigure; + +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; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.autoconfigure.ContextPathHypermediaIntegrationTests.SpringBootHypermediaApplication; +import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; +import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.hateoas.ResourceSupport; +import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.WebApplicationContext; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class) +@WebAppConfiguration +@TestPropertySource(properties = "management.contextPath:/admin") +@DirtiesContext +public class ContextPathHypermediaIntegrationTests { + + @Autowired + private WebApplicationContext context; + + @Autowired + private MvcEndpoints mvcEndpoints; + + private MockMvc mockMvc; + + @Before + public void setUp() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + } + + @Test + public void home() throws Exception { + this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andExpect(jsonPath("$._links").exists()); + } + + @Test + public void links() throws Exception { + this.mockMvc.perform(get("/admin").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andExpect(jsonPath("$._links").exists()); + } + + @Test + public void trace() throws Exception { + this.mockMvc + .perform(get("/admin/trace").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$._links.self.href").value( + "http://localhost/admin/trace")) + .andExpect(jsonPath("$.content").isArray()); + } + + @Test + public void endpointsAllListed() throws Exception { + for (MvcEndpoint endpoint : this.mvcEndpoints.getEndpoints()) { + String path = endpoint.getPath(); + path = path.startsWith("/") ? path.substring(1) : path; + path = path.length() > 0 ? path : "self"; + this.mockMvc + .perform(get("/admin").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$._links.%s.href", path).value( + "http://localhost/admin" + endpoint.getPath())); + } + } + + @MinimalActuatorHypermediaApplication + @RestController + public static class SpringBootHypermediaApplication { + + @RequestMapping("") + public ResourceSupport home() { + ResourceSupport resource = new ResourceSupport(); + resource.add(linkTo(SpringBootHypermediaApplication.class).slash("/") + .withSelfRel()); + return resource; + } + + public static void main(String[] args) { + new SpringApplicationBuilder(SpringBootHypermediaApplication.class) + .properties("management.contextPath:/admin").run(args); + } + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/CustomHomepageHypermediaIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/CustomHomepageHypermediaIntegrationTests.java new file mode 100644 index 0000000000..674c03d0b9 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/CustomHomepageHypermediaIntegrationTests.java @@ -0,0 +1,80 @@ +package org.springframework.boot.actuate.autoconfigure; + +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; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.actuate.autoconfigure.CustomHomepageHypermediaIntegrationTests.SpringBootHypermediaApplication; +import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; +import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.hateoas.ResourceSupport; +import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.WebApplicationContext; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class) +@WebAppConfiguration +@DirtiesContext +public class CustomHomepageHypermediaIntegrationTests { + + @Autowired + private WebApplicationContext context; + + @Autowired + private MvcEndpoints mvcEndpoints; + + private MockMvc mockMvc; + + @Before + public void setUp() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + } + + @Test + public void links() throws Exception { + this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andExpect(jsonPath("$._links").exists()); + } + + @Test + public void endpointsAllListed() throws Exception { + for (MvcEndpoint endpoint : this.mvcEndpoints.getEndpoints()) { + String path = endpoint.getPath(); + path = path.startsWith("/") ? path.substring(1) : path; + this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._links.%s.href", path).exists()); + } + } + + @MinimalActuatorHypermediaApplication + @RestController + public static class SpringBootHypermediaApplication { + + @RequestMapping("") + public ResourceSupport home() { + ResourceSupport resource = new ResourceSupport(); + resource.add(linkTo(SpringBootHypermediaApplication.class).slash("/").withSelfRel()); + return resource; + } + + public static void main(String[] args) { + SpringApplication.run(SpringBootHypermediaApplication.class, args); + } + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointMvcIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointMvcIntegrationTests.java index ec42c628ae..4ebb3e1e73 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointMvcIntegrationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointMvcIntegrationTests.java @@ -16,6 +16,9 @@ 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.ElementType; import java.lang.annotation.Retention; @@ -38,6 +41,7 @@ import org.springframework.boot.actuate.endpoint.Endpoint; import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping; import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMappingCustomizer; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration; import org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration; @@ -59,9 +63,6 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.ModelAndView; 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. * @@ -93,18 +94,19 @@ public class EndpointMvcIntegrationTests { @Retention(RetentionPolicy.RUNTIME) @Documented @Import({ EmbeddedServletContainerAutoConfiguration.class, - ServerPropertiesAutoConfiguration.class, - DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class }) + ServerPropertiesAutoConfiguration.class, + DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, + JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class }) protected static @interface MinimalWebConfiguration { } @Configuration @MinimalWebConfiguration - @Import({ ManagementServerPropertiesAutoConfiguration.class, - EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class }) + @Import({ ManagementServerPropertiesAutoConfiguration.class, JacksonAutoConfiguration.class, + EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class }) @RestController protected static class Application { diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java index 0d22296a90..cb20ac6e8b 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java @@ -16,6 +16,18 @@ 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.net.SocketException; import java.net.URI; @@ -39,6 +51,7 @@ import org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.ShutdownMvcEndpoint; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration; import org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration; @@ -71,18 +84,6 @@ import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; 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}. * @@ -228,6 +229,7 @@ public class EndpointWebMvcAutoConfigurationTests { ServerPortConfig.class, PropertyPlaceholderAutoConfiguration.class, ManagementServerPropertiesAutoConfiguration.class, ServerPropertiesAutoConfiguration.class, + JacksonAutoConfiguration.class, EmbeddedServletContainerAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, @@ -244,7 +246,7 @@ public class EndpointWebMvcAutoConfigurationTests { this.applicationContext.register(RootConfig.class, BaseConfiguration.class, ServerPortConfig.class, EndpointWebMvcAutoConfiguration.class); new ServerPortInfoApplicationContextInitializer() - .initialize(this.applicationContext); + .initialize(this.applicationContext); this.applicationContext.refresh(); Integer localServerPort = this.applicationContext.getEnvironment().getProperty( "local.server.port", Integer.class); @@ -260,7 +262,7 @@ public class EndpointWebMvcAutoConfigurationTests { @Test public void portPropertiesOnDifferentPort() throws Exception { new ServerPortInfoApplicationContextInitializer() - .initialize(this.applicationContext); + .initialize(this.applicationContext); this.applicationContext.register(RootConfig.class, DifferentPortConfig.class, BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class); @@ -295,7 +297,7 @@ public class EndpointWebMvcAutoConfigurationTests { this.applicationContext.refresh(); // /health, /metrics, /env (/shutdown is disabled by default) assertThat(this.applicationContext.getBeansOfType(MvcEndpoint.class).size(), - is(equalTo(4))); + is(equalTo(6))); } @Test @@ -421,12 +423,13 @@ public class EndpointWebMvcAutoConfigurationTests { @Configuration @Import({ PropertyPlaceholderAutoConfiguration.class, - EmbeddedServletContainerAutoConfiguration.class, - EndpointAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, - ManagementServerPropertiesAutoConfiguration.class, - ServerPropertiesAutoConfiguration.class, WebMvcAutoConfiguration.class }) + EmbeddedServletContainerAutoConfiguration.class, + JacksonAutoConfiguration.class, + EndpointAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + DispatcherServletAutoConfiguration.class, + ManagementServerPropertiesAutoConfiguration.class, + ServerPropertiesAutoConfiguration.class, WebMvcAutoConfiguration.class }) protected static class BaseConfiguration { } @@ -584,7 +587,7 @@ public class EndpointWebMvcAutoConfigurationTests { } private static class GrabManagementPort implements - ApplicationListener { + ApplicationListener { private ApplicationContext rootContext; diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthMvcEndpointAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthMvcEndpointAutoConfigurationTests.java index 787ad9d3ed..2aaeaa6d98 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthMvcEndpointAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthMvcEndpointAutoConfigurationTests.java @@ -16,6 +16,8 @@ package org.springframework.boot.actuate.autoconfigure; +import static org.junit.Assert.assertEquals; + import org.junit.After; import org.junit.Test; import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint; @@ -23,14 +25,14 @@ import org.springframework.boot.actuate.health.AbstractHealthIndicator; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Health.Builder; import org.springframework.boot.actuate.health.Status; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration; +import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.mock.web.MockServletContext; import org.springframework.stereotype.Component; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; -import static org.junit.Assert.assertEquals; - /** * Tests for {@link EndpointWebMvcAutoConfiguration} of the {@link HealthMvcEndpoint}. * @@ -53,6 +55,8 @@ public class HealthMvcEndpointAutoConfigurationTests { this.context.setServletContext(new MockServletContext()); this.context.register(SecurityAutoConfiguration.class, ManagementServerPropertiesAutoConfiguration.class, + JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, TestHealthIndicator.class); this.context.refresh(); @@ -67,6 +71,8 @@ public class HealthMvcEndpointAutoConfigurationTests { this.context = new AnnotationConfigWebApplicationContext(); this.context.setServletContext(new MockServletContext()); this.context.register(SecurityAutoConfiguration.class, + JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, ManagementServerPropertiesAutoConfiguration.class, EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, TestHealthIndicator.class); diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementSecurityAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementSecurityAutoConfigurationTests.java index 4097b46669..f42231ef27 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementSecurityAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementSecurityAutoConfigurationTests.java @@ -16,6 +16,13 @@ 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 org.hamcrest.Matchers; @@ -23,9 +30,11 @@ import org.junit.After; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration; import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration; import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -51,13 +60,6 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.util.StringUtils; 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}. * @@ -81,6 +83,7 @@ public class ManagementSecurityAutoConfigurationTests { this.context.setServletContext(new MockServletContext()); this.context.register(SecurityAutoConfiguration.class, ManagementSecurityAutoConfiguration.class, + JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, ManagementServerPropertiesAutoConfiguration.class, @@ -109,6 +112,7 @@ public class ManagementSecurityAutoConfigurationTests { this.context.setServletContext(new MockServletContext()); this.context.register(EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, + JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, ManagementServerPropertiesAutoConfiguration.class, SecurityAutoConfiguration.class, @@ -118,7 +122,7 @@ public class ManagementSecurityAutoConfigurationTests { UserDetails user = getUser(); assertTrue(user.getAuthorities().containsAll( AuthorityUtils - .commaSeparatedStringToAuthorityList("ROLE_USER,ROLE_ADMIN"))); + .commaSeparatedStringToAuthorityList("ROLE_USER,ROLE_ADMIN"))); } private UserDetails getUser() { @@ -153,6 +157,7 @@ public class ManagementSecurityAutoConfigurationTests { this.context = new AnnotationConfigWebApplicationContext(); this.context.setServletContext(new MockServletContext()); this.context.register(HttpMessageConvertersAutoConfiguration.class, + JacksonAutoConfiguration.class, EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, ManagementServerPropertiesAutoConfiguration.class, SecurityAutoConfiguration.class, @@ -203,9 +208,11 @@ public class ManagementSecurityAutoConfigurationTests { this.context.register(AuthenticationConfig.class, SecurityAutoConfiguration.class, ManagementSecurityAutoConfiguration.class, + JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, ManagementServerPropertiesAutoConfiguration.class, + WebMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); this.context.refresh(); @@ -214,20 +221,20 @@ public class ManagementSecurityAutoConfigurationTests { .addFilters(filter).build(); // no user (Main) - mockMvc.perform(MockMvcRequestBuilders.get("/")) - .andExpect(MockMvcResultMatchers.status().isUnauthorized()) - .andExpect(springAuthenticateRealmHeader()); + mockMvc.perform(MockMvcRequestBuilders.get("/home")) + .andExpect(MockMvcResultMatchers.status().isUnauthorized()) + .andExpect(springAuthenticateRealmHeader()); // invalid user (Main) mockMvc.perform( - MockMvcRequestBuilders.get("/").header("authorization", "Basic xxx")) + MockMvcRequestBuilders.get("/home").header("authorization", "Basic xxx")) .andExpect(MockMvcResultMatchers.status().isUnauthorized()) .andExpect(springAuthenticateRealmHeader()); // no user (Management) mockMvc.perform(MockMvcRequestBuilders.get("/beans")) - .andExpect(MockMvcResultMatchers.status().isUnauthorized()) - .andExpect(springAuthenticateRealmHeader()); + .andExpect(MockMvcResultMatchers.status().isUnauthorized()) + .andExpect(springAuthenticateRealmHeader()); // invalid user (Management) mockMvc.perform( @@ -247,7 +254,7 @@ public class ManagementSecurityAutoConfigurationTests { @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("user").password("password") - .roles("USER"); + .roles("USER"); } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MinimalActuatorHypermediaApplication.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MinimalActuatorHypermediaApplication.java new file mode 100644 index 0000000000..00b92c8a4c --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MinimalActuatorHypermediaApplication.java @@ -0,0 +1,55 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration; +import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration; +import org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration; +import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration; +import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * @author Dave Syer + * + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Configuration +@Import({ ServerPropertiesAutoConfiguration.class, + ManagementServerPropertiesAutoConfiguration.class, + EmbeddedServletContainerAutoConfiguration.class, + DispatcherServletAutoConfiguration.class, JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, WebMvcAutoConfiguration.class, + HypermediaAutoConfiguration.class, EndpointAutoConfiguration.class, + EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class }) +public @interface MinimalActuatorHypermediaApplication { + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ServerContextPathHypermediaIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ServerContextPathHypermediaIntegrationTests.java new file mode 100644 index 0000000000..876f97ad0e --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ServerContextPathHypermediaIntegrationTests.java @@ -0,0 +1,82 @@ +package org.springframework.boot.actuate.autoconfigure; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; + +import java.util.Arrays; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.actuate.autoconfigure.ServerContextPathHypermediaIntegrationTests.SpringBootHypermediaApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.TestRestTemplate; +import org.springframework.hateoas.ResourceSupport; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class) +@WebAppConfiguration +@IntegrationTest({ "server.port=0", "server.contextPath=/spring" }) +@DirtiesContext +public class ServerContextPathHypermediaIntegrationTests { + + @Value("${local.server.port}") + private int port; + + @Test + public void links() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); + ResponseEntity entity = new TestRestTemplate().exchange( + "http://localhost:" + this.port + "/spring/", HttpMethod.GET, + new HttpEntity(null, headers), String.class); + assertEquals(HttpStatus.OK, entity.getStatusCode()); + assertTrue("Wrong body: " + entity.getBody(), + entity.getBody().contains("\"_links\":")); + } + + @Test + public void browser() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Arrays.asList(MediaType.TEXT_HTML)); + ResponseEntity entity = new TestRestTemplate().exchange( + "http://localhost:" + this.port + "/spring/", HttpMethod.GET, + new HttpEntity(null, headers), String.class); + assertEquals(HttpStatus.OK, entity.getStatusCode()); + assertTrue("Wrong body: " + entity.getBody(), + entity.getBody().contains(" entity = new TestRestTemplate().exchange( + "http://localhost:" + this.port + "/", HttpMethod.GET, + new HttpEntity(null, headers), String.class); + assertEquals(HttpStatus.OK, entity.getStatusCode()); + assertTrue("Wrong body: " + entity.getBody(), + entity.getBody().contains("\"_links\":")); + } + + @Test + public void browser() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Arrays.asList(MediaType.TEXT_HTML)); + ResponseEntity entity = new TestRestTemplate().exchange( + "http://localhost:" + this.port + "/", HttpMethod.GET, + new HttpEntity(null, headers), String.class); + assertEquals(HttpStatus.OK, entity.getStatusCode()); + assertTrue("Wrong body: " + entity.getBody(), entity.getBody().contains(" 0 ? path : "/"; + this.mockMvc + .perform(get(path).accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$._links.self.href").value( + "http://localhost" + endpoint.getPath())); + } + } + + @MinimalActuatorHypermediaApplication + @Configuration + public static class SpringBootHypermediaApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootHypermediaApplication.class, args); + } + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpointTests.java index 0f866a1cda..e3db7deb29 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpointTests.java @@ -16,6 +16,12 @@ 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.Test; import org.junit.runner.RunWith; @@ -24,6 +30,9 @@ import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfigur import org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration; import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint; import org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpointTests.TestConfiguration; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.context.ConfigurableApplicationContext; @@ -35,13 +44,6 @@ import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; - -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} @@ -70,13 +72,13 @@ public class EnvironmentMvcEndpointTests { @Test public void home() throws Exception { this.mvc.perform(get("/env")).andExpect(status().isOk()) - .andExpect(content().string(containsString("systemProperties"))); + .andExpect(content().string(containsString("systemProperties"))); } @Test public void sub() throws Exception { this.mvc.perform(get("/env/foo")).andExpect(status().isOk()) - .andExpect(content().string(equalToIgnoringCase("bar"))); + .andExpect(content().string(equalToIgnoringCase("bar"))); } @Test @@ -88,13 +90,15 @@ public class EnvironmentMvcEndpointTests { @Test public void regex() throws Exception { this.mvc.perform(get("/env/foo.*")).andExpect(status().isOk()) - .andExpect(content().string(containsString("\"foo\":\"bar\""))) - .andExpect(content().string(containsString("\"fool\":\"baz\""))); + .andExpect(content().string(containsString("\"foo\":\"bar\""))) + .andExpect(content().string(containsString("\"fool\":\"baz\""))); } - @Import({ EndpointWebMvcAutoConfiguration.class, - ManagementServerPropertiesAutoConfiguration.class }) - @EnableWebMvc + @Import({ JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + WebMvcAutoConfiguration.class, + EndpointWebMvcAutoConfiguration.class, + ManagementServerPropertiesAutoConfiguration.class }) @Configuration public static class TestConfiguration { diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/JolokiaMvcEndpointContextPathTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/JolokiaMvcEndpointContextPathTests.java index f50fb4f05c..9b338faabf 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/JolokiaMvcEndpointContextPathTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/JolokiaMvcEndpointContextPathTests.java @@ -16,6 +16,11 @@ 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.Test; import org.junit.runner.RunWith; @@ -25,6 +30,8 @@ import org.springframework.boot.actuate.autoconfigure.JolokiaAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration; import org.springframework.boot.actuate.endpoint.mvc.JolokiaMvcEndpointContextPathTests.Config; import org.springframework.boot.actuate.endpoint.mvc.JolokiaMvcEndpointContextPathTests.ContextPathListener; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.boot.test.SpringApplicationConfiguration; @@ -39,11 +46,6 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; 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 Dave Syer @@ -71,20 +73,22 @@ public class JolokiaMvcEndpointContextPathTests { @Test public void read() throws Exception { this.mvc.perform(get("/admin/jolokia/read/java.lang:type=Memory")) - .andExpect(status().isOk()) - .andExpect(content().string(containsString("NonHeapMemoryUsage"))); + .andExpect(status().isOk()) + .andExpect(content().string(containsString("NonHeapMemoryUsage"))); } @Configuration @EnableConfigurationProperties @EnableWebMvc - @Import({ EndpointWebMvcAutoConfiguration.class, JolokiaAutoConfiguration.class, - ManagementServerPropertiesAutoConfiguration.class }) + @Import({ JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + EndpointWebMvcAutoConfiguration.class, JolokiaAutoConfiguration.class, + ManagementServerPropertiesAutoConfiguration.class }) public static class Config { } public static class ContextPathListener implements - ApplicationContextInitializer { + ApplicationContextInitializer { @Override public void initialize(ConfigurableApplicationContext context) { EnvironmentTestUtils.addEnvironment(context, "management.contextPath:/admin"); diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/JolokiaMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/JolokiaMvcEndpointTests.java index b41647183f..3df45ee534 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/JolokiaMvcEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/JolokiaMvcEndpointTests.java @@ -16,6 +16,14 @@ 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 org.hamcrest.Matcher; @@ -27,6 +35,8 @@ import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfigur import org.springframework.boot.actuate.autoconfigure.JolokiaAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration; import org.springframework.boot.actuate.endpoint.mvc.JolokiaMvcEndpointTests.Config; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.boot.test.SpringApplicationConfiguration; @@ -40,14 +50,6 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; 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} * @@ -84,28 +86,30 @@ public class JolokiaMvcEndpointTests { @Test public void search() throws Exception { this.mvc.perform(get("/jolokia/search/java.lang:*")).andExpect(status().isOk()) - .andExpect(content().string(containsString("GarbageCollector"))); + .andExpect(content().string(containsString("GarbageCollector"))); } @Test public void read() throws Exception { this.mvc.perform(get("/jolokia/read/java.lang:type=Memory")) - .andExpect(status().isOk()) - .andExpect(content().string(containsString("NonHeapMemoryUsage"))); + .andExpect(status().isOk()) + .andExpect(content().string(containsString("NonHeapMemoryUsage"))); } @Test public void list() throws Exception { this.mvc.perform(get("/jolokia/list/java.lang/type=Memory/attr")) - .andExpect(status().isOk()) - .andExpect(content().string(containsString("NonHeapMemoryUsage"))); + .andExpect(status().isOk()) + .andExpect(content().string(containsString("NonHeapMemoryUsage"))); } @Configuration @EnableConfigurationProperties @EnableWebMvc - @Import({ EndpointWebMvcAutoConfiguration.class, JolokiaAutoConfiguration.class, - ManagementServerPropertiesAutoConfiguration.class }) + @Import({ JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + EndpointWebMvcAutoConfiguration.class, JolokiaAutoConfiguration.class, + ManagementServerPropertiesAutoConfiguration.class }) public static class Config { } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpointTests.java index 1d46913733..55a51ceb1f 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpointTests.java @@ -16,6 +16,12 @@ 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.Collection; import java.util.Collections; @@ -30,6 +36,9 @@ import org.springframework.boot.actuate.endpoint.MetricsEndpoint; import org.springframework.boot.actuate.endpoint.PublicMetrics; import org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpointTests.TestConfiguration; import org.springframework.boot.actuate.metrics.Metric; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -39,13 +48,6 @@ import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; - -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} @@ -72,7 +74,7 @@ public class MetricsMvcEndpointTests { @Test public void home() throws Exception { this.mvc.perform(get("/metrics")).andExpect(status().isOk()) - .andExpect(content().string(containsString("\"foo\":1"))); + .andExpect(content().string(containsString("\"foo\":1"))); } @Test @@ -84,7 +86,7 @@ public class MetricsMvcEndpointTests { @Test public void specificMetric() throws Exception { this.mvc.perform(get("/metrics/foo")).andExpect(status().isOk()) - .andExpect(content().string(equalTo("1"))); + .andExpect(content().string(equalTo("1"))); } @Test @@ -100,35 +102,37 @@ public class MetricsMvcEndpointTests { @Test 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()) - .andExpect(content().string(expected)); + .andExpect(content().string(containsString(expected))); } @Test 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()) - .andExpect(content().string(expected)); + .andExpect(content().string(containsString(expected))); } @Test 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()) - .andExpect(content().string(expected)); + .andExpect(content().string(containsString(expected))); } @Test public void specificMetricWithDot() throws Exception { this.mvc.perform(get("/metrics/group2.a")).andExpect(status().isOk()) - .andExpect(content().string("1")); + .andExpect(content().string(containsString("1"))); } - @Import({ EndpointWebMvcAutoConfiguration.class, - ManagementServerPropertiesAutoConfiguration.class }) - @EnableWebMvc + @Import({ JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + EndpointWebMvcAutoConfiguration.class, + WebMvcAutoConfiguration.class, + ManagementServerPropertiesAutoConfiguration.class }) @Configuration public static class TestConfiguration { diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointCorsIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointCorsIntegrationTests.java index 46d32b4e1b..29dd74b717 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointCorsIntegrationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointCorsIntegrationTests.java @@ -16,6 +16,10 @@ 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.Test; import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration; @@ -23,6 +27,7 @@ import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfigur import org.springframework.boot.actuate.autoconfigure.JolokiaAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; import org.springframework.boot.test.EnvironmentTestUtils; @@ -33,10 +38,6 @@ import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.setup.MockMvcBuilders; 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 * @@ -50,7 +51,7 @@ public class MvcEndpointCorsIntegrationTests { public void createContext() { this.context = new AnnotationConfigWebApplicationContext(); this.context.setServletContext(new MockServletContext()); - this.context.register(HttpMessageConvertersAutoConfiguration.class, + this.context.register(JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, ManagementServerPropertiesAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, @@ -62,7 +63,7 @@ public class MvcEndpointCorsIntegrationTests { createMockMvc().perform( options("/beans").header("Origin", "foo.example.com").header( HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")).andExpect( - header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); + header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); } @Test @@ -72,7 +73,7 @@ public class MvcEndpointCorsIntegrationTests { createMockMvc().perform( options("/beans").header("Origin", "bar.example.com").header( HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")).andExpect( - status().isForbidden()); + status().isForbidden()); performAcceptedCorsRequest(); } @@ -99,8 +100,8 @@ public class MvcEndpointCorsIntegrationTests { "endpoints.cors.allowed-origins:foo.example.com"); createMockMvc().perform( options("/beans").header("Origin", "foo.example.com") - .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET") - .header(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Alpha")) + .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET") + .header(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Alpha")) .andExpect(status().isForbidden()); } @@ -110,15 +111,15 @@ public class MvcEndpointCorsIntegrationTests { "endpoints.cors.allowed-origins:foo.example.com", "endpoints.cors.allowed-headers:Alpha,Bravo"); createMockMvc() - .perform( - options("/beans") - .header("Origin", "foo.example.com") - .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET") - .header(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, - "Alpha")) - .andExpect(status().isOk()) - .andExpect( - header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Alpha")); + .perform( + options("/beans") + .header("Origin", "foo.example.com") + .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET") + .header(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, + "Alpha")) + .andExpect(status().isOk()) + .andExpect( + header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Alpha")); } @Test @@ -128,7 +129,7 @@ public class MvcEndpointCorsIntegrationTests { createMockMvc().perform( options("/health").header(HttpHeaders.ORIGIN, "foo.example.com").header( HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "HEAD")).andExpect( - status().isForbidden()); + status().isForbidden()); } @Test @@ -137,10 +138,10 @@ public class MvcEndpointCorsIntegrationTests { "endpoints.cors.allowed-origins:foo.example.com", "endpoints.cors.allowed-methods:GET,HEAD"); createMockMvc() - .perform( - options("/health") - .header(HttpHeaders.ORIGIN, "foo.example.com") - .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "HEAD")) + .perform( + options("/health") + .header(HttpHeaders.ORIGIN, "foo.example.com") + .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "HEAD")) .andExpect(status().isOk()) .andExpect( header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, @@ -172,7 +173,7 @@ public class MvcEndpointCorsIntegrationTests { createMockMvc().perform( options("/jolokia").header("Origin", "bar.example.com").header( HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")).andExpect( - status().isForbidden()); + status().isForbidden()); performAcceptedCorsRequest("/jolokia"); } @@ -189,10 +190,10 @@ public class MvcEndpointCorsIntegrationTests { return createMockMvc() .perform( options(url).header(HttpHeaders.ORIGIN, "foo.example.com") - .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")) - .andExpect( - header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, - "foo.example.com")).andExpect(status().isOk()); + .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")) + .andExpect( + header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, + "foo.example.com")).andExpect(status().isOk()); } } diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 689de2877a..2b20e5c7ab 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -178,6 +178,11 @@ spring-boot-actuator 1.3.0.BUILD-SNAPSHOT + + org.springframework.boot + spring-boot-actuator-docs + 1.3.0.BUILD-SNAPSHOT + org.springframework.boot spring-boot-autoconfigure @@ -1842,6 +1847,11 @@ thymeleaf-extras-springsecurity4 ${thymeleaf-extras-springsecurity4.version} + + org.webjars + hal-browser + b7669f1-1 + org.yaml snakeyaml diff --git a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc index 43e8754c3a..138bbf292b 100644 --- a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc @@ -150,6 +150,28 @@ For example, the following will disable _all_ endpoints except for `info`: endpoints.info.enabled=true ---- +[[production-ready-endpoint-hypermedia]] +=== Hypermedia for MVC Endpoints +If http://projects.spring.io/spring-hateoas[Spring HATEOAS] is on the classpath (e.g. +through the `spring-boot-starter-hateoas` or if you are using +http://projects.spring.io/spring-data-rest[Spring Data REST]) then the HTTP endpoints +from the Actuator are enhanced with hypermedia links, and a "discovery page" is added +with links to all the endpoints. The "discovery page" is actually an endpoint itself, +so it can be disabled along with the rest of the hypermedia by setting +`endpoints.links.enabled=false`. If it is not explicitly disabled the links +endpoint renders a JSON object with a link for each other endpoint, and the default +path is the same as the `management.contentPath` (so "/" by default). + +NOTE: if there is a static home page ("index.html") in your application and the links +endpoint is registered with its default path ("/") then content negotiation will kick in +to determine which content is shown to a client that requests the home page (the +links will show only if the client accepts `application/json`). + +If the https://github.com/mikekelly/hal-browser[HAL Browser] is on the classpath +via its webjar (`org.webjars:hal-browser`), or via the `spring-data-hal-browser` then +the default home page for HTML clients will be the HAL Browser. This is also exposed via an +endpoint ("hal") so it can be disabled and have its path explicitly configured like +the other endpoints. [[production-ready-customizing-endpoints-programmatically]] diff --git a/spring-boot-samples/pom.xml b/spring-boot-samples/pom.xml index 0b4be403c9..00ed00afbd 100644 --- a/spring-boot-samples/pom.xml +++ b/spring-boot-samples/pom.xml @@ -43,6 +43,10 @@ spring-boot-sample-flyway spring-boot-sample-hateoas spring-boot-sample-hornetq + spring-boot-sample-hypermedia + spring-boot-sample-hypermedia-gson + spring-boot-sample-hypermedia-jpa + spring-boot-sample-hypermedia-ui spring-boot-sample-integration spring-boot-sample-jersey spring-boot-sample-jersey1 diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-gson/pom.xml b/spring-boot-samples/spring-boot-sample-hypermedia-gson/pom.xml new file mode 100644 index 0000000000..e8f6fc2c20 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hypermedia-gson/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + spring-boot-sample-hypermedia-gson + jar + + spring-boot-sample-hypermedia-gson + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-samples + 1.3.0.BUILD-SNAPSHOT + + + + UTF-8 + 1.7 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-hateoas + + + com.google.code.gson + gson + + + org.springframework.boot + spring-boot-devtools + true + + + org.springframework.boot + spring-boot-starter-test + test + + + com.jayway.jsonpath + json-path + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-gson/src/main/java/demo/SpringBootHypermediaApplication.java b/spring-boot-samples/spring-boot-sample-hypermedia-gson/src/main/java/demo/SpringBootHypermediaApplication.java new file mode 100644 index 0000000000..954faed6f9 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hypermedia-gson/src/main/java/demo/SpringBootHypermediaApplication.java @@ -0,0 +1,12 @@ +package demo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringBootHypermediaApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootHypermediaApplication.class, args); + } +} diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-gson/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-hypermedia-gson/src/main/resources/application.properties new file mode 100644 index 0000000000..ef0f024d90 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hypermedia-gson/src/main/resources/application.properties @@ -0,0 +1,2 @@ +# management.contextPath=/admin +spring.http.converters.preferred-json-mapper: gson \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-gson/src/test/java/demo/SpringBootHypermediaApplicationTests.java b/spring-boot-samples/spring-boot-sample-hypermedia-gson/src/test/java/demo/SpringBootHypermediaApplicationTests.java new file mode 100644 index 0000000000..54e46efb31 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hypermedia-gson/src/test/java/demo/SpringBootHypermediaApplicationTests.java @@ -0,0 +1,61 @@ +package demo; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.http.MediaType; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class) +@WebAppConfiguration +@TestPropertySource(properties="endpoints.health.sensitive: false") +public class SpringBootHypermediaApplicationTests { + + @Autowired + private WebApplicationContext context; + + private MockMvc mockMvc; + + @Before + public void setUp() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + } + + @Test + public void health() throws Exception { + this.mockMvc + .perform(get("/health").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.links[0].href").value("http://localhost/health")) + .andExpect(jsonPath("$.content.status").exists()); + } + + @Test + public void trace() throws Exception { + this.mockMvc + .perform(get("/trace").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.links[0].href").value("http://localhost/trace")) + .andExpect(jsonPath("$.content").isArray()); + } + + @Test + public void envValue() throws Exception { + this.mockMvc.perform(get("/env/user.home").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._links").doesNotExist()); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-jpa/pom.xml b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/pom.xml new file mode 100644 index 0000000000..b8ead35e0c --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/pom.xml @@ -0,0 +1,82 @@ + + + 4.0.0 + + spring-boot-sample-hypermedia-jpa + jar + + spring-boot-sample-hypermedia-jpa + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-samples + 1.3.0.BUILD-SNAPSHOT + + + + UTF-8 + 1.7 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-hateoas + + + org.springframework.boot + spring-boot-actuator-docs + + + org.springframework.boot + spring-boot-starter-data-rest + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.data + spring-data-rest-hal-browser + + + com.h2database + h2 + + + org.springframework.boot + spring-boot-devtools + true + + + org.springframework.boot + spring-boot-starter-test + test + + + com.jayway.jsonpath + json-path + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/java/demo/Book.java b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/java/demo/Book.java new file mode 100644 index 0000000000..838c1901bd --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/java/demo/Book.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package demo; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +@Entity +public class Book { + @Id + @GeneratedValue + private Long id; + private String title; + public String getTitle() { + return this.title; + } + public void setTitle(String title) { + this.title = title; + } +} \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/java/demo/BookRepository.java b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/java/demo/BookRepository.java new file mode 100644 index 0000000000..6ac159bc81 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/java/demo/BookRepository.java @@ -0,0 +1,22 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package demo; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface BookRepository extends JpaRepository { +} \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/java/demo/JpaHypermediaApplication.java b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/java/demo/JpaHypermediaApplication.java new file mode 100644 index 0000000000..baae2f2a8c --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/java/demo/JpaHypermediaApplication.java @@ -0,0 +1,13 @@ +package demo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class JpaHypermediaApplication { + + public static void main(String[] args) { + SpringApplication.run(JpaHypermediaApplication.class, args); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/resources/application.properties new file mode 100644 index 0000000000..31e1ded85d --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/main/resources/application.properties @@ -0,0 +1,2 @@ +management.contextPath=/admin +endpoints.docs.curies.enabled=true \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/test/java/demo/JpaHypermediaApplicationTests.java b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/test/java/demo/JpaHypermediaApplicationTests.java new file mode 100644 index 0000000000..46ac33e2d9 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/test/java/demo/JpaHypermediaApplicationTests.java @@ -0,0 +1,18 @@ +package demo; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = JpaHypermediaApplication.class) +@WebAppConfiguration +public class JpaHypermediaApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/test/java/demo/VanillaHypermediaIntegrationTests.java b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/test/java/demo/VanillaHypermediaIntegrationTests.java new file mode 100644 index 0000000000..e20d8be0f0 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/test/java/demo/VanillaHypermediaIntegrationTests.java @@ -0,0 +1,74 @@ +package demo; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = JpaHypermediaApplication.class) +@WebAppConfiguration +@DirtiesContext +public class VanillaHypermediaIntegrationTests { + + @Autowired + private WebApplicationContext context; + + @Autowired + private MvcEndpoints mvcEndpoints; + + private MockMvc mockMvc; + + @Before + public void setUp() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + } + + @Test + public void links() throws Exception { + this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andExpect(jsonPath("$._links").exists()); + } + + @Test + public void health() throws Exception { + this.mockMvc.perform(get("/admin/health").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andExpect(jsonPath("$._links").exists()); + } + + @Test + public void adminLinks() throws Exception { + this.mockMvc.perform(get("/admin").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andExpect(jsonPath("$._links").exists()); + } + + @Test + public void docs() throws Exception { + MvcResult response = this.mockMvc.perform(get("/admin/docs/").accept(MediaType.TEXT_HTML)) + .andExpect(status().isOk()).andReturn(); + System.err.println(response.getResponse().getContentAsString()); + } + + @Test + public void browser() throws Exception { + MvcResult response = this.mockMvc.perform(get("/").accept(MediaType.TEXT_HTML)) + .andExpect(status().isFound()).andReturn(); + assertEquals("/browser/index.html#", response.getResponse().getHeaders("location").get(0)); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-ui/pom.xml b/spring-boot-samples/spring-boot-sample-hypermedia-ui/pom.xml new file mode 100644 index 0000000000..2ed2b746cf --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hypermedia-ui/pom.xml @@ -0,0 +1,61 @@ + + + 4.0.0 + + spring-boot-sample-hypermedia-ui + jar + + spring-boot-sample-hypermedia-ui + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-samples + 1.3.0.BUILD-SNAPSHOT + + + + UTF-8 + 1.7 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-hateoas + + + org.springframework.boot + spring-boot-actuator-docs + + + org.springframework.boot + spring-boot-devtools + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-ui/src/main/java/demo/SpringBootHypermediaApplication.java b/spring-boot-samples/spring-boot-sample-hypermedia-ui/src/main/java/demo/SpringBootHypermediaApplication.java new file mode 100644 index 0000000000..954faed6f9 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hypermedia-ui/src/main/java/demo/SpringBootHypermediaApplication.java @@ -0,0 +1,12 @@ +package demo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringBootHypermediaApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootHypermediaApplication.class, args); + } +} diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-ui/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-hypermedia-ui/src/main/resources/application.properties new file mode 100644 index 0000000000..560a46ccde --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hypermedia-ui/src/main/resources/application.properties @@ -0,0 +1 @@ +management.contextPath=/admin \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-ui/src/main/resources/static/index.html b/spring-boot-samples/spring-boot-sample-hypermedia-ui/src/main/resources/static/index.html new file mode 100644 index 0000000000..66a41f708c --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hypermedia-ui/src/main/resources/static/index.html @@ -0,0 +1,5 @@ + + +

Hello World!

+ + \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-ui/src/test/java/demo/HomePageHypermediaApplicationTests.java b/spring-boot-samples/spring-boot-sample-hypermedia-ui/src/test/java/demo/HomePageHypermediaApplicationTests.java new file mode 100644 index 0000000000..e68f6aafe7 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hypermedia-ui/src/test/java/demo/HomePageHypermediaApplicationTests.java @@ -0,0 +1,65 @@ +package demo; + +import static org.junit.Assert.assertTrue; + +import java.net.URI; +import java.util.Arrays; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.TestRestTemplate; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.RequestEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class) +@WebAppConfiguration +@IntegrationTest({ "management.contextPath=", "server.port=0" }) +public class HomePageHypermediaApplicationTests { + + @Value("${local.server.port}") + private int port; + + @Test + public void home() { + String response = new TestRestTemplate().getForObject("http://localhost:" + port, + String.class); + assertTrue("Wrong body: " + response, response.contains("Hello World")); + } + + @Test + public void links() { + String response = new TestRestTemplate().getForObject("http://localhost:" + port + "/links", + String.class); + assertTrue("Wrong body: " + response, response.contains("\"_links\":")); + } + + @Test + public void linksWithJson() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); + ResponseEntity response = new TestRestTemplate().exchange( + new RequestEntity(headers , HttpMethod.GET, new URI("http://localhost:" + + port + "/links")), String.class); + assertTrue("Wrong body: " + response, response.getBody().contains("\"_links\":")); + } + + @Test + public void homeWithHtml() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Arrays.asList(MediaType.TEXT_HTML)); + ResponseEntity response = new TestRestTemplate().exchange( + new RequestEntity(headers , HttpMethod.GET, new URI("http://localhost:" + + port)), String.class); + assertTrue("Wrong body: " + response, response.getBody().contains("Hello World")); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-ui/src/test/java/demo/SpringBootHypermediaApplicationTests.java b/spring-boot-samples/spring-boot-sample-hypermedia-ui/src/test/java/demo/SpringBootHypermediaApplicationTests.java new file mode 100644 index 0000000000..1cf70aeee2 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hypermedia-ui/src/test/java/demo/SpringBootHypermediaApplicationTests.java @@ -0,0 +1,18 @@ +package demo; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class) +@WebAppConfiguration +public class SpringBootHypermediaApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/spring-boot-samples/spring-boot-sample-hypermedia/pom.xml b/spring-boot-samples/spring-boot-sample-hypermedia/pom.xml new file mode 100644 index 0000000000..a937dc548c --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hypermedia/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + spring-boot-sample-hypermedia + jar + + spring-boot-sample-hypermedia + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-samples + 1.3.0.BUILD-SNAPSHOT + + + + UTF-8 + 1.7 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-hateoas + + + org.springframework.boot + spring-boot-actuator-docs + + + org.webjars + hal-browser + + + org.springframework.boot + spring-boot-devtools + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/spring-boot-samples/spring-boot-sample-hypermedia/src/main/java/demo/SpringBootHypermediaApplication.java b/spring-boot-samples/spring-boot-sample-hypermedia/src/main/java/demo/SpringBootHypermediaApplication.java new file mode 100644 index 0000000000..954faed6f9 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hypermedia/src/main/java/demo/SpringBootHypermediaApplication.java @@ -0,0 +1,12 @@ +package demo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringBootHypermediaApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootHypermediaApplication.class, args); + } +} diff --git a/spring-boot-samples/spring-boot-sample-hypermedia/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-hypermedia/src/main/resources/application.properties new file mode 100644 index 0000000000..39f6a14b37 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hypermedia/src/main/resources/application.properties @@ -0,0 +1,2 @@ +# management.contextPath=/admin +endpoints.docs.curies.enabled=true \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-hypermedia/src/test/java/demo/HomePageHypermediaApplicationTests.java b/spring-boot-samples/spring-boot-sample-hypermedia/src/test/java/demo/HomePageHypermediaApplicationTests.java new file mode 100644 index 0000000000..676a937971 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hypermedia/src/test/java/demo/HomePageHypermediaApplicationTests.java @@ -0,0 +1,59 @@ +package demo; + +import static org.junit.Assert.assertTrue; + +import java.net.URI; +import java.util.Arrays; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.TestRestTemplate; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.RequestEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class) +@WebAppConfiguration +@IntegrationTest("server.port=0") +public class HomePageHypermediaApplicationTests { + + @Value("${local.server.port}") + private int port; + + @Test + public void home() { + String response = new TestRestTemplate().getForObject("http://localhost:" + port, + String.class); + assertTrue("Wrong body: " + response, response.contains("\"_links\":")); + assertTrue("Wrong body: " + response, response.contains("\"curies\":")); + } + + @Test + public void homeWithJson() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); + ResponseEntity response = new TestRestTemplate().exchange( + new RequestEntity(headers , HttpMethod.GET, new URI("http://localhost:" + + port + "/")), String.class); + assertTrue("Wrong body: " + response, response.getBody().contains("\"_links\":")); + } + + @Test + public void homeWithHtml() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Arrays.asList(MediaType.TEXT_HTML)); + ResponseEntity response = new TestRestTemplate().exchange( + new RequestEntity(headers , HttpMethod.GET, new URI("http://localhost:" + + port)), String.class); + assertTrue("Wrong body: " + response, response.getBody().contains("HAL Browser")); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-hypermedia/src/test/java/demo/SpringBootHypermediaApplicationTests.java b/spring-boot-samples/spring-boot-sample-hypermedia/src/test/java/demo/SpringBootHypermediaApplicationTests.java new file mode 100644 index 0000000000..fc06e71649 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hypermedia/src/test/java/demo/SpringBootHypermediaApplicationTests.java @@ -0,0 +1,18 @@ +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() { + } + +}