Merge branch 'gh-8042'

pull/11066/head
Andy Wilkinson 7 years ago
commit 3fb86b261e

@ -419,6 +419,11 @@
<artifactId>spring-integration-jmx</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
@ -435,4 +440,157 @@
<optional>true</optional>
</dependency>
</dependencies>
<profiles>
<profile>
<id>full</id>
<activation>
<property>
<name>full</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<dependencies>
<dependency>
<groupId>ant-contrib</groupId>
<artifactId>ant-contrib</artifactId>
<version>1.0b3</version>
<exclusions>
<exclusion>
<groupId>ant</groupId>
<artifactId>ant</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant-nodeps</artifactId>
<version>1.8.1</version>
</dependency>
<dependency>
<groupId>org.tigris.antelope</groupId>
<artifactId>antelopetasks</artifactId>
<version>3.2.10</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>set-up-maven-properties</id>
<phase>prepare-package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<exportAntProperties>true</exportAntProperties>
<target>
<taskdef resource="net/sf/antcontrib/antcontrib.properties" />
<taskdef name="stringutil" classname="ise.antelope.tasks.StringUtilTask" />
<var name="version-type" value="${project.version}" />
<propertyregex property="version-type" override="true"
input="${version-type}" regexp=".*\.(.*)" replace="\1" />
<propertyregex property="version-type" override="true"
input="${version-type}" regexp="(M)\d+" replace="MILESTONE" />
<propertyregex property="version-type" override="true"
input="${version-type}" regexp="(RC)\d+" replace="MILESTONE" />
<propertyregex property="version-type" override="true"
input="${version-type}" regexp="BUILD-(.*)" replace="SNAPSHOT" />
<var name="github-tag" value="v${project.version}" />
<propertyregex property="github-tag" override="true"
input="${github-tag}" regexp=".*SNAPSHOT" replace="master" />
</target>
</configuration>
</execution>
<execution>
<id>package-docs-zip</id>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<zip
destfile="${project.build.directory}/${project.artifactId}-${project.version}-docs.zip">
<zipfileset dir="${project.build.directory}/generated-docs"
includes="index.html" prefix="html" />
<mappedresources>
<fileset dir="${project.build.directory}/generated-docs"
includes="index.pdf" />
<globmapper from="index.pdf"
to="pdf/spring-boot-actuator-web-api.pdf" />
</mappedresources>
</zip>
</target>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<executions>
<execution>
<id>generate-html-documentation</id>
<phase>prepare-package</phase>
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<backend>html</backend>
</configuration>
</execution>
<execution>
<id>generate-pdf-documentation</id>
<phase>prepare-package</phase>
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<backend>pdf</backend>
</configuration>
</execution>
</executions>
<configuration>
<sourceDocumentName>index.adoc</sourceDocumentName>
<attributes>
<version-type>${version-type}</version-type>
<version>${project.version}</version>
<snippets>${project.build.directory}/generated-snippets/</snippets>
</attributes>
</configuration>
<dependencies>
<dependency>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctorj-pdf</artifactId>
<version>1.5.0-alpha.11</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>attach-zip</id>
<goals>
<goal>attach-artifact</goal>
</goals>
<configuration>
<artifacts>
<artifact>
<file>${project.build.directory}/${project.artifactId}-${project.version}-docs.zip</file>
<type>zip</type>
<classifier>docs</classifier>
</artifact>
</artifacts>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

@ -0,0 +1,45 @@
[[audit-events]]
= Audit Events (`auditevents`)
The `auditevents` endpoint provides information about the application's audit events.
[[audit-events-retrieving]]
== Retrieving Audit Events
To retrieve the audit events, make a `GET` request to `/application/auditevents`, as shown
in the following curl-based example:
include::{snippets}auditevents/filtered/curl-request.adoc[]
The preceding example retrieves `logout` events for the principal, `alice`, that occurred
after 09:37 on 7 November 2017 in the UTC timezone. The resulting response is similar to
the following:
include::{snippets}auditevents/filtered/http-response.adoc[]
[[audit-events-retrieving-query-parameters]]
=== Query Parameters
The endpoint uses query parameters to limit the events that it returns. The following
table shows the supported query parameters:
[cols="2,4"]
include::{snippets}auditevents/filtered/request-parameters.adoc[]
The `after` parameter is required. You can also use one or both of the `principal` and
`type` parameters to further limit the results.
[[audit-events-retrieving-response-structure]]
=== Response Structure
The response contains details of all of the audit events that matched the query. The
following table describes the structure of the response:
[cols="2,1,3"]
include::{snippets}auditevents/after/response-fields.adoc[]

@ -0,0 +1,29 @@
[[beans]]
= Beans (`beans`)
The `beans` endpoint provides information about the application's beans.
[[beans-retrieving]]
== Retrieving the Beans
To retrieve the beans, make a `GET` request to `/application/beans`, as shown in the
following curl-based example:
include::{snippets}beans/curl-request.adoc[]
The resulting response is similar to the following:
include::{snippets}beans/http-response.adoc[]
[[beans-retrieving-response-structure]]
=== Response Structure
The response contains details of the application's beans. The following table describes
the structure of the response:
[cols="2,1,3"]
include::{snippets}beans/response-fields.adoc[]

@ -0,0 +1,30 @@
[[conditions]]
= Conditions Evaluation Report (`conditions`)
The `conditions` endpoint provides information about the evaluation of conditions on
configuration and auto-configuration classes.
[[conditions-retrieving]]
== Retrieving the Report
To retrieve the report, make a `GET` request to `/application/conditions`, as shown in
the following curl-based example:
include::{snippets}conditions/curl-request.adoc[]
The resulting response is similar to the following:
include::{snippets}conditions/http-response.adoc[]
[[conditions-retrieving-response-structure]]
=== Response Structure
The response contains details of the application's condition evaluation. The following
table describes the structure of the response:
[cols="3,1,3"]
include::{snippets}conditions/response-fields.adoc[]

@ -0,0 +1,30 @@
[[configprops]]
= Configuration Properties (`configprops`)
The `configprops` endpoint provides information about the application's
`@ConfigurationProperties` beans.
[[configprops-retrieving]]
== Retrieving the `@ConfigurationProperties` Bean
To retrieve the `@ConfigurationProperties` beans, make a `GET` request to
`/application/configprops`, as shown in the following curl-based example:
include::{snippets}configprops/curl-request.adoc[]
The resulting response is similar to the following:
include::{snippets}configprops/http-response.adoc[]
[[configprops-retrieving-response-structure]]
=== Response Structure
The response contains details of the application's `@ConfigurationProperties` beans. The
following table describes the structure of the response:
[cols="2,1,3"]
include::{snippets}configprops/response-fields.adoc[]

@ -0,0 +1,55 @@
[[env]]
= Environment (`env`)
The `env` endpoint provides information about the application's `Environment`.
[[env-entire]]
== Retrieving the Entire Environment
To retrieve the entire environment, make a `GET` request to `/application/env`, as shown in
the following curl-based example:
include::{snippets}env/all/curl-request.adoc[]
The resulting response is similar to the following:
include::{snippets}env/all/http-response.adoc[]
[[env-entire-response-structure]]
=== Response Structure
The response contains details of the application's `Environment`. The following table
describes the structure of the response:
[cols="3,1,3"]
include::{snippets}env/all/response-fields.adoc[]
[[env-single-property]]
== Retrieving a Single Property
To retrieve a single property, make a `GET` request to `/application/env/{property.name}`,
as shown in the following curl-based example:
include::{snippets}env/single/curl-request.adoc[]
The preceding example retrieves information about the property named
`com.example.cache.max-size`. The resulting response is similar to the following:
include::{snippets}env/single/http-response.adoc[]
[[env-single-response-structure]]
=== Response Structure
The response contains details of the requested property. The following table describes the
structure of the response:
[cols="3,1,3"]
include::{snippets}env/single/response-fields.adoc[]

@ -0,0 +1,29 @@
[[flyway]]
= Flyway (`flyway`)
The `flyway` endpoint provides information about database migrations performed by Flyway.
[[flyway-retrieving]]
== Retrieving the Migrations
To retrieve the migrations, make a `GET` request to `/application/flyway`, as shown in the
following curl-based example:
include::{snippets}flyway/curl-request.adoc[]
The resulting response is similar to the following:
include::{snippets}flyway/http-response.adoc[]
[[flyway-retrieving-response-structure]]
=== Response Structure
The response contains details of the application's Flyway migrations. The following table
describes the structure of the response:
[cols="2,1,3"]
include::{snippets}flyway/response-fields.adoc[]

@ -0,0 +1,29 @@
[[health]]
= Health (`health`)
The `health` endpoint provides detailed information about the health of the application.
[[health-retrieving]]
== Retrieving the Health
To retrieve the health of the application, make a `GET` request to `/application/health`,
as shown in the following curl-based example:
include::{snippets}health/curl-request.adoc[]
The resulting response is similar to the following:
include::{snippets}health/http-response.adoc[]
[[health-retrieving-response-structure]]
=== Response Structure
The response contains details of the health of the application. The following table
describes the structure of the response:
[cols="2,1,3"]
include::{snippets}health/response-fields.adoc[]

@ -0,0 +1,20 @@
[[heapdump]]
= Heap Dump (`heapdump`)
The `heapdump` endpoint provides a heap dump from the application's JVM.
[[heapdump-retrieving]]
== Retrieving the Heap Dump
To retrieve the heap dump, make a `GET` request to `/application/heapdump`. The response
is binary data in https://docs.oracle.com/javase/8/docs/technotes/samples/hprof.html[
HPROF] format and can be large. Typically, you should save the response to disk for
subsequent analysis. When using curl, this can be achieved by using the `-O` option,
as shown in the following example:
include::{snippets}heapdump/curl-request.adoc[]
The preceding example results in a file named `heapdump` being written to the current
working directory.

@ -0,0 +1,47 @@
[[info]]
= Info (`info`)
The `info` endpoint provides general information about the application.
[[info-retrieving]]
== Retrieving the Info
To retrieve the information about the application, make a `GET` request to
`/application/info`, as shown in the following curl-based example:
include::{snippets}info/curl-request.adoc[]
The resulting response is similar to the following:
include::{snippets}info/http-response.adoc[]
[[info-retrieving-response-structure]]
=== Response Structure
The response contains general information about the application. Each section of the
response is contributed by an `InfoContributor`. Spring Boot provides `build` and `git`
contributions.
[[info-retrieving-response-structure-build]]
==== `build` Response Structure
The following table describe the structure of the `build` section of the response:
[cols="2,1,3"]
include::{snippets}info/response-fields-beneath-build.adoc[]
[[info-retrieving-response-structure-git]]
==== `git` Response Structure
The following table describes the structure of the `git` section of the response:
[cols="2,1,3"]
include::{snippets}info/response-fields-beneath-git.adoc[]

@ -0,0 +1,30 @@
[[liquibase]]
= Liquibase (`liquibase`)
The `liquibase` endpoint provides information about database change sets applied by
Liquibase.
[[liquibase-retrieving]]
== Retrieving the Changes
To retrieve the changes, make a `GET` request to `/application/liquibase`, as shown in the
following curl-based example:
include::{snippets}liquibase/curl-request.adoc[]
The resulting response is similar to the following:
include::{snippets}liquibase/http-response.adoc[]
[[liquibase-retrieving-response-structure]]
=== Response Structure
The response contains details of the application's Liquibase change sets. The following
table describes the structure of the response:
[cols="2,1,3"]
include::{snippets}liquibase/response-fields.adoc[]

@ -0,0 +1,35 @@
[[log-file]]
= Log File (`logfile`)
The `logfile` endpoint provides access to the contents of the application's log file.
[[logfile-retrieving]]
== Retrieving the Log File
To retrieve the log file, make a `GET` request to `/application/logfile`, as shown in the
following curl-based example:
include::{snippets}logfile/entire/curl-request.adoc[]
The resulting response is similar to the following:
include::{snippets}logfile/entire/http-response.adoc[]
[[logfile-retrieving-part]]
== Retrieving Part of the Log File
NOTE: Retrieving part of the log file is not supported when using Jersey.
To retrieve part of the log file, make a `GET` request to `/application/logfile` by using
the `Range` header, as shown in the following curl-based example:
include::{snippets}logfile/range/curl-request.adoc[]
The preceding example retrieves the first 1024 bytes of the log file. The resulting
response is similar to the following:
include::{snippets}logfile/range/http-response.adoc[]

@ -0,0 +1,93 @@
[[loggers]]
= Loggers (`loggers`)
The `loggers` endpoint provides access to the application's loggers and the configuration
of their levels.
[[loggers-all]]
== Retrieving All Loggers
To retrieve the application's loggers, make a `GET` request to `/application/loggers`, as
shown in the following curl-based example:
include::{snippets}loggers/all/curl-request.adoc[]
The resulting response is similar to the following:
include::{snippets}loggers/all/http-response.adoc[]
[[loggers-all-response-structure]]
=== Response Structure
The response contains details of the application's loggers. The following table describes
the structure of the response:
[cols="3,1,3"]
include::{snippets}loggers/all/response-fields.adoc[]
[[loggers-single]]
== Retrieving a Single Logger
To retrieve a single logger, make a `GET` request to `/application/loggers/{logger.name}`,
as shown in the following curl-based example:
include::{snippets}loggers/single/curl-request.adoc[]
The preceding example retrieves information about the logger named `com.example`. The
resulting response is similar to the following:
include::{snippets}loggers/single/http-response.adoc[]
[[loggerse-single-response-structure]]
=== Response Structure
The response contains details of the requested logger. The following table describes the
structure of the response:
[cols="3,1,3"]
include::{snippets}loggers/single/response-fields.adoc[]
[[loggers-setting-level]]
== Setting a Log Level
To set the level of a logger, make a `POST` request to
`/application/loggers/{logger.name}` with a JSON body that specifies the configured level
for the logger, as shown in the following curl-based example:
include::{snippets}loggers/set/curl-request.adoc[]
The preceding example sets the `configuredLevel` of the `com.example` logger to `DEBUG`.
[[loggers-setting-level-request-structure]]
=== Request Structure
The request specifies the desired level of the logger. The following table describes the
structure of the request:
[cols="3,1,3"]
include::{snippets}loggers/set/request-fields.adoc[]
[[loggers-clearing-level]]
== Clearing a Log Level
To clear the level of a logger, make a `POST` request to
`/application/loggers/{logger.name}` with a JSON body containing an empty object, as shown
in the following curl-based example:
include::{snippets}loggers/clear/curl-request.adoc[]
The preceding example clears the configured level of the `com.example` logger.

@ -0,0 +1,80 @@
[[metrics]]
= Metrics (`metrics`)
The `metrics` endpoint provides access to application metrics.
[[metrics-retrieving-names]]
== Retrieving Metric Names
To retrieve the names of the available metrics, make a `GET` request to
`/application/metrics`, as shown in the following curl-based example:
include::{snippets}metrics/names/curl-request.adoc[]
The resulting response is similar to the following:
include::{snippets}metrics/names/http-response.adoc[]
[[metrics-retrieving-names-response-structure]]
=== Response Structure
The response contains details of the metric names. The following table describes the
structure of the response:
[cols="3,1,2"]
include::{snippets}metrics/names/response-fields.adoc[]
[[metrics-retrieving-metric]]
== Retrieving a Metric
To retrieve a metric, make a `GET` request to `/application/metrics/{metric.name}`, as
shown in the following curl-based example:
include::{snippets}metrics/metric/curl-request.adoc[]
The preceding example retrieves information about the metric named `jvm.memory.max`. The
resulting response is similar to the following:
include::{snippets}metrics/metric/http-response.adoc[]
[[metrics-retrieving-metric-query-parameters]]
=== Query Parameters
The endpoint uses query parameters to <<metrics-drilling-down,drill down>> into a metric
by using its tags. The following table shows the single supported query parameter:
[cols="2,4"]
include::{snippets}metrics/metric-with-tags/request-parameters.adoc[]
[[metrics-retrieving-metric-response-structure]]
=== Response structure
The response contains details of the metric. The following table describes the structure
of the response:
include::{snippets}metrics/metric/response-fields.adoc[]
[[metrics-drilling-down]]
== Drilling Down
To drill down into a metric, make a `GET` request to `/application/metrics/{metric.name}`
using the `tag` query parameter, as shown in the following curl-based example:
include::{snippets}metrics/metric-with-tags/curl-request.adoc[]
The preceding example retrieves the `jvm.memory.max` metric, where the `area` tag has a
value of `nonheap` and the `id` attribute has a value of `Code Cache`. The resulting
response is similar to the following:
include::{snippets}metrics/metric-with-tags/http-response.adoc[]

@ -0,0 +1,19 @@
[[prometheus]]
= Prometheus (`prometheus`)
The `prometheus` endpoint provides Spring Boot application's metrics in the format
required for scraping by a Prometheus server.
[[prometheus-retrieving]]
== Retrieving the Metrics
To retrieve the metrics, make a `GET` request to `/application/prometheus`, as shown in
the following curl-based example:
include::{snippets}prometheus/curl-request.adoc[]
The resulting response is similar to the following:
include::{snippets}prometheus/http-response.adoc[]

@ -0,0 +1,30 @@
[[scheduled-tasks]]
= Scheduled Tasks (`scheduledtasks`)
The `scheduledtasks` endpoint provides information about the application's scheduled
tasks.
[[scheduled-tasks-retrieving]]
== Retrieving the Scheduled Tasks
To retrieve the scheduled tasks, make a `GET` request to `/application/scheduledtasks`,
as shown in the following curl-based example:
include::{snippets}scheduled-tasks/curl-request.adoc[]
The resulting response is similar to the following:
include::{snippets}scheduled-tasks/http-response.adoc[]
[[scheduled-tasks-retrieving-response-structure]]
=== Response Structure
The response contains details of the application's scheduled tasks. The following table
describes the structure of the response:
[cols="2,1,3"]
include::{snippets}scheduled-tasks/response-fields.adoc[]

@ -0,0 +1,84 @@
[[sessions]]
= Sessions (`sessions`)
The `sessions` endpoint provides information about the application's HTTP sessions that
are managed by Spring Session.
[[sessions-retrieving]]
== Retrieving Sessions
To retrieve the sessions, make a `GET` request to `/application/sessions`, as shown in the
following curl-based example:
include::{snippets}sessions/username/curl-request.adoc[]
The preceding examples retrieves all of the sessions for the user whose username is
`alice`.
The resulting response is similar to the following:
include::{snippets}sessions/username/http-response.adoc[]
[[sessions-retrieving-query-parameters]]
=== Query Parameters
The endpoint uses query parameters to limit the sessions that it returns. The following
table shows the single required query parameter:
[cols="2,4"]
include::{snippets}sessions/username/request-parameters.adoc[]
[[sessions-retrieving-response-structure]]
=== Response Structure
The response contains details of the matching sessions. The following table describes the
structure of the response:
[cols="3,1,3"]
include::{snippets}sessions/username/response-fields.adoc[]
[[sessions-retrieving-id]]
== Retrieving a Single Session
To retrieve a single session, make a `GET` request to `/application/sessions/{id}`, as
shown in the following curl-based example:
include::{snippets}sessions/id/curl-request.adoc[]
The preceding example retrieves the session with the `id` of
`4db5efcc-99cb-4d05-a52c-b49acfbb7ea9`. The resulting response is similar to the
following:
include::{snippets}sessions/id/http-response.adoc[]
[[sessions-retrieving-id-response-structure]]
=== Response Structure
The response contains details of the requested session. The following table describes the
structure of the response:
[cols="3,1,3"]
include::{snippets}sessions/id/response-fields.adoc[]
[[sessions-deleting]]
== Deleting a Session
To delete a session, make a `DELETE` request to `/application/sessions/{id}`, as shown in
the following curl-based example:
include::{snippets}sessions/delete/curl-request.adoc[]
The preceding example deletes the session with the `id` of
`4db5efcc-99cb-4d05-a52c-b49acfbb7ea9`.

@ -0,0 +1,29 @@
[[shutdown]]
= Shutdown (`shutdown`)
The `shutdown` endpoint is used to shut down the application.
[[shutdown-shutting-down]]
== Shutting Down the Application
To shut down the application, make a `POST` request to `/application/shutdown`, as shown
in the following curl-based example:
include::{snippets}shutdown/curl-request.adoc[]
A response similar to the following is produced:
include::{snippets}shutdown/http-response.adoc[]
[[shutdowm-shutting-down-response-structure]]
=== Response Structure
The response contains details of the result of the shutdown request. The following table
describes the structure of the response:
[cols="3,1,3"]
include::{snippets}shutdown/response-fields.adoc[]

@ -0,0 +1,29 @@
[[status]]
= Status (`status`)
The `status` endpoint provides an overview of the status of the application.
[[status-retrieving]]
== Retrieving the Status
To retrieve the status of the application, make a `GET` request to `/application/status`,
as shown in the following curl-based example:
include::{snippets}status/curl-request.adoc[]
The resulting response is similar to the following:
include::{snippets}status/http-response.adoc[]
[[status-retrieving-response-structure]]
=== Response Structure
The response contains the status of the application. The following table describes the
structure of the response:
[cols="2,1,3"]
include::{snippets}status/response-fields.adoc[]

@ -0,0 +1,29 @@
[[threaddump]]
= Thread Dump (`threaddump`)
The `threaddump` endpoint provides a thread dump from the application's JVM.
[[threaddump-retrieving]]
== Retrieving the Thread Dump
To retrieve the thread dump, make a `GET` request to `/application/threaddump`, as shown
in the following curl-based example:
include::{snippets}threaddump/curl-request.adoc[]
The resulting response is similar to the following:
include::{snippets}threaddump/http-response.adoc[]
[[threaddump-retrieving-response-structure]]
=== Response Structure
The response contains details of the JVM's threads. The following table describes the
structure of the response:
[cols="3,1,2"]
include::{snippets}threaddump/response-fields.adoc[]

@ -0,0 +1,70 @@
= Spring Boot Actuator Web API Documentation
Andy Wilkinson
:doctype: book
:toc: left
:toclevels: 4
:source-highlighter: prettify
:numbered:
:icons: font
:hide-uri-scheme:
This API documentation describes Spring Boot Actuators web endpoints.
[[overview]]
== Overview
Before you proceed, you should read the following topics:
* <<overview-endpoint-urls>>
* <<overview-timestamps>>
[[overview-endpoint-urls]]
=== URLs
By default, all web endpoints are available beneath the path `/application` with URLs of
the form `/application/{id}`. The `/application` base path can be configured by using the
`management.endpoints.web.base-path` property, as shown in the following example:
[source,properties,indent=0]
----
management.endpoints.web.base-path=/manage
----
The preceding `application.properties` example changes the form of the endpoint URLs from
`/application/{id}` to `/manage/{id}`. For example, the URL `info` endpoint would become
`/manage/info`.
[[overview-timestamps]]
=== Timestamps
All timestamps that are consumed by the endpoints, either as query parameters or in the
request body, must be formatted as an offset date and time as specified in
https://en.wikipedia.org/wiki/ISO_8601[ISO 8601].
include::endpoints/auditevents.adoc[leveloffset=+1]
include::endpoints/beans.adoc[leveloffset=+1]
include::endpoints/conditions.adoc[leveloffset=+1]
include::endpoints/configprops.adoc[leveloffset=+1]
include::endpoints/env.adoc[leveloffset=+1]
include::endpoints/flyway.adoc[leveloffset=+1]
include::endpoints/health.adoc[leveloffset=+1]
include::endpoints/heapdump.adoc[leveloffset=+1]
include::endpoints/info.adoc[leveloffset=+1]
include::endpoints/liquibase.adoc[leveloffset=+1]
include::endpoints/logfile.adoc[leveloffset=+1]
include::endpoints/loggers.adoc[leveloffset=+1]
include::endpoints/metrics.adoc[leveloffset=+1]
include::endpoints/prometheus.adoc[leveloffset=+1]
include::endpoints/scheduledtasks.adoc[leveloffset=+1]
include::endpoints/sessions.adoc[leveloffset=+1]
include::endpoints/shutdown.adoc[leveloffset=+1]
include::endpoints/status.adoc[leveloffset=+1]
include::endpoints/threaddump.adoc[leveloffset=+1]

@ -0,0 +1,142 @@
/*
* Copyright 2012-2017 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.endpoint.web.documentation;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.flywaydb.core.internal.util.StringUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.servlet.WebMvcEndpointManagementContextConfiguration;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.restdocs.JUnitRestDocumentation;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.restdocs.operation.preprocess.ContentModifyingOperationPreprocessor;
import org.springframework.restdocs.operation.preprocess.OperationPreprocessor;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
/**
* Abstract base class for tests that generate endpoint documentation using Spring REST
* Docs.
*
* @author Andy Wilkinson
*/
@RunWith(SpringRunner.class)
@SpringBootTest(properties = { "spring.jackson.serialization.indent_output=true",
"management.endpoints.web.expose=*" })
public abstract class AbstractEndpointDocumentationTests {
@Rule
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
protected MockMvc mockMvc;
@Autowired
private WebApplicationContext applicationContext;
@Before
public void before() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.applicationContext)
.apply(MockMvcRestDocumentation
.documentationConfiguration(this.restDocumentation).uris())
.build();
}
protected String describeEnumValues(Class<? extends Enum<?>> enumType) {
return StringUtils
.collectionToCommaDelimitedString(Stream.of(enumType.getEnumConstants())
.map((constant) -> "`" + constant.name() + "`")
.collect(Collectors.toList()));
}
protected OperationPreprocessor limit(String key) {
return limit(key, (candidate) -> true);
}
@SuppressWarnings("unchecked")
protected <T> OperationPreprocessor limit(String key, Predicate<T> filter) {
return new ContentModifyingOperationPreprocessor((content, mediaType) -> {
ObjectMapper objectMapper = new ObjectMapper()
.enable(SerializationFeature.INDENT_OUTPUT);
try {
Map<String, Object> payload = objectMapper.readValue(content, Map.class);
Object entry = payload.get(key);
if (entry instanceof Map) {
payload.put(key, select((Map<String, Object>) entry, filter));
}
else {
payload.put(key, select((List<Object>) entry, filter));
}
return objectMapper.writeValueAsBytes(payload);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
});
}
@SuppressWarnings("unchecked")
private <T> Map<String, Object> select(Map<String, Object> candidates,
Predicate<T> filter) {
Map<String, Object> selected = new HashMap<String, Object>();
candidates.entrySet().stream().filter((candidate) -> filter.test((T) candidate))
.limit(3)
.forEach((entry) -> selected.put(entry.getKey(), entry.getValue()));
return selected;
}
@SuppressWarnings("unchecked")
private <T> List<Object> select(List<Object> candidates, Predicate<T> filter) {
return candidates.stream().filter((candidate) -> filter.test((T) candidate))
.limit(3).collect(Collectors.toList());
}
@Configuration
@Import({ JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, WebMvcAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, EndpointAutoConfiguration.class,
WebEndpointAutoConfiguration.class,
WebMvcEndpointManagementContextConfiguration.class,
PropertyPlaceholderAutoConfiguration.class })
static class BaseDocumentationConfiguration {
}
}

@ -0,0 +1,118 @@
/*
* Copyright 2012-2017 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.endpoint.web.documentation;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import org.junit.Test;
import org.springframework.boot.actuate.audit.AuditEvent;
import org.springframework.boot.actuate.audit.AuditEventRepository;
import org.springframework.boot.actuate.audit.AuditEventsEndpoint;
import org.springframework.boot.actuate.audit.AuditEventsEndpointWebExtension;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.requestParameters;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for generating documentation describing {@link AuditEventsEndpoint}.
*
* @author Andy Wilkinson
*/
public class AuditEventsEndpointDocumentationTests
extends AbstractEndpointDocumentationTests {
@MockBean
private AuditEventRepository repository;
@Test
public void allAuditEventsAfter() throws Exception {
String queryTimestamp = "2017-11-07T09:37Z";
given(this.repository.find(any(), any(), any())).willReturn(
Arrays.asList(new AuditEvent("alice", "logout", Collections.emptyMap())));
this.mockMvc
.perform(get("/application/auditevents").param("after", queryTimestamp))
.andExpect(status().isOk())
.andDo(document("auditevents/after", responseFields(
fieldWithPath("events").description("An array of audit events."),
fieldWithPath("events.[].timestamp")
.description("The timestamp of when the event occurred."),
fieldWithPath("events.[].principal")
.description("The principal that triggered the event."),
fieldWithPath("events.[].type")
.description("The type of the event."))));
}
@Test
public void filteredAuditEvents() throws Exception {
ZonedDateTime now = ZonedDateTime.now();
String queryTimestamp = DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(now);
Date date = new Date(now.toEpochSecond() * 1000);
given(this.repository.find("alice", date, "logout")).willReturn(
Arrays.asList(new AuditEvent("alice", "logout", Collections.emptyMap())));
this.mockMvc
.perform(get("/application/auditevents").param("principal", "alice")
.param("after", queryTimestamp).param("type", "logout"))
.andExpect(status().isOk())
.andDo(document("auditevents/filtered",
requestParameters(
parameterWithName("after").description(
"Restricts the events to those that occurred "
+ "after the given time. Required."),
parameterWithName("principal").description(
"Restricts the events to those with the given "
+ "principal. Optional."),
parameterWithName("type").description(
"Restricts the events to those with the given "
+ "type. Optional."))));
verify(this.repository).find("alice", date, "logout");
}
@Configuration
@Import(BaseDocumentationConfiguration.class)
static class TestConfiguration {
@Bean
public AuditEventsEndpoint auditEventsEndpoint(AuditEventRepository repository) {
return new AuditEventsEndpoint(repository);
}
@Bean
public AuditEventsEndpointWebExtension adAuditEventsWebEndpointExtension(
AuditEventsEndpoint delegate) {
return new AuditEventsEndpointWebExtension(delegate);
}
}
}

@ -0,0 +1,95 @@
/*
* Copyright 2012-2017 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.endpoint.web.documentation;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.junit.Test;
import org.springframework.boot.actuate.beans.BeansEndpoint;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.restdocs.payload.FieldDescriptor;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.restdocs.payload.ResponseFieldsSnippet;
import org.springframework.util.CollectionUtils;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for generating documentation describing {@link BeansEndpoint}.
*
* @author Andy Wilkinson
*/
public class BeansEndpointDocumentationTests extends AbstractEndpointDocumentationTests {
@Test
public void beans() throws Exception {
List<FieldDescriptor> beanFields = Arrays
.asList(fieldWithPath("aliases").description("Names of any aliases."),
fieldWithPath("scope")
.description("Scope of the bean."),
fieldWithPath("type").description("Fully qualified type of the bean."),
fieldWithPath("resource")
.description("Resource in which the bean was defined, if any.")
.optional(),
fieldWithPath("dependencies").description("Names of any dependencies."));
ResponseFieldsSnippet responseFields = responseFields(
fieldWithPath("contextId").description("ID of the application context."),
fieldWithPath("beans.*")
.description("Beans in the application context keyed by name."))
.andWithPrefix("beans.*.", beanFields)
.and(subsectionWithPath("parent")
.description("Beans in the parent application "
+ "context, if any.")
.type(JsonFieldType.OBJECT).optional());
this.mockMvc.perform(get("/application/beans")).andExpect(status().isOk())
.andDo(document("beans",
preprocessResponse(limit("beans", this::isIndependentBean)),
responseFields));
}
private boolean isIndependentBean(Entry<String, Map<String, Object>> bean) {
return CollectionUtils.isEmpty((Collection<?>) bean.getValue().get("aliases"))
&& CollectionUtils
.isEmpty((Collection<?>) bean.getValue().get("dependencies"));
}
@Configuration
@Import(BaseDocumentationConfiguration.class)
static class TestConfiguration {
@Bean
public BeansEndpoint beansEndpoint(ConfigurableApplicationContext context) {
return new BeansEndpoint(context);
}
}
}

@ -0,0 +1,122 @@
/*
* Copyright 2012-2017 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.endpoint.web.documentation;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.actuate.autoconfigure.condition.ConditionsReportEndpoint;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.restdocs.JUnitRestDocumentation;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for generating documentation describing {@link ConditionsReportEndpoint}.
*
* @author Andy Wilkinson
*/
public class ConditionsReportEndpointDocumentationTests
extends AbstractEndpointDocumentationTests {
@Rule
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
private MockMvc mockMvc;
@Autowired
private WebApplicationContext applicationContext;
@Override
@Before
public void before() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.applicationContext)
.apply(MockMvcRestDocumentation
.documentationConfiguration(this.restDocumentation).uris())
.build();
}
@Test
public void conditions() throws Exception {
this.mockMvc.perform(get("/application/conditions")).andExpect(status().isOk())
.andDo(MockMvcRestDocumentation.document("conditions",
preprocessResponse(limit("positiveMatches"),
limit("negativeMatches")),
responseFields(
fieldWithPath("positiveMatches").description(
"Classes and methods with conditions that were matched."),
fieldWithPath("positiveMatches.*.[].condition")
.description("Name of the condition."),
fieldWithPath("positiveMatches.*.[].message").description(
"Details of why the condition was matched."),
fieldWithPath("negativeMatches").description(
"Classes and methods with conditions that were not matched."),
fieldWithPath("negativeMatches.*.notMatched")
.description("Conditions that were matched."),
fieldWithPath("negativeMatches.*.notMatched.[].condition")
.description("Name of the condition."),
fieldWithPath("negativeMatches.*.notMatched.[].message")
.description(
"Details of why the condition was not matched."),
fieldWithPath("negativeMatches.*.matched")
.description("Conditions that were matched."),
fieldWithPath("negativeMatches.*.matched.[].condition")
.description("Name of the condition.")
.type(JsonFieldType.STRING).optional(),
fieldWithPath("negativeMatches.*.matched.[].message")
.description(
"Details of why the condition was matched.")
.type(JsonFieldType.STRING).optional(),
fieldWithPath("unconditionalClasses").description(
"Names of unconditional auto-configuration classes, if any."))));
}
@Configuration
@Import(BaseDocumentationConfiguration.class)
static class TestConfiguration {
@Bean
public ConditionsReportEndpoint autoConfigurationReportEndpoint(
ConfigurableListableBeanFactory beanFactory) {
ConditionEvaluationReport conditionEvaluationReport = ConditionEvaluationReport
.get(beanFactory);
conditionEvaluationReport.recordEvaluationCandidates(
Arrays.asList(PropertyPlaceholderAutoConfiguration.class.getName()));
return new ConditionsReportEndpoint(conditionEvaluationReport);
}
}
}

@ -0,0 +1,76 @@
/*
* Copyright 2012-2017 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.endpoint.web.documentation;
import org.junit.Test;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.restdocs.payload.JsonFieldType;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for generating documentation describing
* {@link ConfigurationPropertiesReportEndpoint}.
*
* @author Andy Wilkinson
*/
public class ConfigurationPropertiesReportEndpointDocumentationTests
extends AbstractEndpointDocumentationTests {
@Test
public void configProps() throws Exception {
this.mockMvc.perform(get("/application/configprops")).andExpect(status().isOk())
.andDo(MockMvcRestDocumentation.document("configprops",
preprocessResponse(limit("beans")),
responseFields(
fieldWithPath("contextId")
.description("ID of the application context."),
fieldWithPath("beans.*").description(
"`@ConfigurationProperties` beans keyed by bean name."),
fieldWithPath("beans.*.prefix").description(
"Prefix applied to the names of the bean's properties."),
subsectionWithPath("beans.*.properties").description(
"Properties of the bean as name-value pairs."),
subsectionWithPath("parent")
.description(
"`@ConfigurationProperties` beans in the parent "
+ "context, if any.")
.optional().type(JsonFieldType.OBJECT))));
}
@Configuration
@Import(BaseDocumentationConfiguration.class)
static class TestConfiguration {
@Bean
public ConfigurationPropertiesReportEndpoint endpoint() {
return new ConfigurationPropertiesReportEndpoint();
}
}
}

@ -0,0 +1,179 @@
/*
* Copyright 2012-2017 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.endpoint.web.documentation;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.junit.Test;
import org.springframework.boot.actuate.env.EnvironmentEndpoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.http.MediaType;
import org.springframework.restdocs.operation.preprocess.ContentModifyingOperationPreprocessor;
import org.springframework.restdocs.operation.preprocess.OperationPreprocessor;
import org.springframework.restdocs.payload.FieldDescriptor;
import org.springframework.test.context.TestPropertySource;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.replacePattern;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for generating documentation describing the {@link EnvironmentEndpoint}.
*
* @author Andy Wilkinson
*/
@TestPropertySource(properties = "spring.config.location=classpath:/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/")
public class EnvironmentEndpointDocumentationTests
extends AbstractEndpointDocumentationTests {
private static final FieldDescriptor activeProfiles = fieldWithPath("activeProfiles")
.description("Names of the active profiles, if any.");
private static final FieldDescriptor propertySources = fieldWithPath(
"propertySources").description("Property sources in order of precedence.");
private static final FieldDescriptor propertySourceName = fieldWithPath(
"propertySources.[].name").description("Name of the property source.");
@Test
public void env() throws Exception {
this.mockMvc.perform(get("/application/env")).andExpect(status().isOk())
.andDo(document("env/all",
preprocessResponse(replacePattern(
Pattern.compile(
"org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/"),
""), filterProperties()),
responseFields(activeProfiles, propertySources,
propertySourceName,
fieldWithPath("propertySources.[].properties")
.description(
"Properties in the property source keyed by property name."),
fieldWithPath("propertySources.[].properties.*.value")
.description("Value of the property."),
fieldWithPath("propertySources.[].properties.*.origin")
.description("Origin of the property, if any.")
.optional())));
}
@Test
public void singlePropertyFromEnv() throws Exception {
this.mockMvc.perform(get("/application/env/com.example.cache.max-size"))
.andExpect(status().isOk())
.andDo(document("env/single",
preprocessResponse(replacePattern(
Pattern.compile(
"org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/"),
"")),
responseFields(
fieldWithPath("property").description(
"Property from the environment, if found.")
.optional(),
fieldWithPath("property.source").description(
"Name of the source of the property."),
fieldWithPath("property.value")
.description("Value of the property."),
activeProfiles, propertySources, propertySourceName,
fieldWithPath("propertySources.[].property").description(
"Property in the property source, if any.")
.optional(),
fieldWithPath("propertySources.[].property.value")
.description("Value of the property."),
fieldWithPath("propertySources.[].property.origin")
.description("Origin of the property, if any.")
.optional())));
}
private OperationPreprocessor filterProperties() {
return new ContentModifyingOperationPreprocessor(this::filterProperties);
}
@SuppressWarnings("unchecked")
private byte[] filterProperties(byte[] content, MediaType mediaType) {
ObjectMapper objectMapper = new ObjectMapper()
.enable(SerializationFeature.INDENT_OUTPUT);
try {
Map<String, Object> payload = objectMapper.readValue(content, Map.class);
List<Map<String, Object>> propertySources = (List<Map<String, Object>>) payload
.get("propertySources");
for (Map<String, Object> propertySource : propertySources) {
Map<String, String> properties = (Map<String, String>) propertySource
.get("properties");
Set<String> filteredKeys = properties.keySet().stream()
.filter(this::retainKey).limit(3).collect(Collectors.toSet());
properties.keySet().retainAll(filteredKeys);
}
return objectMapper.writeValueAsBytes(payload);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
private boolean retainKey(String key) {
return key.startsWith("java.") || key.equals("JAVA_HOME")
|| key.startsWith("com.example");
}
@Configuration
@Import(BaseDocumentationConfiguration.class)
static class TestConfiguration {
@Bean
public EnvironmentEndpoint endpoint(ConfigurableEnvironment environment) {
return new EnvironmentEndpoint(new AbstractEnvironment() {
@Override
protected void customizePropertySources(
MutablePropertySources propertySources) {
StreamSupport
.stream(environment.getPropertySources().spliterator(), false)
.filter(this::includedPropertySource)
.forEach(propertySources::addLast);
}
private boolean includedPropertySource(PropertySource<?> propertySource) {
return propertySource instanceof EnumerablePropertySource
&& !"Inlined Test Properties"
.equals(propertySource.getName());
}
});
}
}
}

@ -0,0 +1,109 @@
/*
* Copyright 2012-2017 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.endpoint.web.documentation;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.MigrationState;
import org.flywaydb.core.api.MigrationType;
import org.junit.Test;
import org.springframework.boot.actuate.flyway.FlywayEndpoint;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.restdocs.payload.FieldDescriptor;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for generating documentation describing the {@link FlywayEndpoint}.
*
* @author Andy Wilkinson
*/
@AutoConfigureTestDatabase
public class FlywayEndpointDocumentationTests extends AbstractEndpointDocumentationTests {
@Test
public void flyway() throws Exception {
this.mockMvc.perform(get("/application/flyway")).andExpect(status().isOk())
.andDo(MockMvcRestDocumentation.document("flyway",
responseFields(fieldWithPath("*.migrations").description(
"Migrations performed by the Flyway instance, keyed by"
+ " bean name.")).andWithPrefix(
"*.migrations.[].",
migrationFieldDescriptors())));
}
private List<FieldDescriptor> migrationFieldDescriptors() {
return Arrays.asList(
fieldWithPath("checksum")
.description("Checksum of the migration, if any.").optional(),
fieldWithPath("description")
.description("Description of the migration, if any.").optional(),
fieldWithPath("executionTime")
.description(
"Execution time in milliseconds of an applied migration.")
.optional(),
fieldWithPath("installedBy")
.description("User that installed the applied migration, if any.")
.optional(),
fieldWithPath("installedOn").description(
"Timestamp of when the applied migration was installed, "
+ "if any.")
.optional(),
fieldWithPath("installedRank").description(
"Rank of the applied migration, if any. Later migrations have "
+ "higher ranks.")
.optional(),
fieldWithPath("script")
.description(
"Name of the script used to execute the migration, if any.")
.optional(),
fieldWithPath("state").description("State of the migration. ("
+ describeEnumValues(MigrationState.class) + ")"),
fieldWithPath("type").description("Type of the migration. ("
+ describeEnumValues(MigrationType.class) + ")"),
fieldWithPath("version").description(
"Version of the database after applying the migration, "
+ "if any.")
.optional());
}
@Configuration
@Import({ BaseDocumentationConfiguration.class, EmbeddedDataSourceConfiguration.class,
FlywayAutoConfiguration.class })
static class TestConfiguration {
@Bean
public FlywayEndpoint endpoint(Map<String, Flyway> flyways) {
return new FlywayEndpoint(flyways);
}
}
}

@ -0,0 +1,92 @@
/*
* Copyright 2012-2017 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.endpoint.web.documentation;
import java.io.File;
import java.util.Map;
import javax.sql.DataSource;
import org.junit.Test;
import org.springframework.boot.actuate.health.CompositeHealthIndicator;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.actuate.jdbc.DataSourceHealthIndicator;
import org.springframework.boot.actuate.system.DiskSpaceHealthIndicator;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for generating documentation describing the {@link HealthEndpoint}.
*
* @author Andy Wilkinson
*/
public class HealthEndpointDocumentationTests extends AbstractEndpointDocumentationTests {
@Test
public void health() throws Exception {
this.mockMvc.perform(get("/application/health")).andExpect(status().isOk())
.andDo(document("health",
responseFields(
fieldWithPath("status").description(
"Overall status of the application."),
fieldWithPath("details")
.description("Details of the health of the application."),
fieldWithPath("details.*.status").description(
"Status of a specific part of the application."),
subsectionWithPath("details.*.details").description(
"Details of the health of a specific part of the"
+ " application."))));
}
@Configuration
@Import(BaseDocumentationConfiguration.class)
@ImportAutoConfiguration(DataSourceAutoConfiguration.class)
static class TestConfiguration {
@Bean
public HealthEndpoint endpoint(Map<String, HealthIndicator> healthIndicators) {
return new HealthEndpoint(new CompositeHealthIndicator(
new OrderedHealthAggregator(), healthIndicators));
}
@Bean
public DiskSpaceHealthIndicator diskSpaceHealthIndicator() {
return new DiskSpaceHealthIndicator(new File("."), 1024 * 1024 * 10);
}
@Bean
public DataSourceHealthIndicator dataSourceHealthIndicator(
DataSource dataSource) {
return new DataSourceHealthIndicator(dataSource);
}
}
}

@ -0,0 +1,82 @@
/*
* Copyright 2012-2017 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.endpoint.web.documentation;
import java.io.FileWriter;
import java.util.Map;
import org.junit.Test;
import org.springframework.boot.actuate.management.HeapDumpWebEndpoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.restdocs.cli.CliDocumentation;
import org.springframework.restdocs.cli.CurlRequestSnippet;
import org.springframework.restdocs.operation.Operation;
import org.springframework.util.FileCopyUtils;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for generating documentation describing the {@link HeapDumpWebEndpoint}.
*
* @author Andy Wilkinson
*/
public class HeapDumpWebEndpointDocumentationTests
extends AbstractEndpointDocumentationTests {
@Test
public void heapDump() throws Exception {
this.mockMvc.perform(get("/application/heapdump")).andExpect(status().isOk())
.andDo(document("heapdump",
new CurlRequestSnippet(CliDocumentation.multiLineFormat()) {
@Override
protected Map<String, Object> createModel(
Operation operation) {
Map<String, Object> model = super.createModel(operation);
model.put("options", "-O");
return model;
}
}));
}
@Configuration
@Import(BaseDocumentationConfiguration.class)
static class TestConfiguration {
@Bean
public HeapDumpWebEndpoint endpoint() {
return new HeapDumpWebEndpoint() {
@Override
protected HeapDumper createHeapDumper()
throws HeapDumperUnavailableException {
return (file, live) -> FileCopyUtils.copy("<<binary content>>",
new FileWriter(file));
}
};
}
}
}

@ -0,0 +1,117 @@
/*
* Copyright 2012-2017 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.endpoint.web.documentation;
import java.util.List;
import java.util.Properties;
import org.junit.Test;
import org.springframework.boot.actuate.info.BuildInfoContributor;
import org.springframework.boot.actuate.info.GitInfoContributor;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.boot.actuate.info.InfoEndpoint;
import org.springframework.boot.info.BuildProperties;
import org.springframework.boot.info.GitProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.restdocs.payload.JsonFieldType;
import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for generating documentation describing the {@link InfoEndpoint}.
*
* @author Andy Wilkinson
*/
public class InfoEndpointDocumentationTests extends AbstractEndpointDocumentationTests {
@Test
public void info() throws Exception {
this.mockMvc.perform(get("/application/info")).andExpect(status().isOk())
.andDo(MockMvcRestDocumentation.document("info",
responseFields(beneathPath("git"),
fieldWithPath("branch")
.description("Name of the Git branch, if any."),
fieldWithPath("commit")
.description("Details of the Git commit, if any."),
fieldWithPath("commit.time")
.description("Timestamp of the commit, if any.")
.type(JsonFieldType.VARIES),
fieldWithPath("commit.id")
.description("ID of the commit, if any.")),
responseFields(beneathPath("build"),
fieldWithPath("artifact")
.description(
"Artifact ID of the application, if any.")
.optional(),
fieldWithPath("group")
.description(
"Group ID of the application, if any.")
.optional(),
fieldWithPath("name")
.description("Name of the application, if any.")
.type(JsonFieldType.STRING).optional(),
fieldWithPath("version")
.description(
"Version of the application, if any.")
.optional(),
fieldWithPath("time")
.description(
"Timestamp of when the application was built, if any.")
.type(JsonFieldType.VARIES).optional())));
}
@Configuration
@Import(BaseDocumentationConfiguration.class)
static class TestConfiguration {
@Bean
public InfoEndpoint endpoint(List<InfoContributor> infoContributors) {
return new InfoEndpoint(infoContributors);
}
@Bean
public GitInfoContributor gitInfoContributor() {
Properties properties = new Properties();
properties.put("branch", "master");
properties.put("commit.id", "df027cf1ec5aeba2d4fedd7b8c42b88dc5ce38e5");
properties.put("commit.id.abbrev", "df027cf");
properties.put("commit.time", Long.toString(System.currentTimeMillis()));
GitProperties gitProperties = new GitProperties(properties);
return new GitInfoContributor(gitProperties);
}
@Bean
public BuildInfoContributor buildInfoContributor() {
Properties properties = new Properties();
properties.put("group", "com.example");
properties.put("artifact", "application");
properties.put("version", "1.0.3");
BuildProperties buildProperties = new BuildProperties(properties);
return new BuildInfoContributor(buildProperties);
}
}
}

@ -0,0 +1,95 @@
/*
* Copyright 2012-2017 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.endpoint.web.documentation;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import liquibase.changelog.ChangeSet.ExecType;
import liquibase.integration.spring.SpringLiquibase;
import org.junit.Test;
import org.springframework.boot.actuate.liquibase.LiquibaseEndpoint;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.restdocs.payload.FieldDescriptor;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for generating documentation describing the {@link LiquibaseEndpoint}.
*
* @author Andy Wilkinson
*/
public class LiquibaseEndpointDocumentationTests
extends AbstractEndpointDocumentationTests {
@Test
public void liquibase() throws Exception {
this.mockMvc.perform(get("/application/liquibase")).andExpect(status().isOk())
.andDo(MockMvcRestDocumentation.document("liquibase",
responseFields(fieldWithPath("*.changeSets").description(
"Change sets made by the Liquibase beans, keyed by "
+ "bean name.")).andWithPrefix("*.changeSets[].",
getChangeSetFieldDescriptors())));
}
private List<FieldDescriptor> getChangeSetFieldDescriptors() {
return Arrays.asList(
fieldWithPath("author").description("Author of the change set."),
fieldWithPath("changeLog")
.description("Change log that contains the change set."),
fieldWithPath("comments").description("Comments on the change set."),
fieldWithPath("contexts").description("Contexts of the change set."),
fieldWithPath("dateExecuted")
.description("Timestamp of when the change set was executed."),
fieldWithPath("deploymentId")
.description("ID of the deployment that ran the change set."),
fieldWithPath("description")
.description("Description of the change set."),
fieldWithPath("execType").description("Execution type of the change set ("
+ describeEnumValues(ExecType.class) + ")."),
fieldWithPath("id").description("ID of the change set."),
fieldWithPath("labels")
.description("Labels associated with the change set."),
fieldWithPath("checksum").description("Checksum of the change set."),
fieldWithPath("orderExecuted")
.description("Order of the execution of the change set."),
fieldWithPath("tag").description("Tag associated with the change set."));
}
@Configuration
@Import({ BaseDocumentationConfiguration.class, EmbeddedDataSourceConfiguration.class,
LiquibaseAutoConfiguration.class })
static class TestConfiguration {
@Bean
public LiquibaseEndpoint endpoint(Map<String, SpringLiquibase> liquibases) {
return new LiquibaseEndpoint(liquibases);
}
}
}

@ -0,0 +1,65 @@
/*
* Copyright 2012-2017 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.endpoint.web.documentation;
import org.junit.Test;
import org.springframework.boot.actuate.logging.LogFileWebEndpoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.test.context.TestPropertySource;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for generating documentation describing the {@link LogFileWebEndpoint}.
*
* @author Andy Wilkinson
*/
@TestPropertySource(properties = "logging.file=src/test/resources/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/sample.log")
public class LogFileWebEndpointDocumentationTests
extends AbstractEndpointDocumentationTests {
@Test
public void logFile() throws Exception {
this.mockMvc.perform(get("/application/logfile")).andExpect(status().isOk())
.andDo(MockMvcRestDocumentation.document("logfile/entire"));
}
@Test
public void logFileRange() throws Exception {
this.mockMvc.perform(get("/application/logfile").header("Range", "bytes=0-1023"))
.andExpect(status().isPartialContent())
.andDo(MockMvcRestDocumentation.document("logfile/range"));
}
@Configuration
@Import(BaseDocumentationConfiguration.class)
static class TestConfiguration {
@Bean
public LogFileWebEndpoint endpoint(Environment environment) {
return new LogFileWebEndpoint(environment);
}
}
}

@ -0,0 +1,125 @@
/*
* Copyright 2012-2017 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.endpoint.web.documentation;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import org.junit.Test;
import org.springframework.boot.actuate.logging.LoggersEndpoint;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggerConfiguration;
import org.springframework.boot.logging.LoggingSystem;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.restdocs.payload.FieldDescriptor;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for generating documentation describing the {@link LoggersEndpoint}.
*
* @author Andy Wilkinson
*/
public class LoggersEndpointDocumentationTests
extends AbstractEndpointDocumentationTests {
private static final List<FieldDescriptor> levelFields = Arrays.asList(
fieldWithPath("configuredLevel")
.description("Configured level of the logger, if any.").optional(),
fieldWithPath("effectiveLevel")
.description("Effective level of the logger."));
@MockBean
private LoggingSystem loggingSystem;
@Test
public void allLoggers() throws Exception {
given(this.loggingSystem.getSupportedLogLevels())
.willReturn(EnumSet.allOf(LogLevel.class));
given(this.loggingSystem.getLoggerConfigurations()).willReturn(Arrays.asList(
new LoggerConfiguration("ROOT", LogLevel.INFO, LogLevel.INFO),
new LoggerConfiguration("com.example", LogLevel.DEBUG, LogLevel.DEBUG)));
this.mockMvc.perform(get("/application/loggers")).andExpect(status().isOk())
.andDo(MockMvcRestDocumentation.document("loggers/all",
responseFields(
fieldWithPath("levels").description(
"Levels support by the logging system."),
fieldWithPath("loggers").description("Loggers keyed by name."))
.andWithPrefix("loggers.*.", levelFields)));
}
@Test
public void logger() throws Exception {
given(this.loggingSystem.getLoggerConfiguration("com.example")).willReturn(
new LoggerConfiguration("com.example", LogLevel.INFO, LogLevel.INFO));
this.mockMvc.perform(get("/application/loggers/com.example"))
.andExpect(status().isOk()).andDo(MockMvcRestDocumentation
.document("loggers/single", responseFields(levelFields)));
}
@Test
public void setLogLevel() throws Exception {
this.mockMvc
.perform(post("/application/loggers/com.example")
.content("{\"configuredLevel\":\"debug\"}")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent())
.andDo(MockMvcRestDocumentation
.document("loggers/set",
requestFields(fieldWithPath("configuredLevel")
.description("Level for the logger. May be"
+ " omitted to clear the level.")
.optional())));
verify(this.loggingSystem).setLogLevel("com.example", LogLevel.DEBUG);
}
@Test
public void clearLogLevel() throws Exception {
this.mockMvc
.perform(post("/application/loggers/com.example").content("{}")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent())
.andDo(MockMvcRestDocumentation.document("loggers/clear"));
verify(this.loggingSystem).setLogLevel("com.example", null);
}
@Configuration
@Import(BaseDocumentationConfiguration.class)
static class TestConfiguration {
@Bean
public LoggersEndpoint endpoint(LoggingSystem loggingSystem) {
return new LoggersEndpoint(loggingSystem);
}
}
}

@ -0,0 +1,98 @@
/*
* Copyright 2012-2017 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.endpoint.web.documentation;
import io.micrometer.core.instrument.Statistic;
import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.Test;
import org.springframework.boot.actuate.metrics.MetricsEndpoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.requestParameters;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for generating documentation describing the {@link MetricsEndpoint}.
*
* @author Andy Wilkinson
*/
public class MetricsEndpointDocumentationTests
extends AbstractEndpointDocumentationTests {
@Test
public void metricNames() throws Exception {
this.mockMvc.perform(get("/application/metrics")).andExpect(status().isOk())
.andDo(document("metrics/names", responseFields(fieldWithPath("names")
.description("Names of the known metrics."))));
}
@Test
public void metric() throws Exception {
this.mockMvc.perform(get("/application/metrics/jvm.memory.max"))
.andExpect(status().isOk())
.andDo(document("metrics/metric",
responseFields(
fieldWithPath("name").description("Name of the metric"),
fieldWithPath("measurements")
.description("Measurements of the metric"),
fieldWithPath("measurements[].statistic")
.description("Statistic of the measurement. ("
+ describeEnumValues(Statistic.class) + ")."),
fieldWithPath("measurements[].value")
.description("Value of the measurement."),
fieldWithPath("availableTags")
.description("Tags that are available for drill-down."),
fieldWithPath("availableTags[].tag")
.description("Name of the tag."),
fieldWithPath("availableTags[].values")
.description("Possible values of the tag."))));
}
@Test
public void metricWithTags() throws Exception {
this.mockMvc
.perform(get("/application/metrics/jvm.memory.max")
.param("tag", "area:nonheap").param("tag", "id:Code Cache"))
.andExpect(status().isOk())
.andDo(document("metrics/metric-with-tags",
requestParameters(parameterWithName("tag").description(
"A tag to use for drill-down in the form `name:value`."))));
}
@Configuration
@Import(BaseDocumentationConfiguration.class)
static class TestConfiguration {
@Bean
public MetricsEndpoint endpoint() {
SimpleMeterRegistry registry = new SimpleMeterRegistry();
new JvmMemoryMetrics().bindTo(registry);
return new MetricsEndpoint(registry);
}
}
}

@ -0,0 +1,71 @@
/*
* Copyright 2012-2017 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.endpoint.web.documentation;
import io.micrometer.core.instrument.Clock;
import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import io.prometheus.client.CollectorRegistry;
import org.junit.Test;
import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusScrapeEndpoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for generating documentation describing the {@link PrometheusScrapeEndpoint}.
*
* @author Andy Wilkinson
*/
public class PrometheusScrapeEndpointDocumentationTests
extends AbstractEndpointDocumentationTests {
@Test
public void prometheus() throws Exception {
this.mockMvc.perform(get("/application/prometheus")).andExpect(status().isOk())
.andDo(document("prometheus"));
}
@Configuration
@Import(BaseDocumentationConfiguration.class)
static class TestConfiguration {
@Bean
public PrometheusScrapeEndpoint endpoint() {
CollectorRegistry collectorRegistry = new CollectorRegistry(true);
PrometheusMeterRegistry meterRegistry = new PrometheusMeterRegistry(
new PrometheusConfig() {
@Override
public String get(String key) {
return null;
}
}, collectorRegistry, Clock.SYSTEM);
new JvmMemoryMetrics().bindTo(meterRegistry);
return new PrometheusScrapeEndpoint(collectorRegistry);
}
}
}

@ -0,0 +1,116 @@
/*
* Copyright 2012-2017 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.endpoint.web.documentation;
import java.util.Collection;
import java.util.regex.Pattern;
import org.junit.Test;
import org.springframework.boot.actuate.scheduling.ScheduledTasksEndpoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.restdocs.payload.FieldDescriptor;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.config.ScheduledTaskHolder;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.replacePattern;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for generating documentation describing the {@link ScheduledTasksEndpoint}.
*
* @author Andy Wilkinson
*/
public class ScheduledTasksEndpointDocumentationTests
extends AbstractEndpointDocumentationTests {
@Test
public void scheduledTasks() throws Exception {
this.mockMvc.perform(get("/application/scheduledtasks"))
.andExpect(status().isOk())
.andDo(document("scheduled-tasks",
preprocessResponse(replacePattern(
Pattern.compile(
"org.*\\.ScheduledTasksEndpointDocumentationTests\\$"
+ "TestConfiguration"),
"com.example.Processor")),
responseFields(
fieldWithPath("cron").description("Cron tasks, if any."),
targetFieldWithPrefix("cron.[]"),
fieldWithPath("cron.[].expression")
.description("Cron expression."),
fieldWithPath("fixedDelay")
.description("Fixed delay tasks, if any."),
targetFieldWithPrefix("fixedDelay.[]"),
initialDelayWithPrefix("fixedDelay.[]."),
fieldWithPath("fixedDelay.[].interval").description(
"Interval, in milliseconds, between the end of the last"
+ " execution and the start of the next."),
fieldWithPath("fixedRate")
.description("Fixed rate tasks, if any."),
targetFieldWithPrefix("fixedRate.[]."),
fieldWithPath("fixedRate.[].interval").description(
"Interval, in milliseconds, between the start of each execution."),
initialDelayWithPrefix("fixedRate.[]."))));
}
private FieldDescriptor targetFieldWithPrefix(String prefix) {
return fieldWithPath(prefix + "runnable.target")
.description("Target that will be executed.");
}
private FieldDescriptor initialDelayWithPrefix(String prefix) {
return fieldWithPath(prefix + "initialDelay")
.description("Delay, in milliseconds, before first execution.");
}
@Configuration
@EnableScheduling
@Import(BaseDocumentationConfiguration.class)
static class TestConfiguration {
@Bean
public ScheduledTasksEndpoint endpoint(Collection<ScheduledTaskHolder> holders) {
return new ScheduledTasksEndpoint(holders);
}
@Scheduled(cron = "0 0 0/3 1/1 * ?")
public void processOrders() {
}
@Scheduled(fixedDelay = 5000, initialDelay = 5000)
public void purge() {
}
@Scheduled(fixedRate = 3000, initialDelay = 10000)
public void retrieveIssues() {
}
}
}

@ -0,0 +1,150 @@
/*
* Copyright 2012-2017 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.endpoint.web.documentation;
import java.time.Instant;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.junit.Test;
import org.springframework.boot.actuate.context.ShutdownEndpoint;
import org.springframework.boot.actuate.session.SessionsEndpoint;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.restdocs.payload.FieldDescriptor;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.MapSession;
import org.springframework.session.Session;
import org.springframework.test.context.TestPropertySource;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.requestParameters;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for generating documentation describing the {@link ShutdownEndpoint}.
*
* @author Andy Wilkinson
*/
@TestPropertySource(properties = "spring.jackson.serialization.write-dates-as-timestamps=false")
public class SessionsEndpointDocumentationTests
extends AbstractEndpointDocumentationTests {
private static final Session sessionOne = createSession(
Instant.now().minusSeconds(60 * 60 * 12), Instant.now().minusSeconds(45));
private static final Session sessionTwo = createSession(
"4db5efcc-99cb-4d05-a52c-b49acfbb7ea9",
Instant.now().minusSeconds(60 * 60 * 5), Instant.now().minusSeconds(37));
private static final Session sessionThree = createSession(
Instant.now().minusSeconds(60 * 60 * 2), Instant.now().minusSeconds(12));
private static final List<FieldDescriptor> sessionFields = Arrays
.asList(fieldWithPath("id").description("ID of the session."),
fieldWithPath("attributeNames").description(
"Names of the attributes stored in the session."),
fieldWithPath("creationTime")
.description("Timestamp of when the session was created."),
fieldWithPath("lastAccessedTime")
.description("Timestamp of when the session was last accessed."),
fieldWithPath("maxInactiveInterval")
.description("Maximum permitted period of inactivity, in seconds, "
+ "before the session will expire."),
fieldWithPath("expired").description("Whether the session has expired."));
@MockBean
private FindByIndexNameSessionRepository<Session> sessionRepository;
@Test
public void sessionsForUsername() throws Exception {
Map<String, Session> sessions = new HashMap<>();
sessions.put(sessionOne.getId(), sessionOne);
sessions.put(sessionTwo.getId(), sessionTwo);
sessions.put(sessionThree.getId(), sessionThree);
given(this.sessionRepository.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, "alice"))
.willReturn(sessions);
this.mockMvc.perform(get("/application/sessions").param("username", "alice"))
.andExpect(status().isOk())
.andDo(document("sessions/username",
responseFields(fieldWithPath("sessions")
.description("Sessions for the given username."))
.andWithPrefix("sessions.[].", sessionFields),
requestParameters(parameterWithName("username")
.description("Name of the user."))));
}
@Test
public void sessionWithId() throws Exception {
Map<String, Session> sessions = new HashMap<>();
sessions.put(sessionOne.getId(), sessionOne);
sessions.put(sessionTwo.getId(), sessionTwo);
sessions.put(sessionThree.getId(), sessionThree);
given(this.sessionRepository.findById(sessionTwo.getId())).willReturn(sessionTwo);
this.mockMvc.perform(get("/application/sessions/{id}", sessionTwo.getId()))
.andExpect(status().isOk())
.andDo(document("sessions/id", responseFields(sessionFields)));
}
@Test
public void deleteASession() throws Exception {
this.mockMvc.perform(delete("/application/sessions/{id}", sessionTwo.getId()))
.andExpect(status().isNoContent()).andDo(document("sessions/delete"));
verify(this.sessionRepository).deleteById(sessionTwo.getId());
}
private static MapSession createSession(Instant creationTime,
Instant lastAccessedTime) {
return createSession(UUID.randomUUID().toString(), creationTime,
lastAccessedTime);
}
private static MapSession createSession(String id, Instant creationTime,
Instant lastAccessedTime) {
MapSession session = new MapSession(id);
session.setCreationTime(creationTime);
session.setLastAccessedTime(lastAccessedTime);
return session;
}
@Configuration
@Import(BaseDocumentationConfiguration.class)
static class TestConfiguration {
@Bean
public SessionsEndpoint endpoint(
FindByIndexNameSessionRepository<?> sessionRepository) {
return new SessionsEndpoint(sessionRepository);
}
}
}

@ -0,0 +1,65 @@
/*
* Copyright 2012-2017 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.endpoint.web.documentation;
import org.junit.Test;
import org.springframework.boot.actuate.context.ShutdownEndpoint;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.test.context.TestPropertySource;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for generating documentation describing the {@link ShutdownEndpoint}.
*
* @author Andy Wilkinson
*/
@TestPropertySource(properties = "endpoints.shutdown.enabled=true")
public class ShutdownEndpointDocumentationTests
extends AbstractEndpointDocumentationTests {
@Test
public void shutdown() throws Exception {
this.mockMvc.perform(post("/application/shutdown")).andExpect(status().isOk())
.andDo(MockMvcRestDocumentation.document("shutdown",
responseFields(fieldWithPath("message").description(
"Message describing the result of the request."))));
}
@Configuration
@Import(BaseDocumentationConfiguration.class)
static class TestConfiguration {
@Bean
public ShutdownEndpoint endpoint(Environment environment) {
ShutdownEndpoint endpoint = new ShutdownEndpoint();
endpoint.setApplicationContext(new AnnotationConfigApplicationContext());
return endpoint;
}
}
}

@ -0,0 +1,82 @@
/*
* Copyright 2012-2017 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.endpoint.web.documentation;
import java.io.File;
import java.util.Map;
import javax.sql.DataSource;
import org.junit.Test;
import org.springframework.boot.actuate.health.CompositeHealthIndicator;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.actuate.health.StatusEndpoint;
import org.springframework.boot.actuate.jdbc.DataSourceHealthIndicator;
import org.springframework.boot.actuate.system.DiskSpaceHealthIndicator;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for generating documentation describing the {@link StatusEndpoint}.
*
* @author Andy Wilkinson
*/
public class StatusEndpointDocumentationTests extends AbstractEndpointDocumentationTests {
@Test
public void health() throws Exception {
this.mockMvc.perform(get("/application/status")).andExpect(status().isOk())
.andDo(document("status", responseFields(fieldWithPath("status")
.description("Overall status of the application."))));
}
@Configuration
@Import(BaseDocumentationConfiguration.class)
@ImportAutoConfiguration(DataSourceAutoConfiguration.class)
static class TestConfiguration {
@Bean
public StatusEndpoint endpoint(Map<String, HealthIndicator> healthIndicators) {
return new StatusEndpoint(new CompositeHealthIndicator(
new OrderedHealthAggregator(), healthIndicators));
}
@Bean
public DiskSpaceHealthIndicator diskSpaceHealthIndicator() {
return new DiskSpaceHealthIndicator(new File("."), 1024 * 1024 * 10);
}
@Bean
public DataSourceHealthIndicator dataSourceHealthIndicator(
DataSource dataSource) {
return new DataSourceHealthIndicator(dataSource);
}
}
}

@ -0,0 +1,176 @@
/*
* Copyright 2012-2017 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.endpoint.web.documentation;
import org.junit.Test;
import org.springframework.boot.actuate.management.ThreadDumpEndpoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.restdocs.payload.JsonFieldType;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for generating documentation describing {@link ThreadDumpEndpoint}.
*
* @author Andy Wilkinson
*/
public class ThreadDumpEndpointDocumentationTests
extends AbstractEndpointDocumentationTests {
@Test
public void threadDump() throws Exception {
this.mockMvc.perform(get("/application/threaddump")).andExpect(status().isOk())
.andDo(MockMvcRestDocumentation.document("threaddump",
preprocessResponse(limit("threads")),
responseFields(
fieldWithPath("threads").description("JVM's threads."),
fieldWithPath("threads.[].threadName")
.description("Name of the thread."),
fieldWithPath("threads.[].threadId")
.description("ID of the thread."),
fieldWithPath("threads.[].blockedTime").description(
"Time in milliseconds that the thread has spent "
+ "blocked. -1 if thread contention "
+ "monitoring is disabled."),
fieldWithPath("threads.[].blockedCount").description(
"Total number of times that the thread has been "
+ "blocked."),
fieldWithPath("threads.[].waitedTime").description(
"Time in milliseconds that the thread has spent "
+ "waiting. -1 if thread contention "
+ "monitoring is disabled"),
fieldWithPath("threads.[].waitedCount").description(
"Total number of times that the thread has waited"
+ " for notification."),
fieldWithPath("threads.[].lockName")
.description(
"Description of the object on which the "
+ "thread is blocked, if any.")
.optional(),
fieldWithPath("threads.[].lockOwnerId").description(
"ID of the thread that owns the object on which "
+ "the thread is blocked. `-1` if the "
+ "thread is not blocked."),
fieldWithPath("threads.[].lockOwnerName")
.description(
"Name of the thread that owns the object "
+ "on which the thread is blocked.")
.optional(),
fieldWithPath("threads.[].inNative").description(
"Whether the thread is executing native code."),
fieldWithPath("threads.[].suspended")
.description("Whether the thread is suspended."),
fieldWithPath("threads.[].threadState")
.description("State of the thread ("
+ describeEnumValues(Thread.State.class)
+ ")."),
fieldWithPath("threads.[].stackTrace")
.description("Stack trace of the thread."),
fieldWithPath("threads.[].stackTrace.[].methodName")
.description("Name of the method."),
fieldWithPath("threads.[].stackTrace.[].fileName")
.description(
"Name of the source file that contains "
+ "the execution point identified "
+ "by this entry, if any.")
.optional().type(JsonFieldType.STRING),
fieldWithPath("threads.[].stackTrace.[].lineNumber")
.description("Line number of the execution point "
+ "identified by this entry. Negative"
+ " if unknown."),
fieldWithPath("threads.[].stackTrace.[].className")
.description(
"Name of the class that contains the "
+ "execution point identified "
+ "by this entry."),
fieldWithPath("threads.[].stackTrace.[].nativeMethod")
.description(
"Whether the execution point is a native "
+ "method."),
fieldWithPath("threads.[].lockedMonitors").description(
"Monitors locked by this thread, if any"),
fieldWithPath("threads.[].lockedMonitors.[].className")
.description("Class name of the lock object.")
.optional().type(JsonFieldType.STRING),
fieldWithPath(
"threads.[].lockedMonitors.[].identityHashCode")
.description(
"Identity hash code of the lock "
+ "object.")
.optional().type(JsonFieldType.NUMBER),
fieldWithPath(
"threads.[].lockedMonitors.[].lockedStackDepth")
.description(
"Stack depth where the monitor "
+ "was locked.")
.optional().type(JsonFieldType.NUMBER),
subsectionWithPath(
"threads.[].lockedMonitors.[].lockedStackFrame")
.description(
"Stack frame that locked the "
+ "monitor.")
.optional().type(JsonFieldType.OBJECT),
fieldWithPath("threads.[].lockedSynchronizers")
.description(
"Synchronizers locked by this thread."),
fieldWithPath(
"threads.[].lockedSynchronizers.[].className")
.description("Class name of the locked "
+ "synchronizer.")
.optional().type(JsonFieldType.STRING),
fieldWithPath(
"threads.[].lockedSynchronizers.[].identifyHashCode")
.description(
"Identity hash code of the locked "
+ "synchronizer.")
.optional().type(JsonFieldType.NUMBER),
fieldWithPath("threads.[].lockInfo").description(
"Object for which the thread is blocked "
+ "waiting.")
.optional(),
fieldWithPath("threads.[].lockInfo.className")
.description(
"Fully qualified class name of the lock"
+ " object.")
.optional(),
fieldWithPath("threads.[].lockInfo.identityHashCode")
.description(
"Identity hash code of the lock object.")
.optional())));
}
@Configuration
@Import(BaseDocumentationConfiguration.class)
static class TestConfiguration {
@Bean
public ThreadDumpEndpoint endpoint() {
return new ThreadDumpEndpoint();
}
}
}

@ -0,0 +1,31 @@
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot ::
2017-08-08 17:12:30.910 INFO 19866 --- [ main] s.f.SampleWebFreeMarkerApplication : Starting SampleWebFreeMarkerApplication on host.local with PID 19866
2017-08-08 17:12:30.913 INFO 19866 --- [ main] s.f.SampleWebFreeMarkerApplication : No active profile set, falling back to default profiles: default
2017-08-08 17:12:30.952 INFO 19866 --- [ main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@76b10754: startup date [Tue Aug 08 17:12:30 BST 2017]; root of context hierarchy
2017-08-08 17:12:31.878 INFO 19866 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2017-08-08 17:12:31.889 INFO 19866 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2017-08-08 17:12:31.890 INFO 19866 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.16
2017-08-08 17:12:31.978 INFO 19866 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2017-08-08 17:12:31.978 INFO 19866 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1028 ms
2017-08-08 17:12:32.080 INFO 19866 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
2017-08-08 17:12:32.084 INFO 19866 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
2017-08-08 17:12:32.084 INFO 19866 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2017-08-08 17:12:32.084 INFO 19866 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2017-08-08 17:12:32.084 INFO 19866 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]
2017-08-08 17:12:32.349 INFO 19866 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@76b10754: startup date [Tue Aug 08 17:12:30 BST 2017]; root of context hierarchy
2017-08-08 17:12:32.420 INFO 19866 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2017-08-08 17:12:32.421 INFO 19866 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2017-08-08 17:12:32.444 INFO 19866 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-08-08 17:12:32.444 INFO 19866 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-08-08 17:12:32.471 INFO 19866 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-08-08 17:12:32.600 INFO 19866 --- [ main] o.s.w.s.v.f.FreeMarkerConfigurer : ClassTemplateLoader for Spring macros added to FreeMarker configuration
2017-08-08 17:12:32.681 INFO 19866 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2017-08-08 17:12:32.744 INFO 19866 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http)
2017-08-08 17:12:32.750 INFO 19866 --- [ main] s.f.SampleWebFreeMarkerApplication : Started SampleWebFreeMarkerApplication in 2.172 seconds (JVM running for 2.479)

@ -117,7 +117,7 @@ public class LiquibaseEndpoint {
private final String comments;
private final ContextExpression contextExpression;
private final Set<String> contexts;
private final Date dateExecuted;
@ -141,8 +141,7 @@ public class LiquibaseEndpoint {
this.author = ranChangeSet.getAuthor();
this.changeLog = ranChangeSet.getChangeLog();
this.comments = ranChangeSet.getComments();
this.contextExpression = new ContextExpression(
ranChangeSet.getContextExpression().getContexts());
this.contexts = ranChangeSet.getContextExpression().getContexts();
this.dateExecuted = ranChangeSet.getDateExecuted();
this.deploymentId = ranChangeSet.getDeploymentId();
this.description = ranChangeSet.getDescription();
@ -167,8 +166,8 @@ public class LiquibaseEndpoint {
return this.comments;
}
public ContextExpression getContextExpression() {
return this.contextExpression;
public Set<String> getContexts() {
return this.contexts;
}
public Date getDateExecuted() {

@ -151,7 +151,7 @@
<spring-kafka.version>2.1.0.RC1</spring-kafka.version>
<spring-ldap.version>2.3.2.RELEASE</spring-ldap.version>
<spring-plugin.version>1.2.0.RELEASE</spring-plugin.version>
<spring-restdocs.version>2.0.0.RC1</spring-restdocs.version>
<spring-restdocs.version>2.0.0.BUILD-SNAPSHOT</spring-restdocs.version>
<spring-retry.version>1.2.1.RELEASE</spring-retry.version>
<spring-security.version>5.0.0.RC1</spring-security.version>
<spring-session.version>2.0.0.RC1</spring-session.version>

@ -980,6 +980,15 @@
<outputDirectory>${project.build.directory}/contents/gradle-plugin</outputDirectory>
<excludes>META-INF/**</excludes>
</artifactItem>
<artifactItem>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
<version>${revision}</version>
<classifier>docs</classifier>
<type>zip</type>
<outputDirectory>${project.build.directory}/contents/actuator-api</outputDirectory>
<excludes>META-INF/**</excludes>
</artifactItem>
</artifactItems>
</configuration>
</execution>

@ -35,6 +35,7 @@ Phillip Webb; Dave Syer; Josh Long; Stéphane Nicoll; Rob Winch; Andy Wilkinson;
:dc-spring-boot-test-autoconfigure: {dc-root}/org/springframework/boot/test/autoconfigure
:dependency-management-plugin: https://github.com/spring-gradle-plugins/dependency-management-plugin
:dependency-management-plugin-documentation: {dependency-management-plugin}/blob/master/README.md
:spring-boot-actuator-api: http://docs.spring.io/spring-boot/docs/{spring-boot-docs-version}/actuator-api/
:spring-boot-maven-plugin-site: http://docs.spring.io/spring-boot/docs/{spring-boot-docs-version}/maven-plugin/
:spring-boot-gradle-plugin: http://docs.spring.io/spring-boot/docs/{spring-boot-docs-version}/gradle-plugin/
:spring-reference: http://docs.spring.io/spring/docs/{spring-docs-version}/spring-framework-reference/

@ -142,6 +142,12 @@ content.
|===
To learn more about the Actuator's endpoints and their request and response formats,
please refer to the separate API documentation that is available in the following formats:
* {spring-boot-actuator-api}/html[HTML]
* {spring-boot-actuator-api}/pdf/spring-boot-actuator-web-api.pdf[PDF]
[[production-ready-endpoints-exposing-endpoints]]

@ -70,7 +70,7 @@
<module name="com.puppycrawl.tools.checkstyle.checks.imports.AvoidStarImportCheck" />
<module name="com.puppycrawl.tools.checkstyle.checks.imports.AvoidStaticImportCheck">
<property name="excludes"
value="io.restassured.RestAssured.*, org.assertj.core.api.Assertions.*, org.junit.Assert.*, org.junit.Assume.*, org.junit.internal.matchers.ThrowableMessageMatcher.*, org.hamcrest.CoreMatchers.*, org.hamcrest.Matchers.*, org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.*, org.springframework.boot.configurationprocessor.TestCompiler.*, org.springframework.boot.test.autoconfigure.AutoConfigurationImportedCondition.*, org.mockito.Mockito.*, org.mockito.BDDMockito.*, org.mockito.ArgumentMatchers.*, org.mockito.Matchers.*, org.springframework.restdocs.hypermedia.HypermediaDocumentation.*, org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*, org.springframework.restdocs.operation.preprocess.Preprocessors.*, org.springframework.restdocs.restassured3.operation.preprocess.RestAssuredPreprocessors.*, org.springframework.restdocs.restassured3.RestAssuredRestDocumentation.*, org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*, org.springframework.test.web.servlet.result.MockMvcResultMatchers.*, org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*, org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*, org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo, org.springframework.test.web.client.ExpectedCount.*, org.springframework.test.web.client.match.MockRestRequestMatchers.*, org.springframework.test.web.client.response.MockRestResponseCreators.*" />
value="io.restassured.RestAssured.*, org.assertj.core.api.Assertions.*, org.junit.Assert.*, org.junit.Assume.*, org.junit.internal.matchers.ThrowableMessageMatcher.*, org.hamcrest.CoreMatchers.*, org.hamcrest.Matchers.*, org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.*, org.springframework.boot.configurationprocessor.TestCompiler.*, org.springframework.boot.test.autoconfigure.AutoConfigurationImportedCondition.*, org.mockito.Mockito.*, org.mockito.BDDMockito.*, org.mockito.ArgumentMatchers.*, org.mockito.Matchers.*, org.springframework.restdocs.hypermedia.HypermediaDocumentation.*, org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*, org.springframework.restdocs.operation.preprocess.Preprocessors.*, org.springframework.restdocs.payload.PayloadDocumentation.*, org.springframework.restdocs.request.RequestDocumentation.*, org.springframework.restdocs.restassured3.operation.preprocess.RestAssuredPreprocessors.*, org.springframework.restdocs.restassured3.RestAssuredRestDocumentation.*, org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*, org.springframework.test.web.servlet.result.MockMvcResultMatchers.*, org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*, org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*, org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo, org.springframework.test.web.client.ExpectedCount.*, org.springframework.test.web.client.match.MockRestRequestMatchers.*, org.springframework.test.web.client.response.MockRestResponseCreators.*" />
</module>
<module name="com.puppycrawl.tools.checkstyle.checks.imports.IllegalImportCheck" >
<property name="illegalPkgs" value="com.google.common"/>

Loading…
Cancel
Save