From bbe047176034e703d2b4835748c5339d4939f130 Mon Sep 17 00:00:00 2001 From: Ibanga Enoobong Ime Date: Fri, 30 Aug 2019 10:08:52 +0100 Subject: [PATCH 01/16] add kotlin code snippets to spring-boot refdoc --- spring-boot-project/spring-boot-docs/pom.xml | 7 ++ .../src/main/asciidoc/using-spring-boot.adoc | 109 +++++++++++++++++- 2 files changed, 110 insertions(+), 6 deletions(-) diff --git a/spring-boot-project/spring-boot-docs/pom.xml b/spring-boot-project/spring-boot-docs/pom.xml index b280fcb8eabe..02d662d092b2 100644 --- a/spring-boot-project/spring-boot-docs/pom.xml +++ b/spring-boot-project/spring-boot-docs/pom.xml @@ -1440,6 +1440,13 @@ org.asciidoctor asciidoctor-maven-plugin + + + io.spring.asciidoctor + spring-asciidoctor-extensions + 0.1.3.RELEASE + + ${refdocs.build.directory} diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc index e93e40eb3db0..70cebcf08378 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc @@ -339,6 +339,22 @@ The `Application.java` file would declare the `main` method, along with the basi } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package com.example.myapplication; + + import org.springframework.boot.autoconfigure.SpringBootApplication + import org.springframework.boot.runApplication + + @SpringBootApplication + class Application + + fun main(args: Array) { + runApplication(*args) + } +---- + [[using-boot-configuration-classes]] @@ -395,7 +411,8 @@ Doing so enables debug logs for a selection of core loggers and logs a condition === Disabling Specific Auto-configuration Classes If you find that specific auto-configuration classes that you do not want are being applied, you can use the exclude attribute of `@EnableAutoConfiguration` to disable them, as shown in the following example: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- import org.springframework.boot.autoconfigure.*; import org.springframework.boot.autoconfigure.jdbc.*; @@ -407,6 +424,16 @@ If you find that specific auto-configuration classes that you do not want are be } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import org.springframework.boot.autoconfigure.*; + import org.springframework.boot.autoconfigure.jdbc.*; + import org.springframework.context.annotation.*; + + @EnableAutoConfiguration(exclude = [DataSourceAutoConfiguration::class]) + class MyConfiguration +---- If the class is not on the classpath, you can use the `excludeName` attribute of the annotation and specify the fully qualified name instead. Finally, you can also control the list of auto-configuration classes to exclude by using the `spring.autoconfigure.exclude` property. @@ -427,7 +454,8 @@ All of your application components (`@Component`, `@Service`, `@Repository`, `@C The following example shows a `@Service` Bean that uses constructor injection to obtain a required `RiskAssessor` bean: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package com.example.service; @@ -449,9 +477,25 @@ The following example shows a `@Service` Bean that uses constructor injection to } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package com.example.service; + + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.stereotype.Service; + + @Service + class DatabaseAccountService(@Autowired private val riskAssessor: RiskAssessor): AccountService { + + // ... + } +---- + If a bean has one constructor, you can omit the `@Autowired`, as shown in the following example: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Service public class DatabaseAccountService implements AccountService { @@ -467,6 +511,15 @@ If a bean has one constructor, you can omit the `@Autowired`, as shown in the fo } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Service + class DatabaseAccountService(private val riskAssessor: RiskAssessor): AccountService { + // ... + } +---- + TIP: Notice how using constructor injection lets the `riskAssessor` field be marked as `final`, indicating that it cannot be subsequently changed. @@ -481,7 +534,8 @@ A single `@SpringBootApplication` annotation can be used to enable those three f * `@ConfigurationPropertiesScan`: enable `@ConfigurationProperties` scan on the package where the application is located (see <>) * `@Configuration`: allow to register extra beans in the context or import additional configuration classes -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package com.example.myapplication; @@ -498,6 +552,22 @@ A single `@SpringBootApplication` annotation can be used to enable those three f } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package com.example.myapplication; + + import org.springframework.boot.autoconfigure.SpringBootApplication + import org.springframework.boot.runApplication + + @SpringBootApplication // same as @Configuration @EnableAutoConfiguration @ComponentScan @ConfigurationPropertiesScan + class Application + + fun main(args: Array) { + runApplication(*args) + } +---- + NOTE: `@SpringBootApplication` also provides aliases to customize the attributes of `@EnableAutoConfiguration` and `@ComponentScan`. [NOTE] @@ -505,7 +575,8 @@ NOTE: `@SpringBootApplication` also provides aliases to customize the attributes None of these features are mandatory and you may choose to replace this single annotation by any of the features that it enables. For instance, you may not want to use component scan or configuration properties scan in your application: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- package com.example.myapplication; @@ -526,6 +597,22 @@ For instance, you may not want to use component scan or configuration properties } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + package com.example.myapplication; + + import org.springframework.boot.autoconfigure.EnableAutoConfiguration + import org.springframework.context.annotation.Configuration + import org.springframework.context.annotation.Import + + class Application + + fun main(args: Array) { + runApplication(*args) + } +---- + In this example, `Application` is just like any other Spring Boot application except that `@Component`-annotated classes and `@ConfigurationProperties`-annotated classes are not detected automatically and the user-defined beans are imported explicitly (see `@Import`). ==== @@ -782,7 +869,8 @@ In most cases, you can set this property in your `application.properties` (doing If you need to _completely_ disable restart support (for example, because it does not work with a specific library), you need to set the `spring.devtools.restart.enabled` `System` property to `false` before calling `SpringApplication.run(...)`, as shown in the following example: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- public static void main(String[] args) { System.setProperty("spring.devtools.restart.enabled", "false"); @@ -790,6 +878,15 @@ If you need to _completely_ disable restart support (for example, because it doe } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + fun main(args: Array) { + System.setProperty("spring.devtools.restart.enabled", "false") + runApplication(*args) + } +---- + [[using-boot-devtools-restart-triggerfile]] From 8235144e63975b8396c51ba8c6b2dcdc050a729e Mon Sep 17 00:00:00 2001 From: Ibanga Enoobong Ime Date: Fri, 30 Aug 2019 10:39:45 +0100 Subject: [PATCH 02/16] add repo.spring.io repository --- spring-boot-project/spring-boot-docs/pom.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spring-boot-project/spring-boot-docs/pom.xml b/spring-boot-project/spring-boot-docs/pom.xml index 02d662d092b2..75ac00e8d034 100644 --- a/spring-boot-project/spring-boot-docs/pom.xml +++ b/spring-boot-project/spring-boot-docs/pom.xml @@ -15,6 +15,13 @@ ${basedir}/../.. ${project.build.directory}/refdocs/ + + + spring-release + Spring Release + https://repo.spring.io/release + + org.springframework.boot From c62889c67523089a98db60a9e8a78af8f3a8cc00 Mon Sep 17 00:00:00 2001 From: Ibanga Enoobong Ime Date: Fri, 30 Aug 2019 11:02:25 +0100 Subject: [PATCH 03/16] add repo.spring.io as plugin repository --- spring-boot-project/spring-boot-docs/pom.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spring-boot-project/spring-boot-docs/pom.xml b/spring-boot-project/spring-boot-docs/pom.xml index 75ac00e8d034..109f4d297483 100644 --- a/spring-boot-project/spring-boot-docs/pom.xml +++ b/spring-boot-project/spring-boot-docs/pom.xml @@ -15,13 +15,13 @@ ${basedir}/../.. ${project.build.directory}/refdocs/ - - - spring-release - Spring Release + + + springio + Spring IO releases repository https://repo.spring.io/release - - + + org.springframework.boot From ecb3c1f2a06c0aaa794345fd789548d34b424556 Mon Sep 17 00:00:00 2001 From: Ibanga Enoobong Ime Date: Mon, 9 Sep 2019 09:29:24 +0100 Subject: [PATCH 04/16] add runnable code sample for Applicaton class --- spring-boot-project/spring-boot-docs/pom.xml | 67 ++++++++++++++++++- .../src/main/asciidoc/using-spring-boot.adoc | 12 +--- .../boot/docs/KotlinApplication.kt | 20 ++++++ 3 files changed, 87 insertions(+), 12 deletions(-) create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/KotlinApplication.kt diff --git a/spring-boot-project/spring-boot-docs/pom.xml b/spring-boot-project/spring-boot-docs/pom.xml index 109f4d297483..a1ebb04d9c0d 100644 --- a/spring-boot-project/spring-boot-docs/pom.xml +++ b/spring-boot-project/spring-boot-docs/pom.xml @@ -14,6 +14,7 @@ ${basedir}/../.. ${project.build.directory}/refdocs/ + 1.3.50 @@ -706,7 +707,8 @@ org.jetbrains.kotlin - kotlin-stdlib + kotlin-stdlib-jdk8 + ${kotlin.version} true @@ -1128,6 +1130,12 @@ spring-boot-starter-web test + + org.jetbrains.kotlin + kotlin-test + ${kotlin.version} + test + @@ -1140,6 +1148,63 @@ + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + 1.8 + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + compile + compile + + compile + + + + testCompile + test-compile + + testCompile + + + + diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc index 70cebcf08378..1d997e27889b 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc @@ -342,17 +342,7 @@ The `Application.java` file would declare the `main` method, along with the basi [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- - package com.example.myapplication; - - import org.springframework.boot.autoconfigure.SpringBootApplication - import org.springframework.boot.runApplication - - @SpringBootApplication - class Application - - fun main(args: Array) { - runApplication(*args) - } +include::{code-examples}/KotlinApplication.kt[tag=customizer] ---- diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/KotlinApplication.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/KotlinApplication.kt new file mode 100644 index 000000000000..84961204fd60 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/KotlinApplication.kt @@ -0,0 +1,20 @@ +package org.springframework.boot.docs + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication + +/** + * Example showing Application Class with [org.springframework.boot.autoconfigure.SpringBootApplication] + * + * @author Ibanga Enoobong Ime + */ + +// tag::customizer[] +@SpringBootApplication +class KotlinApplication + +fun main(args: Array) { + runApplication(*args) +} + +// end::customizer[] From d3f649a30530671c09ed1953a3276740ce0c358b Mon Sep 17 00:00:00 2001 From: Ibanga Enoobong Ime Date: Wed, 11 Sep 2019 14:45:06 +0100 Subject: [PATCH 05/16] add runnable code sample for auto configuration and service --- spring-boot-project/spring-boot-docs/pom.xml | 2 +- .../src/main/asciidoc/using-spring-boot.adoc | 27 ++++--------------- .../autoconfigure/KotlinMyConfiguration.kt | 12 +++++++++ .../service/KotlinDatabaseAccountService.kt | 18 +++++++++++++ ...nDatabaseAccountServiceWithoutAutowired.kt | 13 +++++++++ 5 files changed, 49 insertions(+), 23 deletions(-) create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinMyConfiguration.kt create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/KotlinDatabaseAccountService.kt create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/KotlinDatabaseAccountServiceWithoutAutowired.kt diff --git a/spring-boot-project/spring-boot-docs/pom.xml b/spring-boot-project/spring-boot-docs/pom.xml index a1ebb04d9c0d..45729aefa987 100644 --- a/spring-boot-project/spring-boot-docs/pom.xml +++ b/spring-boot-project/spring-boot-docs/pom.xml @@ -1132,7 +1132,7 @@ org.jetbrains.kotlin - kotlin-test + kotlin-test-junit ${kotlin.version} test diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc index 1d997e27889b..157f1abc21a0 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc @@ -417,12 +417,7 @@ If you find that specific auto-configuration classes that you do not want are be [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- - import org.springframework.boot.autoconfigure.*; - import org.springframework.boot.autoconfigure.jdbc.*; - import org.springframework.context.annotation.*; - - @EnableAutoConfiguration(exclude = [DataSourceAutoConfiguration::class]) - class MyConfiguration +include::{code-examples}/autoconfigure/KotlinMyConfiguration.kt[tag=customizer] ---- If the class is not on the classpath, you can use the `excludeName` attribute of the annotation and specify the fully qualified name instead. Finally, you can also control the list of auto-configuration classes to exclude by using the `spring.autoconfigure.exclude` property. @@ -470,16 +465,7 @@ The following example shows a `@Service` Bean that uses constructor injection to [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- - package com.example.service; - - import org.springframework.beans.factory.annotation.Autowired; - import org.springframework.stereotype.Service; - - @Service - class DatabaseAccountService(@Autowired private val riskAssessor: RiskAssessor): AccountService { - - // ... - } +include::{code-examples}/service/KotlinDatabaseAccountService.kt[tag=customizer] ---- If a bean has one constructor, you can omit the `@Autowired`, as shown in the following example: @@ -504,10 +490,7 @@ If a bean has one constructor, you can omit the `@Autowired`, as shown in the fo [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- - @Service - class DatabaseAccountService(private val riskAssessor: RiskAssessor): AccountService { - // ... - } +include::{code-examples}/service/KotlinDatabaseAccountServiceWithoutAutowired.kt[tag=customizer] ---- TIP: Notice how using constructor injection lets the `riskAssessor` field be marked as `final`, indicating that it cannot be subsequently changed. @@ -545,7 +528,7 @@ A single `@SpringBootApplication` annotation can be used to enable those three f [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- - package com.example.myapplication; + package com.example.myapplication import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication @@ -590,7 +573,7 @@ For instance, you may not want to use component scan or configuration properties [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- - package com.example.myapplication; + package com.example.myapplication import org.springframework.boot.autoconfigure.EnableAutoConfiguration import org.springframework.context.annotation.Configuration diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinMyConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinMyConfiguration.kt new file mode 100644 index 000000000000..5ca20b78cbd5 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinMyConfiguration.kt @@ -0,0 +1,12 @@ +package org.springframework.boot.docs.autoconfigure + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration + +/** + * Sample class showing how to disable specific Auto-configuration Classes + */ +// tag::customizer[] +@EnableAutoConfiguration(exclude = [DataSourceAutoConfiguration::class]) +class KotlinMyConfiguration +// end::customizer[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/KotlinDatabaseAccountService.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/KotlinDatabaseAccountService.kt new file mode 100644 index 000000000000..30911f9187d8 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/KotlinDatabaseAccountService.kt @@ -0,0 +1,18 @@ +package org.springframework.boot.docs.service + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Service + +/** + * * Sample showing autowiring the [org.springframework.beans.factory.annotation.Autowired] annotation + */ +// tag::customizer[] +@Service +class KotlinDatabaseAccountService(@Autowired private val riskAssessor: RiskAssessor) : AccountService { + //.. +} + +interface RiskAssessor + +interface AccountService +// end::customizer[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/KotlinDatabaseAccountServiceWithoutAutowired.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/KotlinDatabaseAccountServiceWithoutAutowired.kt new file mode 100644 index 000000000000..598c977b84af --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/KotlinDatabaseAccountServiceWithoutAutowired.kt @@ -0,0 +1,13 @@ +package org.springframework.boot.docs.service + +import org.springframework.stereotype.Service + +/** + * Sample showing autowiring without the [org.springframework.beans.factory.annotation.Autowired] annotation + */ +// tag::customizer[] +@Service +class KotlinDatabaseAccountServiceWithoutAutowired(private val riskAssessor: RiskAssessor) : AccountService { + //.. +} +// end::customizer[] From adcce96a8423972bde8afe351d40c00dddadafd8 Mon Sep 17 00:00:00 2001 From: Ibanga Enoobong Ime Date: Wed, 11 Sep 2019 16:38:44 +0100 Subject: [PATCH 06/16] add runnable code sample for auto (manual) configuration and devtools --- .../src/main/asciidoc/using-spring-boot.adoc | 29 ++----------------- .../autoconfigure/KotlinAutoConfiguration.kt | 16 ++++++++++ .../KotlinManualAutoConfiguration.kt | 22 ++++++++++++++ .../boot/docs/configuration/Configurations.kt | 9 ++++++ .../configuration/DisableDevToolsRestart.kt | 14 +++++++++ 5 files changed, 64 insertions(+), 26 deletions(-) create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinAutoConfiguration.kt create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinManualAutoConfiguration.kt create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/Configurations.kt create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/DisableDevToolsRestart.kt diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc index 157f1abc21a0..d7f493276dfc 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc @@ -528,17 +528,7 @@ A single `@SpringBootApplication` annotation can be used to enable those three f [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- - package com.example.myapplication - - import org.springframework.boot.autoconfigure.SpringBootApplication - import org.springframework.boot.runApplication - - @SpringBootApplication // same as @Configuration @EnableAutoConfiguration @ComponentScan @ConfigurationPropertiesScan - class Application - - fun main(args: Array) { - runApplication(*args) - } +include::{code-examples}/autoconfigure/KotlinAutoConfiguration.kt[tag=customizer] ---- NOTE: `@SpringBootApplication` also provides aliases to customize the attributes of `@EnableAutoConfiguration` and `@ComponentScan`. @@ -573,17 +563,7 @@ For instance, you may not want to use component scan or configuration properties [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- - package com.example.myapplication - - import org.springframework.boot.autoconfigure.EnableAutoConfiguration - import org.springframework.context.annotation.Configuration - import org.springframework.context.annotation.Import - - class Application - - fun main(args: Array) { - runApplication(*args) - } +include::{code-examples}/autoconfigure/KotlinManualAutoConfiguration.kt[tag=customizer] ---- In this example, `Application` is just like any other Spring Boot application except that `@Component`-annotated classes and `@ConfigurationProperties`-annotated classes are not detected automatically and the user-defined beans are imported explicitly (see `@Import`). @@ -854,10 +834,7 @@ If you need to _completely_ disable restart support (for example, because it doe [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- - fun main(args: Array) { - System.setProperty("spring.devtools.restart.enabled", "false") - runApplication(*args) - } +include::{code-examples}/configuration/DisableDevToolsRestart.kt[tag=customizer] ---- diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinAutoConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinAutoConfiguration.kt new file mode 100644 index 000000000000..3aa147c7bfdd --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinAutoConfiguration.kt @@ -0,0 +1,16 @@ +package org.springframework.boot.docs.autoconfigure + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication + +/** + * Sample using the [org.springframework.boot.autoconfigure.SpringBootApplication] + */ +// tag::customizer[] +@SpringBootApplication // same as @Configuration @EnableAutoConfiguration @ComponentScan @ConfigurationPropertiesScan +class Application + +fun main(args: Array) { + runApplication(*args) +} +// end::customizer[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinManualAutoConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinManualAutoConfiguration.kt new file mode 100644 index 000000000000..1e0693f90962 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinManualAutoConfiguration.kt @@ -0,0 +1,22 @@ +package org.springframework.boot.docs.autoconfigure + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration +import org.springframework.boot.docs.configuration.MyAnotherConfig +import org.springframework.boot.docs.configuration.MyConfig +import org.springframework.boot.runApplication +import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Import + +/** + * Sample using individual components of [org.springframework.boot.autoconfigure.SpringBootApplication] + */ +// tag::customizer[] +@Configuration(proxyBeanMethods = false) +@EnableAutoConfiguration +@Import(value = [MyConfig::class, MyAnotherConfig::class]) +class Application + +fun main(args: Array) { + runApplication(*args) +} +// end::customizer[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/Configurations.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/Configurations.kt new file mode 100644 index 000000000000..c9ddee409e02 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/Configurations.kt @@ -0,0 +1,9 @@ +package org.springframework.boot.docs.configuration + +import org.springframework.context.annotation.Configuration + +@Configuration +class MyConfig + +@Configuration +class MyAnotherConfig diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/DisableDevToolsRestart.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/DisableDevToolsRestart.kt new file mode 100644 index 000000000000..267721a2b7fd --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/DisableDevToolsRestart.kt @@ -0,0 +1,14 @@ +package org.springframework.boot.docs.configuration + +import org.springframework.boot.docs.Application +import org.springframework.boot.runApplication + +/** + * Sample showing how to disable dev tools restart + */ +// tag::customizer[] +fun main(args: Array) { + System.setProperty("spring.devtools.restart.enabled", "false") + runApplication(*args) +} +// end::customizer[] From b9b23a5eade7f58a3598f22a41cc598f765c1971 Mon Sep 17 00:00:00 2001 From: Ibanga Enoobong Ime Date: Wed, 11 Sep 2019 19:51:49 +0100 Subject: [PATCH 07/16] resolve imports and rename --- .../java/org/springframework/boot/docs/KotlinApplication.kt | 2 +- .../boot/docs/autoconfigure/KotlinManualAutoConfiguration.kt | 4 ++-- .../boot/docs/configuration/DisableDevToolsRestart.kt | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/KotlinApplication.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/KotlinApplication.kt index 84961204fd60..51d04c8a70cc 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/KotlinApplication.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/KotlinApplication.kt @@ -14,7 +14,7 @@ import org.springframework.boot.runApplication class KotlinApplication fun main(args: Array) { - runApplication(*args) + runApplication(*args) } // end::customizer[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinManualAutoConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinManualAutoConfiguration.kt index 1e0693f90962..ea5a19c1887b 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinManualAutoConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinManualAutoConfiguration.kt @@ -14,9 +14,9 @@ import org.springframework.context.annotation.Import @Configuration(proxyBeanMethods = false) @EnableAutoConfiguration @Import(value = [MyConfig::class, MyAnotherConfig::class]) -class Application +class ManualApplication fun main(args: Array) { - runApplication(*args) + runApplication(*args) } // end::customizer[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/DisableDevToolsRestart.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/DisableDevToolsRestart.kt index 267721a2b7fd..2062a179e5af 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/DisableDevToolsRestart.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/DisableDevToolsRestart.kt @@ -1,6 +1,6 @@ package org.springframework.boot.docs.configuration -import org.springframework.boot.docs.Application +import org.springframework.boot.docs.KotlinApplication import org.springframework.boot.runApplication /** @@ -9,6 +9,6 @@ import org.springframework.boot.runApplication // tag::customizer[] fun main(args: Array) { System.setProperty("spring.devtools.restart.enabled", "false") - runApplication(*args) + runApplication(*args) } // end::customizer[] From 080702e218e5ae5f54f7aac91a7b8e4c60c59d82 Mon Sep 17 00:00:00 2001 From: Ibanga Enoobong Ime Date: Thu, 12 Sep 2019 08:24:04 +0100 Subject: [PATCH 08/16] code review corrections --- spring-boot-project/spring-boot-docs/pom.xml | 4 ---- .../boot/docs/autoconfigure/KotlinManualAutoConfiguration.kt | 2 +- .../boot/docs/service/KotlinDatabaseAccountService.kt | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/spring-boot-project/spring-boot-docs/pom.xml b/spring-boot-project/spring-boot-docs/pom.xml index a7a65c5e5dc5..0834c3fd26b1 100644 --- a/spring-boot-project/spring-boot-docs/pom.xml +++ b/spring-boot-project/spring-boot-docs/pom.xml @@ -14,7 +14,6 @@ ${basedir}/../.. ${project.build.directory}/refdocs/ - 1.3.50 @@ -708,7 +707,6 @@ org.jetbrains.kotlin kotlin-stdlib-jdk8 - ${kotlin.version} true @@ -1138,7 +1136,6 @@ org.jetbrains.kotlin kotlin-test-junit - ${kotlin.version} test @@ -1156,7 +1153,6 @@ org.jetbrains.kotlin kotlin-maven-plugin - ${kotlin.version} compile diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinManualAutoConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinManualAutoConfiguration.kt index ea5a19c1887b..dc42abfd2c33 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinManualAutoConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinManualAutoConfiguration.kt @@ -13,7 +13,7 @@ import org.springframework.context.annotation.Import // tag::customizer[] @Configuration(proxyBeanMethods = false) @EnableAutoConfiguration -@Import(value = [MyConfig::class, MyAnotherConfig::class]) +@Import(MyConfig::class, MyAnotherConfig::class) class ManualApplication fun main(args: Array) { diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/KotlinDatabaseAccountService.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/KotlinDatabaseAccountService.kt index 30911f9187d8..557b7a85e987 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/KotlinDatabaseAccountService.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/KotlinDatabaseAccountService.kt @@ -8,7 +8,7 @@ import org.springframework.stereotype.Service */ // tag::customizer[] @Service -class KotlinDatabaseAccountService(@Autowired private val riskAssessor: RiskAssessor) : AccountService { +class KotlinDatabaseAccountService @Autowired constructor(private val riskAssessor: RiskAssessor) : AccountService { //.. } From 6c4cdc2233d6c277dffe12936fc86faa8d1a3954 Mon Sep 17 00:00:00 2001 From: Ibanga Enoobong Ime Date: Thu, 12 Sep 2019 08:59:31 +0100 Subject: [PATCH 09/16] documentation changes add author information --- .../boot/docs/autoconfigure/KotlinAutoConfiguration.kt | 2 ++ .../autoconfigure/KotlinManualAutoConfiguration.kt | 2 ++ .../boot/docs/autoconfigure/KotlinMyConfiguration.kt | 2 ++ .../boot/docs/configuration/Configurations.kt | 10 ++++++++++ .../boot/docs/configuration/DisableDevToolsRestart.kt | 2 ++ .../boot/docs/service/KotlinDatabaseAccountService.kt | 4 +++- .../KotlinDatabaseAccountServiceWithoutAutowired.kt | 2 ++ 7 files changed, 23 insertions(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinAutoConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinAutoConfiguration.kt index 3aa147c7bfdd..3d72bd00dfbc 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinAutoConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinAutoConfiguration.kt @@ -5,6 +5,8 @@ import org.springframework.boot.runApplication /** * Sample using the [org.springframework.boot.autoconfigure.SpringBootApplication] + * + * @author Ibanga Enoobong Ime */ // tag::customizer[] @SpringBootApplication // same as @Configuration @EnableAutoConfiguration @ComponentScan @ConfigurationPropertiesScan diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinManualAutoConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinManualAutoConfiguration.kt index dc42abfd2c33..3f720a3201a3 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinManualAutoConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinManualAutoConfiguration.kt @@ -9,6 +9,8 @@ import org.springframework.context.annotation.Import /** * Sample using individual components of [org.springframework.boot.autoconfigure.SpringBootApplication] + * + * @author Ibanga Enoobong Ime */ // tag::customizer[] @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinMyConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinMyConfiguration.kt index 5ca20b78cbd5..6ea29f53ad62 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinMyConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinMyConfiguration.kt @@ -5,6 +5,8 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration /** * Sample class showing how to disable specific Auto-configuration Classes + * + * @author Ibanga Enoobong Ime */ // tag::customizer[] @EnableAutoConfiguration(exclude = [DataSourceAutoConfiguration::class]) diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/Configurations.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/Configurations.kt index c9ddee409e02..f78ee52fc720 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/Configurations.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/Configurations.kt @@ -2,8 +2,18 @@ package org.springframework.boot.docs.configuration import org.springframework.context.annotation.Configuration +/** + * Sample configuration + * + * @author Ibanga Enoobong Ime + */ @Configuration class MyConfig +/** + * Sample configuration + * + * @author Ibanga Enoobong Ime + */ @Configuration class MyAnotherConfig diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/DisableDevToolsRestart.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/DisableDevToolsRestart.kt index 2062a179e5af..8b87066abdb6 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/DisableDevToolsRestart.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/DisableDevToolsRestart.kt @@ -5,6 +5,8 @@ import org.springframework.boot.runApplication /** * Sample showing how to disable dev tools restart + * + * @author Ibanga Enoobong Ime */ // tag::customizer[] fun main(args: Array) { diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/KotlinDatabaseAccountService.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/KotlinDatabaseAccountService.kt index 557b7a85e987..68962b722159 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/KotlinDatabaseAccountService.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/KotlinDatabaseAccountService.kt @@ -4,7 +4,9 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service /** - * * Sample showing autowiring the [org.springframework.beans.factory.annotation.Autowired] annotation + * Sample showing autowiring the [org.springframework.beans.factory.annotation.Autowired] annotation + * + * @author Ibanga Enoobong Ime */ // tag::customizer[] @Service diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/KotlinDatabaseAccountServiceWithoutAutowired.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/KotlinDatabaseAccountServiceWithoutAutowired.kt index 598c977b84af..c0a41cb66f08 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/KotlinDatabaseAccountServiceWithoutAutowired.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/KotlinDatabaseAccountServiceWithoutAutowired.kt @@ -4,6 +4,8 @@ import org.springframework.stereotype.Service /** * Sample showing autowiring without the [org.springframework.beans.factory.annotation.Autowired] annotation + * + * @author Ibanga Enoobong Ime */ // tag::customizer[] @Service From 7178c9e5df49eb3fb3e4fd3ec6a97efb3846fd0d Mon Sep 17 00:00:00 2001 From: Ibanga Enoobong Ime Date: Thu, 12 Sep 2019 14:21:20 +0100 Subject: [PATCH 10/16] move examples to kotlin package --- .../src/main/asciidoc/using-spring-boot.adoc | 12 ++++++------ .../AutoConfiguration.kt} | 2 +- .../ManualAutoConfiguration.kt} | 6 +++--- .../MyConfiguration.kt} | 4 ++-- .../configuration/{ => kotlin}/Configurations.kt | 2 +- .../{ => kotlin}/DisableDevToolsRestart.kt | 2 +- .../DatabaseAccountService.kt} | 4 ++-- .../DatabaseAccountServiceWithoutAutowired.kt} | 5 +++-- 8 files changed, 19 insertions(+), 18 deletions(-) rename spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/{KotlinAutoConfiguration.kt => kotlin/AutoConfiguration.kt} (89%) rename spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/{KotlinManualAutoConfiguration.kt => kotlin/ManualAutoConfiguration.kt} (76%) rename spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/{KotlinMyConfiguration.kt => kotlin/MyConfiguration.kt} (82%) rename spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/{ => kotlin}/Configurations.kt (81%) rename spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/{ => kotlin}/DisableDevToolsRestart.kt (86%) rename spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/{KotlinDatabaseAccountService.kt => kotlin/DatabaseAccountService.kt} (68%) rename spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/{KotlinDatabaseAccountServiceWithoutAutowired.kt => kotlin/DatabaseAccountServiceWithoutAutowired.kt} (62%) diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc index ad27389f4b67..fa1145f78a7f 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc @@ -419,7 +419,7 @@ If you find that specific auto-configuration classes that you do not want are be [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- -include::{code-examples}/autoconfigure/KotlinMyConfiguration.kt[tag=customizer] +include::{code-examples}/autoconfigure/kotlin/MyConfiguration.kt[tag=customizer] ---- If the class is not on the classpath, you can use the `excludeName` attribute of the annotation and specify the fully qualified name instead. Finally, you can also control the list of auto-configuration classes to exclude by using the `spring.autoconfigure.exclude` property. @@ -467,7 +467,7 @@ The following example shows a `@Service` Bean that uses constructor injection to [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- -include::{code-examples}/service/KotlinDatabaseAccountService.kt[tag=customizer] +include::{code-examples}/service/kotlin/DatabaseAccountService.kt[tag=customizer] ---- If a bean has one constructor, you can omit the `@Autowired`, as shown in the following example: @@ -492,7 +492,7 @@ If a bean has one constructor, you can omit the `@Autowired`, as shown in the fo [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- -include::{code-examples}/service/KotlinDatabaseAccountServiceWithoutAutowired.kt[tag=customizer] +include::{code-examples}/service/kotlin/DatabaseAccountServiceWithoutAutowired.kt[tag=customizer] ---- TIP: Notice how using constructor injection lets the `riskAssessor` field be marked as `final`, indicating that it cannot be subsequently changed. @@ -530,7 +530,7 @@ A single `@SpringBootApplication` annotation can be used to enable those three f [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- -include::{code-examples}/autoconfigure/KotlinAutoConfiguration.kt[tag=customizer] +include::{code-examples}/autoconfigure/kotlin/AutoConfiguration.kt[tag=customizer] ---- NOTE: `@SpringBootApplication` also provides aliases to customize the attributes of `@EnableAutoConfiguration` and `@ComponentScan`. @@ -565,7 +565,7 @@ For instance, you may not want to use component scan or configuration properties [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- -include::{code-examples}/autoconfigure/KotlinManualAutoConfiguration.kt[tag=customizer] +include::{code-examples}/autoconfigure/kotlin/ManualAutoConfiguration.kt[tag=customizer] ---- In this example, `Application` is just like any other Spring Boot application except that `@Component`-annotated classes and `@ConfigurationProperties`-annotated classes are not detected automatically and the user-defined beans are imported explicitly (see `@Import`). @@ -842,7 +842,7 @@ If you need to _completely_ disable restart support (for example, because it doe [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- -include::{code-examples}/configuration/DisableDevToolsRestart.kt[tag=customizer] +include::{code-examples}/configuration/kotlin/DisableDevToolsRestart.kt[tag=customizer] ---- diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinAutoConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/kotlin/AutoConfiguration.kt similarity index 89% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinAutoConfiguration.kt rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/kotlin/AutoConfiguration.kt index 3d72bd00dfbc..f2ec984739ce 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinAutoConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/kotlin/AutoConfiguration.kt @@ -1,4 +1,4 @@ -package org.springframework.boot.docs.autoconfigure +package org.springframework.boot.docs.autoconfigure.kotlin import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinManualAutoConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/kotlin/ManualAutoConfiguration.kt similarity index 76% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinManualAutoConfiguration.kt rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/kotlin/ManualAutoConfiguration.kt index 3f720a3201a3..6c82a5976a16 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinManualAutoConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/kotlin/ManualAutoConfiguration.kt @@ -1,8 +1,8 @@ -package org.springframework.boot.docs.autoconfigure +package org.springframework.boot.docs.autoconfigure.kotlin import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.boot.docs.configuration.MyAnotherConfig -import org.springframework.boot.docs.configuration.MyConfig +import org.springframework.boot.docs.configuration.kotlin.MyAnotherConfig +import org.springframework.boot.docs.configuration.kotlin.MyConfig import org.springframework.boot.runApplication import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Import diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinMyConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/kotlin/MyConfiguration.kt similarity index 82% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinMyConfiguration.kt rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/kotlin/MyConfiguration.kt index 6ea29f53ad62..ad2b1fe36a71 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/KotlinMyConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/autoconfigure/kotlin/MyConfiguration.kt @@ -1,4 +1,4 @@ -package org.springframework.boot.docs.autoconfigure +package org.springframework.boot.docs.autoconfigure.kotlin import org.springframework.boot.autoconfigure.EnableAutoConfiguration import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration @@ -10,5 +10,5 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration */ // tag::customizer[] @EnableAutoConfiguration(exclude = [DataSourceAutoConfiguration::class]) -class KotlinMyConfiguration +class MyConfiguration // end::customizer[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/Configurations.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/Configurations.kt similarity index 81% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/Configurations.kt rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/Configurations.kt index f78ee52fc720..78e8b4ca1ce7 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/Configurations.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/Configurations.kt @@ -1,4 +1,4 @@ -package org.springframework.boot.docs.configuration +package org.springframework.boot.docs.configuration.kotlin import org.springframework.context.annotation.Configuration diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/DisableDevToolsRestart.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/DisableDevToolsRestart.kt similarity index 86% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/DisableDevToolsRestart.kt rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/DisableDevToolsRestart.kt index 8b87066abdb6..3a03185e5fb5 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/DisableDevToolsRestart.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/DisableDevToolsRestart.kt @@ -1,4 +1,4 @@ -package org.springframework.boot.docs.configuration +package org.springframework.boot.docs.configuration.kotlin import org.springframework.boot.docs.KotlinApplication import org.springframework.boot.runApplication diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/KotlinDatabaseAccountService.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/kotlin/DatabaseAccountService.kt similarity index 68% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/KotlinDatabaseAccountService.kt rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/kotlin/DatabaseAccountService.kt index 68962b722159..28e9bfc27a41 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/KotlinDatabaseAccountService.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/kotlin/DatabaseAccountService.kt @@ -1,4 +1,4 @@ -package org.springframework.boot.docs.service +package org.springframework.boot.docs.service.kotlin import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service @@ -10,7 +10,7 @@ import org.springframework.stereotype.Service */ // tag::customizer[] @Service -class KotlinDatabaseAccountService @Autowired constructor(private val riskAssessor: RiskAssessor) : AccountService { +class DatabaseAccountService @Autowired constructor(private val riskAssessor: RiskAssessor) : AccountService { //.. } diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/KotlinDatabaseAccountServiceWithoutAutowired.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/kotlin/DatabaseAccountServiceWithoutAutowired.kt similarity index 62% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/KotlinDatabaseAccountServiceWithoutAutowired.kt rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/kotlin/DatabaseAccountServiceWithoutAutowired.kt index c0a41cb66f08..f9bc5a6e94bf 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/KotlinDatabaseAccountServiceWithoutAutowired.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/kotlin/DatabaseAccountServiceWithoutAutowired.kt @@ -1,4 +1,4 @@ -package org.springframework.boot.docs.service +package org.springframework.boot.docs.service.kotlin import org.springframework.stereotype.Service @@ -9,7 +9,8 @@ import org.springframework.stereotype.Service */ // tag::customizer[] @Service -class KotlinDatabaseAccountServiceWithoutAutowired(private val riskAssessor: RiskAssessor) : AccountService { +class DatabaseAccountServiceWithoutAutowired(private val riskAssessor: RiskAssessor) : + AccountService { //.. } // end::customizer[] From da3c2ce81ab2473971415596d6d6be8df7b3b8f8 Mon Sep 17 00:00:00 2001 From: Ibanga Enoobong Ime Date: Fri, 13 Sep 2019 17:14:16 +0100 Subject: [PATCH 11/16] add more examples --- .../main/asciidoc/spring-boot-features.adoc | 75 ++++++++++++++++--- .../boot/docs/KotlinExitCodeApplication.kt | 31 ++++++++ .../kotlin/SpringApplicationBuilderExample.kt | 35 +++++++++ .../configuration/kotlin/AcmeProperties.kt | 29 +++++++ .../kotlin/CommandLineRunnerBean.kt | 20 +++++ .../kotlin/CustomizedSpringConfiguration.kt | 17 +++++ .../boot/docs/configuration/kotlin/MyBean.kt | 22 ++++++ .../kotlin/MySpringConfiguration.kt | 19 +++++ .../kotlin/UsingExternalizedProperties.kt | 22 ++++++ 9 files changed, 260 insertions(+), 10 deletions(-) create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/KotlinExitCodeApplication.kt create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/builder/kotlin/SpringApplicationBuilderExample.kt create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/AcmeProperties.kt create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/CommandLineRunnerBean.kt create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/CustomizedSpringConfiguration.kt create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/MyBean.kt create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/MySpringConfiguration.kt create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/UsingExternalizedProperties.kt diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index db5ad2d4d309..c0ae179e71d9 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -13,13 +13,20 @@ If you have not already done so, you might want to read the "<) { + System.exit(SpringApplication.exit(SpringApplication.run(KotlinExitCodeApplication::class.java, *args))) + } + } + +} +// end::example[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/builder/kotlin/SpringApplicationBuilderExample.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/builder/kotlin/SpringApplicationBuilderExample.kt new file mode 100644 index 000000000000..f431296779bb --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/builder/kotlin/SpringApplicationBuilderExample.kt @@ -0,0 +1,35 @@ +package org.springframework.boot.docs.builder.kotlin + +import org.springframework.boot.Banner +import org.springframework.boot.builder.SpringApplicationBuilder + +/** + * Examples of using [SpringApplicationBuilder]. + * + * @author Ibanga Enoobong Ime + */ +class SpringApplicationBuilderExample { + + fun hierarchyWithDisabledBanner(args: Array) { + // @formatter:off + // tag::hierarchy[] + SpringApplicationBuilder() + .sources(Parent::class.java) + .child(Application::class.java) + .bannerMode(Banner.Mode.OFF) + .run(*args) + // end::hierarchy[] + // @formatter:on + } + + /** + * Parent application configuration. + */ + internal class Parent + + /** + * Application configuration. + */ + internal class Application + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/AcmeProperties.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/AcmeProperties.kt new file mode 100644 index 000000000000..aabea1bd552d --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/AcmeProperties.kt @@ -0,0 +1,29 @@ +package org.springframework.boot.docs.configuration.kotlin + +/** + * Example configuration that illustrates the use of external properties. + * + * @author Ibanga Enoobong Ime + */ +// tag::customizer[] +import org.springframework.boot.context.properties.ConfigurationProperties +import java.net.InetAddress + +@ConfigurationProperties("acme") +class AcmeProperties { + + private var enabled: Boolean = false + + private lateinit var remoteAddress: InetAddress + + private val security = Security() + + class Security { + private lateinit var username: String + + private lateinit var password: String + + private var roles = arrayListOf("USER") + } +} +// end::customizer[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/CommandLineRunnerBean.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/CommandLineRunnerBean.kt new file mode 100644 index 000000000000..fb88cf93dbbc --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/CommandLineRunnerBean.kt @@ -0,0 +1,20 @@ +package org.springframework.boot.docs.configuration.kotlin + +// tag::customizer[] +import org.springframework.boot.CommandLineRunner +import org.springframework.stereotype.Component + +/** + * Example showing use of [CommandLineRunner] + * + * @author Ibanga Enoobong Ime + */ + +@Component +class CommandLineRunnerBean : CommandLineRunner { + + override fun run(vararg args: String) { + // Do something... + } +} +// end::hierarchy[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/CustomizedSpringConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/CustomizedSpringConfiguration.kt new file mode 100644 index 000000000000..fbd28cd25d4d --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/CustomizedSpringConfiguration.kt @@ -0,0 +1,17 @@ +package org.springframework.boot.docs.configuration.kotlin + +import org.springframework.boot.Banner +import org.springframework.boot.SpringApplication + +/** + * Example showing Application Class with [SpringApplication] + * + * @author Ibanga Enoobong Ime + */ +// tag::customizer[] +fun main(args: Array) { + val app = SpringApplication(MySpringConfiguration::class.java) + app.setBannerMode(Banner.Mode.OFF) + app.run(*args) +} +// end::customizer[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/MyBean.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/MyBean.kt new file mode 100644 index 000000000000..3246c2f479e6 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/MyBean.kt @@ -0,0 +1,22 @@ +package org.springframework.boot.docs.configuration.kotlin + +// tag::customizer[] +import org.springframework.boot.ApplicationArguments +import org.springframework.stereotype.Component + +/** + * Example showing how to access application arguments + * + * @author Ibanga Enoobong Ime + */ + +@Component +class MyBean(args: ApplicationArguments) { + + init { + val debug = args.containsOption("debug") + val files = args.nonOptionArgs + // if run with "--debug logfile.txt" debug=true, files=["logfile.txt"] + } +} +// end::customizer[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/MySpringConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/MySpringConfiguration.kt new file mode 100644 index 000000000000..a2ca10bc6bce --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/MySpringConfiguration.kt @@ -0,0 +1,19 @@ +package org.springframework.boot.docs.configuration.kotlin + +import org.springframework.boot.SpringApplication +import org.springframework.boot.autoconfigure.SpringBootApplication + +/** + * Example showing Application Class with [org.springframework.boot.SpringApplication] + * + * @author Ibanga Enoobong Ime + */ + +@SpringBootApplication +class MySpringConfiguration + +// tag::customizer[] +fun main(args: Array) { + SpringApplication.run(MySpringConfiguration::class.java, *args) +} +// end::customizer[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/UsingExternalizedProperties.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/UsingExternalizedProperties.kt new file mode 100644 index 000000000000..b690ed472f1d --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/UsingExternalizedProperties.kt @@ -0,0 +1,22 @@ +package org.springframework.boot.docs.configuration.kotlin + +// tag::customizer[] +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Component + +/** + * Sample showing how to use externalized properties with the [Value] annotation + * + * @author Ibanga Enoobong Ime + */ + +@Component +class UsingExternalizedProperties { + + @Value("\${name}") + private lateinit var name: String + + // ... + +} +// end::customizer[] From 01ff72fd7a7d587165170dc7d409f9f9edfccf6e Mon Sep 17 00:00:00 2001 From: Ibanga Enoobong Ime Date: Sat, 14 Sep 2019 08:59:17 +0100 Subject: [PATCH 12/16] code review changes --- .../boot/docs/KotlinExitCodeApplication.kt | 11 ++++------- .../configuration/kotlin/CommandLineRunnerBean.kt | 2 +- .../kotlin/CustomizedSpringConfiguration.kt | 7 ++++--- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/KotlinExitCodeApplication.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/KotlinExitCodeApplication.kt index 72cbd98c8fe8..d3ec400d8bfa 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/KotlinExitCodeApplication.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/KotlinExitCodeApplication.kt @@ -3,6 +3,7 @@ package org.springframework.boot.docs import org.springframework.boot.ExitCodeGenerator import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication import org.springframework.context.annotation.Bean /** @@ -18,14 +19,10 @@ class KotlinExitCodeApplication { fun exitCodeGenerator(): ExitCodeGenerator { return ExitCodeGenerator { 42 } } +} - companion object { - - @JvmStatic - fun main(args: Array) { - System.exit(SpringApplication.exit(SpringApplication.run(KotlinExitCodeApplication::class.java, *args))) - } - } +fun main(args: Array) { + System.exit(SpringApplication.exit(runApplication(*args))) } // end::example[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/CommandLineRunnerBean.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/CommandLineRunnerBean.kt index fb88cf93dbbc..a1cfb281f0b5 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/CommandLineRunnerBean.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/CommandLineRunnerBean.kt @@ -17,4 +17,4 @@ class CommandLineRunnerBean : CommandLineRunner { // Do something... } } -// end::hierarchy[] +// end::customizer[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/CustomizedSpringConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/CustomizedSpringConfiguration.kt index fbd28cd25d4d..8db65fbd350f 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/CustomizedSpringConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/CustomizedSpringConfiguration.kt @@ -2,6 +2,7 @@ package org.springframework.boot.docs.configuration.kotlin import org.springframework.boot.Banner import org.springframework.boot.SpringApplication +import org.springframework.boot.runApplication /** * Example showing Application Class with [SpringApplication] @@ -10,8 +11,8 @@ import org.springframework.boot.SpringApplication */ // tag::customizer[] fun main(args: Array) { - val app = SpringApplication(MySpringConfiguration::class.java) - app.setBannerMode(Banner.Mode.OFF) - app.run(*args) + runApplication(*args) { + setBannerMode(Banner.Mode.OFF) + } } // end::customizer[] From 6b9a8096e67006448b7292e8bf8e56d10d6e5691 Mon Sep 17 00:00:00 2001 From: Ibanga Enoobong Ime Date: Sat, 14 Sep 2019 10:36:30 +0100 Subject: [PATCH 13/16] add more kotlin examples --- .../main/asciidoc/spring-boot-features.adoc | 50 +++++++++++++++++-- .../boot/docs/MyApplication.kt | 16 ++++++ .../configuration/kotlin/AcmeProperties.kt | 16 +++--- .../kotlin/ImmutableAcmeProperties.kt | 28 +++++++++++ .../configuration/kotlin/MyConfiguration.kt | 16 ++++++ .../boot/docs/service/kotlin/MyService.kt | 27 ++++++++++ 6 files changed, 141 insertions(+), 12 deletions(-) create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/MyApplication.kt create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/ImmutableAcmeProperties.kt create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/MyConfiguration.kt create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/kotlin/MyService.kt diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index c0ae179e71d9..115510dc5e12 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -817,6 +817,8 @@ It is possible to bind a bean declaring standard JavaBean properties as shown in private InetAddress remoteAddress; + private int port; + private final Security security = new Security(); public boolean isEnabled() { ... } @@ -827,6 +829,10 @@ It is possible to bind a bean declaring standard JavaBean properties as shown in public void setRemoteAddress(InetAddress remoteAddress) { ... } + public int getPort() { ... } + + public void setPort() { ... } + public Security getSecurity() { ... } public static class Security { @@ -897,7 +903,8 @@ Finally, only standard Java Bean properties are considered and binding on static ==== Constructor binding The example in the previous section can be rewritten in an immutable fashion as shown in the following example: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="secondary"] +.Java ---- package com.example; @@ -914,18 +921,23 @@ The example in the previous section can be rewritten in an immutable fashion as private final InetAddress remoteAddress; + private final int port; + private final Security security; - public AcmeProperties(boolean enabled, InetAddress remoteAddress, Security security) { + public AcmeProperties(boolean enabled, InetAddress remoteAddress, int port, Security security) { this.enabled = enabled; this.remoteAddress = remoteAddress; this.security = security; + this.port = port } public boolean isEnabled() { ... } public InetAddress getRemoteAddress() { ... } + public int getPort() { ... } + public Security getSecurity() { ... } public static class Security { @@ -954,6 +966,12 @@ The example in the previous section can be rewritten in an immutable fashion as } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +include::{code-examples}/configuration/kotlin/ImmutableAcmeProperties.kt[tag=customizer] +---- + In this setup one, and only one constructor must be defined with the list of properties that you wish to bind and not other properties than the ones in the constructor are bound. Default values can be specified using `@DefaultValue` and the same conversion service will be applied to coerce the `String` value to the target type of a missing property. @@ -969,7 +987,8 @@ If your application uses `@SpringBootApplication`, classes annotated with `@Conf By default, scanning will occur from the package of the class that declares this annotation. If you want to define specific packages to scan, you can do so using an explicit `@ConfigurationPropertiesScan` directive on your `@SpringBootApplication`-annotated class as shown in the following example: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @SpringBootApplication @ConfigurationPropertiesScan({ "com.example.app", "org.acme.another" }) @@ -977,10 +996,17 @@ If you want to define specific packages to scan, you can do so using an explicit } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +include::{code-examples}/MyApplication.kt[tag=customizer] +---- + Sometimes, classes annotated with `@ConfigurationProperties` might not be suitable for scanning, for example, if you're developing your own auto-configuration. In these cases, you can specify the list of types to process on any `@Configuration` class as shown in the following example: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(AcmeProperties.class) @@ -988,6 +1014,12 @@ In these cases, you can specify the list of types to process on any `@Configurat } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +include::{code-examples}/configuration/kotlin/MyConfiguration.kt[tag=customizer] +---- + [NOTE] ==== When the `@ConfigurationProperties` bean is registered using configuration property scanning or via `@EnableConfigurationProperties`, the bean has a conventional name: `-`, where `` is the environment key prefix specified in the `@ConfigurationProperties` annotation and `` is the fully qualified name of the bean. @@ -1012,6 +1044,7 @@ This style of configuration works particularly well with the `SpringApplication` acme: remote-address: 192.168.1.1 + port: 8080 security: username: admin roles: @@ -1023,7 +1056,8 @@ This style of configuration works particularly well with the `SpringApplication` To work with `@ConfigurationProperties` beans, you can inject them in the same way as any other bean, as shown in the following example: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Service public class MyService { @@ -1046,6 +1080,12 @@ To work with `@ConfigurationProperties` beans, you can inject them in the same w } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +include::{code-examples}/service/kotlin/MyService.kt[tag=customizer] +---- + TIP: Using `@ConfigurationProperties` also lets you generate metadata files that can be used by IDEs to offer auto-completion for your own keys. See the <> for details. diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/MyApplication.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/MyApplication.kt new file mode 100644 index 000000000000..c52f98cb8d61 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/MyApplication.kt @@ -0,0 +1,16 @@ +package org.springframework.boot.docs + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.context.properties.ConfigurationPropertiesScan + +/** + * Enabling [ConfigurationProperties] annotated types in [SpringBootApplication] + * + * @author Ibanga Enoobong Ime + */ +// tag::customizer[] +@SpringBootApplication +@ConfigurationPropertiesScan("com.example.app", "org.acme.another") +class MyApplication +// end::customizer[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/AcmeProperties.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/AcmeProperties.kt index aabea1bd552d..797706fe05fa 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/AcmeProperties.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/AcmeProperties.kt @@ -7,23 +7,25 @@ package org.springframework.boot.docs.configuration.kotlin */ // tag::customizer[] import org.springframework.boot.context.properties.ConfigurationProperties -import java.net.InetAddress +import java.net.InetSocketAddress @ConfigurationProperties("acme") class AcmeProperties { - private var enabled: Boolean = false + var enabled: Boolean = false - private lateinit var remoteAddress: InetAddress + lateinit var remoteAddress: InetSocketAddress - private val security = Security() + var port: Int = 0 + + val security = Security() class Security { - private lateinit var username: String + lateinit var username: String - private lateinit var password: String + lateinit var password: String - private var roles = arrayListOf("USER") + var roles = arrayListOf("USER") } } // end::customizer[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/ImmutableAcmeProperties.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/ImmutableAcmeProperties.kt new file mode 100644 index 000000000000..7061605b5550 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/ImmutableAcmeProperties.kt @@ -0,0 +1,28 @@ +package org.springframework.boot.docs.configuration.kotlin + +/** + * Example configuration that illustrates the use of external properties in immutable fashion + * + * @author Ibanga Enoobong Ime + */ +// tag::customizer[] +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.context.properties.bind.DefaultValue +import java.net.InetAddress + +@ConfigurationProperties("acme") +class ImmutableAcmeProperties( + val enabled: Boolean, + val remoteAddress: InetAddress, + val port: Int, + val security: Security +) { + + class Security( + val username: String, + val password: String, + @DefaultValue("USER") + val roles: List + ) +} +// end::customizer[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/MyConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/MyConfiguration.kt new file mode 100644 index 000000000000..4daabb7ea9ca --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/MyConfiguration.kt @@ -0,0 +1,16 @@ +package org.springframework.boot.docs.configuration.kotlin + +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.context.properties.EnableConfigurationProperties +import org.springframework.context.annotation.Configuration + +/** + * Enabling [ConfigurationProperties] annotated types in [Configuration] + * + * @author Ibanga Enoobong Ime + */ +// tag::customizer[] +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(AcmeProperties::class) +class MyConfiguration +// end::customizer[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/kotlin/MyService.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/kotlin/MyService.kt new file mode 100644 index 000000000000..5e46cca6b908 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/kotlin/MyService.kt @@ -0,0 +1,27 @@ +package org.springframework.boot.docs.service.kotlin + + +import org.eclipse.jetty.server.Server +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.docs.configuration.kotlin.AcmeProperties +import org.springframework.stereotype.Service +import javax.annotation.PostConstruct + +/** + * Example using [ConfigurationProperties] annotated types as bean + * + * @author Ibanga Enoobong Ime + */ +// tag::customizer[] +@Service +class MyService(private val properties: AcmeProperties) { + + //... + + @PostConstruct + fun openConnection() { + val server = Server(properties.port) + // ... + } +} +// end::customizer[] From 1ae32770fedfb3fee3ec9f6b50a939103ff19579 Mon Sep 17 00:00:00 2001 From: Ibanga Enoobong Ime Date: Mon, 7 Oct 2019 12:57:36 +0100 Subject: [PATCH 14/16] add more kotlin examples --- .../main/asciidoc/spring-boot-features.adoc | 82 ++++++++++++++++--- .../boot/docs/configuration/kotlin/Config.kt | 12 +++ .../configuration/kotlin/MyConfiguration.kt | 1 + .../kotlin/ThirdPartyConfiguration.kt | 15 ++++ .../properties/bind}/kotlin/AcmeProperties.kt | 2 +- .../properties/bind/kotlin/AppIoProperties.kt | 24 ++++++ .../bind/kotlin/AppSystemProperties.kt | 24 ++++++ .../bind/kotlin/ComplexAcmeProperties.kt | 14 ++++ .../bind}/kotlin/ImmutableAcmeProperties.kt | 2 +- .../bind/kotlin/MapAcmeProperties.kt | 13 +++ .../kotlin/NestedValidatedAcmeProperties.kt | 29 +++++++ .../properties/bind/kotlin/OwnerProperties.kt | 12 +++ .../bind/kotlin/ValidatedAcmeProperties.kt | 17 ++++ .../boot/docs/service/kotlin/MyService.kt | 2 +- 14 files changed, 235 insertions(+), 14 deletions(-) create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/Config.kt create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/ThirdPartyConfiguration.kt rename spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/{configuration => context/properties/bind}/kotlin/AcmeProperties.kt (89%) create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/AppIoProperties.kt create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/AppSystemProperties.kt create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/ComplexAcmeProperties.kt rename spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/{configuration => context/properties/bind}/kotlin/ImmutableAcmeProperties.kt (90%) create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/MapAcmeProperties.kt create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/NestedValidatedAcmeProperties.kt create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/OwnerProperties.kt create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/ValidatedAcmeProperties.kt diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 115510dc5e12..1eae7bcf6174 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -669,7 +669,8 @@ The preceding example would be transformed into these properties: To bind to properties like that by using Spring Boot's `Binder` utilities (which is what `@ConfigurationProperties` does), you need to have a property in the target bean of type `java.util.List` (or `Set`) and you either need to provide a setter or initialize it with a mutable value. For example, the following example binds to the properties shown previously: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ConfigurationProperties(prefix="my") public class Config { @@ -682,6 +683,11 @@ For example, the following example binds to the properties shown previously: } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +include::{code-examples}/configuration/kotlin/Config.kt[tag=customizer] +---- [[boot-features-external-config-exposing-yaml-to-spring]] @@ -862,7 +868,7 @@ It is possible to bind a bean declaring standard JavaBean properties as shown in [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- -include::{code-examples}/configuration/kotlin/AcmeProperties.kt[tag=customizer] +include::{code-examples}/context/properties/bind/kotlin/AcmeProperties.kt[tag=customizer] ---- The preceding POJO defines the following properties: @@ -969,7 +975,7 @@ The example in the previous section can be rewritten in an immutable fashion as [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- -include::{code-examples}/configuration/kotlin/ImmutableAcmeProperties.kt[tag=customizer] +include::{code-examples}/context/properties/bind/kotlin/ImmutableAcmeProperties.kt[tag=customizer] ---- In this setup one, and only one constructor must be defined with the list of properties that you wish to bind and not other properties than the ones in the constructor are bound. @@ -1098,7 +1104,8 @@ Doing so can be particularly useful when you want to bind properties to third-pa To configure a bean from the `Environment` properties, add `@ConfigurationProperties` to its bean registration, as shown in the following example: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ConfigurationProperties(prefix = "another") @Bean @@ -1107,6 +1114,11 @@ To configure a bean from the `Environment` properties, add `@ConfigurationProper } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +include::{code-examples}/configuration/kotlin/ThirdPartyConfiguration.kt[tag=customizer] +---- Any JavaBean property defined with the `another` prefix is mapped onto that `AnotherComponent` bean in manner similar to the preceding `AcmeProperties` example. @@ -1118,7 +1130,8 @@ Common examples where this is useful include dash-separated environment properti As an example, consider the following `@ConfigurationProperties` class: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ConfigurationProperties(prefix="acme.my-project.person") public class OwnerProperties { @@ -1136,6 +1149,12 @@ As an example, consider the following `@ConfigurationProperties` class: } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +include::{code-examples}/context/properties/bind/kotlin/OwnerProperties.kt[tag=customizer] +---- + With the preceding code, the following properties names can all be used: .relaxed binding @@ -1207,7 +1226,8 @@ When lists are configured in more than one place, overriding works by replacing For example, assume a `MyPojo` object with `name` and `description` attributes that are `null` by default. The following example exposes a list of `MyPojo` objects from `AcmeProperties`: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ConfigurationProperties("acme") public class AcmeProperties { @@ -1221,6 +1241,12 @@ The following example exposes a list of `MyPojo` objects from `AcmeProperties`: } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +include::{code-examples}/context/properties/bind/kotlin/ComplexAcmeProperties.kt[tag=customizer] +---- + Consider the following configuration: [source,yaml,indent=0] @@ -1267,7 +1293,8 @@ For `Map` properties, you can bind with property values drawn from multiple sour However, for the same property in multiple sources, the one with the highest priority is used. The following example exposes a `Map` from `AcmeProperties`: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ConfigurationProperties("acme") public class AcmeProperties { @@ -1281,6 +1308,11 @@ The following example exposes a `Map` from `AcmeProperties`: } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +include::{code-examples}/context/properties/bind/kotlin/MapAcmeProperties.kt[tag=customizer] +---- Consider the following configuration: [source,yaml,indent=0] @@ -1331,11 +1363,18 @@ If you expose a `java.time.Duration` property, the following formats in applicat Consider the following example: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- include::{code-examples}/context/properties/bind/AppSystemProperties.java[tag=example] ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +include::{code-examples}/context/properties/bind/kotlin/AppSystemProperties.kt[tag=example] +---- + To specify a session timeout of 30 seconds, `30`, `PT30S` and `30s` are all equivalent. A read timeout of 500ms can be specified in any of the following form: `500`, `PT0.5S` and `500ms`. @@ -1367,11 +1406,18 @@ If you expose a `DataSize` property, the following formats in application proper Consider the following example: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- include::{code-examples}/context/properties/bind/AppIoProperties.java[tag=example] ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +include::{code-examples}/context/properties/bind/kotlin/AppIoProperties.kt[tag=example] +---- + To specify a buffer size of 10 megabytes, `10` and `10MB` are equivalent. A size threshold of 256 bytes can be specified as `256` or `256B`. @@ -1397,7 +1443,8 @@ Spring Boot attempts to validate `@ConfigurationProperties` classes whenever the You can use JSR-303 `javax.validation` constraint annotations directly on your configuration class. To do so, ensure that a compliant JSR-303 implementation is on your classpath and then add constraint annotations to your fields, as shown in the following example: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ConfigurationProperties(prefix="acme") @Validated @@ -1411,13 +1458,20 @@ To do so, ensure that a compliant JSR-303 implementation is on your classpath an } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +include::{code-examples}/context/properties/bind/kotlin/ValidatedAcmeProperties.kt[tag=example] +---- + TIP: You can also trigger validation by annotating the `@Bean` method that creates the configuration properties with `@Validated`. Although nested properties will also be validated when bound, it's good practice to also annotate the associated field as `@Valid`. This ensures that validation is triggered even if no nested properties are found. The following example builds on the preceding `AcmeProperties` example: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @ConfigurationProperties(prefix="acme") @Validated @@ -1443,6 +1497,12 @@ The following example builds on the preceding `AcmeProperties` example: } ---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- +include::{code-examples}/context/properties/bind/kotlin/NestedValidatedAcmeProperties.kt[tag=example] +---- + You can also add a custom Spring `Validator` by creating a bean definition called `configurationPropertiesValidator`. The `@Bean` method should be declared `static`. The configuration properties validator is created very early in the application's lifecycle, and declaring the `@Bean` method as static lets the bean be created without having to instantiate the `@Configuration` class. diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/Config.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/Config.kt new file mode 100644 index 000000000000..f79d15998ef7 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/Config.kt @@ -0,0 +1,12 @@ +package org.springframework.boot.docs.configuration.kotlin + +import org.springframework.boot.context.properties.ConfigurationProperties +import java.util.ArrayList + +// tag::customizer[] +@ConfigurationProperties(prefix = "my") +class Config { + + val servers: List = ArrayList() +} +// end::customizer[] \ No newline at end of file diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/MyConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/MyConfiguration.kt index 4daabb7ea9ca..9c9eb047d06d 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/MyConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/MyConfiguration.kt @@ -2,6 +2,7 @@ package org.springframework.boot.docs.configuration.kotlin import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.boot.context.properties.EnableConfigurationProperties +import org.springframework.boot.docs.context.properties.bind.kotlin.AcmeProperties import org.springframework.context.annotation.Configuration /** diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/ThirdPartyConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/ThirdPartyConfiguration.kt new file mode 100644 index 000000000000..424f8cf6cc1a --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/ThirdPartyConfiguration.kt @@ -0,0 +1,15 @@ +package org.springframework.boot.docs.configuration.kotlin + +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.context.annotation.Bean + +class AnotherComponent + +// tag::customizer[] +@ConfigurationProperties(prefix = "another") +@Bean +fun anotherComponent(): AnotherComponent { + // ... + return AnotherComponent() +} +// end::customizer[] \ No newline at end of file diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/AcmeProperties.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/AcmeProperties.kt similarity index 89% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/AcmeProperties.kt rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/AcmeProperties.kt index 797706fe05fa..b0a45b5d52f8 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/AcmeProperties.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/AcmeProperties.kt @@ -1,4 +1,4 @@ -package org.springframework.boot.docs.configuration.kotlin +package org.springframework.boot.docs.context.properties.bind.kotlin /** * Example configuration that illustrates the use of external properties. diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/AppIoProperties.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/AppIoProperties.kt new file mode 100644 index 000000000000..79f744f82eb2 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/AppIoProperties.kt @@ -0,0 +1,24 @@ +package org.springframework.boot.docs.context.properties.bind.kotlin + +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.convert.DataSizeUnit +import org.springframework.util.unit.DataSize +import org.springframework.util.unit.DataUnit + +/** + * A [@ConfigurationProperties][ConfigurationProperties] example that uses + * [DataSize]. + * + * @author Ibanga Enoobong Ime + */ +// tag::example[] +@ConfigurationProperties("app.io") +class AppIoProperties { + + @DataSizeUnit(DataUnit.MEGABYTES) + var bufferSize = DataSize.ofMegabytes(2) + + var sizeThreshold = DataSize.ofBytes(512) + +} +// end::example[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/AppSystemProperties.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/AppSystemProperties.kt new file mode 100644 index 000000000000..d2f13052e3ba --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/AppSystemProperties.kt @@ -0,0 +1,24 @@ +package org.springframework.boot.docs.context.properties.bind.kotlin + +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.convert.DurationUnit +import java.time.Duration +import java.time.temporal.ChronoUnit + +/** + * A [@ConfigurationProperties][ConfigurationProperties] example that uses + * [Duration]. + * + * @author Ibanga Enoobong Ime + */ +// tag::example[] +@ConfigurationProperties("app.system") +class AppSystemProperties { + + @DurationUnit(ChronoUnit.SECONDS) + var sessionTimeout = Duration.ofSeconds(30) + + var readTimeout = Duration.ofMillis(1000) + +} +// end::example[] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/ComplexAcmeProperties.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/ComplexAcmeProperties.kt new file mode 100644 index 000000000000..b504b9f6f480 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/ComplexAcmeProperties.kt @@ -0,0 +1,14 @@ +package org.springframework.boot.docs.context.properties.bind.kotlin + +import org.springframework.boot.context.properties.ConfigurationProperties +import java.util.ArrayList + +class MyPojo + +// tag::customizer[] +@ConfigurationProperties("acme") +class ComplexAcmeProperties { + + val list: List = ArrayList() +} +// end::customizer[] \ No newline at end of file diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/ImmutableAcmeProperties.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/ImmutableAcmeProperties.kt similarity index 90% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/ImmutableAcmeProperties.kt rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/ImmutableAcmeProperties.kt index 7061605b5550..569ffd68c184 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/configuration/kotlin/ImmutableAcmeProperties.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/ImmutableAcmeProperties.kt @@ -1,4 +1,4 @@ -package org.springframework.boot.docs.configuration.kotlin +package org.springframework.boot.docs.context.properties.bind.kotlin /** * Example configuration that illustrates the use of external properties in immutable fashion diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/MapAcmeProperties.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/MapAcmeProperties.kt new file mode 100644 index 000000000000..6940c83bd6ec --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/MapAcmeProperties.kt @@ -0,0 +1,13 @@ +package org.springframework.boot.docs.context.properties.bind.kotlin + +import org.springframework.boot.context.properties.ConfigurationProperties +import java.util.HashMap + +// tag::customizer[] +@ConfigurationProperties("acme") +class MapAcmeProperties { + + val map: Map = HashMap() + +} +// end::customizer[] \ No newline at end of file diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/NestedValidatedAcmeProperties.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/NestedValidatedAcmeProperties.kt new file mode 100644 index 000000000000..2b38a30f18ba --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/NestedValidatedAcmeProperties.kt @@ -0,0 +1,29 @@ +package org.springframework.boot.docs.context.properties.bind.kotlin + +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.validation.annotation.Validated +import java.net.InetAddress +import javax.validation.Valid +import javax.validation.constraints.NotEmpty +import javax.validation.constraints.NotNull + +// tag::example[] +@ConfigurationProperties(prefix = "acme") +@Validated +class NestedValidatedAcmeProperties { + + @NotNull + lateinit var remoteAddress: InetAddress + + @Valid + val security = Security() + + class Security { + + @NotEmpty + lateinit var username: String + + + } +} +// end::example[] \ No newline at end of file diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/OwnerProperties.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/OwnerProperties.kt new file mode 100644 index 000000000000..c6de62c7455e --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/OwnerProperties.kt @@ -0,0 +1,12 @@ +package org.springframework.boot.docs.context.properties.bind.kotlin + +import org.springframework.boot.context.properties.ConfigurationProperties + +// tag::customizer[] +@ConfigurationProperties(prefix = "acme.my-project.person") +class OwnerProperties { + + var firstName: String? = null + +} +// end::customizer[] \ No newline at end of file diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/ValidatedAcmeProperties.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/ValidatedAcmeProperties.kt new file mode 100644 index 000000000000..6e12cbbee596 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/kotlin/ValidatedAcmeProperties.kt @@ -0,0 +1,17 @@ +package org.springframework.boot.docs.context.properties.bind.kotlin + +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.validation.annotation.Validated +import java.net.InetAddress +import javax.validation.constraints.NotNull + +// tag::example[] +@ConfigurationProperties(prefix = "acme") +@Validated +class ValidatedAcmeProperties { + + @NotNull + lateinit var remoteAddress: InetAddress + +} +// end::example[] \ No newline at end of file diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/kotlin/MyService.kt b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/kotlin/MyService.kt index 5e46cca6b908..153eac5092f4 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/kotlin/MyService.kt +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/service/kotlin/MyService.kt @@ -3,7 +3,7 @@ package org.springframework.boot.docs.service.kotlin import org.eclipse.jetty.server.Server import org.springframework.boot.context.properties.ConfigurationProperties -import org.springframework.boot.docs.configuration.kotlin.AcmeProperties +import org.springframework.boot.docs.context.properties.bind.kotlin.AcmeProperties import org.springframework.stereotype.Service import javax.annotation.PostConstruct From 43796b9cba5743a47cd11af2d43210001167b8dc Mon Sep 17 00:00:00 2001 From: Ibanga Enoobong Ime Date: Tue, 8 Oct 2019 08:43:38 +0100 Subject: [PATCH 15/16] sync forks --- .bomr/bomr.yaml | 8 +- SUPPORT.adoc | 2 +- ci/images/get-jdk-url.sh | 2 +- ci/scripts/detect-jdk-updates.sh | 2 +- pom.xml | 5 + .../pom.xml | 11 +- .../src/main/asciidoc/endpoints/health.adoc | 3 + ...dryReactiveHealthEndpointWebExtension.java | 9 +- ...loudFoundryHealthEndpointWebExtension.java | 10 +- .../condition/AbstractEndpointCondition.java | 2 +- .../OnAvailableEndpointCondition.java | 2 +- .../web/WebEndpointAutoConfiguration.java | 7 +- ...ndpointManagementContextConfiguration.java | 90 +++- .../AutoConfiguredHealthEndpointGroup.java | 30 +- .../AutoConfiguredHealthEndpointGroups.java | 17 +- ...egistryHealthIndicatorRegistryAdapter.java | 79 +++ .../HealthEndpointAutoConfiguration.java | 12 +- .../health/HealthIndicatorProperties.java | 18 +- .../health/HealthProperties.java | 32 +- ...lthEndpointCompatibilityConfiguration.java | 42 +- ...eactiveHealthIndicatorRegistryAdapter.java | 83 +++ .../metrics/export/atlas/AtlasProperties.java | 15 + .../metrics/export/humio/HumioProperties.java | 6 +- .../humio/HumioPropertiesConfigAdapter.java | 1 + .../kairos/KairosPropertiesConfigAdapter.java | 2 +- .../export/newrelic/NewRelicProperties.java | 31 ++ .../NewRelicPropertiesConfigAdapter.java | 11 + .../JerseyManagementContextConfiguration.java | 8 +- ...seySameManagementContextConfiguration.java | 2 +- ...itional-spring-configuration-metadata.json | 70 --- ...activeHealthEndpointWebExtensionTests.java | 7 +- ...FoundryActuatorAutoConfigurationTests.java | 5 +- ...FoundryActuatorAutoConfigurationTests.java | 7 +- ...oundryHealthEndpointWebExtensionTests.java | 7 +- .../WebEndpointAutoConfigurationTests.java | 3 +- .../HealthEndpointDocumentationTests.java | 27 +- ...ntManagementContextConfigurationTests.java | 6 +- ...utoConfiguredHealthEndpointGroupTests.java | 108 +++- ...toConfiguredHealthEndpointGroupsTests.java | 7 +- ...ryHealthIndicatorRegistryAdapterTests.java | 109 ++++ .../HealthEndpointAutoConfigurationTests.java | 37 +- ...atorRegistryInjectionIntegrationTests.java | 92 ++++ ...veHealthIndicatorRegistryAdapterTests.java | 110 ++++ ...AppOpticsPropertiesConfigAdapterTests.java | 2 +- ...ceMetricsExportAutoConfigurationTests.java | 12 +- .../HumioPropertiesConfigAdapterTests.java | 1 + .../export/humio/HumioPropertiesTests.java | 1 + .../KairosPropertiesConfigAdapterTests.java | 2 +- ...icMetricsExportAutoConfigurationTests.java | 30 ++ .../newrelic/NewRelicPropertiesTests.java | 10 + ...usMetricsExportAutoConfigurationTests.java | 6 +- ...ldManagementContextConfigurationTests.java | 25 - ...meManagementContextConfigurationTests.java | 22 - ...dContextConfigurationIntegrationTests.java | 2 +- .../spring-boot-actuator/pom.xml | 10 + ...ConfigurationPropertiesReportEndpoint.java | 57 +- .../boot/actuate/endpoint/EndpointId.java | 24 + .../actuate/endpoint/InvocationContext.java | 38 +- .../boot/actuate/endpoint/Sanitizer.java | 5 +- .../annotation/EndpointDiscoverer.java | 13 +- .../endpoint/http/ActuatorMediaType.java | 9 +- .../actuate/endpoint/http/ApiVersion.java | 90 ++++ .../reflect/ReflectiveOperationInvoker.java | 7 + .../cache/CachingOperationInvoker.java | 41 +- .../endpoint/web/EndpointMediaTypes.java | 32 ++ .../jersey/JerseyEndpointResourceFactory.java | 7 +- ...AbstractWebFluxEndpointHandlerMapping.java | 9 +- .../AbstractWebMvcEndpointHandlerMapping.java | 11 +- .../boot/actuate/health/CompositeHealth.java | 25 +- .../boot/actuate/health/Health.java | 5 + .../boot/actuate/health/HealthEndpoint.java | 15 +- .../actuate/health/HealthEndpointGroup.java | 14 +- .../actuate/health/HealthEndpointSupport.java | 53 +- .../health/HealthEndpointWebExtension.java | 21 +- .../HealthIndicatorReactiveAdapter.java | 2 +- .../ReactiveHealthEndpointWebExtension.java | 26 +- .../boot/actuate/health/SystemHealth.java | 6 +- .../boot/actuate/metrics/http/Outcome.java | 2 +- .../web/reactive/server/WebFluxTags.java | 15 +- .../redis/RedisReactiveHealthIndicator.java | 3 +- ...itional-spring-configuration-metadata.json | 10 + .../actuate/endpoint/EndpointIdTests.java | 11 + .../endpoint/InvocationContextTests.java | 77 +++ .../endpoint/http/ApiVersionTests.java | 90 ++++ .../ReflectiveOperationInvokerTests.java | 17 +- .../cache/CachingOperationInvokerTests.java | 58 ++ .../endpoint/web/EndpointMediaTypesTests.java | 17 + ...EndpointTestInvocationContextProvider.java | 13 +- .../actuate/health/CompositeHealthTests.java | 27 +- .../health/HealthEndpointSupportTests.java | 98 +++- .../HealthEndpointWebExtensionTests.java | 17 +- .../HealthEndpointWebIntegrationTests.java | 54 +- ...activeHealthEndpointWebExtensionTests.java | 7 +- .../actuate/health/SystemHealthTests.java | 6 +- .../health/TestHealthEndpointGroup.java | 21 +- ...ationGraphEndpointWebIntegrationTests.java | 2 +- .../LoggersEndpointWebIntegrationTests.java | 17 +- ...eapDumpWebEndpointWebIntegrationTests.java | 10 +- .../web/reactive/server/WebFluxTagsTests.java | 14 + .../spring-boot-autoconfigure/pom.xml | 20 + .../amqp/RabbitAutoConfiguration.java | 2 +- .../batch/BatchDataSourceInitializer.java | 2 +- .../cache/CacheAutoConfiguration.java | 9 +- ...oudServiceConnectorsAutoConfiguration.java | 9 + .../mongo/MongoDataAutoConfiguration.java | 2 +- .../MongoReactiveDataAutoConfiguration.java | 2 +- .../flyway/FlywayAutoConfiguration.java | 119 ++-- ...zelcastJpaDependencyAutoConfiguration.java | 13 +- .../ArtemisConnectionFactoryFactory.java | 28 +- .../ArtemisEmbeddedServerConfiguration.java | 33 +- .../liquibase/LiquibaseAutoConfiguration.java | 30 +- .../EmbeddedMongoAutoConfiguration.java | 20 +- .../jpa/DataSourceInitializedPublisher.java | 38 +- .../quartz/QuartzDataSourceInitializer.java | 2 +- .../quartz/QuartzProperties.java | 11 +- .../RSocketMessagingAutoConfiguration.java | 7 - .../rsocket/RSocketProperties.java | 6 +- .../RSocketServerAutoConfiguration.java | 16 +- .../RSocketStrategiesAutoConfiguration.java | 7 + .../RSocketWebSocketNettyRouteProvider.java | 18 +- ...ReactiveOAuth2ClientAutoConfiguration.java | 43 +- .../ReactiveOAuth2ClientConfigurations.java | 97 ++++ ...veUserDetailsServiceAutoConfiguration.java | 29 +- .../RSocketSecurityAutoConfiguration.java | 44 ++ .../RegistrationConfiguredCondition.java | 59 ++ .../saml2/Saml2LoginConfiguration.java | 45 ++ .../Saml2RelyingPartyAutoConfiguration.java | 44 ++ .../saml2/Saml2RelyingPartyProperties.java | 182 +++++++ ...RelyingPartyRegistrationConfiguration.java | 121 +++++ .../HazelcastSessionConfiguration.java | 4 +- .../session/JdbcSessionConfiguration.java | 4 +- .../MongoReactiveSessionConfiguration.java | 4 +- .../session/MongoSessionConfiguration.java | 4 +- .../RedisReactiveSessionConfiguration.java | 4 +- .../session/RedisSessionConfiguration.java | 4 +- .../session/SessionAutoConfiguration.java | 16 +- .../task/TaskExecutionAutoConfiguration.java | 2 +- .../TransactionAutoConfiguration.java | 12 +- .../autoconfigure/web/ServerProperties.java | 19 +- .../JettyWebServerFactoryCustomizer.java | 2 +- .../TomcatWebServerFactoryCustomizer.java | 1 + .../DefaultErrorWebExceptionHandler.java | 2 +- .../servlet/error/BasicErrorController.java | 5 +- ...itional-spring-configuration-metadata.json | 11 +- .../main/resources/META-INF/spring.factories | 2 + .../Flyway5xAutoConfigurationTests.java | 121 +++++ .../flyway/FlywayAutoConfigurationTests.java | 2 +- ...stJpaDependencyAutoConfigurationTests.java | 13 +- .../ArtemisAutoConfigurationTests.java | 75 ++- .../AbstractJpaAutoConfigurationTests.java | 2 +- .../HibernateJpaAutoConfigurationTests.java | 25 +- .../QuartzDataSourceInitializerTests.java | 30 +- ...SocketMessagingAutoConfigurationTests.java | 7 +- .../RSocketServerAutoConfigurationTests.java | 38 +- ...ocketStrategiesAutoConfigurationTests.java | 2 + ...ocketWebSocketNettyRouteProviderTests.java | 13 +- ...iveOAuth2ClientAutoConfigurationTests.java | 79 ++- ...2ResourceServerAutoConfigurationTests.java | 2 +- ...2ResourceServerAutoConfigurationTests.java | 2 +- ...eactiveSecurityAutoConfigurationTests.java | 14 + ...rDetailsServiceAutoConfigurationTests.java | 22 + ...RSocketSecurityAutoConfigurationTests.java | 74 +++ ...ml2RelyingPartyAutoConfigurationTests.java | 133 +++++ ...SessionRepositoryFailureAnalyzerTests.java | 10 +- ...iveSessionAutoConfigurationMongoTests.java | 10 +- ...iveSessionAutoConfigurationRedisTests.java | 10 +- ...essionAutoConfigurationHazelcastTests.java | 24 +- ...sionAutoConfigurationIntegrationTests.java | 4 +- .../SessionAutoConfigurationJdbcTests.java | 24 +- .../SessionAutoConfigurationMongoTests.java | 16 +- .../SessionAutoConfigurationRedisTests.java | 16 +- .../TransactionAutoConfigurationTests.java | 196 ++++--- .../web/ServerPropertiesTests.java | 4 +- .../JettyWebServerFactoryCustomizerTests.java | 4 +- ...TomcatWebServerFactoryCustomizerTests.java | 2 + .../BasicErrorControllerMockMvcTests.java | 27 +- .../{tables_h2.sql => tables_#_comments.sql} | 4 +- .../quartz/tables_--_comments.sql | 10 + .../quartz/tables_custom_comment_prefix.sql | 10 + .../test/resources/saml/certificate-location | 24 + .../test/resources/saml/private-key-location | 16 + spring-boot-project/spring-boot-cli/pom.xml | 5 + .../spring-boot-dependencies/pom.xml | 359 ++++++------ .../spring-boot-devtools/pom.xml | 10 + .../DevToolsDataSourceAutoConfiguration.java | 14 +- .../OnEnabledDevToolsCondition.java | 2 +- .../DevToolsHomePropertiesPostProcessor.java | 54 +- ...DevToolsPropertyDefaultsPostProcessor.java | 2 +- .../boot/devtools/restart/ChangeableUrls.java | 9 +- ...assLoaderFilesResourcePatternResolver.java | 69 +-- .../restart/DefaultRestartInitializer.java | 2 +- .../DevToolsEnablementDeducer.java | 2 +- .../boot/devtools/system/package-info.java | 20 + .../tunnel/payload/HttpTunnelPayload.java | 22 +- ...ooledDataSourceAutoConfigurationTests.java | 14 +- ...ToolsHomePropertiesPostProcessorTests.java | 18 +- .../livereload/LiveReloadServerTests.java | 10 +- .../devtools/restart/ChangeableUrlsTests.java | 7 +- ...aderFilesResourcePatternResolverTests.java | 24 + spring-boot-project/spring-boot-docs/pom.xml | 56 +- ...c => appendix-application-properties.adoc} | 35 +- ... appendix-auto-configuration-classes.adoc} | 9 +- ...c => appendix-configuration-metadata.adoc} | 45 +- ...adoc => appendix-dependency-versions.adoc} | 10 +- ...oc => appendix-executable-jar-format.adoc} | 27 +- ... => appendix-test-auto-configuration.adoc} | 8 +- .../src/main/asciidoc/appendix.adoc | 11 - .../src/main/asciidoc/attributes.adoc | 16 +- .../src/main/asciidoc/build-tool-plugins.adoc | 8 +- .../src/main/asciidoc/deployment.adoc | 20 +- .../main/asciidoc/documentation-overview.adoc | 8 +- .../src/main/asciidoc/getting-started.adoc | 6 +- .../src/main/asciidoc/howto.adoc | 110 ++-- .../src/main/asciidoc/index.htmladoc | 13 +- .../src/main/asciidoc/index.htmlsingleadoc | 13 +- .../asciidoc/production-ready-features.adoc | 199 ++++--- .../src/main/asciidoc/spring-boot-cli.adoc | 7 +- .../main/asciidoc/spring-boot-features.adoc | 510 +++++++++--------- .../asciidoc/spring-boot-reference.pdfadoc | 13 +- .../src/main/asciidoc/using-spring-boot.adoc | 52 +- .../HibernateSearchElasticsearchExample.java | 9 +- .../spring-boot-parent/pom.xml | 64 +-- .../spring-boot-properties-migrator/pom.xml | 5 + .../spring-boot-starters/README.adoc | 2 +- .../spring-boot-starters/pom.xml | 12 + .../spring-boot-starter-activemq/pom.xml | 5 + .../spring-boot-starter-actuator/pom.xml | 5 + .../spring-boot-starter-amqp/pom.xml | 5 + .../spring-boot-starter-aop/pom.xml | 5 + .../spring-boot-starter-artemis/pom.xml | 5 + .../spring-boot-starter-batch/pom.xml | 5 + .../spring-boot-starter-cache/pom.xml | 5 + .../pom.xml | 8 +- .../pom.xml | 5 + .../pom.xml | 5 + .../pom.xml | 5 + .../pom.xml | 5 + .../pom.xml | 5 + .../spring-boot-starter-data-jdbc/pom.xml | 5 + .../spring-boot-starter-data-jpa/pom.xml | 5 + .../spring-boot-starter-data-ldap/pom.xml | 5 + .../pom.xml | 5 + .../spring-boot-starter-data-mongodb/pom.xml | 5 + .../spring-boot-starter-data-neo4j/pom.xml | 5 + .../pom.xml | 5 + .../spring-boot-starter-data-redis/pom.xml | 5 + .../spring-boot-starter-data-rest/pom.xml | 5 + .../spring-boot-starter-data-solr/pom.xml | 5 + .../spring-boot-starter-freemarker/pom.xml | 5 + .../pom.xml | 5 + .../spring-boot-starter-hateoas/pom.xml | 9 +- .../spring-boot-starter-integration/pom.xml | 5 + .../spring-boot-starter-jdbc/pom.xml | 5 + .../spring-boot-starter-jersey/pom.xml | 11 + .../spring-boot-starter-jetty/pom.xml | 5 + .../spring-boot-starter-jooq/pom.xml | 5 + .../spring-boot-starter-json/pom.xml | 5 + .../spring-boot-starter-jta-atomikos/pom.xml | 5 + .../spring-boot-starter-jta-bitronix/pom.xml | 5 + .../spring-boot-starter-log4j2/pom.xml | 5 + .../spring-boot-starter-logging/pom.xml | 5 + .../spring-boot-starter-mail/pom.xml | 5 + .../spring-boot-starter-mustache/pom.xml | 5 + .../spring-boot-starter-oauth2-client/pom.xml | 5 + .../pom.xml | 5 + .../spring-boot-starter-quartz/pom.xml | 5 + .../spring-boot-starter-reactor-netty/pom.xml | 5 + .../spring-boot-starter-security/pom.xml | 5 + .../spring-boot-starter-test/pom.xml | 5 + .../spring-boot-starter-thymeleaf/pom.xml | 5 + .../spring-boot-starter-tomcat/pom.xml | 5 + .../spring-boot-starter-undertow/pom.xml | 5 + .../spring-boot-starter-validation/pom.xml | 5 + .../spring-boot-starter-web-services/pom.xml | 5 + .../spring-boot-starter-web/pom.xml | 5 + .../spring-boot-starter-webflux/pom.xml | 5 + .../spring-boot-starter-websocket/pom.xml | 5 + .../spring-boot-starter/pom.xml | 5 + .../spring-boot-test-autoconfigure/pom.xml | 10 +- ...ConfigurationContextCustomizerFactory.java | 2 +- .../autoconfigure/data/jdbc/DataJdbcTest.java | 6 +- .../autoconfigure/data/ldap/DataLdapTest.java | 6 +- .../data/mongo/DataMongoTest.java | 6 +- .../data/neo4j/DataNeo4jTest.java | 6 +- .../data/redis/DataRedisTest.java | 8 +- .../TypeExcludeFiltersContextCustomizer.java | 4 +- .../test/autoconfigure/jdbc/JdbcTest.java | 9 +- .../test/autoconfigure/jooq/JooqTest.java | 7 +- .../test/autoconfigure/json/JsonTest.java | 7 +- .../autoconfigure/orm/jpa/DataJpaTest.java | 7 +- .../PropertyMappingContextCustomizer.java | 4 +- .../web/client/RestClientTest.java | 8 +- ...ingBootWebTestClientBuilderCustomizer.java | 1 + .../web/reactive/WebFluxTest.java | 6 +- .../WebTestClientAutoConfiguration.java | 1 + .../WebTestClientBuilderCustomizer.java | 12 +- .../autoconfigure/web/servlet/WebMvcTest.java | 6 +- .../web/client/ExampleProperties.java | 8 +- ...nfigurationPropertiesIntegrationTests.java | 4 +- spring-boot-project/spring-boot-test/pom.xml | 5 + .../SpringBootTestContextBootstrapper.java | 3 +- .../assertj/ApplicationContextAssert.java | 2 +- .../ExcludeFilterContextCustomizer.java | 8 +- .../context/filter/TestTypeExcludeFilter.java | 3 +- ...ateJsonObjectContextCustomizerFactory.java | 5 +- .../boot/test/system/OutputCapture.java | 2 +- .../TestRestTemplateContextCustomizer.java | 5 +- .../test/web/htmlunit/LocalHostWebClient.java | 4 +- .../WebTestClientBuilderCustomizer.java | 38 ++ .../WebTestClientContextCustomizer.java | 10 +- ...tractJupiterTestWithConfigAndTestable.java | 31 ++ .../filter/TestTypeExcludeFilterTests.java | 6 + .../MockitoTestExecutionListenerTests.java | 2 +- .../web/client/TestRestTemplateTests.java | 35 +- ...ientContextCustomizerIntegrationTests.java | 14 + spring-boot-project/spring-boot-tools/pom.xml | 5 + .../spring-boot-antlib/pom.xml | 5 + .../pom.xml | 5 + .../AutoConfigureAnnotationProcessor.java | 5 +- ...AutoConfigureAnnotationProcessorTests.java | 18 +- .../TestAutoConfigureAnnotationProcessor.java | 10 +- .../TestClassConfiguration.java | 1 - .../TestMethodConfiguration.java | 1 - .../TestOnBeanWithNameClassConfiguration.java | 1 - .../SingleConfigurationTableEntry.java | 2 +- .../SingleConfigurationTableEntryTests.java | 4 +- .../pom.xml | 5 + .../pom.xml | 5 + ...figurationMetadataAnnotationProcessor.java | 39 +- .../MetadataGenerationEnvironment.java | 51 +- .../PropertyDescriptorResolver.java | 96 +++- ...ablePropertiesMetadataGenerationTests.java | 41 ++ .../MetadataGenerationEnvironmentFactory.java | 1 + .../PropertyDescriptorResolverTests.java | 40 +- ...figurationMetadataAnnotationProcessor.java | 7 + .../ConstructorBinding.java | 36 ++ .../MetaConstructorBinding.java} | 13 +- .../DeducedImmutableClassProperties.java | 55 ++ ...ableClassConstructorBindingProperties.java | 39 ++ .../ImmutableMultiConstructorProperties.java | 46 ++ .../immutable/ImmutableSimpleProperties.java | 2 + ...nvalidDefaultValueCharacterProperties.java | 2 + ...idDefaultValueFloatingPointProperties.java | 2 + .../InvalidDefaultValueNumberProperties.java | 2 + ...chingConstructorNoDirectiveProperties.java | 40 ++ ...ructorsClassConstructorBindingExample.java | 50 ++ .../spring-boot-gradle-plugin/pom.xml | 20 +- .../tasks/bundling/BootArchiveSupport.java | 4 +- .../boot/gradle/tasks/bundling/BootJar.java | 2 + .../boot/gradle/tasks/run/BootRun.java | 18 +- .../spring-boot-loader-tools/pom.xml | 5 + .../boot/loader/tools/launch.script | 15 + .../tools/ZipHeaderPeekInputStreamTests.java | 10 +- .../spring-boot-loader/pom.xml | 18 +- .../loader/jar/CentralDirectoryEndRecord.java | 125 ++++- .../boot/loader/PropertiesLauncherTests.java | 12 +- .../loader/archive/JarFileArchiveTests.java | 25 +- .../boot/loader/jar/JarFileTests.java | 66 +++ .../spring-boot-maven-plugin/pom.xml | 5 + .../src/it/run-fork/verify.groovy | 19 +- .../springframework/boot/maven/RunMojo.java | 14 +- .../spring-boot-test-support/pom.xml | 5 + spring-boot-project/spring-boot/pom.xml | 15 + .../org/springframework/boot/ImageBanner.java | 71 ++- ...nitializationBeanFactoryPostProcessor.java | 55 +- .../boot/LazyInitializationExcludeFilter.java | 76 +++ .../springframework/boot/ResourceBanner.java | 1 + .../boot/StartupInfoLogger.java | 2 +- .../boot/ansi/Ansi8BitColor.java | 88 +++ .../springframework/boot/ansi/AnsiColors.java | 124 ++++- .../boot/ansi/AnsiPropertySource.java | 121 +++-- .../ConfigurationBeanFactoryMetadata.java | 15 +- .../properties/ConfigurationProperties.java | 21 +- .../ConfigurationPropertiesBean.java | 305 +++++++++++ ...ConfigurationPropertiesBeanDefinition.java | 70 --- ...tionPropertiesBeanDefinitionRegistrar.java | 112 ---- ...tionPropertiesBeanDefinitionValidator.java | 80 +++ .../ConfigurationPropertiesBeanRegistrar.java | 96 +++- .../ConfigurationPropertiesBindException.java | 21 +- .../ConfigurationPropertiesBinder.java | 104 ++-- ...urationPropertiesBindingPostProcessor.java | 93 ++-- ...opertiesBindingPostProcessorRegistrar.java | 43 +- .../ConfigurationPropertiesScan.java | 7 +- .../ConfigurationPropertiesScanRegistrar.java | 75 ++- ...onPropertiesValueObjectBeanDefinition.java | 56 ++ .../properties/ConstructorBinding.java | 40 ++ .../EnableConfigurationProperties.java | 8 +- ...nableConfigurationPropertiesRegistrar.java | 56 ++ .../ImmutableConfigurationProperties.java | 60 +++ .../properties/PropertySourcesDeducer.java | 23 +- .../context/properties/bind/Bindable.java | 40 +- .../boot/context/properties/bind/Binder.java | 4 +- .../properties/bind/ValueObjectBinder.java | 20 +- .../properties/source/PropertyMapper.java | 2 +- ...ngIterableConfigurationPropertySource.java | 6 +- .../convert/ApplicationConversionService.java | 1 + .../boot/convert/StringToFileConverter.java | 63 +++ .../env/OriginTrackedMapPropertySource.java | 8 +- .../boot/logging/LoggingSystemProperties.java | 8 + .../logback/DefaultLogbackConfiguration.java | 3 +- .../logging/logback/LogbackLoggingSystem.java | 2 + .../boot/origin/OriginLookup.java | 10 + .../context/LocalRSocketServerPort.java | 42 ++ ...PortInfoApplicationContextInitializer.java | 95 ++++ .../RSocketServerBootstrap.java | 25 +- .../RSocketServerInitializedEvent.java | 7 +- .../rsocket/netty/NettyRSocketServer.java | 20 +- .../netty/NettyRSocketServerFactory.java | 70 +-- .../ConfigurableRSocketServerFactory.java | 2 +- .../boot/rsocket/server/RSocketServer.java | 12 +- .../server/RSocketServerException.java | 1 - ...ava => ServerRSocketFactoryProcessor.java} | 18 +- .../ApplicationContextRequestMatcher.java | 16 +- .../boot/web/client/RestTemplateBuilder.java | 20 +- ...eBuilderClientHttpRequestInitializer.java} | 18 +- .../client/RestTemplateRequestCustomizer.java | 2 + .../netty/NettyReactiveWebServerFactory.java | 9 +- .../TomcatEmbeddedWebappClassLoader.java | 10 +- .../web/embedded/tomcat/TomcatErrorPage.java | 88 --- .../tomcat/TomcatServletWebServerFactory.java | 6 +- ...itional-spring-configuration-metadata.json | 19 + .../main/resources/META-INF/spring.factories | 1 + .../boot/logging/logback/file-appender.xml | 2 +- .../boot/ImageBannerTests.java | 29 +- .../LazyInitializationExcludeFilterTests.java | 47 ++ .../boot/ResourceBannerTests.java | 17 + .../boot/SpringApplicationTests.java | 38 +- ...Tests.java => StartupInfoLoggerTests.java} | 17 +- .../boot/ansi/Ansi8BitColorTests.java | 67 +++ .../boot/ansi/AnsiColorsTests.java | 100 ++-- .../boot/ansi/AnsiPropertySourceTests.java | 7 + .../SpringApplicationBuilderTests.java | 6 +- .../LoggingApplicationListenerTests.java | 7 +- ...igurationPropertiesBeanRegistrarTests.java | 118 ++-- .../ConfigurationPropertiesBeanTests.java | 421 +++++++++++++++ ...igurationPropertiesBindExceptionTests.java | 57 ++ ...igurationPropertiesScanRegistrarTests.java | 13 +- .../ConfigurationPropertiesTests.java | 186 ++++++- .../ConversionServiceDeducerTests.java | 106 ++++ ...ConfigurationPropertiesRegistrarTests.java | 150 ++++++ ...ltiConstructorConfigurationProperties.java | 54 ++ .../PropertySourcesDeducerTests.java | 128 +++++ .../properties/bind/BindableTests.java | 29 + .../context/properties/bind/BinderTests.java | 2 +- .../bind/ValueObjectBinderTests.java | 21 +- ...figurationPropertiesScanConfiguration.java | 3 +- .../scan/valid/b/BScanConfiguration.java | 3 +- .../convert/StringToFileConverterTests.java | 60 +++ ...tionJsonEnvironmentPostProcessorTests.java | 4 +- .../jdbc/DatabaseDriverClassNameTests.java | 8 +- .../logging/LoggingSystemPropertiesTests.java | 10 + .../logback/LogbackConfigurationTests.java | 30 ++ .../logback/LogbackLoggingSystemTests.java | 15 + .../context/LocalRSocketServerPortTests.java | 55 ++ .../netty/NettyRSocketServerFactoryTests.java | 69 ++- ...ApplicationContextRequestMatcherTests.java | 73 +++ ...rClientHttpRequestFactoryWrapperTests.java | 117 ---- ...lderClientHttpRequestInitializerTests.java | 94 ++++ .../web/client/RestTemplateBuilderTests.java | 36 +- .../NettyReactiveWebServerFactoryTests.java | 15 - .../TomcatServletWebServerFactoryTests.java | 10 +- ...UndertowReactiveWebServerFactoryTests.java | 11 +- .../UndertowServletWebServerFactoryTests.java | 10 +- ...AbstractReactiveWebServerFactoryTests.java | 19 +- ...nfigurationPropertiesBeanRegistrarTests.kt | 27 +- ...-file-log-pattern-with-fileNamePattern.xml | 4 + .../spring-boot-deployment-test-tomee/pom.xml | 2 +- .../spring-boot-devtools-tests/pom.xml | 9 +- .../AbstractDevToolsIntegrationTests.java | 114 ++++ .../boot/devtools/tests/ApplicationState.java | 63 +++ .../tests/DevToolsIntegrationTests.java | 98 +--- ...ithLazyInitializationIntegrationTests.java | 102 +--- .../boot/devtools/tests/FileContents.java | 60 +++ .../tests/RemoteApplicationLauncher.java | 44 +- .../launchscript/SysVinitLaunchScriptIT.java | 32 ++ .../launch-with-run-as-invalid-user.sh | 7 + ...with-run-as-user-preferred-to-jar-owner.sh | 13 + .../launch-with-run-as-user-root-required.sh | 9 + .../scripts/launch-with-run-as-user.sh | 10 + .../spring-boot-server-tests/pom.xml | 5 + .../embedded/AbstractApplicationLauncher.java | 14 +- .../spring-boot-smoke-tests/pom.xml | 2 + .../actuator/SampleLegacyEndpoint.java | 35 ++ .../src/main/resources/application.properties | 1 + ...pertiesSampleActuatorApplicationTests.java | 13 +- ...gementAddressActuatorApplicationTests.java | 11 +- ...entPortSampleActuatorApplicationTests.java | 6 +- ...entPathSampleActuatorApplicationTests.java | 14 +- ...AndPathSampleActuatorApplicationTests.java | 43 +- ...entPortSampleActuatorApplicationTests.java | 30 +- ...agementSampleActuatorApplicationTests.java | 19 +- .../SampleActuatorApplicationTests.java | 91 ++-- ...letPathSampleActuatorApplicationTests.java | 22 +- ...hutdownSampleActuatorApplicationTests.java | 20 +- .../src/main/resources/application.properties | 2 + .../pom.xml | 5 + .../SampleIntegrationApplicationTests.java | 63 +-- .../spring-boot-smoke-test-jersey/pom.xml | 3 - .../jersey/JerseyManagementPortTests.java | 95 ++++ .../spring-boot-smoke-test-kafka/pom.xml | 5 + .../kafka/SampleKafkaApplicationTests.java | 10 +- .../pom.xml | 5 + ...mpleIntegrationParentApplicationTests.java | 32 +- .../spring-boot-smoke-test-quartz/pom.xml | 5 + .../quartz/SampleQuartzApplicationTests.java | 11 +- .../pom.xml | 4 + ...eReactiveOAuth2ClientApplicationTests.java | 6 + .../spring-boot-smoke-test-rsocket/pom.xml | 67 +++ .../main/java/smoketest/rsocket/Project.java | 38 ++ .../smoketest/rsocket/ProjectController.java | 33 ++ .../rsocket/SampleRSocketApplication.java | 29 + .../src/main/resources/application.properties | 2 + .../SampleRSocketApplicationTests.java | 62 +++ .../pom.xml | 45 ++ .../serviceprovider/ExampleController.java | 32 ++ .../SampleSaml2RelyingPartyApplication.java | 29 + .../src/main/resources/application.yml | 27 + .../src/main/resources/saml/certificate.txt | 24 + .../src/main/resources/saml/privatekey.txt | 16 + ...mpleSaml2RelyingPartyApplicationTests.java | 56 ++ .../src/main/resources/application.properties | 1 + 521 files changed, 10860 insertions(+), 3574 deletions(-) create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthContributorRegistryHealthIndicatorRegistryAdapter.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapter.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthContributorRegistryHealthIndicatorRegistryAdapterTests.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorRegistryInjectionIntegrationTests.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapterTests.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/http/ApiVersion.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/InvocationContextTests.java create mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/http/ApiVersionTests.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientConfigurations.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/rsocket/RSocketSecurityAutoConfiguration.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/RegistrationConfiguredCondition.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2LoginConfiguration.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyAutoConfiguration.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyProperties.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyRegistrationConfiguration.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway5xAutoConfigurationTests.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/rsocket/RSocketSecurityAutoConfigurationTests.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyAutoConfigurationTests.java rename spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/quartz/{tables_h2.sql => tables_#_comments.sql} (55%) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/quartz/tables_--_comments.sql create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/quartz/tables_custom_comment_prefix.sql create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/resources/saml/certificate-location create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/resources/saml/private-key-location rename spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/{ => system}/DevToolsEnablementDeducer.java (97%) create mode 100644 spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/system/package-info.java rename spring-boot-project/spring-boot-docs/src/main/asciidoc/{appendix/application-properties.adoc => appendix-application-properties.adoc} (81%) rename spring-boot-project/spring-boot-docs/src/main/asciidoc/{appendix/auto-configuration-classes.adoc => appendix-auto-configuration-classes.adoc} (76%) rename spring-boot-project/spring-boot-docs/src/main/asciidoc/{appendix/configuration-metadata.adoc => appendix-configuration-metadata.adoc} (97%) rename spring-boot-project/spring-boot-docs/src/main/asciidoc/{appendix/dependency-versions.adoc => appendix-dependency-versions.adoc} (68%) rename spring-boot-project/spring-boot-docs/src/main/asciidoc/{appendix/executable-jar-format.adoc => appendix-executable-jar-format.adoc} (96%) rename spring-boot-project/spring-boot-docs/src/main/asciidoc/{appendix/test-auto-configuration.adoc => appendix-test-auto-configuration.adoc} (60%) delete mode 100644 spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix.adoc create mode 100644 spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/WebTestClientBuilderCustomizer.java create mode 100644 spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/filter/AbstractJupiterTestWithConfigAndTestable.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/DeducedImmutablePropertiesMetadataGenerationTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/ConstructorBinding.java rename spring-boot-project/spring-boot-tools/{spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestConfiguration.java => spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/MetaConstructorBinding.java} (76%) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/DeducedImmutableClassProperties.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/ImmutableClassConstructorBindingProperties.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/ImmutableMultiConstructorProperties.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/MatchingConstructorNoDirectiveProperties.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/TwoConstructorsClassConstructorBindingExample.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/LazyInitializationExcludeFilter.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ansi/Ansi8BitColor.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBean.java delete mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanDefinition.java delete mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanDefinitionRegistrar.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanDefinitionValidator.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesValueObjectBeanDefinition.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConstructorBinding.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesRegistrar.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ImmutableConfigurationProperties.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/StringToFileConverter.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/rsocket/context/LocalRSocketServerPort.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/rsocket/context/RSocketPortInfoApplicationContextInitializer.java rename spring-boot-project/spring-boot/src/main/java/org/springframework/boot/rsocket/{server => context}/RSocketServerBootstrap.java (67%) rename spring-boot-project/spring-boot/src/main/java/org/springframework/boot/rsocket/server/{ServerRSocketFactoryCustomizer.java => ServerRSocketFactoryProcessor.java} (60%) rename spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/{RestTemplateBuilderClientHttpRequestFactoryWrapper.java => RestTemplateBuilderClientHttpRequestInitializer.java} (70%) delete mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatErrorPage.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/LazyInitializationExcludeFilterTests.java rename spring-boot-project/spring-boot/src/test/java/org/springframework/boot/{StartUpLoggerTests.java => StartupInfoLoggerTests.java} (71%) create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ansi/Ansi8BitColorTests.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanTests.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindExceptionTests.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConversionServiceDeducerTests.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesRegistrarTests.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/MultiConstructorConfigurationProperties.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/PropertySourcesDeducerTests.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/StringToFileConverterTests.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/rsocket/context/LocalRSocketServerPortTests.java delete mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/RestTemplateBuilderClientHttpRequestFactoryWrapperTests.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/RestTemplateBuilderClientHttpRequestInitializerTests.java create mode 100644 spring-boot-project/spring-boot/src/test/resources/custom-file-log-pattern-with-fileNamePattern.xml create mode 100644 spring-boot-tests/spring-boot-integration-tests/spring-boot-devtools-tests/src/test/java/org/springframework/boot/devtools/tests/AbstractDevToolsIntegrationTests.java create mode 100644 spring-boot-tests/spring-boot-integration-tests/spring-boot-devtools-tests/src/test/java/org/springframework/boot/devtools/tests/ApplicationState.java create mode 100644 spring-boot-tests/spring-boot-integration-tests/spring-boot-devtools-tests/src/test/java/org/springframework/boot/devtools/tests/FileContents.java create mode 100644 spring-boot-tests/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-run-as-invalid-user.sh create mode 100644 spring-boot-tests/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-run-as-user-preferred-to-jar-owner.sh create mode 100644 spring-boot-tests/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-run-as-user-root-required.sh create mode 100644 spring-boot-tests/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-run-as-user.sh create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/main/java/smoketest/actuator/SampleLegacyEndpoint.java create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-animated-banner/src/main/resources/application.properties create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jersey/src/test/java/smoketest/jersey/JerseyManagementPortTests.java create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-rsocket/pom.xml create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-rsocket/src/main/java/smoketest/rsocket/Project.java create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-rsocket/src/main/java/smoketest/rsocket/ProjectController.java create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-rsocket/src/main/java/smoketest/rsocket/SampleRSocketApplication.java create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-rsocket/src/main/resources/application.properties create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-rsocket/src/test/java/smoketest/rsocket/SampleRSocketApplicationTests.java create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/pom.xml create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/java/smoketest/saml2/serviceprovider/ExampleController.java create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/java/smoketest/saml2/serviceprovider/SampleSaml2RelyingPartyApplication.java create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/resources/application.yml create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/resources/saml/certificate.txt create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/resources/saml/privatekey.txt create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/test/java/smoketest/saml2/serviceprovider/SampleSaml2RelyingPartyApplicationTests.java diff --git a/.bomr/bomr.yaml b/.bomr/bomr.yaml index f8a1dc479986..c2b3aca40f7e 100644 --- a/.bomr/bomr.yaml +++ b/.bomr/bomr.yaml @@ -8,14 +8,14 @@ bomr: - 'type: dependency-upgrade' policy: same-major-version prohibited: - - project: maven-invoker-plugin - versions: - # NPE in InstallMojo.installProjectPom (InstallMojo.java:387) - - '[3.2.0]' - project: derby versions: # 10.15 requires Java 9 - '[10.15,)' + - project: netty + versions: + # https://github.com/reactor/reactor-netty/issues/844 + - '[4.1.40.Final,)' verify: ignored-dependencies: # Avoid conflicting transitive requirements for diff --git a/SUPPORT.adoc b/SUPPORT.adoc index 28ee9c36acaa..cecbe28a748e 100755 --- a/SUPPORT.adoc +++ b/SUPPORT.adoc @@ -13,7 +13,7 @@ reproduces the problem. == Stack Overflow The Spring Boot community monitors the https://stackoverflow.com/tags/spring-boot[`spring-boot`] tag on Stack Overflow. Before -asking a question, please familiar yourself with Stack Overflow's +asking a question, please familiarize yourself with Stack Overflow's https://stackoverflow.com/help/how-to-ask[advice on how to ask a good question]. == Gitter diff --git a/ci/images/get-jdk-url.sh b/ci/images/get-jdk-url.sh index 71402ed6cb18..791f5eab52c5 100755 --- a/ci/images/get-jdk-url.sh +++ b/ci/images/get-jdk-url.sh @@ -12,7 +12,7 @@ case "$1" in echo "https://github.com/AdoptOpenJDK/openjdk12-binaries/releases/download/jdk-12.0.2%2B10/OpenJDK12U-jdk_x64_linux_hotspot_12.0.2_10.tar.gz" ;; java13) - echo "https://github.com/AdoptOpenJDK/openjdk13-binaries/releases/download/jdk13u-2019-09-03-11-49/OpenJDK13U-jdk_x64_linux_hotspot_2019-09-03-11-49.tar.gz" + echo "https://github.com/AdoptOpenJDK/openjdk13-binaries/releases/download/jdk-13%2B33/OpenJDK13U-jdk_x64_linux_hotspot_13_33.tar.gz" ;; *) echo $"Unknown java version" diff --git a/ci/scripts/detect-jdk-updates.sh b/ci/scripts/detect-jdk-updates.sh index 4a80971562e3..c7d0e4f52cc7 100755 --- a/ci/scripts/detect-jdk-updates.sh +++ b/ci/scripts/detect-jdk-updates.sh @@ -14,7 +14,7 @@ case "$JDK_VERSION" in ISSUE_TITLE="Upgrade Java 12 version in CI image" ;; java13) - BASE_URL="https://api.adoptopenjdk.net/v2/info/nightly/openjdk13" + BASE_URL="https://api.adoptopenjdk.net/v2/info/releases/openjdk13" ISSUE_TITLE="Upgrade Java 13 version in CI image" ;; *) diff --git a/pom.xml b/pom.xml index f9712576e773..f2976718a3cd 100644 --- a/pom.xml +++ b/pom.xml @@ -193,6 +193,11 @@ false + + spring-release + Spring Release + https://repo.spring.io/release + spring-milestone Spring Milestone diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml b/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml index ada47616650b..3768e9f4f2e6 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml @@ -16,6 +16,11 @@ ${basedir}/../.. ${project.build.directory}/refdocs/ + + ${git.url} + ${git.connection} + ${git.developerConnection} + @@ -648,7 +653,7 @@ wget - https://repo.spring.io/release/io/spring/docresources/spring-doc-resources/${spring-doc-resources.version}/spring-doc-resources-${spring-doc-resources.version}.zip + ${spring-doc-resources.url} true ${refdocs.build.directory} @@ -758,7 +763,7 @@ book js/highlight - atom-one-dark-reasonable + github true ./images font @@ -799,7 +804,7 @@ org.asciidoctor asciidoctorj-pdf - 1.5.0-alpha.11 + 1.5.0-alpha.18 diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/asciidoc/endpoints/health.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/asciidoc/endpoints/health.adoc index 3d55e176a619..a7c1998dde37 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/asciidoc/endpoints/health.adoc +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/asciidoc/endpoints/health.adoc @@ -24,6 +24,9 @@ The following table describes the structure of the response: [cols="2,1,3"] include::{snippets}health/response-fields.adoc[] +NOTE: The response fields above are for the V3 API. +If you need to return V2 JSON you should use an accept header or `application/vnd.spring-boot.actuator.v2+json` + [[health-retrieving-component]] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtension.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtension.java index 564618222146..589f952bc770 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtension.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtension.java @@ -24,6 +24,7 @@ import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.annotation.Selector.Match; +import org.springframework.boot.actuate.endpoint.http.ApiVersion; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.health.HealthComponent; import org.springframework.boot.actuate.health.HealthEndpoint; @@ -46,14 +47,14 @@ public CloudFoundryReactiveHealthEndpointWebExtension(ReactiveHealthEndpointWebE } @ReadOperation - public Mono> health() { - return this.delegate.health(SecurityContext.NONE, true); + public Mono> health(ApiVersion apiVersion) { + return this.delegate.health(apiVersion, SecurityContext.NONE, true); } @ReadOperation - public Mono> health( + public Mono> health(ApiVersion apiVersion, @Selector(match = Match.ALL_REMAINING) String... path) { - return this.delegate.health(SecurityContext.NONE, true, path); + return this.delegate.health(apiVersion, SecurityContext.NONE, true, path); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryHealthEndpointWebExtension.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryHealthEndpointWebExtension.java index 8f1328b0c500..e91a1fbe4b54 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryHealthEndpointWebExtension.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryHealthEndpointWebExtension.java @@ -22,6 +22,7 @@ import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.annotation.Selector.Match; +import org.springframework.boot.actuate.endpoint.http.ApiVersion; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.health.HealthComponent; import org.springframework.boot.actuate.health.HealthEndpoint; @@ -44,13 +45,14 @@ public CloudFoundryHealthEndpointWebExtension(HealthEndpointWebExtension delegat } @ReadOperation - public WebEndpointResponse health() { - return this.delegate.health(SecurityContext.NONE, true); + public WebEndpointResponse health(ApiVersion apiVersion) { + return this.delegate.health(apiVersion, SecurityContext.NONE, true); } @ReadOperation - public WebEndpointResponse health(@Selector(match = Match.ALL_REMAINING) String... path) { - return this.delegate.health(SecurityContext.NONE, true, path); + public WebEndpointResponse health(ApiVersion apiVersion, + @Selector(match = Match.ALL_REMAINING) String... path) { + return this.delegate.health(apiVersion, SecurityContext.NONE, true, path); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/AbstractEndpointCondition.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/AbstractEndpointCondition.java index 1747da82b648..b8e9a76ecdfc 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/AbstractEndpointCondition.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/AbstractEndpointCondition.java @@ -62,7 +62,7 @@ protected ConditionOutcome getEnablementOutcome(ConditionContext context, Annota Class annotationClass) { Environment environment = context.getEnvironment(); AnnotationAttributes attributes = getEndpointAttributes(annotationClass, context, metadata); - EndpointId id = EndpointId.of(attributes.getString("id")); + EndpointId id = EndpointId.of(environment, attributes.getString("id")); String key = "management.endpoint." + id.toLowerCaseString() + ".enabled"; Boolean userDefinedEnabled = environment.getProperty(key, Boolean.class); if (userDefinedEnabled != null) { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnAvailableEndpointCondition.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnAvailableEndpointCondition.java index 36809efa2dc6..b13faff3d783 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnAvailableEndpointCondition.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnAvailableEndpointCondition.java @@ -62,7 +62,7 @@ public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeM } AnnotationAttributes attributes = getEndpointAttributes(ConditionalOnAvailableEndpoint.class, context, metadata); - EndpointId id = EndpointId.of(attributes.getString("id")); + EndpointId id = EndpointId.of(environment, attributes.getString("id")); Set exposureInformations = getExposureInformation(environment); for (ExposureInformation exposureInformation : exposureInformations) { if (exposureInformation.isExposed(id)) { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfiguration.java index e54837ad1417..ea5eac25d2b8 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfiguration.java @@ -16,10 +16,8 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.web; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.List; import java.util.stream.Collectors; import org.springframework.beans.factory.ObjectProvider; @@ -28,7 +26,6 @@ import org.springframework.boot.actuate.endpoint.EndpointFilter; import org.springframework.boot.actuate.endpoint.EndpointsSupplier; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; -import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType; import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor; import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; @@ -67,8 +64,6 @@ @EnableConfigurationProperties(WebEndpointProperties.class) public class WebEndpointAutoConfiguration { - private static final List MEDIA_TYPES = Arrays.asList(ActuatorMediaType.V2_JSON, "application/json"); - private final ApplicationContext applicationContext; private final WebEndpointProperties properties; @@ -86,7 +81,7 @@ public PathMapper webEndpointPathMapper() { @Bean @ConditionalOnMissingBean public EndpointMediaTypes endpointMediaTypes() { - return new EndpointMediaTypes(MEDIA_TYPES, MEDIA_TYPES); + return EndpointMediaTypes.DEFAULT; } @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/jersey/JerseyWebEndpointManagementContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/jersey/JerseyWebEndpointManagementContextConfiguration.java index d04c739c5d6f..d22961a5f62f 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/jersey/JerseyWebEndpointManagementContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/jersey/JerseyWebEndpointManagementContextConfiguration.java @@ -18,12 +18,15 @@ import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashSet; import java.util.List; +import javax.annotation.PostConstruct; + import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.model.Resource; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType; @@ -32,6 +35,7 @@ import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; +import org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint; import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier; import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier; @@ -64,22 +68,15 @@ class JerseyWebEndpointManagementContextConfiguration { @Bean - ResourceConfigCustomizer webEndpointRegistrar(WebEndpointsSupplier webEndpointsSupplier, + JerseyWebEndpointsResourcesRegistrar jerseyWebEndpointsResourcesRegistrar(Environment environment, + ObjectProvider resourceConfig, WebEndpointsSupplier webEndpointsSupplier, ServletEndpointsSupplier servletEndpointsSupplier, EndpointMediaTypes endpointMediaTypes, - WebEndpointProperties webEndpointProperties, Environment environment) { - List> allEndpoints = new ArrayList<>(); - allEndpoints.addAll(webEndpointsSupplier.getEndpoints()); - allEndpoints.addAll(servletEndpointsSupplier.getEndpoints()); - return (resourceConfig) -> { - JerseyEndpointResourceFactory resourceFactory = new JerseyEndpointResourceFactory(); - String basePath = webEndpointProperties.getBasePath(); - EndpointMapping endpointMapping = new EndpointMapping(basePath); - Collection webEndpoints = Collections - .unmodifiableCollection(webEndpointsSupplier.getEndpoints()); - resourceConfig.registerResources(new HashSet<>(resourceFactory.createEndpointResources(endpointMapping, - webEndpoints, endpointMediaTypes, new EndpointLinksResolver(allEndpoints, basePath), - shouldRegisterLinksMapping(environment, basePath)))); - }; + WebEndpointProperties webEndpointProperties) { + String basePath = webEndpointProperties.getBasePath(); + boolean shouldRegisterLinks = shouldRegisterLinksMapping(environment, basePath); + shouldRegisterLinksMapping(environment, basePath); + return new JerseyWebEndpointsResourcesRegistrar(resourceConfig.getIfAvailable(), webEndpointsSupplier, + servletEndpointsSupplier, endpointMediaTypes, basePath, shouldRegisterLinks); } private boolean shouldRegisterLinksMapping(Environment environment, String basePath) { @@ -87,4 +84,65 @@ private boolean shouldRegisterLinksMapping(Environment environment, String baseP || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT); } + /** + * Register endpoints with the {@link ResourceConfig}. The + * {@link ResourceConfigCustomizer} cannot be used because we don't want to apply + */ + static class JerseyWebEndpointsResourcesRegistrar { + + private final ResourceConfig resourceConfig; + + private final WebEndpointsSupplier webEndpointsSupplier; + + private final ServletEndpointsSupplier servletEndpointsSupplier; + + private final EndpointMediaTypes mediaTypes; + + private final String basePath; + + private final boolean shouldRegisterLinks; + + JerseyWebEndpointsResourcesRegistrar(ResourceConfig resourceConfig, WebEndpointsSupplier webEndpointsSupplier, + ServletEndpointsSupplier servletEndpointsSupplier, EndpointMediaTypes endpointMediaTypes, + String basePath, boolean shouldRegisterLinks) { + super(); + this.resourceConfig = resourceConfig; + this.webEndpointsSupplier = webEndpointsSupplier; + this.servletEndpointsSupplier = servletEndpointsSupplier; + this.mediaTypes = endpointMediaTypes; + this.basePath = basePath; + this.shouldRegisterLinks = shouldRegisterLinks; + } + + @PostConstruct + void register() { + // We can't easily use @ConditionalOnBean because @AutoConfigureBefore is + // not an option for management contexts. Instead we manually check if + // the resource config bean exists + if (this.resourceConfig == null) { + return; + } + Collection webEndpoints = this.webEndpointsSupplier.getEndpoints(); + Collection servletEndpoints = this.servletEndpointsSupplier.getEndpoints(); + EndpointLinksResolver linksResolver = getLinksResolver(webEndpoints, servletEndpoints); + EndpointMapping mapping = new EndpointMapping(this.basePath); + JerseyEndpointResourceFactory resourceFactory = new JerseyEndpointResourceFactory(); + register(resourceFactory.createEndpointResources(mapping, webEndpoints, this.mediaTypes, linksResolver, + this.shouldRegisterLinks)); + } + + private EndpointLinksResolver getLinksResolver(Collection webEndpoints, + Collection servletEndpoints) { + List> endpoints = new ArrayList<>(webEndpoints.size() + servletEndpoints.size()); + endpoints.addAll(webEndpoints); + endpoints.addAll(servletEndpoints); + return new EndpointLinksResolver(endpoints, this.basePath); + } + + private void register(Collection resources) { + this.resourceConfig.registerResources(new HashSet<>(resources)); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointGroup.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointGroup.java index 8922d148c400..52c44bd28260 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointGroup.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointGroup.java @@ -19,7 +19,7 @@ import java.util.Collection; import java.util.function.Predicate; -import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.ShowDetails; +import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.Show; import org.springframework.boot.actuate.endpoint.SecurityContext; import org.springframework.boot.actuate.health.HealthEndpointGroup; import org.springframework.boot.actuate.health.HttpCodeStatusMapper; @@ -40,7 +40,9 @@ class AutoConfiguredHealthEndpointGroup implements HealthEndpointGroup { private final HttpCodeStatusMapper httpCodeStatusMapper; - private final ShowDetails showDetails; + private final Show showComponents; + + private final Show showDetails; private final Collection roles; @@ -49,14 +51,17 @@ class AutoConfiguredHealthEndpointGroup implements HealthEndpointGroup { * @param members a predicate used to test for group membership * @param statusAggregator the status aggregator to use * @param httpCodeStatusMapper the HTTP code status mapper to use + * @param showComponents the show components setting * @param showDetails the show details setting * @param roles the roles to match */ AutoConfiguredHealthEndpointGroup(Predicate members, StatusAggregator statusAggregator, - HttpCodeStatusMapper httpCodeStatusMapper, ShowDetails showDetails, Collection roles) { + HttpCodeStatusMapper httpCodeStatusMapper, Show showComponents, Show showDetails, + Collection roles) { this.members = members; this.statusAggregator = statusAggregator; this.httpCodeStatusMapper = httpCodeStatusMapper; + this.showComponents = showComponents; this.showDetails = showDetails; this.roles = roles; } @@ -67,9 +72,20 @@ public boolean isMember(String name) { } @Override - public boolean includeDetails(SecurityContext securityContext) { - ShowDetails showDetails = this.showDetails; - switch (showDetails) { + public boolean showComponents(SecurityContext securityContext) { + if (this.showComponents == null) { + return showDetails(securityContext); + } + return getShowResult(securityContext, this.showComponents); + } + + @Override + public boolean showDetails(SecurityContext securityContext) { + return getShowResult(securityContext, this.showDetails); + } + + private boolean getShowResult(SecurityContext securityContext, Show show) { + switch (show) { case NEVER: return false; case ALWAYS: @@ -77,7 +93,7 @@ public boolean includeDetails(SecurityContext securityContext) { case WHEN_AUTHORIZED: return isAuthorized(securityContext); } - throw new IllegalStateException("Unsupported ShowDetails value " + showDetails); + throw new IllegalStateException("Unsupported 'show' value " + show); } private boolean isAuthorized(SecurityContext securityContext) { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointGroups.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointGroups.java index 30a2e78c9f88..71dc2d575ba6 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointGroups.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointGroups.java @@ -31,7 +31,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointProperties.Group; -import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.ShowDetails; +import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.Show; import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.Status; import org.springframework.boot.actuate.health.HealthEndpointGroup; import org.springframework.boot.actuate.health.HealthEndpointGroups; @@ -65,7 +65,8 @@ class AutoConfiguredHealthEndpointGroups implements HealthEndpointGroups { AutoConfiguredHealthEndpointGroups(ApplicationContext applicationContext, HealthEndpointProperties properties) { ListableBeanFactory beanFactory = (applicationContext instanceof ConfigurableApplicationContext) ? ((ConfigurableApplicationContext) applicationContext).getBeanFactory() : applicationContext; - ShowDetails showDetails = properties.getShowDetails(); + Show showComponents = properties.getShowComponents(); + Show showDetails = properties.getShowDetails(); Set roles = properties.getRoles(); StatusAggregator statusAggregator = getNonQualifiedBean(beanFactory, StatusAggregator.class); if (statusAggregator == null) { @@ -76,18 +77,20 @@ class AutoConfiguredHealthEndpointGroups implements HealthEndpointGroups { httpCodeStatusMapper = new SimpleHttpCodeStatusMapper(properties.getStatus().getHttpMapping()); } this.primaryGroup = new AutoConfiguredHealthEndpointGroup(ALL, statusAggregator, httpCodeStatusMapper, - showDetails, roles); + showComponents, showDetails, roles); this.groups = createGroups(properties.getGroup(), beanFactory, statusAggregator, httpCodeStatusMapper, - showDetails, roles); + showComponents, showDetails, roles); } private Map createGroups(Map groupProperties, BeanFactory beanFactory, StatusAggregator defaultStatusAggregator, HttpCodeStatusMapper defaultHttpCodeStatusMapper, - ShowDetails defaultShowDetails, Set defaultRoles) { + Show defaultShowComponents, Show defaultShowDetails, Set defaultRoles) { Map groups = new LinkedHashMap(); groupProperties.forEach((groupName, group) -> { Status status = group.getStatus(); - ShowDetails showDetails = (group.getShowDetails() != null) ? group.getShowDetails() : defaultShowDetails; + Show showComponents = (group.getShowComponents() != null) ? group.getShowComponents() + : defaultShowComponents; + Show showDetails = (group.getShowDetails() != null) ? group.getShowDetails() : defaultShowDetails; Set roles = !CollectionUtils.isEmpty(group.getRoles()) ? group.getRoles() : defaultRoles; StatusAggregator statusAggregator = getQualifiedBean(beanFactory, StatusAggregator.class, groupName, () -> { if (!CollectionUtils.isEmpty(status.getOrder())) { @@ -104,7 +107,7 @@ private Map createGroups(Map groupPr }); Predicate members = new IncludeExcludeGroupMemberPredicate(group.getInclude(), group.getExclude()); groups.put(groupName, new AutoConfiguredHealthEndpointGroup(members, statusAggregator, httpCodeStatusMapper, - showDetails, roles)); + showComponents, showDetails, roles)); }); return Collections.unmodifiableMap(groups); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthContributorRegistryHealthIndicatorRegistryAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthContributorRegistryHealthIndicatorRegistryAdapter.java new file mode 100644 index 000000000000..41c38f3ccbe4 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthContributorRegistryHealthIndicatorRegistryAdapter.java @@ -0,0 +1,79 @@ +/* + * Copyright 2012-2019 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 + * + * https://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.health; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.boot.actuate.health.HealthContributor; +import org.springframework.boot.actuate.health.HealthContributorRegistry; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.actuate.health.HealthIndicatorRegistry; +import org.springframework.boot.actuate.health.NamedContributor; +import org.springframework.util.Assert; + +/** + * Adapter class to convert a {@link HealthContributorRegistry} to a legacy + * {@link HealthIndicatorRegistry}. + * + * @author Phillip Webb + */ +@SuppressWarnings("deprecation") +class HealthContributorRegistryHealthIndicatorRegistryAdapter implements HealthIndicatorRegistry { + + private final HealthContributorRegistry contributorRegistry; + + HealthContributorRegistryHealthIndicatorRegistryAdapter(HealthContributorRegistry contributorRegistry) { + Assert.notNull(contributorRegistry, "ContributorRegistry must not be null"); + this.contributorRegistry = contributorRegistry; + } + + @Override + public void register(String name, HealthIndicator healthIndicator) { + this.contributorRegistry.registerContributor(name, healthIndicator); + } + + @Override + public HealthIndicator unregister(String name) { + HealthContributor contributor = this.contributorRegistry.unregisterContributor(name); + if (contributor instanceof HealthIndicator) { + return (HealthIndicator) contributor; + } + return null; + } + + @Override + public HealthIndicator get(String name) { + HealthContributor contributor = this.contributorRegistry.getContributor(name); + if (contributor instanceof HealthIndicator) { + return (HealthIndicator) contributor; + } + return null; + } + + @Override + public Map getAll() { + Map all = new LinkedHashMap(); + for (NamedContributor namedContributor : this.contributorRegistry) { + if (namedContributor.getContributor() instanceof HealthIndicator) { + all.put(namedContributor.getName(), (HealthIndicator) namedContributor.getContributor()); + } + } + return all; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfiguration.java index e0d17b632097..972ee5d3a616 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfiguration.java @@ -20,6 +20,7 @@ import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -32,11 +33,20 @@ * @since 2.0.0 */ @Configuration(proxyBeanMethods = false) -@EnableConfigurationProperties(HealthEndpointProperties.class) @ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class) +@EnableConfigurationProperties @Import({ LegacyHealthEndpointAdaptersConfiguration.class, LegacyHealthEndpointCompatibilityConfiguration.class, HealthEndpointConfiguration.class, ReactiveHealthEndpointConfiguration.class, HealthEndpointWebExtensionConfiguration.class, HealthEndpointReactiveWebExtensionConfiguration.class }) public class HealthEndpointAutoConfiguration { + @Bean + @SuppressWarnings("deprecation") + HealthEndpointProperties healthEndpointProperties(HealthIndicatorProperties healthIndicatorProperties) { + HealthEndpointProperties healthEndpointProperties = new HealthEndpointProperties(); + healthEndpointProperties.getStatus().getOrder().addAll(healthIndicatorProperties.getOrder()); + healthEndpointProperties.getStatus().getHttpMapping().putAll(healthIndicatorProperties.getHttpMapping()); + return healthEndpointProperties; + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorProperties.java index 37019a17f103..b72d49f03626 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorProperties.java @@ -16,9 +16,12 @@ package org.springframework.boot.actuate.autoconfigure.health; +import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; /** @@ -29,26 +32,25 @@ * @deprecated since 2.2.0 in favor of {@link HealthEndpointProperties} */ @Deprecated +@ConfigurationProperties(prefix = "management.health.status") public class HealthIndicatorProperties { - private final HealthEndpointProperties healthEndpointProperties; + private List order = new ArrayList<>(); - HealthIndicatorProperties(HealthEndpointProperties healthEndpointProperties) { - this.healthEndpointProperties = healthEndpointProperties; - } + private final Map httpMapping = new LinkedHashMap(); @DeprecatedConfigurationProperty(replacement = "management.endpoint.health.status.order") public List getOrder() { - return this.healthEndpointProperties.getStatus().getOrder(); + return this.order; } - public void setOrder(List statusOrder) { - this.healthEndpointProperties.getStatus().setOrder(statusOrder); + public void setOrder(List order) { + this.order = order; } @DeprecatedConfigurationProperty(replacement = "management.endpoint.health.status.http-mapping") public Map getHttpMapping() { - return this.healthEndpointProperties.getStatus().getHttpMapping(); + return this.httpMapping; } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthProperties.java index 7ce78e2d31fd..901c4031d53e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthProperties.java @@ -16,6 +16,7 @@ package org.springframework.boot.actuate.autoconfigure.health; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -37,10 +38,15 @@ public abstract class HealthProperties { @NestedConfigurationProperty private final Status status = new Status(); + /** + * When to show components. If not specified the 'show-details' setting will be used. + */ + private Show showComponents; + /** * When to show full health details. */ - private ShowDetails showDetails = ShowDetails.NEVER; + private Show showDetails = Show.NEVER; /** * Roles used to determine whether or not a user is authorized to be shown details. @@ -52,11 +58,19 @@ public Status getStatus() { return this.status; } - public ShowDetails getShowDetails() { + public Show getShowComponents() { + return this.showComponents; + } + + public void setShowComponents(Show showComponents) { + this.showComponents = showComponents; + } + + public Show getShowDetails() { return this.showDetails; } - public void setShowDetails(ShowDetails showDetails) { + public void setShowDetails(Show showDetails) { this.showDetails = showDetails; } @@ -76,7 +90,7 @@ public static class Status { /** * Comma-separated list of health statuses in order of severity. */ - private List order = null; + private List order = new ArrayList<>(); /** * Mapping of health statuses to HTTP status codes. By default, registered health @@ -101,23 +115,23 @@ public Map getHttpMapping() { } /** - * Options for showing details in responses from the {@link HealthEndpoint} web + * Options for showing items in responses from the {@link HealthEndpoint} web * extensions. */ - public enum ShowDetails { + public enum Show { /** - * Never show details in the response. + * Never show the item in the response. */ NEVER, /** - * Show details in the response when accessed by an authorized user. + * Show the item in the response when accessed by an authorized user. */ WHEN_AUTHORIZED, /** - * Always show details in the response. + * Always show the item in the response. */ ALWAYS diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/LegacyHealthEndpointCompatibilityConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/LegacyHealthEndpointCompatibilityConfiguration.java index 89c5377b8f2c..0c5b32ff2ec6 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/LegacyHealthEndpointCompatibilityConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/LegacyHealthEndpointCompatibilityConfiguration.java @@ -16,13 +16,21 @@ package org.springframework.boot.actuate.autoconfigure.health; +import io.micrometer.shaded.reactor.core.publisher.Mono; + import org.springframework.boot.actuate.health.HealthAggregator; +import org.springframework.boot.actuate.health.HealthContributorRegistry; +import org.springframework.boot.actuate.health.HealthIndicatorRegistry; import org.springframework.boot.actuate.health.HealthStatusHttpMapper; import org.springframework.boot.actuate.health.OrderedHealthAggregator; +import org.springframework.boot.actuate.health.ReactiveHealthContributorRegistry; +import org.springframework.boot.actuate.health.ReactiveHealthIndicatorRegistry; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.util.CollectionUtils; /** * Configuration to adapt legacy deprecated health endpoint classes and interfaces. @@ -32,19 +40,14 @@ */ @Configuration(proxyBeanMethods = false) @SuppressWarnings("deprecation") +@EnableConfigurationProperties(HealthIndicatorProperties.class) class LegacyHealthEndpointCompatibilityConfiguration { - @Bean - @ConfigurationProperties(prefix = "management.health.status") - HealthIndicatorProperties healthIndicatorProperties(HealthEndpointProperties healthEndpointProperties) { - return new HealthIndicatorProperties(healthEndpointProperties); - } - @Bean @ConditionalOnMissingBean HealthAggregator healthAggregator(HealthIndicatorProperties healthIndicatorProperties) { OrderedHealthAggregator aggregator = new OrderedHealthAggregator(); - if (healthIndicatorProperties.getOrder() != null) { + if (!CollectionUtils.isEmpty(healthIndicatorProperties.getOrder())) { aggregator.setStatusOrder(healthIndicatorProperties.getOrder()); } return aggregator; @@ -54,10 +57,31 @@ HealthAggregator healthAggregator(HealthIndicatorProperties healthIndicatorPrope @ConditionalOnMissingBean HealthStatusHttpMapper healthStatusHttpMapper(HealthIndicatorProperties healthIndicatorProperties) { HealthStatusHttpMapper mapper = new HealthStatusHttpMapper(); - if (healthIndicatorProperties.getHttpMapping() != null) { + if (!CollectionUtils.isEmpty(healthIndicatorProperties.getHttpMapping())) { mapper.setStatusMapping(healthIndicatorProperties.getHttpMapping()); } return mapper; } + @Bean + @ConditionalOnMissingBean(HealthIndicatorRegistry.class) + HealthContributorRegistryHealthIndicatorRegistryAdapter healthIndicatorRegistry( + HealthContributorRegistry healthContributorRegistry) { + return new HealthContributorRegistryHealthIndicatorRegistryAdapter(healthContributorRegistry); + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(Mono.class) + static class LegacyReactiveHealthEndpointCompatibilityConfiguration { + + @Bean + @ConditionalOnMissingBean(ReactiveHealthIndicatorRegistry.class) + ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapter reactiveHealthIndicatorRegistry( + ReactiveHealthContributorRegistry reactiveHealthContributorRegistry) { + return new ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapter( + reactiveHealthContributorRegistry); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapter.java new file mode 100644 index 000000000000..5b1cdce8d80a --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapter.java @@ -0,0 +1,83 @@ +/* + * Copyright 2012-2019 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 + * + * https://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.health; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.boot.actuate.health.HealthContributorRegistry; +import org.springframework.boot.actuate.health.HealthIndicatorRegistry; +import org.springframework.boot.actuate.health.NamedContributor; +import org.springframework.boot.actuate.health.ReactiveHealthContributor; +import org.springframework.boot.actuate.health.ReactiveHealthContributorRegistry; +import org.springframework.boot.actuate.health.ReactiveHealthIndicator; +import org.springframework.boot.actuate.health.ReactiveHealthIndicatorRegistry; +import org.springframework.util.Assert; + +/** + * Adapter class to convert a {@link HealthContributorRegistry} to a legacy + * {@link HealthIndicatorRegistry}. + * + * @author Phillip Webb + */ +@SuppressWarnings("deprecation") +class ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapter + implements ReactiveHealthIndicatorRegistry { + + private final ReactiveHealthContributorRegistry contributorRegistry; + + ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapter( + ReactiveHealthContributorRegistry contributorRegistry) { + Assert.notNull(contributorRegistry, "ContributorRegistry must not be null"); + this.contributorRegistry = contributorRegistry; + } + + @Override + public void register(String name, ReactiveHealthIndicator healthIndicator) { + this.contributorRegistry.registerContributor(name, healthIndicator); + } + + @Override + public ReactiveHealthIndicator unregister(String name) { + ReactiveHealthContributor contributor = this.contributorRegistry.unregisterContributor(name); + if (contributor instanceof ReactiveHealthIndicator) { + return (ReactiveHealthIndicator) contributor; + } + return null; + } + + @Override + public ReactiveHealthIndicator get(String name) { + ReactiveHealthContributor contributor = this.contributorRegistry.getContributor(name); + if (contributor instanceof ReactiveHealthIndicator) { + return (ReactiveHealthIndicator) contributor; + } + return null; + } + + @Override + public Map getAll() { + Map all = new LinkedHashMap<>(); + for (NamedContributor namedContributor : this.contributorRegistry) { + if (namedContributor.getContributor() instanceof ReactiveHealthIndicator) { + all.put(namedContributor.getName(), (ReactiveHealthIndicator) namedContributor.getContributor()); + } + } + return all; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/atlas/AtlasProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/atlas/AtlasProperties.java index 09ebf5c594c6..33da891230a0 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/atlas/AtlasProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/atlas/AtlasProperties.java @@ -32,6 +32,11 @@ @ConfigurationProperties(prefix = "management.metrics.export.atlas") public class AtlasProperties extends StepRegistryProperties { + /** + * Number of threads to use with the metrics publishing scheduler. + */ + private Integer numThreads = 4; + /** * URI of the Atlas server. */ @@ -68,6 +73,16 @@ public class AtlasProperties extends StepRegistryProperties { */ private String evalUri = "http://localhost:7101/lwc/api/v1/evaluate"; + @Override + public Integer getNumThreads() { + return this.numThreads; + } + + @Override + public void setNumThreads(Integer numThreads) { + this.numThreads = numThreads; + } + public String getUri() { return this.uri; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/humio/HumioProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/humio/HumioProperties.java index 043eb5f32ddc..a14df5a84e6b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/humio/HumioProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/humio/HumioProperties.java @@ -22,6 +22,7 @@ import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryProperties; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; /** * {@link ConfigurationProperties @ConfigurationProperties} for configuring Humio metrics @@ -46,7 +47,7 @@ public class HumioProperties extends StepRegistryProperties { /** * Name of the repository to publish metrics to. */ - private String repository = "sandbox"; + private String repository = ""; /** * Humio tags describing the data source in which metrics will be stored. Humio tags @@ -79,10 +80,13 @@ public void setConnectTimeout(Duration connectTimeout) { this.connectTimeout = connectTimeout; } + @Deprecated + @DeprecatedConfigurationProperty(reason = "No longer used as repository is resolved from the api token.") public String getRepository() { return this.repository; } + @Deprecated public void setRepository(String repository) { this.repository = repository; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/humio/HumioPropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/humio/HumioPropertiesConfigAdapter.java index d614f6bee089..830c3ae9dde8 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/humio/HumioPropertiesConfigAdapter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/humio/HumioPropertiesConfigAdapter.java @@ -44,6 +44,7 @@ public String uri() { } @Override + @Deprecated public String repository() { return get(HumioProperties::getRepository, HumioConfig.super::repository); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/kairos/KairosPropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/kairos/KairosPropertiesConfigAdapter.java index 1b753108d068..0a946f13c7fa 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/kairos/KairosPropertiesConfigAdapter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/kairos/KairosPropertiesConfigAdapter.java @@ -21,7 +21,7 @@ import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryPropertiesConfigAdapter; /** - * Adapter to convert {@link KairosProperties} to an {@link KairosConfig}. + * Adapter to convert {@link KairosProperties} to a {@link KairosConfig}. * * @author Stephane Nicoll */ diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/newrelic/NewRelicProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/newrelic/NewRelicProperties.java index 54a248f070f0..de7370e6c8bb 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/newrelic/NewRelicProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/newrelic/NewRelicProperties.java @@ -26,11 +26,26 @@ * @author Jon Schneider * @author Andy Wilkinson * @author Stephane Nicoll + * @author Neil Powell * @since 2.0.0 */ @ConfigurationProperties(prefix = "management.metrics.export.newrelic") public class NewRelicProperties extends StepRegistryProperties { + /** + * Whether to send the meter name as the event type instead of using the 'event-type' + * configuration property value. Can be set to 'true' if New Relic guidelines are not + * being followed or event types consistent with previous Spring Boot releases are + * required. + */ + private boolean meterNameEventTypeEnabled; + + /** + * The event type that should be published. This property will be ignored if + * 'meter-name-event-type-enabled' is set to 'true'. + */ + private String eventType = "SpringBootSample"; + /** * New Relic API key. */ @@ -46,6 +61,22 @@ public class NewRelicProperties extends StepRegistryProperties { */ private String uri = "https://insights-collector.newrelic.com"; + public boolean isMeterNameEventTypeEnabled() { + return this.meterNameEventTypeEnabled; + } + + public void setMeterNameEventTypeEnabled(boolean meterNameEventTypeEnabled) { + this.meterNameEventTypeEnabled = meterNameEventTypeEnabled; + } + + public String getEventType() { + return this.eventType; + } + + public void setEventType(String eventType) { + this.eventType = eventType; + } + public String getApiKey() { return this.apiKey; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/newrelic/NewRelicPropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/newrelic/NewRelicPropertiesConfigAdapter.java index 55f03a12d162..d684df089f13 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/newrelic/NewRelicPropertiesConfigAdapter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/newrelic/NewRelicPropertiesConfigAdapter.java @@ -24,6 +24,7 @@ * Adapter to convert {@link NewRelicProperties} to a {@link NewRelicConfig}. * * @author Jon Schneider + * @author Neil Powell * @since 2.0.0 */ public class NewRelicPropertiesConfigAdapter extends StepRegistryPropertiesConfigAdapter @@ -33,6 +34,16 @@ public NewRelicPropertiesConfigAdapter(NewRelicProperties properties) { super(properties); } + @Override + public boolean meterNameEventTypeEnabled() { + return get(NewRelicProperties::isMeterNameEventTypeEnabled, NewRelicConfig.super::meterNameEventTypeEnabled); + } + + @Override + public String eventType() { + return get(NewRelicProperties::getEventType, NewRelicConfig.super::eventType); + } + @Override public String apiKey() { return get(NewRelicProperties::getApiKey, NewRelicConfig.super::apiKey); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/jersey/JerseyManagementContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/jersey/JerseyManagementContextConfiguration.java index dc409df60846..ed9117dda685 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/jersey/JerseyManagementContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/jersey/JerseyManagementContextConfiguration.java @@ -18,8 +18,6 @@ import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.servlet.ServletContainer; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer; import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; @@ -41,10 +39,8 @@ ServletRegistrationBean jerseyServletRegistration(JerseyApplic } @Bean - ResourceConfig resourceConfig(ObjectProvider resourceConfigCustomizers) { - ResourceConfig resourceConfig = new ResourceConfig(); - resourceConfigCustomizers.orderedStream().forEach((customizer) -> customizer.customize(resourceConfig)); - return resourceConfig; + ResourceConfig resourceConfig() { + return new ResourceConfig(); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/jersey/JerseySameManagementContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/jersey/JerseySameManagementContextConfiguration.java index 83e88a32a7e5..5a2f1edfb3f9 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/jersey/JerseySameManagementContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/jersey/JerseySameManagementContextConfiguration.java @@ -38,9 +38,9 @@ * @since 2.1.0 */ @ManagementContextConfiguration(ManagementContextType.SAME) -@ConditionalOnMissingBean(ResourceConfig.class) @Import(JerseyManagementContextConfiguration.class) @EnableConfigurationProperties(JerseyProperties.class) +@ConditionalOnMissingBean(ResourceConfig.class) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) @ConditionalOnClass(ResourceConfig.class) @ConditionalOnMissingClass("org.springframework.web.servlet.DispatcherServlet") diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index a63498afea0f..1be8a99d862d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -1816,76 +1816,6 @@ } ], "hints": [ - { - "name": "management.endpoints.web.path-mapping.keys", - "values": [ - { - "value": "auditevents" - }, - { - "value": "beans" - }, - { - "value": "conditions" - }, - { - "value": "configprops" - }, - { - "value": "env" - }, - { - "value": "flyway" - }, - { - "value": "health" - }, - { - "value": "heapdump" - }, - { - "value": "httptrace" - }, - { - "value": "info" - }, - { - "value": "liquibase" - }, - { - "value": "logfile" - }, - { - "value": "loggers" - }, - { - "value": "mappings" - }, - { - "value": "metrics" - }, - { - "value": "prometheus" - }, - { - "value": "scheduledtasks" - }, - { - "value": "sessions" - }, - { - "value": "shutdown" - }, - { - "value": "threaddump" - } - ], - "providers": [ - { - "name": "any" - } - ] - }, { "name": "management.endpoints.web.cors.allowed-headers", "values": [ diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtensionTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtensionTests.java index 0890ce3d0fc1..1cd7882a08e1 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtensionTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtensionTests.java @@ -25,6 +25,7 @@ import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; +import org.springframework.boot.actuate.endpoint.http.ApiVersion; import org.springframework.boot.actuate.health.CompositeHealth; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthComponent; @@ -62,12 +63,12 @@ class CloudFoundryReactiveHealthEndpointWebExtensionTests { .withUserConfiguration(TestHealthIndicator.class); @Test - void healthDetailsAlwaysPresent() { + void healthComponentsAlwaysPresent() { this.contextRunner.run((context) -> { CloudFoundryReactiveHealthEndpointWebExtension extension = context .getBean(CloudFoundryReactiveHealthEndpointWebExtension.class); - HealthComponent body = extension.health().block(Duration.ofSeconds(30)).getBody(); - HealthComponent health = ((CompositeHealth) body).getDetails().entrySet().iterator().next().getValue(); + HealthComponent body = extension.health(ApiVersion.V3).block(Duration.ofSeconds(30)).getBody(); + HealthComponent health = ((CompositeHealth) body).getComponents().entrySet().iterator().next().getValue(); assertThat(((Health) health).getDetails()).containsEntry("spring", "boot"); }); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java index ce7f8aac151a..62fe9f9f3aa4 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java @@ -45,6 +45,7 @@ import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; import org.springframework.boot.actuate.endpoint.web.WebOperation; +import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; @@ -299,7 +300,9 @@ private CloudFoundryWebFluxEndpointHandlerMapping getHandlerMapping(ApplicationC private WebOperation findOperationWithRequestPath(ExposableWebEndpoint endpoint, String requestPath) { for (WebOperation operation : endpoint.getOperations()) { - if (operation.getRequestPredicate().getPath().equals(requestPath)) { + WebOperationRequestPredicate predicate = operation.getRequestPredicate(); + if (predicate.getPath().equals(requestPath) + && predicate.getProduces().contains(ActuatorMediaType.V3_JSON)) { return operation; } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfigurationTests.java index dea8001a8d39..d895df495c7a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfigurationTests.java @@ -34,6 +34,7 @@ import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; import org.springframework.boot.actuate.endpoint.web.WebOperation; +import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; @@ -98,7 +99,7 @@ void cloudfoundryapplicationProducesActuatorMediaType() throws Exception { "vcap.application.cf_api:https://my-cloud-controller.com").run((context) -> { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); mockMvc.perform(get("/cloudfoundryapplication")) - .andExpect(header().string("Content-Type", ActuatorMediaType.V2_JSON)); + .andExpect(header().string("Content-Type", ActuatorMediaType.V3_JSON)); }); } @@ -243,7 +244,9 @@ private CloudFoundryWebEndpointServletHandlerMapping getHandlerMapping(Applicati private WebOperation findOperationWithRequestPath(ExposableWebEndpoint endpoint, String requestPath) { for (WebOperation operation : endpoint.getOperations()) { - if (operation.getRequestPredicate().getPath().equals(requestPath)) { + WebOperationRequestPredicate predicate = operation.getRequestPredicate(); + if (predicate.getPath().equals(requestPath) + && predicate.getProduces().contains(ActuatorMediaType.V3_JSON)) { return operation; } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryHealthEndpointWebExtensionTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryHealthEndpointWebExtensionTests.java index 9db6e3df49a5..7a940aebfca5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryHealthEndpointWebExtensionTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryHealthEndpointWebExtensionTests.java @@ -24,6 +24,7 @@ import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; +import org.springframework.boot.actuate.endpoint.http.ApiVersion; import org.springframework.boot.actuate.health.CompositeHealth; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthComponent; @@ -59,12 +60,12 @@ class CloudFoundryHealthEndpointWebExtensionTests { .withUserConfiguration(TestHealthIndicator.class); @Test - void healthDetailsAlwaysPresent() { + void healthComponentsAlwaysPresent() { this.contextRunner.run((context) -> { CloudFoundryHealthEndpointWebExtension extension = context .getBean(CloudFoundryHealthEndpointWebExtension.class); - HealthComponent body = extension.health().getBody(); - HealthComponent health = ((CompositeHealth) body).getDetails().entrySet().iterator().next().getValue(); + HealthComponent body = extension.health(ApiVersion.V3).getBody(); + HealthComponent health = ((CompositeHealth) body).getComponents().entrySet().iterator().next().getValue(); assertThat(((Health) health).getDetails()).containsEntry("spring", "boot"); }); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfigurationTests.java index f5b564d50d7b..89fc5ae40c7d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfigurationTests.java @@ -61,7 +61,8 @@ class WebEndpointAutoConfigurationTests { void webApplicationConfiguresEndpointMediaTypes() { this.contextRunner.run((context) -> { EndpointMediaTypes endpointMediaTypes = context.getBean(EndpointMediaTypes.class); - assertThat(endpointMediaTypes.getConsumed()).containsExactly(ActuatorMediaType.V2_JSON, "application/json"); + assertThat(endpointMediaTypes.getConsumed()).containsExactly(ActuatorMediaType.V3_JSON, + ActuatorMediaType.V2_JSON, "application/json"); }); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HealthEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HealthEndpointDocumentationTests.java index b097ba58bfe9..588bbe881ace 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HealthEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HealthEndpointDocumentationTests.java @@ -48,6 +48,7 @@ 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.payload.FieldDescriptor; import org.springframework.util.unit.DataSize; @@ -73,28 +74,31 @@ class HealthEndpointDocumentationTests extends MockMvcEndpointDocumentationTests @Test void health() throws Exception { FieldDescriptor status = fieldWithPath("status").description("Overall status of the application."); - FieldDescriptor components = fieldWithPath("details").description("The components that make up the health."); - FieldDescriptor componentStatus = fieldWithPath("details.*.status") + FieldDescriptor components = fieldWithPath("components").description("The components that make up the health."); + FieldDescriptor componentStatus = fieldWithPath("components.*.status") .description("Status of a specific part of the application."); - FieldDescriptor componentDetails = subsectionWithPath("details.*.details") + FieldDescriptor nestedComponents = subsectionWithPath("components.*.components") + .description("The nested components that make up the health.").optional(); + FieldDescriptor componentDetails = subsectionWithPath("components.*.details") .description("Details of the health of a specific part of the application. " + "Presence is controlled by `management.endpoint.health.show-details`. May contain nested " + "components that make up the health.") .optional(); - this.mockMvc.perform(get("/actuator/health")).andExpect(status().isOk()) - .andDo(document("health", responseFields(status, components, componentStatus, componentDetails))); + this.mockMvc.perform(get("/actuator/health").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()) + .andDo(document("health", + responseFields(status, components, componentStatus, nestedComponents, componentDetails))); } @Test void healthComponent() throws Exception { - this.mockMvc.perform(get("/actuator/health/db")).andExpect(status().isOk()) + this.mockMvc.perform(get("/actuator/health/db").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()) .andDo(document("health/component", responseFields(componentFields))); } @Test void healthComponentInstance() throws Exception { - this.mockMvc.perform(get("/actuator/health/broker/us1")).andExpect(status().isOk()) - .andDo(document("health/instance", responseFields(componentFields))); + this.mockMvc.perform(get("/actuator/health/broker/us1").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andDo(document("health/instance", responseFields(componentFields))); } @Configuration(proxyBeanMethods = false) @@ -142,7 +146,12 @@ public boolean isMember(String name) { } @Override - public boolean includeDetails(SecurityContext securityContext) { + public boolean showComponents(SecurityContext securityContext) { + return true; + } + + @Override + public boolean showDetails(SecurityContext securityContext) { return true; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/jersey/JerseyWebEndpointManagementContextConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/jersey/JerseyWebEndpointManagementContextConfigurationTests.java index 014a9cc6ccc6..5e161080ed5e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/jersey/JerseyWebEndpointManagementContextConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/jersey/JerseyWebEndpointManagementContextConfigurationTests.java @@ -22,10 +22,10 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.web.jersey.JerseyWebEndpointManagementContextConfiguration.JerseyWebEndpointsResourcesRegistrar; import org.springframework.boot.actuate.autoconfigure.web.jersey.JerseySameManagementContextConfiguration; import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; @@ -46,8 +46,8 @@ class JerseyWebEndpointManagementContextConfigurationTests { .withBean(WebEndpointsSupplier.class, () -> Collections::emptyList); @Test - void resourceConfigCustomizerForEndpointsIsAutoConfigured() { - this.runner.run((context) -> assertThat(context).hasSingleBean(ResourceConfigCustomizer.class)); + void jerseyWebEndpointsResourcesRegistrarForEndpointsIsAutoConfigured() { + this.runner.run((context) -> assertThat(context).hasSingleBean(JerseyWebEndpointsResourcesRegistrar.class)); } @Test diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointGroupTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointGroupTests.java index e1fe2ea7484a..8a31c44361d8 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointGroupTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointGroupTests.java @@ -25,7 +25,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.ShowDetails; +import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.Show; import org.springframework.boot.actuate.endpoint.SecurityContext; import org.springframework.boot.actuate.health.HttpCodeStatusMapper; import org.springframework.boot.actuate.health.StatusAggregator; @@ -60,7 +60,7 @@ void setup() { @Test void isMemberWhenMemberPredicateMatchesAcceptsTrue() { AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> name.startsWith("a"), - this.statusAggregator, this.httpCodeStatusMapper, ShowDetails.ALWAYS, Collections.emptySet()); + this.statusAggregator, this.httpCodeStatusMapper, null, Show.ALWAYS, Collections.emptySet()); assertThat(group.isMember("albert")).isTrue(); assertThat(group.isMember("arnold")).isTrue(); } @@ -68,72 +68,134 @@ void isMemberWhenMemberPredicateMatchesAcceptsTrue() { @Test void isMemberWhenMemberPredicateRejectsReturnsTrue() { AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> name.startsWith("a"), - this.statusAggregator, this.httpCodeStatusMapper, ShowDetails.ALWAYS, Collections.emptySet()); + this.statusAggregator, this.httpCodeStatusMapper, null, Show.ALWAYS, Collections.emptySet()); assertThat(group.isMember("bert")).isFalse(); assertThat(group.isMember("ernie")).isFalse(); } @Test - void includeDetailsWhenShowDetailsIsNeverReturnsFalse() { + void showDetailsWhenShowDetailsIsNeverReturnsFalse() { AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true, - this.statusAggregator, this.httpCodeStatusMapper, ShowDetails.NEVER, Collections.emptySet()); - assertThat(group.includeDetails(SecurityContext.NONE)).isFalse(); + this.statusAggregator, this.httpCodeStatusMapper, null, Show.NEVER, Collections.emptySet()); + assertThat(group.showDetails(SecurityContext.NONE)).isFalse(); } @Test - void includeDetailsWhenShowDetailsIsAlwaysReturnsTrue() { + void showDetailsWhenShowDetailsIsAlwaysReturnsTrue() { AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true, - this.statusAggregator, this.httpCodeStatusMapper, ShowDetails.ALWAYS, Collections.emptySet()); - assertThat(group.includeDetails(SecurityContext.NONE)).isTrue(); + this.statusAggregator, this.httpCodeStatusMapper, null, Show.ALWAYS, Collections.emptySet()); + assertThat(group.showDetails(SecurityContext.NONE)).isTrue(); } @Test - void includeDetailsWhenShowDetailsIsWhenAuthorizedAndPrincipalIsNullReturnsFalse() { + void showDetailsWhenShowDetailsIsWhenAuthorizedAndPrincipalIsNullReturnsFalse() { AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true, - this.statusAggregator, this.httpCodeStatusMapper, ShowDetails.WHEN_AUTHORIZED, Collections.emptySet()); + this.statusAggregator, this.httpCodeStatusMapper, null, Show.WHEN_AUTHORIZED, Collections.emptySet()); given(this.securityContext.getPrincipal()).willReturn(null); - assertThat(group.includeDetails(this.securityContext)).isFalse(); + assertThat(group.showDetails(this.securityContext)).isFalse(); } @Test - void includeDetailsWhenShowDetailsIsWhenAuthorizedAndRolesAreEmptyReturnsTrue() { + void showDetailsWhenShowDetailsIsWhenAuthorizedAndRolesAreEmptyReturnsTrue() { AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true, - this.statusAggregator, this.httpCodeStatusMapper, ShowDetails.WHEN_AUTHORIZED, Collections.emptySet()); + this.statusAggregator, this.httpCodeStatusMapper, null, Show.WHEN_AUTHORIZED, Collections.emptySet()); given(this.securityContext.getPrincipal()).willReturn(this.principal); - assertThat(group.includeDetails(this.securityContext)).isTrue(); + assertThat(group.showDetails(this.securityContext)).isTrue(); } @Test - void includeDetailsWhenShowDetailsIsWhenAuthorizedAndUseIsInRoleReturnsTrue() { + void showDetailsWhenShowDetailsIsWhenAuthorizedAndUseIsInRoleReturnsTrue() { AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true, - this.statusAggregator, this.httpCodeStatusMapper, ShowDetails.WHEN_AUTHORIZED, + this.statusAggregator, this.httpCodeStatusMapper, null, Show.WHEN_AUTHORIZED, Arrays.asList("admin", "root", "bossmode")); given(this.securityContext.getPrincipal()).willReturn(this.principal); given(this.securityContext.isUserInRole("root")).willReturn(true); - assertThat(group.includeDetails(this.securityContext)).isTrue(); + assertThat(group.showDetails(this.securityContext)).isTrue(); } @Test - void includeDetailsWhenShowDetailsIsWhenAuthorizedAndUseIsNotInRoleReturnsFalse() { + void showDetailsWhenShowDetailsIsWhenAuthorizedAndUseIsNotInRoleReturnsFalse() { AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true, - this.statusAggregator, this.httpCodeStatusMapper, ShowDetails.WHEN_AUTHORIZED, + this.statusAggregator, this.httpCodeStatusMapper, null, Show.WHEN_AUTHORIZED, Arrays.asList("admin", "rot", "bossmode")); given(this.securityContext.getPrincipal()).willReturn(this.principal); given(this.securityContext.isUserInRole("root")).willReturn(true); - assertThat(group.includeDetails(this.securityContext)).isFalse(); + assertThat(group.showDetails(this.securityContext)).isFalse(); + } + + @Test + void showComponentsWhenShowComponentsIsNullDelegatesToShowDetails() { + AutoConfiguredHealthEndpointGroup alwaysGroup = new AutoConfiguredHealthEndpointGroup((name) -> true, + this.statusAggregator, this.httpCodeStatusMapper, null, Show.ALWAYS, Collections.emptySet()); + assertThat(alwaysGroup.showComponents(SecurityContext.NONE)).isTrue(); + AutoConfiguredHealthEndpointGroup neverGroup = new AutoConfiguredHealthEndpointGroup((name) -> true, + this.statusAggregator, this.httpCodeStatusMapper, null, Show.NEVER, Collections.emptySet()); + assertThat(neverGroup.showComponents(SecurityContext.NONE)).isFalse(); + } + + @Test + void showComponentsWhenShowDetailsIsNeverReturnsFalse() { + AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true, + this.statusAggregator, this.httpCodeStatusMapper, Show.NEVER, Show.ALWAYS, Collections.emptySet()); + assertThat(group.showComponents(SecurityContext.NONE)).isFalse(); + } + + @Test + void showComponentsWhenShowDetailsIsAlwaysReturnsTrue() { + AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true, + this.statusAggregator, this.httpCodeStatusMapper, Show.ALWAYS, Show.NEVER, Collections.emptySet()); + assertThat(group.showComponents(SecurityContext.NONE)).isTrue(); + } + + @Test + void showComponentsWhenShowDetailsIsWhenAuthorizedAndPrincipalIsNullReturnsFalse() { + AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true, + this.statusAggregator, this.httpCodeStatusMapper, Show.WHEN_AUTHORIZED, Show.NEVER, + Collections.emptySet()); + given(this.securityContext.getPrincipal()).willReturn(null); + assertThat(group.showComponents(this.securityContext)).isFalse(); + } + + @Test + void showComponentsWhenShowDetailsIsWhenAuthorizedAndRolesAreEmptyReturnsTrue() { + AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true, + this.statusAggregator, this.httpCodeStatusMapper, Show.WHEN_AUTHORIZED, Show.NEVER, + Collections.emptySet()); + given(this.securityContext.getPrincipal()).willReturn(this.principal); + assertThat(group.showComponents(this.securityContext)).isTrue(); + } + + @Test + void showComponentsWhenShowDetailsIsWhenAuthorizedAndUseIsInRoleReturnsTrue() { + AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true, + this.statusAggregator, this.httpCodeStatusMapper, Show.WHEN_AUTHORIZED, Show.NEVER, + Arrays.asList("admin", "root", "bossmode")); + given(this.securityContext.getPrincipal()).willReturn(this.principal); + given(this.securityContext.isUserInRole("root")).willReturn(true); + assertThat(group.showComponents(this.securityContext)).isTrue(); + } + + @Test + void showComponentsWhenShowDetailsIsWhenAuthorizedAndUseIsNotInRoleReturnsFalse() { + AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true, + this.statusAggregator, this.httpCodeStatusMapper, Show.WHEN_AUTHORIZED, Show.NEVER, + Arrays.asList("admin", "rot", "bossmode")); + given(this.securityContext.getPrincipal()).willReturn(this.principal); + given(this.securityContext.isUserInRole("root")).willReturn(true); + assertThat(group.showComponents(this.securityContext)).isFalse(); } @Test void getStatusAggregatorReturnsStatusAggregator() { AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true, - this.statusAggregator, this.httpCodeStatusMapper, ShowDetails.ALWAYS, Collections.emptySet()); + this.statusAggregator, this.httpCodeStatusMapper, null, Show.ALWAYS, Collections.emptySet()); assertThat(group.getStatusAggregator()).isSameAs(this.statusAggregator); } @Test void getHttpCodeStatusMapperReturnsHttpCodeStatusMapper() { AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true, - this.statusAggregator, this.httpCodeStatusMapper, ShowDetails.ALWAYS, Collections.emptySet()); + this.statusAggregator, this.httpCodeStatusMapper, null, Show.ALWAYS, Collections.emptySet()); assertThat(group.getHttpCodeStatusMapper()).isSameAs(this.httpCodeStatusMapper); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointGroupsTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointGroupsTests.java index d5ee14ecda76..b63154031a4a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointGroupsTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointGroupsTests.java @@ -89,12 +89,13 @@ void getGroupWhenGroupDoesNotExistReturnsNull() { @Test void createWhenNoDefinedBeansAdaptsProperties() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=always", - "management.endpoint.health.status.order=up,down", + this.contextRunner.withPropertyValues("management.endpoint.health.show-components=always", + "management.endpoint.health.show-details=never", "management.endpoint.health.status.order=up,down", "management.endpoint.health.status.http-mapping.down=200").run((context) -> { HealthEndpointGroups groups = context.getBean(HealthEndpointGroups.class); HealthEndpointGroup primary = groups.getPrimary(); - assertThat(primary.includeDetails(SecurityContext.NONE)).isTrue(); + assertThat(primary.showComponents(SecurityContext.NONE)).isTrue(); + assertThat(primary.showDetails(SecurityContext.NONE)).isFalse(); assertThat(primary.getStatusAggregator().getAggregateStatus(Status.UP, Status.DOWN)) .isEqualTo(Status.UP); assertThat(primary.getHttpCodeStatusMapper().getStatusCode(Status.DOWN)).isEqualTo(200); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthContributorRegistryHealthIndicatorRegistryAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthContributorRegistryHealthIndicatorRegistryAdapterTests.java new file mode 100644 index 000000000000..e815ccfc812a --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthContributorRegistryHealthIndicatorRegistryAdapterTests.java @@ -0,0 +1,109 @@ +/* + * Copyright 2012-2019 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 + * + * https://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.health; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.health.CompositeHealthContributor; +import org.springframework.boot.actuate.health.DefaultHealthContributorRegistry; +import org.springframework.boot.actuate.health.HealthContributorRegistry; +import org.springframework.boot.actuate.health.HealthIndicator; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.entry; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link HealthContributorRegistryHealthIndicatorRegistryAdapter}. + * + * @author Phillip Webb + */ +class HealthContributorRegistryHealthIndicatorRegistryAdapterTests { + + private HealthContributorRegistry contributorRegistry = new DefaultHealthContributorRegistry(); + + private HealthContributorRegistryHealthIndicatorRegistryAdapter adapter = new HealthContributorRegistryHealthIndicatorRegistryAdapter( + this.contributorRegistry); + + @Test + void createWhenContributorRegistryIsNullThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new HealthContributorRegistryHealthIndicatorRegistryAdapter(null)) + .withMessage("ContributorRegistry must not be null"); + } + + @Test + void registerDelegatesToContributorRegistry() { + HealthIndicator healthIndicator = mock(HealthIndicator.class); + this.adapter.register("test", healthIndicator); + assertThat(this.contributorRegistry.getContributor("test")).isSameAs(healthIndicator); + } + + @Test + void unregisterWhenDelegatesToContributorRegistry() { + HealthIndicator healthIndicator = mock(HealthIndicator.class); + this.contributorRegistry.registerContributor("test", healthIndicator); + HealthIndicator unregistered = this.adapter.unregister("test"); + assertThat(unregistered).isSameAs(healthIndicator); + assertThat(this.contributorRegistry.getContributor("test")).isNull(); + } + + @Test + void unregisterWhenContributorRegistryResultIsNotHealthIndicatorReturnsNull() { + CompositeHealthContributor healthContributor = mock(CompositeHealthContributor.class); + this.contributorRegistry.registerContributor("test", healthContributor); + HealthIndicator unregistered = this.adapter.unregister("test"); + assertThat(unregistered).isNull(); + assertThat(this.contributorRegistry.getContributor("test")).isNull(); + } + + @Test + void getDelegatesToContributorRegistry() { + HealthIndicator healthIndicator = mock(HealthIndicator.class); + this.contributorRegistry.registerContributor("test", healthIndicator); + assertThat(this.adapter.get("test")).isSameAs(healthIndicator); + } + + @Test + void getWhenContributorRegistryResultIsNotHealthIndicatorReturnsNull() { + CompositeHealthContributor healthContributor = mock(CompositeHealthContributor.class); + this.contributorRegistry.registerContributor("test", healthContributor); + assertThat(this.adapter.get("test")).isNull(); + } + + @Test + void getAllDelegatesContributorRegistry() { + HealthIndicator healthIndicator = mock(HealthIndicator.class); + this.contributorRegistry.registerContributor("test", healthIndicator); + Map all = this.adapter.getAll(); + assertThat(all).containsOnly(entry("test", healthIndicator)); + } + + @Test + void getAllWhenContributorRegistryContainsNonHealthIndicatorInstancesReturnsFilteredMap() { + CompositeHealthContributor healthContributor = mock(CompositeHealthContributor.class); + this.contributorRegistry.registerContributor("test1", healthContributor); + HealthIndicator healthIndicator = mock(HealthIndicator.class); + this.contributorRegistry.registerContributor("test2", healthIndicator); + Map all = this.adapter.getAll(); + assertThat(all).containsOnly(entry("test2", healthIndicator)); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfigurationTests.java index f70fad83bb55..ca1986172a50 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfigurationTests.java @@ -17,12 +17,15 @@ package org.springframework.boot.actuate.autoconfigure.health; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; import org.springframework.boot.actuate.endpoint.SecurityContext; +import org.springframework.boot.actuate.endpoint.http.ApiVersion; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.health.AbstractHealthAggregator; import org.springframework.boot.actuate.health.DefaultHealthContributorRegistry; @@ -229,7 +232,8 @@ void runWhenHasReactiveHealthContributorRegistryBeanDoesNotCreateAdditionalReact void runCreatesHealthEndpointWebExtension() { this.contextRunner.run((context) -> { HealthEndpointWebExtension webExtension = context.getBean(HealthEndpointWebExtension.class); - WebEndpointResponse response = webExtension.health(SecurityContext.NONE, true, "simple"); + WebEndpointResponse response = webExtension.health(ApiVersion.V3, SecurityContext.NONE, + true, "simple"); Health health = (Health) response.getBody(); assertThat(response.getStatus()).isEqualTo(200); assertThat(health.getDetails()).containsEntry("counter", 42); @@ -240,7 +244,8 @@ void runCreatesHealthEndpointWebExtension() { void runWhenHasHealthEndpointWebExtensionBeanDoesNotCreateExtraHealthEndpointWebExtension() { this.contextRunner.withUserConfiguration(HealthEndpointWebExtensionConfiguration.class).run((context) -> { HealthEndpointWebExtension webExtension = context.getBean(HealthEndpointWebExtension.class); - WebEndpointResponse response = webExtension.health(SecurityContext.NONE, true, "simple"); + WebEndpointResponse response = webExtension.health(ApiVersion.V3, SecurityContext.NONE, + true, "simple"); assertThat(response).isNull(); }); } @@ -249,8 +254,8 @@ void runWhenHasHealthEndpointWebExtensionBeanDoesNotCreateExtraHealthEndpointWeb void runCreatesReactiveHealthEndpointWebExtension() { this.reactiveContextRunner.run((context) -> { ReactiveHealthEndpointWebExtension webExtension = context.getBean(ReactiveHealthEndpointWebExtension.class); - Mono> response = webExtension.health(SecurityContext.NONE, - true, "simple"); + Mono> response = webExtension.health(ApiVersion.V3, + SecurityContext.NONE, true, "simple"); Health health = (Health) (response.block().getBody()); assertThat(health.getDetails()).containsEntry("counter", 42); }); @@ -262,12 +267,32 @@ void runWhenHasReactiveHealthEndpointWebExtensionBeanDoesNotCreateExtraReactiveH .run((context) -> { ReactiveHealthEndpointWebExtension webExtension = context .getBean(ReactiveHealthEndpointWebExtension.class); - Mono> response = webExtension - .health(SecurityContext.NONE, true, "simple"); + Mono> response = webExtension.health(ApiVersion.V3, + SecurityContext.NONE, true, "simple"); assertThat(response).isNull(); }); } + @Test // gh-18354 + void runCreatesLegacyHealthAggregator() { + this.contextRunner.run((context) -> { + HealthAggregator aggregator = context.getBean(HealthAggregator.class); + Map healths = new LinkedHashMap<>(); + healths.put("one", Health.up().build()); + healths.put("two", Health.down().build()); + Health result = aggregator.aggregate(healths); + assertThat(result.getStatus()).isEqualTo(Status.DOWN); + }); + } + + @Test // gh-18354 + void runCreatesLegacyHealthStatusHttpMapper() { + this.contextRunner.run((context) -> { + HealthStatusHttpMapper mapper = context.getBean(HealthStatusHttpMapper.class); + assertThat(mapper.mapStatus(Status.DOWN)).isEqualTo(503); + }); + } + @Configuration(proxyBeanMethods = false) static class HealthIndicatorsConfiguration { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorRegistryInjectionIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorRegistryInjectionIntegrationTests.java new file mode 100644 index 000000000000..3d7469c50bd1 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorRegistryInjectionIntegrationTests.java @@ -0,0 +1,92 @@ +/* + * Copyright 2012-2019 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 + * + * https://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.health; + +import java.util.List; + +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.MeterRegistry; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; +import org.springframework.boot.actuate.health.CompositeHealthIndicator; +import org.springframework.boot.actuate.health.HealthAggregator; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.actuate.health.HealthIndicatorRegistry; +import org.springframework.boot.actuate.health.Status; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration test to ensure that the legacy {@link HealthIndicatorRegistry} can still be + * injected. + * + * @author Phillip Webb + */ +@SuppressWarnings("deprecation") +@SpringBootTest(webEnvironment = WebEnvironment.NONE) +public class HealthIndicatorRegistryInjectionIntegrationTests { + + // gh-18194 + + @Test + void meterRegistryBeanHasBeenConfigured(@Autowired MeterRegistry meterRegistry) { + assertThat(meterRegistry).isNotNull(); + assertThat(meterRegistry.get("health").gauge()).isNotNull(); + } + + @Configuration + @ImportAutoConfiguration({ HealthEndpointAutoConfiguration.class, HealthContributorAutoConfiguration.class, + CompositeMeterRegistryAutoConfiguration.class, MetricsAutoConfiguration.class }) + static class Config { + + Config(HealthAggregator healthAggregator, HealthIndicatorRegistry healthIndicatorRegistry, + List healthIndicators, MeterRegistry registry) { + CompositeHealthIndicator healthIndicator = new CompositeHealthIndicator(healthAggregator, + healthIndicatorRegistry); + for (int i = 0; i < healthIndicators.size(); i++) { + healthIndicatorRegistry.register(Integer.toString(i), healthIndicators.get(i)); + } + Gauge.builder("health", healthIndicator, this::getGuageValue) + .description("Spring boot health indicator. 3=UP, 2=OUT_OF_SERVICE, 1=DOWN, 0=UNKNOWN") + .strongReference(true).register(registry); + } + + private double getGuageValue(CompositeHealthIndicator health) { + Status status = health.health().getStatus(); + switch (status.getCode()) { + case "UP": + return 3; + case "OUT_OF_SERVICE": + return 2; + case "DOWN": + return 1; + case "UNKNOWN": + default: + return 0; + } + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapterTests.java new file mode 100644 index 000000000000..3d125ca0d06c --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapterTests.java @@ -0,0 +1,110 @@ +/* + * Copyright 2012-2019 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 + * + * https://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.health; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.health.CompositeReactiveHealthContributor; +import org.springframework.boot.actuate.health.DefaultReactiveHealthContributorRegistry; +import org.springframework.boot.actuate.health.ReactiveHealthContributorRegistry; +import org.springframework.boot.actuate.health.ReactiveHealthIndicator; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.entry; +import static org.mockito.Mockito.mock; + +/** + * Test for + * {@link ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapter}. + * + * @author Phillip Webb + */ +class ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapterTests { + + private ReactiveHealthContributorRegistry contributorRegistry = new DefaultReactiveHealthContributorRegistry(); + + private ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapter adapter = new ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapter( + this.contributorRegistry); + + @Test + void createWhenContributorRegistryIsNullThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapter(null)) + .withMessage("ContributorRegistry must not be null"); + } + + @Test + void registerDelegatesToContributorRegistry() { + ReactiveHealthIndicator healthIndicator = mock(ReactiveHealthIndicator.class); + this.adapter.register("test", healthIndicator); + assertThat(this.contributorRegistry.getContributor("test")).isSameAs(healthIndicator); + } + + @Test + void unregisterWhenDelegatesToContributorRegistry() { + ReactiveHealthIndicator healthIndicator = mock(ReactiveHealthIndicator.class); + this.contributorRegistry.registerContributor("test", healthIndicator); + ReactiveHealthIndicator unregistered = this.adapter.unregister("test"); + assertThat(unregistered).isSameAs(healthIndicator); + assertThat(this.contributorRegistry.getContributor("test")).isNull(); + } + + @Test + void unregisterWhenContributorRegistryResultIsNotHealthIndicatorReturnsNull() { + CompositeReactiveHealthContributor healthContributor = mock(CompositeReactiveHealthContributor.class); + this.contributorRegistry.registerContributor("test", healthContributor); + ReactiveHealthIndicator unregistered = this.adapter.unregister("test"); + assertThat(unregistered).isNull(); + assertThat(this.contributorRegistry.getContributor("test")).isNull(); + } + + @Test + void getDelegatesToContributorRegistry() { + ReactiveHealthIndicator healthIndicator = mock(ReactiveHealthIndicator.class); + this.contributorRegistry.registerContributor("test", healthIndicator); + assertThat(this.adapter.get("test")).isSameAs(healthIndicator); + } + + @Test + void getWhenContributorRegistryResultIsNotHealthIndicatorReturnsNull() { + CompositeReactiveHealthContributor healthContributor = mock(CompositeReactiveHealthContributor.class); + this.contributorRegistry.registerContributor("test", healthContributor); + assertThat(this.adapter.get("test")).isNull(); + } + + @Test + void getAllDelegatesContributorRegistry() { + ReactiveHealthIndicator healthIndicator = mock(ReactiveHealthIndicator.class); + this.contributorRegistry.registerContributor("test", healthIndicator); + Map all = this.adapter.getAll(); + assertThat(all).containsOnly(entry("test", healthIndicator)); + } + + @Test + void getAllWhenContributorRegistryContainsNonHealthIndicatorInstancesReturnsFilteredMap() { + CompositeReactiveHealthContributor healthContributor = mock(CompositeReactiveHealthContributor.class); + this.contributorRegistry.registerContributor("test1", healthContributor); + ReactiveHealthIndicator healthIndicator = mock(ReactiveHealthIndicator.class); + this.contributorRegistry.registerContributor("test2", healthIndicator); + Map all = this.adapter.getAll(); + assertThat(all).containsOnly(entry("test2", healthIndicator)); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/appoptics/AppOpticsPropertiesConfigAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/appoptics/AppOpticsPropertiesConfigAdapterTests.java index 819cdeabef13..ff619497c5b2 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/appoptics/AppOpticsPropertiesConfigAdapterTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/appoptics/AppOpticsPropertiesConfigAdapterTests.java @@ -41,7 +41,7 @@ protected AppOpticsPropertiesConfigAdapter createConfigAdapter(AppOpticsProperti } @Test - void whenPropertiesUrisIsSetAdapterUriReturnsIt() { + void whenPropertiesUriIsSetAdapterUriReturnsIt() { AppOpticsProperties properties = createProperties(); properties.setUri("https://appoptics.example.com/v1/measurements"); assertThat(createConfigAdapter(properties).uri()).isEqualTo("https://appoptics.example.com/v1/measurements"); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/dynatrace/DynatraceMetricsExportAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/dynatrace/DynatraceMetricsExportAutoConfigurationTests.java index 42b92de20686..a08be99a48f9 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/dynatrace/DynatraceMetricsExportAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/dynatrace/DynatraceMetricsExportAutoConfigurationTests.java @@ -116,16 +116,16 @@ static class CustomConfigConfiguration { @Bean DynatraceConfig customConfig() { return (key) -> { - if ("dynatrace.uri".equals(key)) { + switch (key) { + case "dynatrace.uri": return "https://dynatrace.example.com"; - } - if ("dynatrace.apiToken".equals(key)) { + case "dynatrace.apiToken": return "abcde"; - } - if ("dynatrace.deviceId".equals(key)) { + case "dynatrace.deviceId": return "test"; + default: + return null; } - return null; }; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/humio/HumioPropertiesConfigAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/humio/HumioPropertiesConfigAdapterTests.java index 12de2a729112..5f8fdcc176d3 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/humio/HumioPropertiesConfigAdapterTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/humio/HumioPropertiesConfigAdapterTests.java @@ -37,6 +37,7 @@ void whenApiTokenIsSetAdapterApiTokenReturnsIt() { } @Test + @Deprecated void whenPropertiesRepositoryIsSetAdapterRepositoryReturnsIt() { HumioProperties properties = new HumioProperties(); properties.setRepository("test"); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/humio/HumioPropertiesTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/humio/HumioPropertiesTests.java index 336bcb667207..cc141cdc1a76 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/humio/HumioPropertiesTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/humio/HumioPropertiesTests.java @@ -31,6 +31,7 @@ class HumioPropertiesTests extends StepRegistryPropertiesTests { @Test + @SuppressWarnings("deprecation") void defaultValuesAreConsistent() { HumioProperties properties = new HumioProperties(); HumioConfig config = (key) -> null; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/kairos/KairosPropertiesConfigAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/kairos/KairosPropertiesConfigAdapterTests.java index 777e28fd1814..711069faff72 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/kairos/KairosPropertiesConfigAdapterTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/kairos/KairosPropertiesConfigAdapterTests.java @@ -41,7 +41,7 @@ protected KairosPropertiesConfigAdapter createConfigAdapter(KairosProperties pro } @Test - void whenPropertiesUrisIsSetAdapterUriReturnsIt() { + void whenPropertiesUriIsSetAdapterUriReturnsIt() { KairosProperties properties = createProperties(); properties.setUri("https://kairos.example.com:8080/api/v1/datapoints"); assertThat(createConfigAdapter(properties).uri()) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/newrelic/NewRelicMetricsExportAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/newrelic/NewRelicMetricsExportAutoConfigurationTests.java index 98da1b03ceb7..8df7196c21e5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/newrelic/NewRelicMetricsExportAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/newrelic/NewRelicMetricsExportAutoConfigurationTests.java @@ -59,6 +59,36 @@ void failsWithoutAnAccountId() { .run((context) -> assertThat(context).hasFailed()); } + @Test + void failsToAutoConfigureWithoutEventType() { + this.contextRunner.withUserConfiguration(BaseConfiguration.class) + .withPropertyValues("management.metrics.export.newrelic.api-key=abcde", + "management.metrics.export.newrelic.account-id=12345", + "management.metrics.export.newrelic.event-type=") + .run((context) -> assertThat(context).hasFailed()); + } + + @Test + void autoConfiguresWithEventTypeOverriden() { + this.contextRunner.withUserConfiguration(BaseConfiguration.class) + .withPropertyValues("management.metrics.export.newrelic.api-key=abcde", + "management.metrics.export.newrelic.account-id=12345", + "management.metrics.export.newrelic.event-type=wxyz") + .run((context) -> assertThat(context).hasSingleBean(NewRelicMeterRegistry.class) + .hasSingleBean(Clock.class).hasSingleBean(NewRelicConfig.class)); + } + + @Test + void autoConfiguresWithMeterNameEventTypeEnabledAndWithoutEventType() { + this.contextRunner.withUserConfiguration(BaseConfiguration.class) + .withPropertyValues("management.metrics.export.newrelic.api-key=abcde", + "management.metrics.export.newrelic.account-id=12345", + "management.metrics.export.newrelic.event-type=", + "management.metrics.export.newrelic.meter-name-event-type-enabled=true") + .run((context) -> assertThat(context).hasSingleBean(NewRelicMeterRegistry.class) + .hasSingleBean(Clock.class).hasSingleBean(NewRelicConfig.class)); + } + @Test void autoConfiguresWithAccountIdAndApiKey() { this.contextRunner.withUserConfiguration(BaseConfiguration.class) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/newrelic/NewRelicPropertiesTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/newrelic/NewRelicPropertiesTests.java index af63e34ee026..b2c416f9869b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/newrelic/NewRelicPropertiesTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/newrelic/NewRelicPropertiesTests.java @@ -37,6 +37,16 @@ void defaultValuesAreConsistent() { assertStepRegistryDefaultValues(properties, config); // apiKey and account are mandatory assertThat(properties.getUri()).isEqualTo(config.uri()); + assertThat(properties.isMeterNameEventTypeEnabled()).isEqualTo(config.meterNameEventTypeEnabled()); + } + + @Test + void eventTypeDefaultValueIsOverriden() { + NewRelicProperties properties = new NewRelicProperties(); + NewRelicConfig config = (key) -> null; + assertThat(properties.getEventType()).isNotEqualTo(config.eventType()); + assertThat(properties.getEventType()).isEqualTo("SpringBootSample"); + assertThat(config.eventType()).isEqualTo("MicrometerSample"); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfigurationTests.java index 061d8259c404..d1b947dba601 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfigurationTests.java @@ -129,7 +129,7 @@ void withPushGatewayEnabled(CapturedOutput output) { .withPropertyValues("management.metrics.export.prometheus.pushgateway.enabled=true") .withUserConfiguration(BaseConfiguration.class).run((context) -> { assertThat(output).doesNotContain("Invalid PushGateway base url"); - hasGatewayURL(context, "http://localhost:9091/metrics/job/"); + hasGatewayURL(context, "http://localhost:9091/metrics/"); }); } @@ -141,7 +141,7 @@ void withCustomLegacyPushGatewayURL(CapturedOutput output) { "management.metrics.export.prometheus.pushgateway.base-url=localhost:9090") .withUserConfiguration(BaseConfiguration.class).run((context) -> { assertThat(output).contains("Invalid PushGateway base url").contains("localhost:9090"); - hasGatewayURL(context, "http://localhost:9090/metrics/job/"); + hasGatewayURL(context, "http://localhost:9090/metrics/"); }); } @@ -151,7 +151,7 @@ void withCustomPushGatewayURL() { .withPropertyValues("management.metrics.export.prometheus.pushgateway.enabled=true", "management.metrics.export.prometheus.pushgateway.base-url=https://example.com:8080") .withUserConfiguration(BaseConfiguration.class) - .run((context) -> hasGatewayURL(context, "https://example.com:8080/metrics/job/")); + .run((context) -> hasGatewayURL(context, "https://example.com:8080/metrics/")); } private void hasGatewayURL(AssertableApplicationContext context, String url) { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/jersey/JerseyChildManagementContextConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/jersey/JerseyChildManagementContextConfigurationTests.java index 4d34a9315884..9d57fa5f6ed7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/jersey/JerseyChildManagementContextConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/jersey/JerseyChildManagementContextConfigurationTests.java @@ -21,19 +21,14 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer; import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import org.springframework.boot.web.servlet.ServletRegistrationBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; /** * Tests for {@link JerseyChildManagementContextConfiguration}. @@ -61,16 +56,6 @@ void autoConfigurationIsConditionalOnClassResourceConfig() { .run((context) -> assertThat(context).doesNotHaveBean(JerseySameManagementContextConfiguration.class)); } - @Test - void resourceConfigIsCustomizedWithResourceConfigCustomizerBean() { - this.contextRunner.withUserConfiguration(CustomizerConfiguration.class).run((context) -> { - assertThat(context).hasSingleBean(ResourceConfig.class); - ResourceConfig config = context.getBean(ResourceConfig.class); - ResourceConfigCustomizer customizer = context.getBean(ResourceConfigCustomizer.class); - verify(customizer).customize(config); - }); - } - @Test void jerseyApplicationPathIsAutoConfigured() { this.contextRunner.run((context) -> { @@ -93,14 +78,4 @@ void resourceConfigCustomizerBeanIsNotRequired() { this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ResourceConfig.class)); } - @Configuration(proxyBeanMethods = false) - static class CustomizerConfiguration { - - @Bean - ResourceConfigCustomizer resourceConfigCustomizer() { - return mock(ResourceConfigCustomizer.class); - } - - } - } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/jersey/JerseySameManagementContextConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/jersey/JerseySameManagementContextConfigurationTests.java index 235efb96a217..d29c5e8d7390 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/jersey/JerseySameManagementContextConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/jersey/JerseySameManagementContextConfigurationTests.java @@ -20,7 +20,6 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer; import org.springframework.boot.autoconfigure.web.servlet.DefaultJerseyApplicationPath; import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath; import org.springframework.boot.test.context.FilteredClassLoader; @@ -33,7 +32,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; /** * Tests for {@link JerseySameManagementContextConfiguration}. @@ -60,16 +58,6 @@ void autoConfigurationIsConditionalOnClassResourceConfig() { .run((context) -> assertThat(context).doesNotHaveBean(JerseySameManagementContextConfiguration.class)); } - @Test - void resourceConfigIsCustomizedWithResourceConfigCustomizerBean() { - this.contextRunner.withUserConfiguration(CustomizerConfiguration.class).run((context) -> { - assertThat(context).hasSingleBean(ResourceConfig.class); - ResourceConfig config = context.getBean(ResourceConfig.class); - ResourceConfigCustomizer customizer = context.getBean(ResourceConfigCustomizer.class); - verify(customizer).customize(config); - }); - } - @Test void jerseyApplicationPathIsAutoConfiguredWhenNeeded() { this.contextRunner.run((context) -> assertThat(context).hasSingleBean(DefaultJerseyApplicationPath.class)); @@ -122,14 +110,4 @@ ResourceConfig customResourceConfig() { } - @Configuration(proxyBeanMethods = false) - static class CustomizerConfiguration { - - @Bean - ResourceConfigCustomizer resourceConfigCustomizer() { - return mock(ResourceConfigCustomizer.class); - } - - } - } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfigurationIntegrationTests.java index 0c88579e3a81..d82ea973b7d7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfigurationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 the original author or authors. + * Copyright 2012-2019 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. diff --git a/spring-boot-project/spring-boot-actuator/pom.xml b/spring-boot-project/spring-boot-actuator/pom.xml index e667322d9279..c18b3ce871e8 100644 --- a/spring-boot-project/spring-boot-actuator/pom.xml +++ b/spring-boot-project/spring-boot-actuator/pom.xml @@ -15,6 +15,11 @@ ${basedir}/../.. + + ${git.url} + ${git.connection} + ${git.developerConnection} + @@ -345,6 +350,11 @@ jersey-media-json-jackson test + + org.awaitility + awaitility + test + com.jayway.jsonpath json-path diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java index 0a70be4447c3..1441bd7bb770 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java @@ -49,8 +49,8 @@ import org.springframework.boot.actuate.endpoint.Sanitizer; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; -import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.ConfigurationPropertiesBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.util.ClassUtils; @@ -108,36 +108,17 @@ private ApplicationConfigurationProperties extract(ApplicationContext context) { private ContextConfigurationProperties describeConfigurationProperties(ApplicationContext context, ObjectMapper mapper) { - ConfigurationBeanFactoryMetadata beanFactoryMetadata = getBeanFactoryMetadata(context); - Map beans = getConfigurationPropertiesBeans(context, beanFactoryMetadata); - Map beanDescriptors = new HashMap<>(); + Map beans = ConfigurationPropertiesBean.getAll(context); + Map descriptors = new HashMap<>(); beans.forEach((beanName, bean) -> { - String prefix = extractPrefix(context, beanFactoryMetadata, beanName); - beanDescriptors.put(beanName, new ConfigurationPropertiesBeanDescriptor(prefix, - sanitize(prefix, safeSerialize(mapper, bean, prefix)))); + String prefix = bean.getAnnotation().prefix(); + descriptors.put(beanName, new ConfigurationPropertiesBeanDescriptor(prefix, + sanitize(prefix, safeSerialize(mapper, bean.getInstance(), prefix)))); }); - return new ContextConfigurationProperties(beanDescriptors, + return new ContextConfigurationProperties(descriptors, (context.getParent() != null) ? context.getParent().getId() : null); } - private ConfigurationBeanFactoryMetadata getBeanFactoryMetadata(ApplicationContext context) { - Map beans = context - .getBeansOfType(ConfigurationBeanFactoryMetadata.class); - if (beans.size() == 1) { - return beans.values().iterator().next(); - } - return null; - } - - private Map getConfigurationPropertiesBeans(ApplicationContext context, - ConfigurationBeanFactoryMetadata beanFactoryMetadata) { - Map beans = new HashMap<>(context.getBeansWithAnnotation(ConfigurationProperties.class)); - if (beanFactoryMetadata != null) { - beans.putAll(beanFactoryMetadata.getBeansWithFactoryAnnotation(ConfigurationProperties.class)); - } - return beans; - } - /** * Cautiously serialize the bean to a map (returning a map with an error message * instead of throwing an exception if there is a problem). @@ -197,30 +178,6 @@ private void applyConfigurationPropertiesFilter(ObjectMapper mapper) { new SimpleFilterProvider().setDefaultFilter(new ConfigurationPropertiesPropertyFilter())); } - /** - * Extract configuration prefix from - * {@link ConfigurationProperties @ConfigurationProperties} annotation. - * @param context the application context - * @param beanFactoryMetaData the bean factory meta-data - * @param beanName the bean name - * @return the prefix - */ - private String extractPrefix(ApplicationContext context, ConfigurationBeanFactoryMetadata beanFactoryMetaData, - String beanName) { - ConfigurationProperties annotation = context.findAnnotationOnBean(beanName, ConfigurationProperties.class); - if (beanFactoryMetaData != null) { - ConfigurationProperties override = beanFactoryMetaData.findFactoryAnnotation(beanName, - ConfigurationProperties.class); - if (override != null) { - // The @Bean-level @ConfigurationProperties overrides the one at type - // level when binding. Arguably we should render them both, but this one - // might be the most relevant for a starting point. - annotation = override; - } - } - return annotation.prefix(); - } - /** * Sanitize all unwanted configuration properties to avoid leaking of sensitive * information. diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EndpointId.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EndpointId.java index b39d33e5420d..4df8a27f0598 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EndpointId.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EndpointId.java @@ -24,6 +24,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.core.env.Environment; import org.springframework.util.Assert; /** @@ -44,6 +45,8 @@ public final class EndpointId { private static final Pattern WARNING_PATTERN = Pattern.compile("[\\.\\-]+"); + private static final String MIGRATE_LEGACY_NAMES_PROPRTY = "management.endpoints.migrate-legacy-ids"; + private final String value; private final String lowerCaseValue; @@ -112,6 +115,27 @@ public static EndpointId of(String value) { return new EndpointId(value); } + /** + * Factory method to create a new {@link EndpointId} of the specified value. This + * variant will respect the {@code management.endpoints.migrate-legacy-names} property + * if it has been set in the {@link Environment}. + * @param environment the Spring environment + * @param value the endpoint ID value + * @return an {@link EndpointId} instance + * @since 2.2.0 + */ + public static EndpointId of(Environment environment, String value) { + Assert.notNull(environment, "Environment must not be null"); + return new EndpointId(migrateLegacyId(environment, value)); + } + + private static String migrateLegacyId(Environment environment, String value) { + if (environment.getProperty(MIGRATE_LEGACY_NAMES_PROPRTY, Boolean.class, false)) { + return value.replace(".", ""); + } + return value; + } + /** * Factory method to create a new {@link EndpointId} from a property value. More * lenient than {@link #of(String)} to allow for common "relaxed" property variants. diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/InvocationContext.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/InvocationContext.java index 1ea4430f6ffe..8c8022541235 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/InvocationContext.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/InvocationContext.java @@ -18,6 +18,7 @@ import java.util.Map; +import org.springframework.boot.actuate.endpoint.http.ApiVersion; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.util.Assert; @@ -25,6 +26,7 @@ * The context for the {@link OperationInvoker invocation of an operation}. * * @author Andy Wilkinson + * @author Phillip Webb * @since 2.0.0 */ public class InvocationContext { @@ -33,23 +35,55 @@ public class InvocationContext { private final Map arguments; + private final ApiVersion apiVersion; + /** - * Creates a new context for an operation being invoked by the given {@code principal} - * with the given available {@code arguments}. + * Creates a new context for an operation being invoked by the given + * {@code securityContext} with the given available {@code arguments}. * @param securityContext the current security context. Never {@code null} * @param arguments the arguments available to the operation. Never {@code null} */ public InvocationContext(SecurityContext securityContext, Map arguments) { + this(null, securityContext, arguments); + } + + /** + * Creates a new context for an operation being invoked by the given + * {@code securityContext} with the given available {@code arguments}. + * @param apiVersion the API version or {@code null} to use the latest + * @param securityContext the current security context. Never {@code null} + * @param arguments the arguments available to the operation. Never {@code null} + * @since 2.2.0 + */ + public InvocationContext(ApiVersion apiVersion, SecurityContext securityContext, Map arguments) { Assert.notNull(securityContext, "SecurityContext must not be null"); Assert.notNull(arguments, "Arguments must not be null"); + this.apiVersion = (apiVersion != null) ? apiVersion : ApiVersion.LATEST; this.securityContext = securityContext; this.arguments = arguments; } + /** + * Return the API version in use. + * @return the apiVersion the API version + * @since 2.2.0 + */ + public ApiVersion getApiVersion() { + return this.apiVersion; + } + + /** + * Return the security context to use for the invocation. + * @return the security context + */ public SecurityContext getSecurityContext() { return this.securityContext; } + /** + * Return the invocation arguments. + * @return the arguments + */ public Map getArguments() { return this.arguments; } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/Sanitizer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/Sanitizer.java index 6a72d25d8cb9..7cb865f0523b 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/Sanitizer.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/Sanitizer.java @@ -101,10 +101,11 @@ public Object sanitize(String key, Object value) { } private Object sanitizeUri(Object value) { - Matcher matcher = URI_USERINFO_PATTERN.matcher(value.toString()); + String uriString = value.toString(); + Matcher matcher = URI_USERINFO_PATTERN.matcher(uriString); String password = matcher.matches() ? matcher.group(1) : null; if (password != null) { - return StringUtils.replace(value.toString(), ":" + password + "@", ":******@"); + return StringUtils.replace(uriString, ":" + password + "@", ":******@"); } return value; } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscoverer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscoverer.java index bc966fc5bc5d..01259e98f587 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscoverer.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscoverer.java @@ -47,6 +47,7 @@ import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; +import org.springframework.core.env.Environment; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedMultiValueMap; @@ -140,7 +141,7 @@ private Collection createEndpointBeans() { private EndpointBean createEndpointBean(String beanName) { Object bean = this.applicationContext.getBean(beanName); - return new EndpointBean(beanName, bean); + return new EndpointBean(this.applicationContext.getEnvironment(), beanName, bean); } private void addExtensionBeans(Collection endpointBeans) { @@ -159,7 +160,7 @@ private void addExtensionBeans(Collection endpointBeans) { private ExtensionBean createExtensionBean(String beanName) { Object bean = this.applicationContext.getBean(beanName); - return new ExtensionBean(beanName, bean); + return new ExtensionBean(this.applicationContext.getEnvironment(), beanName, bean); } private void addExtensionBean(EndpointBean endpointBean, ExtensionBean extensionBean) { @@ -401,7 +402,7 @@ private static class EndpointBean { private Set extensions = new LinkedHashSet<>(); - EndpointBean(String beanName, Object bean) { + EndpointBean(Environment environment, String beanName, Object bean) { MergedAnnotation annotation = MergedAnnotations .from(bean.getClass(), SearchStrategy.TYPE_HIERARCHY).get(Endpoint.class); String id = annotation.getString("id"); @@ -409,7 +410,7 @@ private static class EndpointBean { () -> "No @Endpoint id attribute specified for " + bean.getClass().getName()); this.beanName = beanName; this.bean = bean; - this.id = EndpointId.of(id); + this.id = EndpointId.of(environment, id); this.enabledByDefault = annotation.getBoolean("enableByDefault"); this.filter = getFilter(this.bean.getClass()); } @@ -462,7 +463,7 @@ private static class ExtensionBean { private final Class filter; - ExtensionBean(String beanName, Object bean) { + ExtensionBean(Environment environment, String beanName, Object bean) { this.bean = bean; this.beanName = beanName; MergedAnnotation extensionAnnotation = MergedAnnotations @@ -472,7 +473,7 @@ private static class ExtensionBean { .from(endpointType, SearchStrategy.TYPE_HIERARCHY).get(Endpoint.class); Assert.state(endpointAnnotation.isPresent(), () -> "Extension " + endpointType.getName() + " does not specify an endpoint"); - this.endpointId = EndpointId.of(endpointAnnotation.getString("id")); + this.endpointId = EndpointId.of(environment, endpointAnnotation.getString("id")); this.filter = extensionAnnotation.getClass("filter"); } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/http/ActuatorMediaType.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/http/ActuatorMediaType.java index 596cb8dd0911..831f4e0c4b93 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/http/ActuatorMediaType.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/http/ActuatorMediaType.java @@ -27,14 +27,21 @@ public final class ActuatorMediaType { /** * Constant for the Actuator V1 media type. + * @deprecated since 2.2.0 as the v1 format is no longer supported */ + @Deprecated public static final String V1_JSON = "application/vnd.spring-boot.actuator.v1+json"; /** - * Constant for the Actuator V2 media type. + * Constant for the Actuator {@link ApiVersion#V2 v2} media type. */ public static final String V2_JSON = "application/vnd.spring-boot.actuator.v2+json"; + /** + * Constant for the Actuator {@link ApiVersion#V3 v3} media type. + */ + public static final String V3_JSON = "application/vnd.spring-boot.actuator.v3+json"; + private ActuatorMediaType() { } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/http/ApiVersion.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/http/ApiVersion.java new file mode 100644 index 000000000000..6267ef8f8515 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/http/ApiVersion.java @@ -0,0 +1,90 @@ +/* + * Copyright 2012-2019 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.endpoint.http; + +import java.util.List; +import java.util.Map; + +import org.springframework.util.CollectionUtils; +import org.springframework.util.MimeTypeUtils; + +/** + * API versions supported for the actuator HTTP API. This enum may be injected into + * actuator endpoints in order to return a response compatible with the requested version. + * + * @author Phillip Webb + * @since 2.2.0 + */ +public enum ApiVersion { + + /** + * Version 2 (supported by Spring Boot 2.0+). + */ + V2, + + /** + * Version 3 (supported by Spring Boot 2.2+). + */ + V3; + + private static final String MEDIA_TYPE_PREFIX = "application/vnd.spring-boot.actuator."; + + /** + * The latest API version. + */ + public static final ApiVersion LATEST = ApiVersion.V3; + + /** + * Return the {@link ApiVersion} to use based on the HTTP request headers. The version + * will be deduced based on the {@code Accept} header. + * @param headers the HTTP headers + * @return the API version to use + */ + public static ApiVersion fromHttpHeaders(Map> headers) { + ApiVersion version = null; + List accepts = headers.get("Accept"); + if (!CollectionUtils.isEmpty(accepts)) { + for (String accept : accepts) { + for (String type : MimeTypeUtils.tokenize(accept)) { + version = mostRecent(version, forType(type)); + } + } + } + return (version != null) ? version : LATEST; + } + + private static ApiVersion forType(String type) { + if (type.startsWith(MEDIA_TYPE_PREFIX)) { + type = type.substring(MEDIA_TYPE_PREFIX.length()); + int suffixIndex = type.indexOf("+"); + type = (suffixIndex != -1) ? type.substring(0, suffixIndex) : type; + try { + return valueOf(type.toUpperCase()); + } + catch (IllegalArgumentException ex) { + } + } + return null; + } + + private static ApiVersion mostRecent(ApiVersion existing, ApiVersion candidate) { + int existingOrdinal = (existing != null) ? existing.ordinal() : -1; + int candidateOrdinal = (candidate != null) ? candidate.ordinal() : -1; + return (candidateOrdinal > existingOrdinal) ? candidate : existing; + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/reflect/ReflectiveOperationInvoker.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/reflect/ReflectiveOperationInvoker.java index dbb74b3ce3e1..38137d9ee13a 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/reflect/ReflectiveOperationInvoker.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/reflect/ReflectiveOperationInvoker.java @@ -23,6 +23,7 @@ import org.springframework.boot.actuate.endpoint.InvocationContext; import org.springframework.boot.actuate.endpoint.SecurityContext; +import org.springframework.boot.actuate.endpoint.http.ApiVersion; import org.springframework.boot.actuate.endpoint.invoke.MissingParametersException; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.boot.actuate.endpoint.invoke.OperationParameter; @@ -88,6 +89,9 @@ private boolean isMissing(InvocationContext context, OperationParameter paramete if (!parameter.isMandatory()) { return false; } + if (ApiVersion.class.equals(parameter.getType())) { + return false; + } if (Principal.class.equals(parameter.getType())) { return context.getSecurityContext().getPrincipal() == null; } @@ -103,6 +107,9 @@ private Object[] resolveArguments(InvocationContext context) { } private Object resolveArgument(OperationParameter parameter, InvocationContext context) { + if (ApiVersion.class.equals(parameter.getType())) { + return context.getApiVersion(); + } if (Principal.class.equals(parameter.getType())) { return context.getSecurityContext().getPrincipal(); } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvoker.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvoker.java index 23701ce62a7e..e19d68763b1c 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvoker.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvoker.java @@ -16,12 +16,17 @@ package org.springframework.boot.actuate.endpoint.invoker.cache; +import java.time.Duration; import java.util.Map; import java.util.Objects; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + import org.springframework.boot.actuate.endpoint.InvocationContext; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; /** @@ -29,10 +34,14 @@ * configurable time to live. * * @author Stephane Nicoll + * @author Christoph Dreis + * @author Phillip Webb * @since 2.0.0 */ public class CachingOperationInvoker implements OperationInvoker { + private static final boolean IS_REACTOR_PRESENT = ClassUtils.isPresent("reactor.core.publisher.Mono", null); + private final OperationInvoker invoker; private final long timeToLive; @@ -68,8 +77,8 @@ public Object invoke(InvocationContext context) { CachedResponse cached = this.cachedResponse; if (cached == null || cached.isStale(accessTime, this.timeToLive)) { Object response = this.invoker.invoke(context); - this.cachedResponse = new CachedResponse(response, accessTime); - return response; + cached = createCachedResponse(response, accessTime); + this.cachedResponse = cached; } return cached.getResponse(); } @@ -85,6 +94,13 @@ private boolean hasInput(InvocationContext context) { return false; } + private CachedResponse createCachedResponse(Object response, long accessTime) { + if (IS_REACTOR_PRESENT) { + return new ReactiveCachedResponse(response, accessTime, this.timeToLive); + } + return new CachedResponse(response, accessTime); + } + /** * Apply caching configuration when appropriate to the given invoker. * @param invoker the invoker to wrap @@ -124,4 +140,25 @@ Object getResponse() { } + /** + * {@link CachedResponse} variant used when Reactor is present. + */ + static class ReactiveCachedResponse extends CachedResponse { + + ReactiveCachedResponse(Object response, long creationTime, long timeToLive) { + super(applyCaching(response, timeToLive), creationTime); + } + + private static Object applyCaching(Object response, long timeToLive) { + if (response instanceof Mono) { + return ((Mono) response).cache(Duration.ofMillis(timeToLive)); + } + if (response instanceof Flux) { + return ((Flux) response).cache(Duration.ofMillis(timeToLive)); + } + return response; + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/EndpointMediaTypes.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/EndpointMediaTypes.java index 7141a3bddcb7..0a7e46d3a230 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/EndpointMediaTypes.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/EndpointMediaTypes.java @@ -16,9 +16,11 @@ package org.springframework.boot.actuate.endpoint.web; +import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType; import org.springframework.util.Assert; /** @@ -29,10 +31,40 @@ */ public class EndpointMediaTypes { + private static final String JSON_MEDIA_TYPE = "application/json"; + + /** + * Default {@link EndpointMediaTypes} for this version of Spring Boot. + */ + public static final EndpointMediaTypes DEFAULT = new EndpointMediaTypes(ActuatorMediaType.V3_JSON, + ActuatorMediaType.V2_JSON, JSON_MEDIA_TYPE); + private final List produced; private final List consumed; + /** + * Creates a new {@link EndpointMediaTypes} with the given {@code produced} and + * {@code consumed} media types. + * @param producedAndConsumed the default media types that are produced and consumed + * by an endpoint. Must not be {@code null}. + * @since 2.2.0 + */ + public EndpointMediaTypes(String... producedAndConsumed) { + this((producedAndConsumed != null) ? Arrays.asList(producedAndConsumed) : (List) null); + } + + /** + * Creates a new {@link EndpointMediaTypes} with the given {@code produced} and + * {@code consumed} media types. + * @param producedAndConsumed the default media types that are produced and consumed + * by an endpoint. Must not be {@code null}. + * @since 2.2.0 + */ + public EndpointMediaTypes(List producedAndConsumed) { + this(producedAndConsumed, producedAndConsumed); + } + /** * Creates a new {@link EndpointMediaTypes} with the given {@code produced} and * {@code consumed} media types. diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/jersey/JerseyEndpointResourceFactory.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/jersey/JerseyEndpointResourceFactory.java index bb6009e125e7..6c8690758104 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/jersey/JerseyEndpointResourceFactory.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/jersey/JerseyEndpointResourceFactory.java @@ -43,6 +43,7 @@ import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException; import org.springframework.boot.actuate.endpoint.InvocationContext; import org.springframework.boot.actuate.endpoint.SecurityContext; +import org.springframework.boot.actuate.endpoint.http.ApiVersion; import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; @@ -150,8 +151,10 @@ public Response apply(ContainerRequestContext data) { arguments.putAll(extractPathParameters(data)); arguments.putAll(extractQueryParameters(data)); try { - Object response = this.operation - .invoke(new InvocationContext(new JerseySecurityContext(data.getSecurityContext()), arguments)); + ApiVersion apiVersion = ApiVersion.fromHttpHeaders(data.getHeaders()); + JerseySecurityContext securityContext = new JerseySecurityContext(data.getSecurityContext()); + InvocationContext invocationContext = new InvocationContext(apiVersion, securityContext, arguments); + Object response = this.operation.invoke(invocationContext); return convertToJaxRsResponse(response, data.getRequest().getMethod()); } catch (InvalidEndpointRequestException ex) { diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AbstractWebFluxEndpointHandlerMapping.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AbstractWebFluxEndpointHandlerMapping.java index cddfa3b36971..a9aaff3664f1 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AbstractWebFluxEndpointHandlerMapping.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AbstractWebFluxEndpointHandlerMapping.java @@ -33,6 +33,7 @@ import org.springframework.boot.actuate.endpoint.InvocationContext; import org.springframework.boot.actuate.endpoint.OperationType; import org.springframework.boot.actuate.endpoint.SecurityContext; +import org.springframework.boot.actuate.endpoint.http.ApiVersion; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; @@ -223,7 +224,8 @@ public Collection getEndpoints() { /** * An {@link OperationInvoker} that performs the invocation of a blocking operation on - * a separate thread using Reactor's {@link Schedulers#elastic() elastic scheduler}. + * a separate thread using Reactor's {@link Schedulers#boundedElastic() bounded + * elastic scheduler}. */ protected static final class ElasticSchedulerInvoker implements OperationInvoker { @@ -235,7 +237,7 @@ public ElasticSchedulerInvoker(OperationInvoker invoker) { @Override public Object invoke(InvocationContext context) { - return Mono.fromCallable(() -> this.invoker.invoke(context)).subscribeOn(Schedulers.elastic()); + return Mono.fromCallable(() -> this.invoker.invoke(context)).subscribeOn(Schedulers.boundedElastic()); } } @@ -308,6 +310,7 @@ Mono emptySecurityContext() { @Override public Mono> handle(ServerWebExchange exchange, Map body) { + ApiVersion apiVersion = ApiVersion.fromHttpHeaders(exchange.getRequest().getHeaders()); Map arguments = getArguments(exchange, body); String matchAllRemainingPathSegmentsVariable = this.operation.getRequestPredicate() .getMatchAllRemainingPathSegmentsVariable(); @@ -316,7 +319,7 @@ public Mono> handle(ServerWebExchange exchange, Map new InvocationContext(securityContext, arguments)) + .map((securityContext) -> new InvocationContext(apiVersion, securityContext, arguments)) .flatMap((invocationContext) -> handleResult((Publisher) this.invoker.invoke(invocationContext), exchange.getRequest().getMethod())); } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/AbstractWebMvcEndpointHandlerMapping.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/AbstractWebMvcEndpointHandlerMapping.java index abbba127ec28..0b7f9970516e 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/AbstractWebMvcEndpointHandlerMapping.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/AbstractWebMvcEndpointHandlerMapping.java @@ -33,6 +33,7 @@ import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException; import org.springframework.boot.actuate.endpoint.InvocationContext; import org.springframework.boot.actuate.endpoint.SecurityContext; +import org.springframework.boot.actuate.endpoint.http.ApiVersion; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; @@ -40,9 +41,11 @@ import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.endpoint.web.WebOperation; import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.util.AntPathMatcher; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; @@ -293,11 +296,13 @@ private class ServletWebOperationAdapter implements ServletWebOperation { @Override public Object handle(HttpServletRequest request, @RequestBody(required = false) Map body) { + HttpHeaders headers = new ServletServerHttpRequest(request).getHeaders(); Map arguments = getArguments(request, body); try { - return handleResult( - this.operation.invoke(new InvocationContext(new ServletSecurityContext(request), arguments)), - HttpMethod.valueOf(request.getMethod())); + ApiVersion apiVersion = ApiVersion.fromHttpHeaders(headers); + ServletSecurityContext securityContext = new ServletSecurityContext(request); + InvocationContext invocationContext = new InvocationContext(apiVersion, securityContext, arguments); + return handleResult(this.operation.invoke(invocationContext), HttpMethod.resolve(request.getMethod())); } catch (InvalidEndpointRequestException ex) { throw new BadOperationRequestException(ex.getReason()); diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealth.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealth.java index f55a40ce2024..8e897dd148b5 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealth.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealth.java @@ -21,7 +21,9 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.springframework.boot.actuate.endpoint.http.ApiVersion; import org.springframework.util.Assert; /** @@ -35,14 +37,21 @@ */ public class CompositeHealth extends HealthComponent { - private Status status; + private final Status status; - private Map details; + private final Map components; - CompositeHealth(Status status, Map details) { + private final Map details; + + CompositeHealth(ApiVersion apiVersion, Status status, Map components) { Assert.notNull(status, "Status must not be null"); this.status = status; - this.details = (details != null) ? new TreeMap<>(details) : details; + this.components = (apiVersion != ApiVersion.V3) ? null : sort(components); + this.details = (apiVersion != ApiVersion.V2) ? null : sort(components); + } + + private Map sort(Map components) { + return (components != null) ? new TreeMap<>(components) : components; } @Override @@ -51,7 +60,13 @@ public Status getStatus() { } @JsonInclude(Include.NON_EMPTY) - public Map getDetails() { + public Map getComponents() { + return this.components; + } + + @JsonInclude(Include.NON_EMPTY) + @JsonProperty + Map getDetails() { return this.details; } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java index 9c394bbb4cfa..438bec08c651 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java @@ -65,6 +65,11 @@ private Health(Builder builder) { this.details = Collections.unmodifiableMap(builder.details); } + Health(Status status, Map details) { + this.status = status; + this.details = details; + } + /** * Return the status of the health. * @return the status (never {@code null}) diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpoint.java index a8729af2454e..54efc0813ba4 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpoint.java @@ -24,6 +24,7 @@ import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.annotation.Selector.Match; +import org.springframework.boot.actuate.endpoint.http.ApiVersion; /** * {@link Endpoint @Endpoint} to expose application health information. @@ -61,12 +62,16 @@ public HealthEndpoint(HealthContributorRegistry registry, HealthEndpointGroups g @ReadOperation public HealthComponent health() { - return healthForPath(EMPTY_PATH); + return health(ApiVersion.V3, EMPTY_PATH); } @ReadOperation public HealthComponent healthForPath(@Selector(match = Match.ALL_REMAINING) String... path) { - HealthResult result = getHealth(SecurityContext.NONE, true, path); + return health(ApiVersion.V3, path); + } + + private HealthComponent health(ApiVersion apiVersion, String... path) { + HealthResult result = getHealth(apiVersion, SecurityContext.NONE, true, path); return (result != null) ? result.getHealth() : null; } @@ -76,9 +81,9 @@ protected HealthComponent getHealth(HealthContributor contributor, boolean inclu } @Override - protected HealthComponent aggregateContributions(Map contributions, - StatusAggregator statusAggregator, boolean includeDetails, Set groupNames) { - return getCompositeHealth(contributions, statusAggregator, includeDetails, groupNames); + protected HealthComponent aggregateContributions(ApiVersion apiVersion, Map contributions, + StatusAggregator statusAggregator, boolean showComponents, Set groupNames) { + return getCompositeHealth(apiVersion, contributions, statusAggregator, showComponents, groupNames); } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointGroup.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointGroup.java index f8b053175676..ec8a29035aa9 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointGroup.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointGroup.java @@ -35,12 +35,20 @@ public interface HealthEndpointGroup { boolean isMember(String name); /** - * Returns if {@link Health#getDetails() health details} should be included in the + * Returns if {@link CompositeHealth#getComponents() health components} should be + * shown in the response. + * @param securityContext the endpoint security context + * @return {@code true} to shown details or {@code false} to hide them + */ + boolean showComponents(SecurityContext securityContext); + + /** + * Returns if {@link Health#getDetails() health details} should be shown in the * response. * @param securityContext the endpoint security context - * @return {@code true} to included details or {@code false} to hide them + * @return {@code true} to shown details or {@code false} to hide them */ - boolean includeDetails(SecurityContext securityContext); + boolean showDetails(SecurityContext securityContext); /** * Returns the status aggregator that should be used for this group. diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointSupport.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointSupport.java index 486b04ed33ae..c82e62013824 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointSupport.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointSupport.java @@ -22,6 +22,7 @@ import java.util.stream.Collectors; import org.springframework.boot.actuate.endpoint.SecurityContext; +import org.springframework.boot.actuate.endpoint.http.ApiVersion; import org.springframework.util.Assert; /** @@ -59,24 +60,26 @@ abstract class HealthEndpointSupport { this.groups = groups; } - HealthResult getHealth(SecurityContext securityContext, boolean alwaysIncludeDetails, String... path) { + HealthResult getHealth(ApiVersion apiVersion, SecurityContext securityContext, boolean showAll, String... path) { HealthEndpointGroup group = (path.length > 0) ? this.groups.get(path[0]) : null; if (group != null) { - return getHealth(group, securityContext, alwaysIncludeDetails, path, 1); + return getHealth(apiVersion, group, securityContext, showAll, path, 1); } - return getHealth(this.groups.getPrimary(), securityContext, alwaysIncludeDetails, path, 0); + return getHealth(apiVersion, this.groups.getPrimary(), securityContext, showAll, path, 0); } - private HealthResult getHealth(HealthEndpointGroup group, SecurityContext securityContext, - boolean alwaysIncludeDetails, String[] path, int pathOffset) { - boolean includeDetails = alwaysIncludeDetails || group.includeDetails(securityContext); + private HealthResult getHealth(ApiVersion apiVersion, HealthEndpointGroup group, SecurityContext securityContext, + boolean showAll, String[] path, int pathOffset) { + boolean showComponents = showAll || group.showComponents(securityContext); + boolean showDetails = showAll || group.showDetails(securityContext); boolean isSystemHealth = group == this.groups.getPrimary() && pathOffset == 0; boolean isRoot = path.length - pathOffset == 0; - if (!includeDetails && !isRoot) { + if (!showComponents && !isRoot) { return null; } Object contributor = getContributor(path, pathOffset); - T health = getContribution(group, contributor, includeDetails, isSystemHealth ? this.groups.getNames() : null); + T health = getContribution(apiVersion, group, contributor, showComponents, showDetails, + isSystemHealth ? this.groups.getNames() : null); return (health != null) ? new HealthResult(health, group) : null; } @@ -94,44 +97,48 @@ private Object getContributor(String[] path, int pathOffset) { } @SuppressWarnings("unchecked") - private T getContribution(HealthEndpointGroup group, Object contributor, boolean includeDetails, - Set groupNames) { + private T getContribution(ApiVersion apiVersion, HealthEndpointGroup group, Object contributor, + boolean showComponents, boolean showDetails, Set groupNames) { if (contributor instanceof NamedContributors) { - return getAggregateHealth(group, (NamedContributors) contributor, includeDetails, groupNames); + return getAggregateHealth(apiVersion, group, (NamedContributors) contributor, showComponents, + showDetails, groupNames); } - return (contributor != null) ? getHealth((C) contributor, includeDetails) : null; + return (contributor != null) ? getHealth((C) contributor, showDetails) : null; } - private T getAggregateHealth(HealthEndpointGroup group, NamedContributors namedContributors, - boolean includeDetails, Set groupNames) { + private T getAggregateHealth(ApiVersion apiVersion, HealthEndpointGroup group, + NamedContributors namedContributors, boolean showComponents, boolean showDetails, + Set groupNames) { Map contributions = new LinkedHashMap<>(); for (NamedContributor namedContributor : namedContributors) { String name = namedContributor.getName(); if (group.isMember(name)) { - T contribution = getContribution(group, namedContributor.getContributor(), includeDetails, null); + T contribution = getContribution(apiVersion, group, namedContributor.getContributor(), showComponents, + showDetails, null); contributions.put(name, contribution); } } if (contributions.isEmpty()) { return null; } - return aggregateContributions(contributions, group.getStatusAggregator(), includeDetails, groupNames); + return aggregateContributions(apiVersion, contributions, group.getStatusAggregator(), showComponents, + groupNames); } protected abstract T getHealth(C contributor, boolean includeDetails); - protected abstract T aggregateContributions(Map contributions, StatusAggregator statusAggregator, - boolean includeDetails, Set groupNames); + protected abstract T aggregateContributions(ApiVersion apiVersion, Map contributions, + StatusAggregator statusAggregator, boolean showComponents, Set groupNames); - protected final CompositeHealth getCompositeHealth(Map components, - StatusAggregator statusAggregator, boolean includeDetails, Set groupNames) { + protected final CompositeHealth getCompositeHealth(ApiVersion apiVersion, Map components, + StatusAggregator statusAggregator, boolean showComponents, Set groupNames) { Status status = statusAggregator.getAggregateStatus( components.values().stream().map(HealthComponent::getStatus).collect(Collectors.toSet())); - Map includedComponents = includeDetails ? components : null; + Map instances = showComponents ? components : null; if (groupNames != null) { - return new SystemHealth(status, includedComponents, groupNames); + return new SystemHealth(apiVersion, status, instances, groupNames); } - return new CompositeHealth(status, includedComponents); + return new CompositeHealth(apiVersion, status, instances); } /** diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointWebExtension.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointWebExtension.java index fb6eeaecd59d..499da09f9ee4 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointWebExtension.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointWebExtension.java @@ -23,6 +23,7 @@ import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.annotation.Selector.Match; +import org.springframework.boot.actuate.endpoint.http.ApiVersion; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension; @@ -64,19 +65,19 @@ public HealthEndpointWebExtension(HealthContributorRegistry registry, HealthEndp } @ReadOperation - public WebEndpointResponse health(SecurityContext securityContext) { - return health(securityContext, NO_PATH); + public WebEndpointResponse health(ApiVersion apiVersion, SecurityContext securityContext) { + return health(apiVersion, securityContext, false, NO_PATH); } @ReadOperation - public WebEndpointResponse health(SecurityContext securityContext, + public WebEndpointResponse health(ApiVersion apiVersion, SecurityContext securityContext, @Selector(match = Match.ALL_REMAINING) String... path) { - return health(securityContext, false, path); + return health(apiVersion, securityContext, false, path); } - public WebEndpointResponse health(SecurityContext securityContext, boolean alwaysIncludeDetails, - String... path) { - HealthResult result = getHealth(securityContext, alwaysIncludeDetails, path); + public WebEndpointResponse health(ApiVersion apiVersion, SecurityContext securityContext, + boolean showAll, String... path) { + HealthResult result = getHealth(apiVersion, securityContext, showAll, path); if (result == null) { return new WebEndpointResponse<>(WebEndpointResponse.STATUS_NOT_FOUND); } @@ -92,9 +93,9 @@ protected HealthComponent getHealth(HealthContributor contributor, boolean inclu } @Override - protected HealthComponent aggregateContributions(Map contributions, - StatusAggregator statusAggregator, boolean includeDetails, Set groupNames) { - return getCompositeHealth(contributions, statusAggregator, includeDetails, groupNames); + protected HealthComponent aggregateContributions(ApiVersion apiVersion, Map contributions, + StatusAggregator statusAggregator, boolean showComponents, Set groupNames) { + return getCompositeHealth(apiVersion, contributions, statusAggregator, showComponents, groupNames); } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorReactiveAdapter.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorReactiveAdapter.java index 9e29835ca151..1a6a3a697053 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorReactiveAdapter.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorReactiveAdapter.java @@ -43,7 +43,7 @@ public HealthIndicatorReactiveAdapter(HealthIndicator delegate) { @Override public Mono health() { - return Mono.fromCallable(this.delegate::health).subscribeOn(Schedulers.elastic()); + return Mono.fromCallable(this.delegate::health).subscribeOn(Schedulers.boundedElastic()); } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthEndpointWebExtension.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthEndpointWebExtension.java index fc8ecf79e824..5f2122da1f6b 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthEndpointWebExtension.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthEndpointWebExtension.java @@ -26,6 +26,7 @@ import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.annotation.Selector.Match; +import org.springframework.boot.actuate.endpoint.http.ApiVersion; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension; @@ -65,19 +66,20 @@ public ReactiveHealthEndpointWebExtension(ReactiveHealthContributorRegistry regi } @ReadOperation - public Mono> health(SecurityContext securityContext) { - return health(securityContext, NO_PATH); + public Mono> health(ApiVersion apiVersion, + SecurityContext securityContext) { + return health(apiVersion, securityContext, false, NO_PATH); } @ReadOperation - public Mono> health(SecurityContext securityContext, - @Selector(match = Match.ALL_REMAINING) String... path) { - return health(securityContext, false, path); + public Mono> health(ApiVersion apiVersion, + SecurityContext securityContext, @Selector(match = Match.ALL_REMAINING) String... path) { + return health(apiVersion, securityContext, false, path); } - public Mono> health(SecurityContext securityContext, - boolean alwaysIncludeDetails, String... path) { - HealthResult> result = getHealth(securityContext, alwaysIncludeDetails, path); + public Mono> health(ApiVersion apiVersion, + SecurityContext securityContext, boolean showAll, String... path) { + HealthResult> result = getHealth(apiVersion, securityContext, showAll, path); if (result == null) { return Mono.just(new WebEndpointResponse<>(WebEndpointResponse.STATUS_NOT_FOUND)); } @@ -94,12 +96,12 @@ protected Mono getHealth(ReactiveHealthContributor co } @Override - protected Mono aggregateContributions( + protected Mono aggregateContributions(ApiVersion apiVersion, Map> contributions, StatusAggregator statusAggregator, - boolean includeDetails, Set groupNames) { + boolean showComponents, Set groupNames) { return Flux.fromIterable(contributions.entrySet()).flatMap(NamedHealthComponent::create) - .collectMap(NamedHealthComponent::getName, NamedHealthComponent::getHealth) - .map((components) -> this.getCompositeHealth(components, statusAggregator, includeDetails, groupNames)); + .collectMap(NamedHealthComponent::getName, NamedHealthComponent::getHealth).map((components) -> this + .getCompositeHealth(apiVersion, components, statusAggregator, showComponents, groupNames)); } /** diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SystemHealth.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SystemHealth.java index bb3acd6e4f9d..3ea7ce787cda 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SystemHealth.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SystemHealth.java @@ -23,6 +23,8 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import org.springframework.boot.actuate.endpoint.http.ApiVersion; + /** * A {@link HealthComponent} that represents the overall system health and the available * groups. @@ -34,8 +36,8 @@ public final class SystemHealth extends CompositeHealth { private final Set groups; - SystemHealth(Status status, Map instances, Set groups) { - super(status, instances); + SystemHealth(ApiVersion apiVersion, Status status, Map instances, Set groups) { + super(apiVersion, status, instances); this.groups = (groups != null) ? new TreeSet<>(groups) : null; } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/http/Outcome.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/http/Outcome.java index dcbc759eba99..eea82e097c0c 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/http/Outcome.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/http/Outcome.java @@ -71,7 +71,7 @@ public Tag asTag() { } /** - * Return the @code Outcome} for the given HTTP {@code status} code. + * Return the {@code Outcome} for the given HTTP {@code status} code. * @param status the HTTP status code * @return the matching Outcome */ diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTags.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTags.java index 1e26db803356..f01ac80675b7 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTags.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTags.java @@ -20,6 +20,8 @@ import org.springframework.boot.actuate.metrics.http.Outcome; import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.AbstractServerHttpResponse; +import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.util.StringUtils; import org.springframework.web.reactive.HandlerMapping; import org.springframework.web.server.ServerWebExchange; @@ -133,9 +135,18 @@ public static Tag exception(Throwable exception) { * @since 2.1.0 */ public static Tag outcome(ServerWebExchange exchange) { - HttpStatus status = exchange.getResponse().getStatusCode(); - Outcome outcome = (status != null) ? Outcome.forStatus(status.value()) : Outcome.UNKNOWN; + Integer statusCode = extractStatusCode(exchange); + Outcome outcome = (statusCode != null) ? Outcome.forStatus(statusCode) : Outcome.UNKNOWN; return outcome.asTag(); } + private static Integer extractStatusCode(ServerWebExchange exchange) { + ServerHttpResponse response = exchange.getResponse(); + if (response instanceof AbstractServerHttpResponse) { + return ((AbstractServerHttpResponse) response).getStatusCodeValue(); + } + HttpStatus status = response.getStatusCode(); + return (status != null) ? status.value() : null; + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/redis/RedisReactiveHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/redis/RedisReactiveHealthIndicator.java index 29f99c52af05..7708fa878093 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/redis/RedisReactiveHealthIndicator.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/redis/RedisReactiveHealthIndicator.java @@ -56,7 +56,8 @@ private Mono doHealthCheck(Health.Builder builder, ReactiveRedisConnecti } private Mono getConnection() { - return Mono.fromSupplier(this.connectionFactory::getReactiveConnection).subscribeOn(Schedulers.parallel()); + return Mono.fromSupplier(this.connectionFactory::getReactiveConnection) + .subscribeOn(Schedulers.boundedElastic()); } private Health up(Health.Builder builder, Properties info) { diff --git a/spring-boot-project/spring-boot-actuator/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-actuator/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 000000000000..c50a6d86e8d1 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,10 @@ +{ + "properties": [ + { + "name": "management.endpoints.migrate-legacy-ids", + "type": "java.lang.Boolean", + "description": "Whether to transparently migrate legacy endpoint IDs.", + "defaultValue": false + } + ] +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EndpointIdTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EndpointIdTests.java index 393ede404dea..a25ee122a1c6 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EndpointIdTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EndpointIdTests.java @@ -21,6 +21,7 @@ import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.mock.env.MockEnvironment; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -92,6 +93,16 @@ void ofWhenContainsDeprecatedCharsLogsWarning(CapturedOutput output) { .contains("Endpoint ID 'foo-bar' contains invalid characters, please migrate to a valid format"); } + @Test + void ofWhenMigratingLegacyNameRemovesDots(CapturedOutput output) { + EndpointId.resetLoggedWarnings(); + MockEnvironment environment = new MockEnvironment(); + environment.setProperty("management.endpoints.migrate-legacy-ids", "true"); + EndpointId endpointId = EndpointId.of(environment, "foo.bar"); + assertThat(endpointId.toString()).isEqualTo("foobar"); + assertThat(output).doesNotContain("contains invalid characters"); + } + @Test void equalsAndHashCode() { EndpointId one = EndpointId.of("foobar1"); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/InvocationContextTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/InvocationContextTests.java new file mode 100644 index 000000000000..b71184c10971 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/InvocationContextTests.java @@ -0,0 +1,77 @@ +/* + * Copyright 2012-2019 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.endpoint; + +import java.util.Collections; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.endpoint.http.ApiVersion; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link InvocationContext}. + * + * @author Phillip Webb + */ +class InvocationContextTests { + + private final SecurityContext securityContext = mock(SecurityContext.class); + + private final Map arguments = Collections.singletonMap("test", "value"); + + @Test + void createWhenApiVersionIsNullUsesLatestVersion() { + InvocationContext context = new InvocationContext(null, this.securityContext, this.arguments); + assertThat(context.getApiVersion()).isEqualTo(ApiVersion.LATEST); + } + + @Test + void createWhenSecurityContextIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> new InvocationContext(null, this.arguments)) + .withMessage("SecurityContext must not be null"); + } + + @Test + void createWhenArgumentsIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> new InvocationContext(this.securityContext, null)) + .withMessage("Arguments must not be null"); + } + + @Test + void getApiVersionReturnsApiVersion() { + InvocationContext context = new InvocationContext(ApiVersion.V2, this.securityContext, this.arguments); + assertThat(context.getApiVersion()).isEqualTo(ApiVersion.V2); + } + + @Test + void getSecurityContextReturnsSecurityContext() { + InvocationContext context = new InvocationContext(this.securityContext, this.arguments); + assertThat(context.getSecurityContext()).isEqualTo(this.securityContext); + } + + @Test + void getArgumentsReturnsArguments() { + InvocationContext context = new InvocationContext(this.securityContext, this.arguments); + assertThat(context.getArguments()).isEqualTo(this.arguments); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/http/ApiVersionTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/http/ApiVersionTests.java new file mode 100644 index 000000000000..45408d21581c --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/http/ApiVersionTests.java @@ -0,0 +1,90 @@ +/* + * Copyright 2012-2019 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.endpoint.http; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for {@link ApiVersion}. + * + * @author Phillip Webb + */ +class ApiVersionTests { + + @Test + void latestIsLatestVersion() { + ApiVersion[] values = ApiVersion.values(); + assertThat(ApiVersion.LATEST).isEqualTo(values[values.length - 1]); + } + + @Test + void fromHttpHeadersWhenEmptyReturnsLatest() { + ApiVersion version = ApiVersion.fromHttpHeaders(Collections.emptyMap()); + assertThat(version).isEqualTo(ApiVersion.V3); + } + + @Test + void fromHttpHeadersWhenHasSingleV2HeaderReturnsV2() { + ApiVersion version = ApiVersion.fromHttpHeaders(acceptHeader(ActuatorMediaType.V2_JSON)); + assertThat(version).isEqualTo(ApiVersion.V2); + } + + @Test + void fromHttpHeadersWhenHasSingleV3HeaderReturnsV3() { + ApiVersion version = ApiVersion.fromHttpHeaders(acceptHeader(ActuatorMediaType.V3_JSON)); + assertThat(version).isEqualTo(ApiVersion.V3); + } + + @Test + void fromHttpHeadersWhenHasV2AndV3HeaderReturnsV3() { + ApiVersion version = ApiVersion + .fromHttpHeaders(acceptHeader(ActuatorMediaType.V2_JSON, ActuatorMediaType.V3_JSON)); + assertThat(version).isEqualTo(ApiVersion.V3); + } + + @Test + void fromHttpHeadersWhenHasV2AndV3AsOneHeaderReturnsV3() { + ApiVersion version = ApiVersion + .fromHttpHeaders(acceptHeader(ActuatorMediaType.V2_JSON + "," + ActuatorMediaType.V3_JSON)); + assertThat(version).isEqualTo(ApiVersion.V3); + } + + @Test + void fromHttpHeadersWhenHasSingleHeaderWithoutJsonReturnsHeader() { + ApiVersion version = ApiVersion.fromHttpHeaders(acceptHeader("application/vnd.spring-boot.actuator.v2")); + assertThat(version).isEqualTo(ApiVersion.V2); + } + + @Test + void fromHttpHeadersWhenHasUnknownVersionReturnsLatest() { + ApiVersion version = ApiVersion.fromHttpHeaders(acceptHeader("application/vnd.spring-boot.actuator.v200")); + assertThat(version).isEqualTo(ApiVersion.V3); + } + + private Map> acceptHeader(String... types) { + List value = Arrays.asList(types); + return value.isEmpty() ? Collections.emptyMap() : Collections.singletonMap("Accept", value); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoke/reflect/ReflectiveOperationInvokerTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoke/reflect/ReflectiveOperationInvokerTests.java index 8fe3adc03e12..11eb5bae88fa 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoke/reflect/ReflectiveOperationInvokerTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoke/reflect/ReflectiveOperationInvokerTests.java @@ -24,6 +24,7 @@ import org.springframework.boot.actuate.endpoint.InvocationContext; import org.springframework.boot.actuate.endpoint.OperationType; import org.springframework.boot.actuate.endpoint.SecurityContext; +import org.springframework.boot.actuate.endpoint.http.ApiVersion; import org.springframework.boot.actuate.endpoint.invoke.MissingParametersException; import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; import org.springframework.lang.Nullable; @@ -50,8 +51,8 @@ class ReflectiveOperationInvokerTests { @BeforeEach void setup() { this.target = new Example(); - this.operationMethod = new OperationMethod(ReflectionUtils.findMethod(Example.class, "reverse", String.class), - OperationType.READ); + this.operationMethod = new OperationMethod(ReflectionUtils.findMethod(Example.class, "reverse", + ApiVersion.class, SecurityContext.class, String.class), OperationType.READ); this.parameterValueMapper = (parameter, value) -> (value != null) ? value.toString() : null; } @@ -95,8 +96,8 @@ void invokeWhenMissingNonNullableArgumentShouldThrowException() { @Test void invokeWhenMissingNullableArgumentShouldInvoke() { - OperationMethod operationMethod = new OperationMethod( - ReflectionUtils.findMethod(Example.class, "reverseNullable", String.class), OperationType.READ); + OperationMethod operationMethod = new OperationMethod(ReflectionUtils.findMethod(Example.class, + "reverseNullable", ApiVersion.class, SecurityContext.class, String.class), OperationType.READ); ReflectiveOperationInvoker invoker = new ReflectiveOperationInvoker(this.target, operationMethod, this.parameterValueMapper); Object result = invoker @@ -115,11 +116,15 @@ void invokeShouldResolveParameters() { static class Example { - String reverse(String name) { + String reverse(ApiVersion apiVersion, SecurityContext securityContext, String name) { + assertThat(apiVersion).isEqualTo(ApiVersion.LATEST); + assertThat(securityContext).isNotNull(); return new StringBuilder(name).reverse().toString(); } - String reverseNullable(@Nullable String name) { + String reverseNullable(ApiVersion apiVersion, SecurityContext securityContext, @Nullable String name) { + assertThat(apiVersion).isEqualTo(ApiVersion.LATEST); + assertThat(securityContext).isNotNull(); return new StringBuilder(String.valueOf(name)).reverse().toString(); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerTests.java index 770effcda2a4..fabefe5c5d67 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerTests.java @@ -17,14 +17,18 @@ package org.springframework.boot.actuate.endpoint.invoker.cache; import java.security.Principal; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; import org.springframework.boot.actuate.endpoint.InvocationContext; import org.springframework.boot.actuate.endpoint.SecurityContext; +import org.springframework.boot.actuate.endpoint.invoke.MissingParametersException; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import static org.assertj.core.api.Assertions.assertThat; @@ -39,6 +43,8 @@ * Tests for {@link CachingOperationInvoker}. * * @author Stephane Nicoll + * @author Christoph Dreis + * @author Phillip Webb */ class CachingOperationInvokerTests { @@ -62,6 +68,30 @@ void cacheInTtlWithNullParameters() { assertCacheIsUsed(parameters); } + @Test + void cacheInTtlWithMonoResponse() { + MonoOperationInvoker.invocations = 0; + MonoOperationInvoker target = new MonoOperationInvoker(); + InvocationContext context = new InvocationContext(mock(SecurityContext.class), Collections.emptyMap()); + CachingOperationInvoker invoker = new CachingOperationInvoker(target, 500L); + Object response = ((Mono) invoker.invoke(context)).block(); + Object cachedResponse = ((Mono) invoker.invoke(context)).block(); + assertThat(MonoOperationInvoker.invocations).isEqualTo(1); + assertThat(response).isSameAs(cachedResponse); + } + + @Test + void cacheInTtlWithFluxResponse() { + FluxOperationInvoker.invocations = 0; + FluxOperationInvoker target = new FluxOperationInvoker(); + InvocationContext context = new InvocationContext(mock(SecurityContext.class), Collections.emptyMap()); + CachingOperationInvoker invoker = new CachingOperationInvoker(target, 500L); + Object response = ((Flux) invoker.invoke(context)).blockLast(); + Object cachedResponse = ((Flux) invoker.invoke(context)).blockLast(); + assertThat(FluxOperationInvoker.invocations).isEqualTo(1); + assertThat(response).isSameAs(cachedResponse); + } + private void assertCacheIsUsed(Map parameters) { OperationInvoker target = mock(OperationInvoker.class); Object expected = new Object(); @@ -122,4 +152,32 @@ void targetInvokedWhenCacheExpires() throws InterruptedException { verify(target, times(2)).invoke(context); } + private static class MonoOperationInvoker implements OperationInvoker { + + static int invocations; + + @Override + public Object invoke(InvocationContext context) throws MissingParametersException { + return Mono.fromCallable(() -> { + invocations++; + return Mono.just("test"); + }); + } + + } + + private static class FluxOperationInvoker implements OperationInvoker { + + static int invocations; + + @Override + public Object invoke(InvocationContext context) throws MissingParametersException { + return Flux.fromIterable(() -> { + invocations++; + return Arrays.asList("spring", "boot").iterator(); + }); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/EndpointMediaTypesTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/EndpointMediaTypesTests.java index 2874f85014b8..cee1d83bd922 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/EndpointMediaTypesTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/EndpointMediaTypesTests.java @@ -22,6 +22,8 @@ import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -32,6 +34,14 @@ */ class EndpointMediaTypesTests { + @Test + void defaultReturnsExpectedProducedAndConsumedTypes() { + assertThat(EndpointMediaTypes.DEFAULT.getProduced()).containsExactly(ActuatorMediaType.V3_JSON, + ActuatorMediaType.V2_JSON, "application/json"); + assertThat(EndpointMediaTypes.DEFAULT.getConsumed()).containsExactly(ActuatorMediaType.V3_JSON, + ActuatorMediaType.V2_JSON, "application/json"); + } + @Test void createWhenProducedIsNullShouldThrowException() { assertThatIllegalArgumentException().isThrownBy(() -> new EndpointMediaTypes(null, Collections.emptyList())) @@ -44,6 +54,13 @@ void createWhenConsumedIsNullShouldThrowException() { .withMessageContaining("Consumed must not be null"); } + @Test + void createFromProducedAndConsumedUsesSameListForBoth() { + EndpointMediaTypes types = new EndpointMediaTypes("spring/framework", "spring/boot"); + assertThat(types.getProduced()).containsExactly("spring/framework", "spring/boot"); + assertThat(types.getConsumed()).containsExactly("spring/framework", "spring/boot"); + } + @Test void getProducedShouldReturnProduced() { List produced = Arrays.asList("a", "b", "c"); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/WebEndpointTestInvocationContextProvider.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/WebEndpointTestInvocationContextProvider.java index a409302b3656..41bf39c07faf 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/WebEndpointTestInvocationContextProvider.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/WebEndpointTestInvocationContextProvider.java @@ -17,7 +17,6 @@ package org.springframework.boot.actuate.endpoint.web.test; import java.time.Duration; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -38,7 +37,6 @@ import org.junit.jupiter.api.extension.TestTemplateInvocationContext; import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; -import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType; import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper; import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; @@ -68,7 +66,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; -import org.springframework.http.MediaType; import org.springframework.http.server.reactive.HttpHandler; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.util.ClassUtils; @@ -233,9 +230,7 @@ ResourceConfigCustomizer webEndpointRegistrar() { } private void customize(ResourceConfig config) { - List mediaTypes = Arrays.asList(javax.ws.rs.core.MediaType.APPLICATION_JSON, - ActuatorMediaType.V2_JSON); - EndpointMediaTypes endpointMediaTypes = new EndpointMediaTypes(mediaTypes, mediaTypes); + EndpointMediaTypes endpointMediaTypes = EndpointMediaTypes.DEFAULT; WebEndpointDiscoverer discoverer = new WebEndpointDiscoverer(this.applicationContext, new ConversionServiceParameterValueMapper(), endpointMediaTypes, null, Collections.emptyList(), Collections.emptyList()); @@ -281,8 +276,7 @@ HttpHandler httpHandler(ApplicationContext applicationContext) { @Bean WebFluxEndpointHandlerMapping webEndpointReactiveHandlerMapping() { - List mediaTypes = Arrays.asList(MediaType.APPLICATION_JSON_VALUE, ActuatorMediaType.V2_JSON); - EndpointMediaTypes endpointMediaTypes = new EndpointMediaTypes(mediaTypes, mediaTypes); + EndpointMediaTypes endpointMediaTypes = EndpointMediaTypes.DEFAULT; WebEndpointDiscoverer discoverer = new WebEndpointDiscoverer(this.applicationContext, new ConversionServiceParameterValueMapper(), endpointMediaTypes, null, Collections.emptyList(), Collections.emptyList()); @@ -311,8 +305,7 @@ TomcatServletWebServerFactory tomcat() { @Bean WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping() { - List mediaTypes = Arrays.asList(MediaType.APPLICATION_JSON_VALUE, ActuatorMediaType.V2_JSON); - EndpointMediaTypes endpointMediaTypes = new EndpointMediaTypes(mediaTypes, mediaTypes); + EndpointMediaTypes endpointMediaTypes = EndpointMediaTypes.DEFAULT; WebEndpointDiscoverer discoverer = new WebEndpointDiscoverer(this.applicationContext, new ConversionServiceParameterValueMapper(), endpointMediaTypes, null, Collections.emptyList(), Collections.emptyList()); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthTests.java index cd13ef75f58a..ec4e1ee3bea9 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthTests.java @@ -23,6 +23,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.endpoint.http.ApiVersion; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -35,13 +37,14 @@ class CompositeHealthTests { @Test void createWhenStatusIsNullThrowsException() { - assertThatIllegalArgumentException().isThrownBy(() -> new CompositeHealth(null, Collections.emptyMap())) + assertThatIllegalArgumentException() + .isThrownBy(() -> new CompositeHealth(ApiVersion.V3, null, Collections.emptyMap())) .withMessage("Status must not be null"); } @Test void getStatusReturnsStatus() { - CompositeHealth health = new CompositeHealth(Status.UP, Collections.emptyMap()); + CompositeHealth health = new CompositeHealth(ApiVersion.V3, Status.UP, Collections.emptyMap()); assertThat(health.getStatus()).isEqualTo(Status.UP); } @@ -49,16 +52,28 @@ void getStatusReturnsStatus() { void getComponentReturnsComponents() { Map components = new LinkedHashMap<>(); components.put("a", Health.up().build()); - CompositeHealth health = new CompositeHealth(Status.UP, components); - assertThat(health.getDetails()).isEqualTo(components); + CompositeHealth health = new CompositeHealth(ApiVersion.V3, Status.UP, components); + assertThat(health.getComponents()).isEqualTo(components); + } + + @Test + void serializeV3WithJacksonReturnsValidJson() throws Exception { + Map components = new LinkedHashMap<>(); + components.put("db1", Health.up().build()); + components.put("db2", Health.down().withDetail("a", "b").build()); + CompositeHealth health = new CompositeHealth(ApiVersion.V3, Status.UP, components); + ObjectMapper mapper = new ObjectMapper(); + String json = mapper.writeValueAsString(health); + assertThat(json).isEqualTo("{\"status\":\"UP\",\"components\":{" + "\"db1\":{\"status\":\"UP\"}," + + "\"db2\":{\"status\":\"DOWN\",\"details\":{\"a\":\"b\"}}}}"); } @Test - void serializeWithJacksonReturnsValidJson() throws Exception { + void serializeV2WithJacksonReturnsValidJson() throws Exception { Map components = new LinkedHashMap<>(); components.put("db1", Health.up().build()); components.put("db2", Health.down().withDetail("a", "b").build()); - CompositeHealth health = new CompositeHealth(Status.UP, components); + CompositeHealth health = new CompositeHealth(ApiVersion.V2, Status.UP, components); ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(health); assertThat(json).isEqualTo("{\"status\":\"UP\",\"details\":{" + "\"db1\":{\"status\":\"UP\"}," diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointSupportTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointSupportTests.java index 58583e16dbb0..60e1421b3828 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointSupportTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointSupportTests.java @@ -25,6 +25,7 @@ import org.mockito.MockitoAnnotations; import org.springframework.boot.actuate.endpoint.SecurityContext; +import org.springframework.boot.actuate.endpoint.http.ApiVersion; import org.springframework.boot.actuate.health.HealthEndpointSupport.HealthResult; import static org.assertj.core.api.Assertions.assertThat; @@ -75,89 +76,134 @@ void createWhenGroupsIsNullThrowsException() { } @Test - void getHealthResultWhenPathIsEmptyUsesPrimaryGroup() { + void getHealthWhenPathIsEmptyUsesPrimaryGroup() { this.registry.registerContributor("test", createContributor(this.up)); - HealthResult result = create(this.registry, this.groups).getHealth(SecurityContext.NONE, false); + HealthResult result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE, + false); assertThat(result.getGroup()).isEqualTo(this.primaryGroup); assertThat(getHealth(result)).isNotSameAs(this.up); assertThat(getHealth(result).getStatus()).isEqualTo(Status.UP); } @Test - void getHealthResultWhenPathIsNotGroupReturnsResultFromPrimaryGroup() { + void getHealthWhenPathIsNotGroupReturnsResultFromPrimaryGroup() { this.registry.registerContributor("test", createContributor(this.up)); - HealthResult result = create(this.registry, this.groups).getHealth(SecurityContext.NONE, false, "test"); + HealthResult result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE, + false, "test"); assertThat(result.getGroup()).isEqualTo(this.primaryGroup); assertThat(getHealth(result)).isEqualTo(this.up); } @Test - void getHealthResultWhenPathIsGroupReturnsResultFromGroup() { + void getHealthWhenPathIsGroupReturnsResultFromGroup() { this.registry.registerContributor("atest", createContributor(this.up)); - HealthResult result = create(this.registry, this.groups).getHealth(SecurityContext.NONE, false, "alltheas", - "atest"); + HealthResult result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE, + false, "alltheas", "atest"); assertThat(result.getGroup()).isEqualTo(this.allTheAs); assertThat(getHealth(result)).isEqualTo(this.up); } @Test - void getHealthResultWhenAlwaysIncludesDetailsIsFalseAndGroupIsTrueIncludesDetails() { + void getHealthWhenAlwaysShowIsFalseAndGroupIsTrueShowsComponents() { + C contributor = createContributor(this.up); + C compositeContributor = createCompositeContributor(Collections.singletonMap("spring", contributor)); + this.registry.registerContributor("test", compositeContributor); + HealthResult result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE, + false, "test"); + CompositeHealth health = (CompositeHealth) getHealth(result); + assertThat(health.getComponents()).containsKey("spring"); + } + + @Test + void getHealthWhenAlwaysShowIsFalseAndGroupIsFalseCannotAccessComponent() { + this.primaryGroup.setShowComponents(false); + C contributor = createContributor(this.up); + C compositeContributor = createCompositeContributor(Collections.singletonMap("spring", contributor)); + this.registry.registerContributor("test", compositeContributor); + HealthEndpointSupport endpoint = create(this.registry, this.groups); + HealthResult rootResult = endpoint.getHealth(ApiVersion.V3, SecurityContext.NONE, false); + assertThat(((CompositeHealth) getHealth(rootResult)).getComponents()).isNullOrEmpty(); + HealthResult componentResult = endpoint.getHealth(ApiVersion.V3, SecurityContext.NONE, false, "test"); + assertThat(componentResult).isNull(); + } + + @Test + void getHealthWhenAlwaysShowIsTrueShowsComponents() { + this.primaryGroup.setShowComponents(true); + C contributor = createContributor(this.up); + C compositeContributor = createCompositeContributor(Collections.singletonMap("spring", contributor)); + this.registry.registerContributor("test", compositeContributor); + HealthEndpointSupport endpoint = create(this.registry, this.groups); + HealthResult rootResult = endpoint.getHealth(ApiVersion.V3, SecurityContext.NONE, false); + assertThat(((CompositeHealth) getHealth(rootResult)).getComponents()).containsKey("test"); + HealthResult componentResult = endpoint.getHealth(ApiVersion.V3, SecurityContext.NONE, false, "test"); + assertThat(((CompositeHealth) getHealth(componentResult)).getComponents()).containsKey("spring"); + } + + @Test + void getHealthWhenAlwaysShowIsFalseAndGroupIsTrueShowsDetails() { this.registry.registerContributor("test", createContributor(this.up)); - HealthResult result = create(this.registry, this.groups).getHealth(SecurityContext.NONE, false, "test"); + HealthResult result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE, + false, "test"); assertThat(((Health) getHealth(result)).getDetails()).containsEntry("spring", "boot"); } @Test - void getHealthResultWhenAlwaysIncludesDetailsIsFalseAndGroupIsFalseIncludesNoDetails() { - this.primaryGroup.setIncludeDetails(false); + void getHealthWhenAlwaysShowIsFalseAndGroupIsFalseShowsNoDetails() { + this.primaryGroup.setShowDetails(false); this.registry.registerContributor("test", createContributor(this.up)); HealthEndpointSupport endpoint = create(this.registry, this.groups); - HealthResult rootResult = endpoint.getHealth(SecurityContext.NONE, false); - HealthResult componentResult = endpoint.getHealth(SecurityContext.NONE, false, "test"); + HealthResult rootResult = endpoint.getHealth(ApiVersion.V3, SecurityContext.NONE, false); + HealthResult componentResult = endpoint.getHealth(ApiVersion.V3, SecurityContext.NONE, false, "test"); assertThat(((CompositeHealth) getHealth(rootResult)).getStatus()).isEqualTo(Status.UP); assertThat(componentResult).isNull(); } @Test - void getHealthResultWhenAlwaysIncludesDetailsIsTrueIncludesDetails() { - this.primaryGroup.setIncludeDetails(false); + void getHealthWhenAlwaysShowIsTrueShowsDetails() { + this.primaryGroup.setShowDetails(false); this.registry.registerContributor("test", createContributor(this.up)); - HealthResult result = create(this.registry, this.groups).getHealth(SecurityContext.NONE, true, "test"); + HealthResult result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE, true, + "test"); assertThat(((Health) getHealth(result)).getDetails()).containsEntry("spring", "boot"); } @Test - void getHealthResultWhenCompositeReturnsAggregateResult() { + void getHealthWhenCompositeReturnsAggregateResult() { Map contributors = new LinkedHashMap<>(); contributors.put("a", createContributor(this.up)); contributors.put("b", createContributor(this.down)); this.registry.registerContributor("test", createCompositeContributor(contributors)); - HealthResult result = create(this.registry, this.groups).getHealth(SecurityContext.NONE, false); + HealthResult result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE, + false); CompositeHealth root = (CompositeHealth) getHealth(result); - CompositeHealth component = (CompositeHealth) root.getDetails().get("test"); + CompositeHealth component = (CompositeHealth) root.getComponents().get("test"); assertThat(root.getStatus()).isEqualTo(Status.DOWN); assertThat(component.getStatus()).isEqualTo(Status.DOWN); - assertThat(component.getDetails()).containsOnlyKeys("a", "b"); + assertThat(component.getComponents()).containsOnlyKeys("a", "b"); } @Test - void getHealthResultWhenPathDoesNotExistReturnsNull() { - HealthResult result = create(this.registry, this.groups).getHealth(SecurityContext.NONE, false, "missing"); + void getHealthWhenPathDoesNotExistReturnsNull() { + HealthResult result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE, + false, "missing"); assertThat(result).isNull(); } @Test - void getHealthResultWhenPathIsEmptyIncludesGroups() { + void getHealthWhenPathIsEmptyIncludesGroups() { this.registry.registerContributor("test", createContributor(this.up)); - HealthResult result = create(this.registry, this.groups).getHealth(SecurityContext.NONE, false); + HealthResult result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE, + false); assertThat(((SystemHealth) getHealth(result)).getGroups()).containsOnly("alltheas"); } @Test - void getHealthResultWhenPathIsGroupDoesNotIncludesGroups() { + void getHealthWhenPathIsGroupDoesNotIncludesGroups() { this.registry.registerContributor("atest", createContributor(this.up)); - HealthResult result = create(this.registry, this.groups).getHealth(SecurityContext.NONE, false, "alltheas"); + HealthResult result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE, + false, "alltheas"); assertThat(getHealth(result)).isNotInstanceOf(SystemHealth.class); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebExtensionTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebExtensionTests.java index 2e191e626918..8d36fc096974 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebExtensionTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebExtensionTests.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.endpoint.SecurityContext; +import org.springframework.boot.actuate.endpoint.http.ApiVersion; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.health.HealthEndpointSupport.HealthResult; @@ -42,15 +43,15 @@ void createWhenUsingDeprecatedConstructorThrowsException() { HealthEndpoint delegate = mock(HealthEndpoint.class); HealthWebEndpointResponseMapper responseMapper = mock(HealthWebEndpointResponseMapper.class); assertThatIllegalStateException().isThrownBy(() -> new HealthEndpointWebExtension(delegate, responseMapper)) - .withMessage( - "Unable to create class org.springframework.boot.actuate.health.HealthEndpointWebExtension " - + "using deprecated constructor"); + .withMessage("Unable to create class org.springframework.boot.actuate." + + "health.HealthEndpointWebExtension using deprecated constructor"); } @Test void healthReturnsSystemHealth() { this.registry.registerContributor("test", createContributor(this.up)); - WebEndpointResponse response = create(this.registry, this.groups).health(SecurityContext.NONE); + WebEndpointResponse response = create(this.registry, this.groups).health(ApiVersion.LATEST, + SecurityContext.NONE); HealthComponent health = response.getBody(); assertThat(health.getStatus()).isEqualTo(Status.UP); assertThat(health).isInstanceOf(SystemHealth.class); @@ -60,8 +61,8 @@ void healthReturnsSystemHealth() { @Test void healthWhenPathDoesNotExistReturnsHttp404() { this.registry.registerContributor("test", createContributor(this.up)); - WebEndpointResponse response = create(this.registry, this.groups).health(SecurityContext.NONE, - "missing"); + WebEndpointResponse response = create(this.registry, this.groups).health(ApiVersion.LATEST, + SecurityContext.NONE, "missing"); assertThat(response.getBody()).isNull(); assertThat(response.getStatus()).isEqualTo(404); } @@ -69,8 +70,8 @@ void healthWhenPathDoesNotExistReturnsHttp404() { @Test void healthWhenPathExistsReturnsHealth() { this.registry.registerContributor("test", createContributor(this.up)); - WebEndpointResponse response = create(this.registry, this.groups).health(SecurityContext.NONE, - "test"); + WebEndpointResponse response = create(this.registry, this.groups).health(ApiVersion.LATEST, + SecurityContext.NONE, "test"); assertThat(response.getBody()).isEqualTo(this.up); assertThat(response.getStatus()).isEqualTo(200); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebIntegrationTests.java index b75729cee8cb..ca11aec206d2 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebIntegrationTests.java @@ -23,13 +23,16 @@ import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import reactor.core.publisher.Mono; +import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType; import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.util.ReflectionUtils; @@ -44,20 +47,45 @@ class HealthEndpointWebIntegrationTests { @WebEndpointTest void whenHealthIsUp200ResponseIsReturned(WebTestClient client) { - client.get().uri("/actuator/health").exchange().expectStatus().isOk().expectBody().jsonPath("status") - .isEqualTo("UP").jsonPath("details.alpha.status").isEqualTo("UP").jsonPath("details.bravo.status") + client.get().uri("/actuator/health").accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk() + .expectBody().jsonPath("status").isEqualTo("UP").jsonPath("components.alpha.status").isEqualTo("UP") + .jsonPath("components.bravo.status").isEqualTo("UP"); + } + + @WebEndpointTest + void whenHealthIsUpAndAcceptsV3Request200ResponseIsReturned(WebTestClient client) { + client.get().uri("/actuator/health") + .headers((headers) -> headers.set(HttpHeaders.ACCEPT, ActuatorMediaType.V3_JSON)).exchange() + .expectStatus().isOk().expectBody().jsonPath("status").isEqualTo("UP") + .jsonPath("components.alpha.status").isEqualTo("UP").jsonPath("components.bravo.status") + .isEqualTo("UP"); + } + + @WebEndpointTest + void whenHealthIsUpAndAcceptsAllRequest200ResponseIsReturned(WebTestClient client) { + client.get().uri("/actuator/health").headers((headers) -> headers.set(HttpHeaders.ACCEPT, "*/*")).exchange() + .expectStatus().isOk().expectBody().jsonPath("status").isEqualTo("UP") + .jsonPath("components.alpha.status").isEqualTo("UP").jsonPath("components.bravo.status") .isEqualTo("UP"); } + @WebEndpointTest + void whenHealthIsUpAndV2Request200ResponseIsReturnedInV2Format(WebTestClient client) { + client.get().uri("/actuator/health") + .headers((headers) -> headers.set(HttpHeaders.ACCEPT, ActuatorMediaType.V2_JSON)).exchange() + .expectStatus().isOk().expectBody().jsonPath("status").isEqualTo("UP").jsonPath("details.alpha.status") + .isEqualTo("UP").jsonPath("details.bravo.status").isEqualTo("UP"); + } + @WebEndpointTest void whenHealthIsDown503ResponseIsReturned(ApplicationContext context, WebTestClient client) { HealthIndicator healthIndicator = () -> Health.down().build(); ReactiveHealthIndicator reactiveHealthIndicator = () -> Mono.just(Health.down().build()); withHealthContributor(context, "charlie", healthIndicator, reactiveHealthIndicator, - () -> client.get().uri("/actuator/health").exchange().expectStatus() + () -> client.get().uri("/actuator/health").accept(MediaType.APPLICATION_JSON).exchange().expectStatus() .isEqualTo(HttpStatus.SERVICE_UNAVAILABLE).expectBody().jsonPath("status").isEqualTo("DOWN") - .jsonPath("details.alpha.status").isEqualTo("UP").jsonPath("details.bravo.status") - .isEqualTo("UP").jsonPath("details.charlie.status").isEqualTo("DOWN")); + .jsonPath("components.alpha.status").isEqualTo("UP").jsonPath("components.bravo.status") + .isEqualTo("UP").jsonPath("components.charlie.status").isEqualTo("DOWN")); } @WebEndpointTest @@ -65,8 +93,9 @@ void whenComponentHealthIsDown503ResponseIsReturned(ApplicationContext context, HealthIndicator healthIndicator = () -> Health.down().build(); ReactiveHealthIndicator reactiveHealthIndicator = () -> Mono.just(Health.down().build()); withHealthContributor(context, "charlie", healthIndicator, reactiveHealthIndicator, - () -> client.get().uri("/actuator/health/charlie").exchange().expectStatus() - .isEqualTo(HttpStatus.SERVICE_UNAVAILABLE).expectBody().jsonPath("status").isEqualTo("DOWN")); + () -> client.get().uri("/actuator/health/charlie").accept(MediaType.APPLICATION_JSON).exchange() + .expectStatus().isEqualTo(HttpStatus.SERVICE_UNAVAILABLE).expectBody().jsonPath("status") + .isEqualTo("DOWN")); } @WebEndpointTest @@ -78,8 +107,9 @@ void whenComponentInstanceHealthIsDown503ResponseIsReturned(ApplicationContext c CompositeReactiveHealthContributor reactiveComposite = CompositeReactiveHealthContributor .fromMap(Collections.singletonMap("one", reactiveHealthIndicator)); withHealthContributor(context, "charlie", composite, reactiveComposite, - () -> client.get().uri("/actuator/health/charlie/one").exchange().expectStatus() - .isEqualTo(HttpStatus.SERVICE_UNAVAILABLE).expectBody().jsonPath("status").isEqualTo("DOWN")); + () -> client.get().uri("/actuator/health/charlie/one").accept(MediaType.APPLICATION_JSON).exchange() + .expectStatus().isEqualTo(HttpStatus.SERVICE_UNAVAILABLE).expectBody().jsonPath("status") + .isEqualTo("DOWN")); } private void withHealthContributor(ApplicationContext context, String name, HealthContributor healthContributor, @@ -122,9 +152,9 @@ void whenHealthIndicatorIsRemovedResponseIsAltered(WebTestClient client, Applica ReactiveHealthContributor reactiveBravo = (reactiveHealthContributorRegistry != null) ? reactiveHealthContributorRegistry.unregisterContributor(name) : null; try { - client.get().uri("/actuator/health").exchange().expectStatus().isOk().expectBody().jsonPath("status") - .isEqualTo("UP").jsonPath("details.alpha.status").isEqualTo("UP").jsonPath("details.bravo.status") - .doesNotExist(); + client.get().uri("/actuator/health").accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk() + .expectBody().jsonPath("status").isEqualTo("UP").jsonPath("components.alpha.status").isEqualTo("UP") + .jsonPath("components.bravo.status").doesNotExist(); } finally { healthContributorRegistry.registerContributor(name, bravo); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthEndpointWebExtensionTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthEndpointWebExtensionTests.java index bf7302ce6fbd..2820aa4e499e 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthEndpointWebExtensionTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthEndpointWebExtensionTests.java @@ -22,6 +22,7 @@ import reactor.core.publisher.Mono; import org.springframework.boot.actuate.endpoint.SecurityContext; +import org.springframework.boot.actuate.endpoint.http.ApiVersion; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.health.HealthEndpointSupport.HealthResult; @@ -52,7 +53,7 @@ void createWhenUsingDeprecatedConstructorThrowsException() { void healthReturnsSystemHealth() { this.registry.registerContributor("test", createContributor(this.up)); WebEndpointResponse response = create(this.registry, this.groups) - .health(SecurityContext.NONE).block(); + .health(ApiVersion.LATEST, SecurityContext.NONE).block(); HealthComponent health = response.getBody(); assertThat(health.getStatus()).isEqualTo(Status.UP); assertThat(health).isInstanceOf(SystemHealth.class); @@ -63,7 +64,7 @@ void healthReturnsSystemHealth() { void healthWhenPathDoesNotExistReturnsHttp404() { this.registry.registerContributor("test", createContributor(this.up)); WebEndpointResponse response = create(this.registry, this.groups) - .health(SecurityContext.NONE, "missing").block(); + .health(ApiVersion.LATEST, SecurityContext.NONE, "missing").block(); assertThat(response.getBody()).isNull(); assertThat(response.getStatus()).isEqualTo(404); } @@ -72,7 +73,7 @@ void healthWhenPathDoesNotExistReturnsHttp404() { void healthWhenPathExistsReturnsHealth() { this.registry.registerContributor("test", createContributor(this.up)); WebEndpointResponse response = create(this.registry, this.groups) - .health(SecurityContext.NONE, "test").block(); + .health(ApiVersion.LATEST, SecurityContext.NONE, "test").block(); assertThat(response.getBody()).isEqualTo(this.up); assertThat(response.getStatus()).isEqualTo(200); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SystemHealthTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SystemHealthTests.java index 46dc358164b7..45eafce64e54 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SystemHealthTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SystemHealthTests.java @@ -25,6 +25,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.endpoint.http.ApiVersion; + import static org.assertj.core.api.Assertions.assertThat; /** @@ -40,10 +42,10 @@ void serializeWithJacksonReturnsValidJson() throws Exception { components.put("db1", Health.up().build()); components.put("db2", Health.down().withDetail("a", "b").build()); Set groups = new LinkedHashSet<>(Arrays.asList("liveness", "readiness")); - CompositeHealth health = new SystemHealth(Status.UP, components, groups); + CompositeHealth health = new SystemHealth(ApiVersion.V3, Status.UP, components, groups); ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(health); - assertThat(json).isEqualTo("{\"status\":\"UP\",\"details\":{" + "\"db1\":{\"status\":\"UP\"}," + assertThat(json).isEqualTo("{\"status\":\"UP\",\"components\":{" + "\"db1\":{\"status\":\"UP\"}," + "\"db2\":{\"status\":\"DOWN\",\"details\":{\"a\":\"b\"}}}," + "\"groups\":[\"liveness\",\"readiness\"]}"); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/TestHealthEndpointGroup.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/TestHealthEndpointGroup.java index cc8c6cab568c..eb041bf90841 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/TestHealthEndpointGroup.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/TestHealthEndpointGroup.java @@ -33,7 +33,9 @@ class TestHealthEndpointGroup implements HealthEndpointGroup { private final Predicate memberPredicate; - private boolean includeDetails = true; + private Boolean showComponents; + + private boolean showDetails = true; TestHealthEndpointGroup() { this((name) -> true); @@ -49,12 +51,21 @@ public boolean isMember(String name) { } @Override - public boolean includeDetails(SecurityContext securityContext) { - return this.includeDetails; + public boolean showComponents(SecurityContext securityContext) { + return (this.showComponents != null) ? this.showComponents : this.showDetails; + } + + void setShowComponents(Boolean showComponents) { + this.showComponents = showComponents; + } + + @Override + public boolean showDetails(SecurityContext securityContext) { + return this.showDetails; } - void setIncludeDetails(boolean includeDetails) { - this.includeDetails = includeDetails; + void setShowDetails(boolean includeDetails) { + this.showDetails = includeDetails; } @Override diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/integration/IntegrationGraphEndpointWebIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/integration/IntegrationGraphEndpointWebIntegrationTests.java index 92666342949c..0189fb596c23 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/integration/IntegrationGraphEndpointWebIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/integration/IntegrationGraphEndpointWebIntegrationTests.java @@ -36,7 +36,7 @@ class IntegrationGraphEndpointWebIntegrationTests { void graph(WebTestClient client) { client.get().uri("/actuator/integrationgraph").accept(MediaType.APPLICATION_JSON).exchange().expectStatus() .isOk().expectBody().jsonPath("contentDescriptor.providerVersion").isNotEmpty() - .jsonPath("contentDescriptor.providerFormatVersion").isEqualTo(1.0f) + .jsonPath("contentDescriptor.providerFormatVersion").isEqualTo(1.1f) .jsonPath("contentDescriptor.provider").isEqualTo("spring-integration"); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/logging/LoggersEndpointWebIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/logging/LoggersEndpointWebIntegrationTests.java index e7411d979c58..dadc3d150014 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/logging/LoggersEndpointWebIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/logging/LoggersEndpointWebIntegrationTests.java @@ -132,6 +132,15 @@ void setLoggerUsingActuatorV2JsonShouldSetLogLevel() { verify(this.loggingSystem).setLogLevel("ROOT", LogLevel.DEBUG); } + @WebEndpointTest + void setLoggerUsingActuatorV3JsonShouldSetLogLevel() { + this.client.post().uri("/actuator/loggers/ROOT") + .contentType(MediaType.parseMediaType(ActuatorMediaType.V3_JSON)) + .bodyValue(Collections.singletonMap("configuredLevel", "debug")).exchange().expectStatus() + .isNoContent(); + verify(this.loggingSystem).setLogLevel("ROOT", LogLevel.DEBUG); + } + @WebEndpointTest void setLoggerGroupUsingActuatorV2JsonShouldSetLogLevel() { this.client.post().uri("/actuator/loggers/test") @@ -162,7 +171,7 @@ void setLoggerOrLoggerGroupWithWrongLogLevelResultInBadRequestResponse() { @WebEndpointTest void setLoggerWithNullLogLevel() { this.client.post().uri("/actuator/loggers/ROOT") - .contentType(MediaType.parseMediaType(ActuatorMediaType.V2_JSON)) + .contentType(MediaType.parseMediaType(ActuatorMediaType.V3_JSON)) .bodyValue(Collections.singletonMap("configuredLevel", null)).exchange().expectStatus().isNoContent(); verify(this.loggingSystem).setLogLevel("ROOT", null); } @@ -170,7 +179,7 @@ void setLoggerWithNullLogLevel() { @WebEndpointTest void setLoggerWithNoLogLevel() { this.client.post().uri("/actuator/loggers/ROOT") - .contentType(MediaType.parseMediaType(ActuatorMediaType.V2_JSON)).bodyValue(Collections.emptyMap()) + .contentType(MediaType.parseMediaType(ActuatorMediaType.V3_JSON)).bodyValue(Collections.emptyMap()) .exchange().expectStatus().isNoContent(); verify(this.loggingSystem).setLogLevel("ROOT", null); } @@ -178,7 +187,7 @@ void setLoggerWithNoLogLevel() { @WebEndpointTest void setLoggerGroupWithNullLogLevel() { this.client.post().uri("/actuator/loggers/test") - .contentType(MediaType.parseMediaType(ActuatorMediaType.V2_JSON)) + .contentType(MediaType.parseMediaType(ActuatorMediaType.V3_JSON)) .bodyValue(Collections.singletonMap("configuredLevel", null)).exchange().expectStatus().isNoContent(); verify(this.loggingSystem).setLogLevel("test.member1", null); verify(this.loggingSystem).setLogLevel("test.member2", null); @@ -187,7 +196,7 @@ void setLoggerGroupWithNullLogLevel() { @WebEndpointTest void setLoggerGroupWithNoLogLevel() { this.client.post().uri("/actuator/loggers/test") - .contentType(MediaType.parseMediaType(ActuatorMediaType.V2_JSON)).bodyValue(Collections.emptyMap()) + .contentType(MediaType.parseMediaType(ActuatorMediaType.V3_JSON)).bodyValue(Collections.emptyMap()) .exchange().expectStatus().isNoContent(); verify(this.loggingSystem).setLogLevel("test.member1", null); verify(this.loggingSystem).setLogLevel("test.member2", null); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/management/HeapDumpWebEndpointWebIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/management/HeapDumpWebEndpointWebIntegrationTests.java index 07b95102f810..baaf6e1a18c5 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/management/HeapDumpWebEndpointWebIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/management/HeapDumpWebEndpointWebIntegrationTests.java @@ -18,8 +18,10 @@ import java.io.File; import java.io.IOException; +import java.time.Duration; import java.util.concurrent.TimeUnit; +import org.awaitility.Awaitility; import org.junit.jupiter.api.BeforeEach; import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest; @@ -31,7 +33,7 @@ import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.util.FileCopyUtils; -import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.is; /** * Integration tests for {@link HeapDumpWebEndpoint} exposed by Jersey, Spring MVC, and @@ -64,11 +66,7 @@ void getRequestShouldReturnHeapDumpInResponseBody(WebTestClient client) throws E } private void assertHeapDumpFileIsDeleted() throws InterruptedException { - long end = System.currentTimeMillis() + 5000; - while (System.currentTimeMillis() < end && this.endpoint.file.exists()) { - Thread.sleep(100); - } - assertThat(this.endpoint.file.exists()).isFalse(); + Awaitility.waitAtMost(Duration.ofSeconds(5)).until(this.endpoint.file::exists, is(false)); } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsTests.java index 53f8742076b5..b71348519189 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsTests.java @@ -152,4 +152,18 @@ void outcomeTagIsServerErrorWhenResponseIs5xx() { assertThat(tag.getValue()).isEqualTo("SERVER_ERROR"); } + @Test + void outcomeTagIsClientErrorWhenResponseIsNonStandardInClientSeries() { + this.exchange.getResponse().setStatusCodeValue(490); + Tag tag = WebFluxTags.outcome(this.exchange); + assertThat(tag.getValue()).isEqualTo("CLIENT_ERROR"); + } + + @Test + void outcomeTagIsUnknownWhenResponseStatusIsInUnknownSeries() { + this.exchange.getResponse().setStatusCodeValue(701); + Tag tag = WebFluxTags.outcome(this.exchange); + assertThat(tag.getValue()).isEqualTo("UNKNOWN"); + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/pom.xml b/spring-boot-project/spring-boot-autoconfigure/pom.xml index 1237d4dd153a..70f9e1349ac9 100755 --- a/spring-boot-project/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-project/spring-boot-autoconfigure/pom.xml @@ -15,6 +15,11 @@ ${basedir}/../.. + + ${git.url} + ${git.connection} + ${git.developerConnection} + @@ -677,6 +682,16 @@ spring-security-oauth2-resource-server true + + org.springframework.security + spring-security-rsocket + true + + + org.springframework.security + spring-security-saml2-service-provider + true + org.springframework.security spring-security-web @@ -897,6 +912,11 @@ tomcat-embed-jasper test + + org.awaitility + awaitility + test + org.hsqldb hsqldb diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfiguration.java index a2a6f2f8a01e..412079480174 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfiguration.java @@ -48,7 +48,7 @@ *

* This configuration class is active only when the RabbitMQ and Spring AMQP client * libraries are on the classpath. - *

+ *

* Registers the following beans: *