diff --git a/.sdkmanrc b/.sdkmanrc index 67983ac1f83a..efa0e43ec434 100644 --- a/.sdkmanrc +++ b/.sdkmanrc @@ -1,3 +1,3 @@ # Enable auto-env through the sdkman_auto_env config # Add key=value pairs of SDKs to use below -java=8.0.362-librca +java=8.0.372-librca diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index f1ee4b87525f..5e31fcc37b23 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -9,35 +9,43 @@ repositories { gradlePluginPortal() } -sourceCompatibility = 1.8 -targetCompatibility = 1.8 - -ext { - def propertiesFile = new File(new File("$projectDir").parentFile, "gradle.properties") - propertiesFile.withInputStream { - def properties = new Properties() - properties.load(it) - set("kotlinVersion", properties["kotlinVersion"]) +new File(new File("$projectDir").parentFile, "gradle.properties").withInputStream { + def properties = new Properties() + properties.load(it) + ext.set("kotlinVersion", properties["kotlinVersion"]) + ext.set("springFrameworkVersion", properties["springFrameworkVersion"]) + if (properties["springFrameworkVersion"].contains("-")) { + repositories { + maven { url "https://repo.spring.io/milestone" } + maven { url "https://repo.spring.io/snapshot" } + } } } +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + dependencies { checkstyle "io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}" - implementation(platform("org.springframework:spring-framework-bom:5.3.15")) + + implementation(platform("org.springframework:spring-framework-bom:${springFrameworkVersion}")) implementation("com.fasterxml.jackson.core:jackson-databind:2.11.4") implementation("com.gradle:gradle-enterprise-gradle-plugin:3.12.1") implementation("com.tngtech.archunit:archunit:1.0.0") implementation("commons-codec:commons-codec:1.13") + implementation("io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}") implementation("org.apache.maven:maven-embedder:3.6.2") implementation("org.asciidoctor:asciidoctor-gradle-jvm:3.3.2") implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}") implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:${kotlinVersion}") + implementation("org.springframework:spring-context") implementation("org.springframework:spring-core") implementation("org.springframework:spring-web") - implementation("io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}") + testImplementation("org.assertj:assertj-core:3.11.1") testImplementation("org.apache.logging.log4j:log4j-core:2.17.1") testImplementation("org.junit.jupiter:junit-jupiter:5.6.0") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java new file mode 100644 index 000000000000..e6dba8e5cff5 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java @@ -0,0 +1,186 @@ +/* + * Copyright 2022-2023 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.build.architecture; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.tngtech.archunit.base.DescribedPredicate; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClass.Predicates; +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.domain.JavaMethod; +import com.tngtech.archunit.core.domain.JavaParameter; +import com.tngtech.archunit.core.domain.properties.CanBeAnnotated; +import com.tngtech.archunit.core.importer.ClassFileImporter; +import com.tngtech.archunit.lang.ArchCondition; +import com.tngtech.archunit.lang.ArchRule; +import com.tngtech.archunit.lang.ConditionEvents; +import com.tngtech.archunit.lang.EvaluationResult; +import com.tngtech.archunit.lang.SimpleConditionEvent; +import com.tngtech.archunit.lang.syntax.ArchRuleDefinition; +import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition; +import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; +import org.gradle.api.Task; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.FileCollection; +import org.gradle.api.file.FileTree; +import org.gradle.api.tasks.IgnoreEmptyDirectories; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.SkipWhenEmpty; +import org.gradle.api.tasks.TaskAction; + +/** + * {@link Task} that checks for architecture problems. + * + * @author Andy Wilkinson + */ +public abstract class ArchitectureCheck extends DefaultTask { + + private FileCollection classes; + + public ArchitectureCheck() { + getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName())); + } + + @TaskAction + void checkArchitecture() throws IOException { + JavaClasses javaClasses = new ClassFileImporter() + .importPaths(this.classes.getFiles().stream().map(File::toPath).collect(Collectors.toList())); + List violations = Stream.of(allPackagesShouldBeFreeOfTangles(), + allBeanPostProcessorBeanMethodsShouldBeStaticAndHaveParametersThatWillNotCausePrematureInitialization(), + allBeanFactoryPostProcessorBeanMethodsShouldBeStaticAndHaveNoParameters()) + .map((rule) -> rule.evaluate(javaClasses)) + .filter(EvaluationResult::hasViolation) + .collect(Collectors.toList()); + File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile(); + outputFile.getParentFile().mkdirs(); + if (!violations.isEmpty()) { + StringBuilder report = new StringBuilder(); + for (EvaluationResult violation : violations) { + report.append(violation.getFailureReport().toString()); + report.append(String.format("%n")); + } + Files.write(outputFile.toPath(), report.toString().getBytes(StandardCharsets.UTF_8), + StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + throw new GradleException("Architecture check failed. See '" + outputFile + "' for details."); + } + else { + outputFile.createNewFile(); + } + } + + private ArchRule allPackagesShouldBeFreeOfTangles() { + return SlicesRuleDefinition.slices().matching("(**)").should().beFreeOfCycles(); + } + + private ArchRule allBeanPostProcessorBeanMethodsShouldBeStaticAndHaveParametersThatWillNotCausePrematureInitialization() { + return ArchRuleDefinition.methods() + .that() + .areAnnotatedWith("org.springframework.context.annotation.Bean") + .and() + .haveRawReturnType(Predicates.assignableTo("org.springframework.beans.factory.config.BeanPostProcessor")) + .should(onlyHaveParametersThatWillNotCauseEagerInitialization()) + .andShould() + .beStatic() + .allowEmptyShould(true); + } + + private ArchCondition onlyHaveParametersThatWillNotCauseEagerInitialization() { + DescribedPredicate notAnnotatedWithLazy = DescribedPredicate + .not(CanBeAnnotated.Predicates.annotatedWith("org.springframework.context.annotation.Lazy")); + DescribedPredicate notOfASafeType = DescribedPredicate + .not(Predicates.assignableTo("org.springframework.beans.factory.ObjectProvider") + .or(Predicates.assignableTo("org.springframework.context.ApplicationContext")) + .or(Predicates.assignableTo("org.springframework.core.env.Environment"))); + return new ArchCondition("not have parameters that will cause eager initialization") { + + @Override + public void check(JavaMethod item, ConditionEvents events) { + item.getParameters() + .stream() + .filter(notAnnotatedWithLazy) + .filter((parameter) -> notOfASafeType.test(parameter.getRawType())) + .forEach((parameter) -> events.add(SimpleConditionEvent.violated(parameter, + parameter.getDescription() + " will cause eager initialization as it is " + + notAnnotatedWithLazy.getDescription() + " and is " + + notOfASafeType.getDescription()))); + } + + }; + } + + private ArchRule allBeanFactoryPostProcessorBeanMethodsShouldBeStaticAndHaveNoParameters() { + return ArchRuleDefinition.methods() + .that() + .areAnnotatedWith("org.springframework.context.annotation.Bean") + .and() + .haveRawReturnType( + Predicates.assignableTo("org.springframework.beans.factory.config.BeanFactoryPostProcessor")) + .should(haveNoParameters()) + .andShould() + .beStatic() + .allowEmptyShould(true); + } + + private ArchCondition haveNoParameters() { + return new ArchCondition("have no parameters") { + + @Override + public void check(JavaMethod item, ConditionEvents events) { + List parameters = item.getParameters(); + if (!parameters.isEmpty()) { + events + .add(SimpleConditionEvent.violated(item, item.getDescription() + " should have no parameters")); + } + } + + }; + } + + public void setClasses(FileCollection classes) { + this.classes = classes; + } + + @Internal + public FileCollection getClasses() { + return this.classes; + } + + @InputFiles + @SkipWhenEmpty + @IgnoreEmptyDirectories + @PathSensitive(PathSensitivity.RELATIVE) + final FileTree getInputClasses() { + return this.classes.getAsFileTree(); + } + + @OutputDirectory + public abstract DirectoryProperty getOutputDirectory(); + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitecturePlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitecturePlugin.java index c95c433572e8..102ddf72e002 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitecturePlugin.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitecturePlugin.java @@ -44,14 +44,14 @@ public void apply(Project project) { private void registerTasks(Project project) { JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class); - List> packageTangleChecks = new ArrayList<>(); + List> packageTangleChecks = new ArrayList<>(); for (SourceSet sourceSet : javaPluginExtension.getSourceSets()) { - TaskProvider checkPackageTangles = project.getTasks() - .register("checkForPackageTangles" + StringUtils.capitalize(sourceSet.getName()), - PackageTangleCheck.class, (task) -> { + TaskProvider checkPackageTangles = project.getTasks() + .register("checkArchitecture" + StringUtils.capitalize(sourceSet.getName()), ArchitectureCheck.class, + (task) -> { task.setClasses(sourceSet.getOutput().getClassesDirs()); - task.setDescription("Checks the classes of the " + sourceSet.getName() - + " source set for package tangles."); + task.setDescription("Checks the architecture of the classes of the " + sourceSet.getName() + + " source set."); task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); }); packageTangleChecks.add(checkPackageTangles); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/architecture/PackageTangleCheck.java b/buildSrc/src/main/java/org/springframework/boot/build/architecture/PackageTangleCheck.java deleted file mode 100644 index 715ac5f68618..000000000000 --- a/buildSrc/src/main/java/org/springframework/boot/build/architecture/PackageTangleCheck.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2022-2023 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.build.architecture; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.StandardOpenOption; -import java.util.stream.Collectors; - -import com.tngtech.archunit.core.domain.JavaClasses; -import com.tngtech.archunit.core.importer.ClassFileImporter; -import com.tngtech.archunit.lang.EvaluationResult; -import com.tngtech.archunit.library.dependencies.SliceRule; -import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition; -import org.gradle.api.DefaultTask; -import org.gradle.api.GradleException; -import org.gradle.api.Task; -import org.gradle.api.file.DirectoryProperty; -import org.gradle.api.file.FileCollection; -import org.gradle.api.file.FileTree; -import org.gradle.api.tasks.IgnoreEmptyDirectories; -import org.gradle.api.tasks.InputFiles; -import org.gradle.api.tasks.Internal; -import org.gradle.api.tasks.OutputDirectory; -import org.gradle.api.tasks.PathSensitive; -import org.gradle.api.tasks.PathSensitivity; -import org.gradle.api.tasks.SkipWhenEmpty; -import org.gradle.api.tasks.TaskAction; - -import org.springframework.util.FileCopyUtils; - -/** - * {@link Task} that checks for package tangles. - * - * @author Andy Wilkinson - */ -public abstract class PackageTangleCheck extends DefaultTask { - - private FileCollection classes; - - public PackageTangleCheck() { - getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName())); - } - - @TaskAction - void checkForPackageTangles() throws IOException { - JavaClasses javaClasses = new ClassFileImporter() - .importPaths(this.classes.getFiles().stream().map(File::toPath).collect(Collectors.toList())); - SliceRule freeOfCycles = SlicesRuleDefinition.slices().matching("(**)").should().beFreeOfCycles(); - EvaluationResult result = freeOfCycles.evaluate(javaClasses); - File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile(); - outputFile.getParentFile().mkdirs(); - if (result.hasViolation()) { - Files.write(outputFile.toPath(), result.getFailureReport().toString().getBytes(StandardCharsets.UTF_8), - StandardOpenOption.CREATE); - FileWriter writer = new FileWriter(outputFile); - FileCopyUtils.copy(result.getFailureReport().toString(), writer); - throw new GradleException("Package tangle check failed. See '" + outputFile + "' for details."); - } - else { - outputFile.createNewFile(); - } - } - - public void setClasses(FileCollection classes) { - this.classes = classes; - } - - @Internal - public FileCollection getClasses() { - return this.classes; - } - - @InputFiles - @SkipWhenEmpty - @IgnoreEmptyDirectories - @PathSensitive(PathSensitivity.RELATIVE) - final FileTree getInputClasses() { - return this.classes.getAsFileTree(); - } - - @OutputDirectory - public abstract DirectoryProperty getOutputDirectory(); - -} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForUnnecessaryExclusions.java b/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForUnnecessaryExclusions.java index 6ea9aa3dc95e..13b50991a547 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForUnnecessaryExclusions.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForUnnecessaryExclusions.java @@ -18,7 +18,6 @@ import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -107,14 +106,13 @@ public void checkForUnnecessaryExclusions() { this.exclusionsByDependencyId.forEach((dependencyId, exclusions) -> { if (!exclusions.isEmpty()) { Dependency toCheck = this.dependencyById.get(dependencyId); - List dependencies = this.configurations.detachedConfiguration(toCheck, this.platform) + this.configurations.detachedConfiguration(toCheck, this.platform) .getIncoming() .getArtifacts() .getArtifacts() .stream() .map(this::getId) - .collect(Collectors.toList()); - exclusions.removeAll(dependencies); + .forEach(exclusions::remove); removeProfileExclusions(dependencyId, exclusions); if (!exclusions.isEmpty()) { unnecessaryExclusions.put(dependencyId, exclusions); diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java new file mode 100644 index 000000000000..61001c784339 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java @@ -0,0 +1,148 @@ +/* + * Copyright 2012-2023 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.build.architecture; + +import java.io.File; +import java.io.IOException; + +import org.gradle.api.GradleException; +import org.gradle.api.Project; +import org.gradle.testfixtures.ProjectBuilder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.util.FileSystemUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +/** + * Tests for {@link ArchitectureCheck}. + * + * @author Andy Wilkinson + */ +class ArchitectureCheckTests { + + @TempDir + File temp; + + @Test + void whenPackagesAreTangledTaskFailsAndWritesAReport() throws Exception { + prepareTask("tangled", (architectureCheck) -> { + assertThatExceptionOfType(GradleException.class).isThrownBy(() -> architectureCheck.checkArchitecture()); + assertThat(failureReport(architectureCheck).length()).isGreaterThan(0); + }); + } + + @Test + void whenPackagesAreNotTangledTaskSucceedsAndWritesAnEmptyReport() throws Exception { + prepareTask("untangled", (architectureCheck) -> { + architectureCheck.checkArchitecture(); + assertThat(failureReport(architectureCheck).length()).isZero(); + }); + } + + File failureReport(ArchitectureCheck architectureCheck) { + return new File(architectureCheck.getProject().getBuildDir(), "checkArchitecture/failure-report.txt"); + } + + @Test + void whenBeanPostProcessorBeanMethodIsNotStaticTaskFailsAndWritesAReport() throws Exception { + prepareTask("bpp/nonstatic", (architectureCheck) -> { + assertThatExceptionOfType(GradleException.class).isThrownBy(() -> architectureCheck.checkArchitecture()); + assertThat(failureReport(architectureCheck).length()).isGreaterThan(0); + }); + } + + @Test + void whenBeanPostProcessorBeanMethodIsStaticAndHasUnsafeParametersTaskFailsAndWritesAReport() throws Exception { + prepareTask("bpp/unsafeparameters", (architectureCheck) -> { + assertThatExceptionOfType(GradleException.class).isThrownBy(() -> architectureCheck.checkArchitecture()); + assertThat(failureReport(architectureCheck).length()).isGreaterThan(0); + }); + } + + @Test + void whenBeanPostProcessorBeanMethodIsStaticAndHasSafeParametersTaskSucceedsAndWritesAnEmptyReport() + throws Exception { + prepareTask("bpp/safeparameters", (architectureCheck) -> { + architectureCheck.checkArchitecture(); + assertThat(failureReport(architectureCheck).length()).isZero(); + }); + } + + @Test + void whenBeanPostProcessorBeanMethodIsStaticAndHasNoParametersTaskSucceedsAndWritesAnEmptyReport() + throws Exception { + prepareTask("bpp/noparameters", (architectureCheck) -> { + architectureCheck.checkArchitecture(); + assertThat(failureReport(architectureCheck).length()).isZero(); + }); + } + + @Test + void whenBeanFactoryPostProcessorBeanMethodIsNotStaticTaskFailsAndWritesAReport() throws Exception { + prepareTask("bfpp/nonstatic", (architectureCheck) -> { + assertThatExceptionOfType(GradleException.class).isThrownBy(() -> architectureCheck.checkArchitecture()); + assertThat(failureReport(architectureCheck).length()).isGreaterThan(0); + }); + } + + @Test + void whenBeanFactoryPostProcessorBeanMethodIsStaticAndHasParametersTaskFailsAndWritesAReport() throws Exception { + prepareTask("bfpp/parameters", (architectureCheck) -> { + assertThatExceptionOfType(GradleException.class).isThrownBy(() -> architectureCheck.checkArchitecture()); + assertThat(failureReport(architectureCheck).length()).isGreaterThan(0); + }); + } + + @Test + void whenBeanFactoryPostProcessorBeanMethodIsStaticAndHasNoParametersTaskSucceedsAndWritesAnEmptyReport() + throws Exception { + prepareTask("bfpp/noparameters", (architectureCheck) -> { + architectureCheck.checkArchitecture(); + assertThat(failureReport(architectureCheck).length()).isZero(); + }); + } + + private void prepareTask(String classes, Callback callback) throws Exception { + File projectDir = new File(this.temp, "project"); + projectDir.mkdirs(); + copyClasses(classes, projectDir); + Project project = ProjectBuilder.builder().withProjectDir(projectDir).build(); + ArchitectureCheck architectureCheck = project.getTasks() + .create("checkArchitecture", ArchitectureCheck.class, (task) -> task.setClasses(project.files("classes"))); + callback.accept(architectureCheck); + } + + private void copyClasses(String name, File projectDir) throws IOException { + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + Resource root = resolver.getResource("classpath:org/springframework/boot/build/architecture/" + name); + FileSystemUtils.copyRecursively(root.getFile(), + new File(projectDir, "classes/org/springframework/boot/build/architecture/" + name)); + + } + + private interface Callback { + + void accept(T item) throws Exception; + + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/PackageTangleCheckTests.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/PackageTangleCheckTests.java deleted file mode 100644 index a0909e6f8a2c..000000000000 --- a/buildSrc/src/test/java/org/springframework/boot/build/architecture/PackageTangleCheckTests.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2012-2023 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.build.architecture; - -import java.io.File; -import java.io.IOException; - -import org.gradle.api.GradleException; -import org.gradle.api.Project; -import org.gradle.testfixtures.ProjectBuilder; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import org.springframework.core.io.Resource; -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; -import org.springframework.util.FileSystemUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * Tests for {@link PackageTangleCheck}. - * - * @author Andy Wilkinson - */ -class PackageTangleCheckTests { - - @TempDir - File temp; - - @Test - void whenPackagesAreTangledTaskFailsAndWritesAReport() throws Exception { - prepareTask("tangled", (packageTangleCheck) -> { - assertThatExceptionOfType(GradleException.class) - .isThrownBy(() -> packageTangleCheck.checkForPackageTangles()); - assertThat( - new File(packageTangleCheck.getProject().getBuildDir(), "checkForPackageTangles/failure-report.txt") - .length()) - .isGreaterThan(0); - }); - } - - @Test - void whenPackagesAreNotTangledTaskSucceedsAndWritesAnEmptyReport() throws Exception { - prepareTask("untangled", (packageTangleCheck) -> { - packageTangleCheck.checkForPackageTangles(); - assertThat( - new File(packageTangleCheck.getProject().getBuildDir(), "checkForPackageTangles/failure-report.txt") - .length()) - .isEqualTo(0); - }); - } - - private void prepareTask(String classes, Callback callback) throws Exception { - File projectDir = new File(this.temp, "project"); - projectDir.mkdirs(); - copyClasses(classes, projectDir); - Project project = ProjectBuilder.builder().withProjectDir(projectDir).build(); - PackageTangleCheck packageTangleCheck = project.getTasks() - .create("checkForPackageTangles", PackageTangleCheck.class, - (task) -> task.setClasses(project.files("classes"))); - callback.accept(packageTangleCheck); - } - - private void copyClasses(String name, File projectDir) throws IOException { - PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); - Resource root = resolver.getResource("classpath:org/springframework/boot/build/architecture/" + name); - FileSystemUtils.copyRecursively(root.getFile(), - new File(projectDir, "classes/org/springframework/boot/build/architecture/" + name)); - - } - - private interface Callback { - - void accept(T item) throws Exception; - - } - -} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/bfpp/nonstatic/NonStaticBeanFactoryPostProcessorConfiguration.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bfpp/nonstatic/NonStaticBeanFactoryPostProcessorConfiguration.java new file mode 100644 index 000000000000..13bf730aaa07 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bfpp/nonstatic/NonStaticBeanFactoryPostProcessorConfiguration.java @@ -0,0 +1,30 @@ +/* + * Copyright 2012-2023 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.build.architecture.bfpp.nonstatic; + +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.context.annotation.Bean; + +class NonStaticBeanFactoryPostProcessorConfiguration { + + @Bean + BeanFactoryPostProcessor nonStaticBeanFactoryPostProcessor() { + return (beanFactory) -> { + }; + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/bfpp/noparameters/NoParametersBeanFactoryPostProcessorConfiguration.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bfpp/noparameters/NoParametersBeanFactoryPostProcessorConfiguration.java new file mode 100644 index 000000000000..659c9c7960db --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bfpp/noparameters/NoParametersBeanFactoryPostProcessorConfiguration.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-2023 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.build.architecture.bfpp.noparameters; + +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.context.annotation.Bean; + +class NoParametersBeanFactoryPostProcessorConfiguration { + + @Bean + static BeanFactoryPostProcessor noParametersBeanFactoryPostProcessor() { + return (beanFactory) -> { + }; + } + + @Bean + Integer beanOne() { + return 1; + } + + @Bean + String beanTwo() { + return "test"; + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/bfpp/parameters/ParametersBeanFactoryPostProcessorConfiguration.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bfpp/parameters/ParametersBeanFactoryPostProcessorConfiguration.java new file mode 100644 index 000000000000..e090a654d1f7 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bfpp/parameters/ParametersBeanFactoryPostProcessorConfiguration.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-2023 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.build.architecture.bfpp.parameters; + +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.context.annotation.Bean; + +class ParametersBeanFactoryPostProcessorConfiguration { + + @Bean + static BeanFactoryPostProcessor parametersBeanFactoryPostProcessor(Integer param) { + return (beanFactory) -> { + }; + } + + @Bean + Integer beanOne() { + return 1; + } + + @Bean + String beanTwo() { + return "test"; + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/nonstatic/NonStaticBeanPostProcessorConfiguration.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/nonstatic/NonStaticBeanPostProcessorConfiguration.java new file mode 100644 index 000000000000..fe2ab7c11645 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/nonstatic/NonStaticBeanPostProcessorConfiguration.java @@ -0,0 +1,31 @@ +/* + * Copyright 2012-2023 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.build.architecture.bpp.nonstatic; + +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.annotation.Bean; + +class NonStaticBeanPostProcessorConfiguration { + + @Bean + BeanPostProcessor nonStaticBeanPostProcessor() { + return new BeanPostProcessor() { + + }; + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/noparameters/NoParametersBeanPostProcessorConfiguration.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/noparameters/NoParametersBeanPostProcessorConfiguration.java new file mode 100644 index 000000000000..39d30105ec25 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/noparameters/NoParametersBeanPostProcessorConfiguration.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2023 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.build.architecture.bpp.noparameters; + +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.annotation.Bean; + +class NoParametersBeanPostProcessorConfiguration { + + @Bean + static BeanPostProcessor noParametersBeanPostProcessor() { + return new BeanPostProcessor() { + + }; + } + + @Bean + Integer beanOne() { + return 1; + } + + @Bean + String beanTwo() { + return "test"; + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/safeparameters/SafeParametersBeanPostProcessorConfiguration.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/safeparameters/SafeParametersBeanPostProcessorConfiguration.java new file mode 100644 index 000000000000..ae793225fef9 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/safeparameters/SafeParametersBeanPostProcessorConfiguration.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2023 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.build.architecture.bpp.safeparameters; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Lazy; +import org.springframework.core.env.Environment; + +class SafeParametersBeanPostProcessorConfiguration { + + @Bean + static BeanPostProcessor safeParametersBeanPostProcessor(ApplicationContext context, ObjectProvider beanOne, + ObjectProvider beanTwo, Environment environment, @Lazy String beanThree) { + return new BeanPostProcessor() { + + }; + } + + @Bean + Integer beanOne() { + return 1; + } + + @Bean + String beanTwo() { + return "test"; + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/unsafeparameters/UnsafeParametersBeanPostProcessorConfiguration.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/unsafeparameters/UnsafeParametersBeanPostProcessorConfiguration.java new file mode 100644 index 000000000000..29d9be81c712 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/unsafeparameters/UnsafeParametersBeanPostProcessorConfiguration.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2023 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.build.architecture.bpp.unsafeparameters; + +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; + +class UnsafeParametersBeanPostProcessorConfiguration { + + @Bean + static BeanPostProcessor unsafeParametersBeanPostProcessor(ApplicationContext context, Integer beanOne, + String beanTwo) { + return new BeanPostProcessor() { + + }; + } + + @Bean + Integer beanOne() { + return 1; + } + + @Bean + String beanTwo() { + return "test"; + } + +} diff --git a/ci/images/ci-image-jdk11/Dockerfile b/ci/images/ci-image-jdk11/Dockerfile index fae9fa86b109..7ecc6edc92da 100644 --- a/ci/images/ci-image-jdk11/Dockerfile +++ b/ci/images/ci-image-jdk11/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:jammy-20230308 +FROM ubuntu:jammy-20230425 ADD setup.sh /setup.sh ADD get-jdk-url.sh /get-jdk-url.sh diff --git a/ci/images/ci-image-jdk17/Dockerfile b/ci/images/ci-image-jdk17/Dockerfile index 9516e68ec170..71ded4f77b8a 100644 --- a/ci/images/ci-image-jdk17/Dockerfile +++ b/ci/images/ci-image-jdk17/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:jammy-20230308 +FROM ubuntu:jammy-20230425 ADD setup.sh /setup.sh ADD get-jdk-url.sh /get-jdk-url.sh diff --git a/ci/images/ci-image-jdk20/Dockerfile b/ci/images/ci-image-jdk20/Dockerfile index 0c5b7d4e064a..bcc8a0d25428 100644 --- a/ci/images/ci-image-jdk20/Dockerfile +++ b/ci/images/ci-image-jdk20/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:jammy-20230308 +FROM ubuntu:jammy-20230425 ADD setup.sh /setup.sh ADD get-jdk-url.sh /get-jdk-url.sh diff --git a/ci/images/ci-image/Dockerfile b/ci/images/ci-image/Dockerfile index 4d786702a430..871bf1cca9f7 100644 --- a/ci/images/ci-image/Dockerfile +++ b/ci/images/ci-image/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:jammy-20230308 +FROM ubuntu:jammy-20230425 ADD setup.sh /setup.sh ADD get-jdk-url.sh /get-jdk-url.sh diff --git a/ci/images/get-docker-url.sh b/ci/images/get-docker-url.sh index 1461fe814c3c..d139cd8c4a46 100755 --- a/ci/images/get-docker-url.sh +++ b/ci/images/get-docker-url.sh @@ -1,5 +1,5 @@ #!/bin/bash set -e -version="23.0.3" +version="24.0.0" echo "https://download.docker.com/linux/static/stable/x86_64/docker-$version.tgz"; diff --git a/ci/images/get-jdk-url.sh b/ci/images/get-jdk-url.sh index 60b50def3505..0c030b44371e 100755 --- a/ci/images/get-jdk-url.sh +++ b/ci/images/get-jdk-url.sh @@ -3,16 +3,16 @@ set -e case "$1" in java8) - echo "https://github.com/bell-sw/Liberica/releases/download/8u362+9/bellsoft-jdk8u362+9-linux-amd64.tar.gz" + echo "https://github.com/bell-sw/Liberica/releases/download/8u372+7/bellsoft-jdk8u372+7-linux-amd64.tar.gz" ;; java11) - echo "https://github.com/bell-sw/Liberica/releases/download/11.0.18+10/bellsoft-jdk11.0.18+10-linux-amd64.tar.gz" + echo "https://github.com/bell-sw/Liberica/releases/download/11.0.19+7/bellsoft-jdk11.0.19+7-linux-amd64.tar.gz" ;; java17) - echo "https://github.com/bell-sw/Liberica/releases/download/17.0.6+10/bellsoft-jdk17.0.6+10-linux-amd64.tar.gz" + echo "https://github.com/bell-sw/Liberica/releases/download/17.0.7+7/bellsoft-jdk17.0.7+7-linux-amd64.tar.gz" ;; java20) - echo "https://github.com/bell-sw/Liberica/releases/download/20+37/bellsoft-jdk20+37-linux-amd64.tar.gz" + echo "https://github.com/bell-sw/Liberica/releases/download/20.0.1+10/bellsoft-jdk20.0.1+10-linux-amd64.tar.gz" ;; *) echo $"Unknown java version" diff --git a/ci/tasks/run-system-tests.yml b/ci/tasks/run-system-tests.yml index 6dc0bef5c084..06d745d9cab8 100644 --- a/ci/tasks/run-system-tests.yml +++ b/ci/tasks/run-system-tests.yml @@ -20,6 +20,10 @@ run: args: - -ec - | + mkdir -p /root/.docker + cat > /root/.docker/config.json < handlerMapping) { return new WebFilterChainPostProcessor(handlerMapping); } @@ -164,16 +166,17 @@ WebFilterChainPostProcessor webFilterChainPostProcessor( static class WebFilterChainPostProcessor implements BeanPostProcessor { - private final PathMappedEndpoints pathMappedEndpoints; + private final Supplier pathMappedEndpoints; - WebFilterChainPostProcessor(CloudFoundryWebFluxEndpointHandlerMapping handlerMapping) { - this.pathMappedEndpoints = new PathMappedEndpoints(BASE_PATH, handlerMapping::getAllEndpoints); + WebFilterChainPostProcessor(ObjectProvider handlerMapping) { + this.pathMappedEndpoints = SingletonSupplier + .of(() -> new PathMappedEndpoints(BASE_PATH, () -> handlerMapping.getObject().getAllEndpoints())); } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof WebFilterChainProxy) { - return postProcess((WebFilterChainProxy) bean, this.pathMappedEndpoints); + return postProcess((WebFilterChainProxy) bean, this.pathMappedEndpoints.get()); } return bean; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/PropertiesMeterFilterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/PropertiesMeterFilterTests.java index 5db70a5285f4..e1db8683bb46 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/PropertiesMeterFilterTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/PropertiesMeterFilterTests.java @@ -175,33 +175,33 @@ void configureWhenAllHistogramTrueSetPercentilesHistogramToTrue() { @Test void configureWhenHasPercentilesShouldSetPercentilesToValue() { PropertiesMeterFilter filter = new PropertiesMeterFilter( - createProperties("distribution.percentiles.spring.boot=1,1.5,2")); + createProperties("distribution.percentiles.spring.boot=0.2,0.4,0.8")); assertThat(filter.configure(createMeterId("spring.boot"), DistributionStatisticConfig.DEFAULT).getPercentiles()) - .containsExactly(1, 1.5, 2); + .containsExactly(0.2, 0.4, 0.8); } @Test void configureWhenHasHigherPercentilesShouldSetPercentilesToValue() { PropertiesMeterFilter filter = new PropertiesMeterFilter( - createProperties("distribution.percentiles.spring=1,1.5,2")); + createProperties("distribution.percentiles.spring=0.2,0.4,0.8")); assertThat(filter.configure(createMeterId("spring.boot"), DistributionStatisticConfig.DEFAULT).getPercentiles()) - .containsExactly(1, 1.5, 2); + .containsExactly(0.2, 0.4, 0.8); } @Test void configureWhenHasHigherPercentilesAndLowerShouldSetPercentilesToLower() { PropertiesMeterFilter filter = new PropertiesMeterFilter(createProperties( - "distribution.percentiles.spring=1,1.5,2", "distribution.percentiles.spring.boot=3,3.5,4")); + "distribution.percentiles.spring=0.2,0.4,0.8", "distribution.percentiles.spring.boot=0.85,0.9,0.95")); assertThat(filter.configure(createMeterId("spring.boot"), DistributionStatisticConfig.DEFAULT).getPercentiles()) - .containsExactly(3, 3.5, 4); + .containsExactly(0.85, 0.9, 0.95); } @Test void configureWhenAllPercentilesSetShouldSetPercentilesToValue() { PropertiesMeterFilter filter = new PropertiesMeterFilter( - createProperties("distribution.percentiles.all=1,1.5,2")); + createProperties("distribution.percentiles.all=0.2,0.4,0.8")); assertThat(filter.configure(createMeterId("spring.boot"), DistributionStatisticConfig.DEFAULT).getPercentiles()) - .containsExactly(1, 1.5, 2); + .containsExactly(0.2, 0.4, 0.8); } @Test diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/mail/MailHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/mail/MailHealthIndicator.java index a7abf862f64a..afd67045f7fe 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/mail/MailHealthIndicator.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/mail/MailHealthIndicator.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2023 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. @@ -38,7 +38,9 @@ public MailHealthIndicator(JavaMailSenderImpl mailSender) { @Override protected void doHealthCheck(Builder builder) throws Exception { - builder.withDetail("location", this.mailSender.getHost() + ":" + this.mailSender.getPort()); + int port = this.mailSender.getPort(); + builder.withDetail("location", (port != JavaMailSenderImpl.DEFAULT_PORT) + ? this.mailSender.getHost() + ":" + this.mailSender.getPort() : this.mailSender.getHost()); this.mailSender.testConnection(); builder.up(); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointProxyTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointProxyTests.java index f359af84e950..ef8515555887 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointProxyTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointProxyTests.java @@ -108,7 +108,7 @@ PlatformTransactionManager transactionManager(DataSource dataSource) { } @Bean - MethodValidationPostProcessor testPostProcessor() { + static MethodValidationPostProcessor testPostProcessor() { return new MethodValidationPostProcessor(); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/BaseConfiguration.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/BaseConfiguration.java index c9784db2c105..9b8b8094db9e 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/BaseConfiguration.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/BaseConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2023 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. @@ -70,7 +70,7 @@ WebEndpointDiscoverer webEndpointDiscoverer(EndpointMediaTypes endpointMediaType } @Bean - PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { + static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { return new PropertySourcesPlaceholderConfigurer(); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/mail/MailHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/mail/MailHealthIndicatorTests.java index 3cf1b7595fc3..1fdb6dba44a3 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/mail/MailHealthIndicatorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/mail/MailHealthIndicatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2023 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. @@ -57,25 +57,47 @@ void setup() { session.addProvider(new Provider(Type.TRANSPORT, "success", SuccessTransport.class.getName(), "Test", "1.0.0")); this.mailSender = mock(JavaMailSenderImpl.class); given(this.mailSender.getHost()).willReturn("smtp.acme.org"); - given(this.mailSender.getPort()).willReturn(25); given(this.mailSender.getSession()).willReturn(session); this.indicator = new MailHealthIndicator(this.mailSender); } @Test - void smtpIsUp() { + void smtpOnDefaultPortIsUp() { + given(this.mailSender.getPort()).willReturn(-1); given(this.mailSender.getProtocol()).willReturn("success"); Health health = this.indicator.health(); assertThat(health.getStatus()).isEqualTo(Status.UP); - assertThat(health.getDetails().get("location")).isEqualTo("smtp.acme.org:25"); + assertThat(health.getDetails().get("location")).isEqualTo("smtp.acme.org"); } @Test - void smtpIsDown() throws MessagingException { + void smtpOnDefaultPortIsDown() throws MessagingException { + given(this.mailSender.getPort()).willReturn(-1); willThrow(new MessagingException("A test exception")).given(this.mailSender).testConnection(); Health health = this.indicator.health(); assertThat(health.getStatus()).isEqualTo(Status.DOWN); - assertThat(health.getDetails().get("location")).isEqualTo("smtp.acme.org:25"); + assertThat(health.getDetails().get("location")).isEqualTo("smtp.acme.org"); + Object errorMessage = health.getDetails().get("error"); + assertThat(errorMessage).isNotNull(); + assertThat(errorMessage.toString().contains("A test exception")).isTrue(); + } + + @Test + void smtpOnCustomPortIsUp() { + given(this.mailSender.getPort()).willReturn(1234); + given(this.mailSender.getProtocol()).willReturn("success"); + Health health = this.indicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertThat(health.getDetails().get("location")).isEqualTo("smtp.acme.org:1234"); + } + + @Test + void smtpOnCustomPortIsDown() throws MessagingException { + given(this.mailSender.getPort()).willReturn(1234); + willThrow(new MessagingException("A test exception")).given(this.mailSender).testConnection(); + Health health = this.indicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.DOWN); + assertThat(health.getDetails().get("location")).isEqualTo("smtp.acme.org:1234"); Object errorMessage = health.getDetails().get("error"); assertThat(errorMessage).isNotNull(); assertThat(errorMessage.toString().contains("A test exception")).isTrue(); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/neo4j/Neo4jReactiveHealthIndicatorIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/neo4j/Neo4jReactiveHealthIndicatorIntegrationTests.java index e1f78865c688..1620441cf535 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/neo4j/Neo4jReactiveHealthIndicatorIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/neo4j/Neo4jReactiveHealthIndicatorIntegrationTests.java @@ -19,6 +19,7 @@ import java.time.Duration; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.OS; import org.testcontainers.containers.Neo4jContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -29,6 +30,7 @@ import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.testsupport.junit.DisabledOnOs; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -44,6 +46,8 @@ */ @SpringBootTest @Testcontainers(disabledWithoutDocker = true) +@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", + disabledReason = "The Neo4j image has no ARM support") public class Neo4jReactiveHealthIndicatorIntegrationTests { // gh-33428 diff --git a/spring-boot-project/spring-boot-autoconfigure/build.gradle b/spring-boot-project/spring-boot-autoconfigure/build.gradle index 6c6cdc76bac5..724c0f502e5e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-autoconfigure/build.gradle @@ -73,6 +73,7 @@ dependencies { exclude group: "commons-logging", module: "commons-logging" } optional("org.apache.httpcomponents.client5:httpclient5") + optional("org.apache.httpcomponents.core5:httpcore5-reactive") optional("org.apache.kafka:kafka-streams") optional("org.apache.solr:solr-solrj") { exclude group: "org.slf4j", module: "jcl-over-slf4j" @@ -275,4 +276,4 @@ tasks.named("checkSpringConfigurationMetadata").configure { "spring.datasource.tomcat.*", "spring.groovy.template.configuration.*" ] -} \ No newline at end of file +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/SpringBootApplication.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/SpringBootApplication.java index 83e16137708a..99d9ce03646e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/SpringBootApplication.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/SpringBootApplication.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2023 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. @@ -40,7 +40,7 @@ * Indicates a {@link Configuration configuration} class that declares one or more * {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration * auto-configuration} and {@link ComponentScan component scanning}. This is a convenience - * annotation that is equivalent to declaring {@code @Configuration}, + * annotation that is equivalent to declaring {@code @SpringBootConfiguration}, * {@code @EnableAutoConfiguration} and {@code @ComponentScan}. * * @author Phillip Webb diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/availability/ApplicationAvailabilityAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/availability/ApplicationAvailabilityAutoConfiguration.java index 769c5a3cc8a9..e9e19df7f00c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/availability/ApplicationAvailabilityAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/availability/ApplicationAvailabilityAutoConfiguration.java @@ -34,8 +34,8 @@ public class ApplicationAvailabilityAutoConfiguration { @Bean - @ConditionalOnMissingBean - public ApplicationAvailability applicationAvailability() { + @ConditionalOnMissingBean(ApplicationAvailability.class) + public ApplicationAvailabilityBean applicationAvailability() { return new ApplicationAvailabilityBean(); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java index 4a945908f2d6..cb987b1af3a9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java @@ -147,7 +147,7 @@ public List getExclusions() { */ public Set getUnconditionalClasses() { Set filtered = new HashSet<>(this.unconditionalClasses); - filtered.removeAll(this.exclusions); + this.exclusions.forEach(filtered::remove); return Collections.unmodifiableSet(filtered); } @@ -166,7 +166,7 @@ public ConditionEvaluationReport getParent() { * @return the {@link ConditionEvaluationReport} or {@code null} */ public static ConditionEvaluationReport find(BeanFactory beanFactory) { - if (beanFactory != null && beanFactory instanceof ConfigurableListableBeanFactory) { + if (beanFactory instanceof ConfigurableListableBeanFactory) { return ConditionEvaluationReport.get((ConfigurableListableBeanFactory) beanFactory); } return null; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java index 9c8663e32e60..46edd4f81849 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -601,7 +601,7 @@ public void setUser(String user) { } public String getPassword() { - return (this.password != null) ? this.password : ""; + return this.password; } public void setPassword(String password) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/ConnectionFactoryConfigurations.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/ConnectionFactoryConfigurations.java index a4c636d2a151..25ec1ea363ef 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/ConnectionFactoryConfigurations.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/ConnectionFactoryConfigurations.java @@ -103,6 +103,8 @@ ConnectionPool connectionFactory(R2dbcProperties properties, ResourceLoader reso map.from(pool.getMaxSize()).to(builder::maxSize); map.from(pool.getValidationQuery()).whenHasText().to(builder::validationQuery); map.from(pool.getValidationDepth()).to(builder::validationDepth); + map.from(pool.getMinIdle()).to(builder::minIdle); + map.from(pool.getMaxValidationTime()).to(builder::maxValidationTime); return new ConnectionPool(builder.build()); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcProperties.java index bcaab87b894e..e07b1dd0a06c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2023 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. @@ -136,6 +136,11 @@ public String determineUniqueName() { public static class Pool { + /** + * Minimal number of idle connections. + */ + private int minIdle = 0; + /** * Maximum amount of time that a connection is allowed to sit idle in the pool. */ @@ -153,6 +158,12 @@ public static class Pool { */ private Duration maxAcquireTime; + /** + * Maximum time to validate a connection from the pool. By default, wait + * indefinitely. + */ + private Duration maxValidationTime; + /** * Maximum time to wait to create a new connection. By default, wait indefinitely. */ @@ -183,6 +194,14 @@ public static class Pool { */ private boolean enabled = true; + public int getMinIdle() { + return this.minIdle; + } + + public void setMinIdle(int minIdle) { + this.minIdle = minIdle; + } + public Duration getMaxIdleTime() { return this.maxIdleTime; } @@ -199,6 +218,14 @@ public void setMaxLifeTime(Duration maxLifeTime) { this.maxLifeTime = maxLifeTime; } + public Duration getMaxValidationTime() { + return this.maxValidationTime; + } + + public void setMaxValidationTime(Duration maxValidationTime) { + this.maxValidationTime = maxValidationTime; + } + public Duration getMaxAcquireTime() { return this.maxAcquireTime; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionRepositoryFilterConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionRepositoryFilterConfiguration.java index 3338603aeb11..6b8785d07441 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionRepositoryFilterConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionRepositoryFilterConfiguration.java @@ -21,12 +21,14 @@ import javax.servlet.DispatcherType; +import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.session.web.http.SessionRepositoryFilter; +import org.springframework.util.Assert; /** * Configuration for customizing the registration of the {@link SessionRepositoryFilter}. @@ -39,9 +41,12 @@ class SessionRepositoryFilterConfiguration { @Bean - FilterRegistrationBean> sessionRepositoryFilterRegistration( - SessionProperties sessionProperties, SessionRepositoryFilter filter) { - FilterRegistrationBean> registration = new FilterRegistrationBean<>(filter); + DelegatingFilterProxyRegistrationBean sessionRepositoryFilterRegistration(SessionProperties sessionProperties, + ListableBeanFactory beanFactory) { + String[] targetBeanNames = beanFactory.getBeanNamesForType(SessionRepositoryFilter.class, false, false); + Assert.state(targetBeanNames.length == 1, "Expected single SessionRepositoryFilter bean"); + DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean( + targetBeanNames[0]); registration.setDispatcherTypes(getDispatcherTypes(sessionProperties)); registration.setOrder(sessionProperties.getServlet().getFilterOrder()); return registration; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java index 49f160cbb735..060cebc0e44e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java @@ -459,6 +459,7 @@ public static class Tomcat { /** * Whether to reject requests with illegal header names or values. */ + @Deprecated private boolean rejectIllegalHeader = true; /** @@ -612,6 +613,7 @@ public void setConnectionTimeout(Duration connectionTimeout) { this.connectionTimeout = connectionTimeout; } + @DeprecatedConfigurationProperty(reason = "The setting has been deprecated in Tomcat") public boolean isRejectIllegalHeader() { return this.rejectIllegalHeader; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.java index 2705680328ca..42a5dba82fa5 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.java @@ -220,6 +220,7 @@ private void customizeRelaxedQueryChars(ConfigurableTomcatWebServerFactory facto factory.addConnectorCustomizers((connector) -> connector.setProperty("relaxedQueryChars", relaxedChars)); } + @SuppressWarnings("deprecation") private void customizeRejectIllegalHeader(ConfigurableTomcatWebServerFactory factory, boolean rejectIllegalHeader) { factory.addConnectorCustomizers((connector) -> { ProtocolHandler handler = connector.getProtocolHandler(); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorConfiguration.java index 85ec0c5392f3..be43dbbbe60c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorConfiguration.java @@ -18,6 +18,7 @@ import org.apache.hc.client5.http.impl.async.HttpAsyncClients; import org.apache.hc.core5.http.nio.AsyncRequestProducer; +import org.apache.hc.core5.reactive.ReactiveResponseConsumer; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.util.ssl.SslContextFactory; @@ -87,7 +88,7 @@ JettyClientHttpConnector jettyClientHttpConnector(JettyResourceFactory jettyReso } @Configuration(proxyBeanMethods = false) - @ConditionalOnClass({ HttpAsyncClients.class, AsyncRequestProducer.class }) + @ConditionalOnClass({ HttpAsyncClients.class, AsyncRequestProducer.class, ReactiveResponseConsumer.class }) @ConditionalOnMissingBean(ClientHttpConnector.class) static class HttpClient5 { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java index f7ffc9f23dd6..ea5ea442620f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java @@ -108,6 +108,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver; +import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping; import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; import org.springframework.web.servlet.i18n.FixedLocaleResolver; import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver; @@ -437,12 +438,29 @@ protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() { @Bean public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { - WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping( - new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(), - this.mvcProperties.getStaticPathPattern()); - welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider)); - welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations()); - return welcomePageHandlerMapping; + return createWelcomePageHandlerMapping(applicationContext, mvcConversionService, mvcResourceUrlProvider, + WelcomePageHandlerMapping::new); + } + + @Bean + public WelcomePageNotAcceptableHandlerMapping welcomePageNotAcceptableHandlerMapping( + ApplicationContext applicationContext, FormattingConversionService mvcConversionService, + ResourceUrlProvider mvcResourceUrlProvider) { + return createWelcomePageHandlerMapping(applicationContext, mvcConversionService, mvcResourceUrlProvider, + WelcomePageNotAcceptableHandlerMapping::new); + } + + private T createWelcomePageHandlerMapping( + ApplicationContext applicationContext, FormattingConversionService mvcConversionService, + ResourceUrlProvider mvcResourceUrlProvider, WelcomePageHandlerMappingFactory factory) { + TemplateAvailabilityProviders templateAvailabilityProviders = new TemplateAvailabilityProviders( + applicationContext); + String staticPathPattern = this.mvcProperties.getStaticPathPattern(); + T handlerMapping = factory.create(templateAvailabilityProviders, applicationContext, getIndexHtmlResource(), + staticPathPattern); + handlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider)); + handlerMapping.setCorsConfigurations(getCorsConfigurations()); + return handlerMapping; } @Override @@ -471,25 +489,25 @@ public FlashMapManager flashMapManager() { return super.flashMapManager(); } - private Resource getWelcomePage() { + private Resource getIndexHtmlResource() { for (String location : this.resourceProperties.getStaticLocations()) { - Resource indexHtml = getIndexHtml(location); + Resource indexHtml = getIndexHtmlResource(location); if (indexHtml != null) { return indexHtml; } } ServletContext servletContext = getServletContext(); if (servletContext != null) { - return getIndexHtml(new ServletContextResource(servletContext, SERVLET_LOCATION)); + return getIndexHtmlResource(new ServletContextResource(servletContext, SERVLET_LOCATION)); } return null; } - private Resource getIndexHtml(String location) { - return getIndexHtml(this.resourceLoader.getResource(location)); + private Resource getIndexHtmlResource(String location) { + return getIndexHtmlResource(this.resourceLoader.getResource(location)); } - private Resource getIndexHtml(Resource location) { + private Resource getIndexHtmlResource(Resource location) { try { Resource resource = location.createRelative("index.html"); if (resource.exists() && (resource.getURL() != null)) { @@ -603,6 +621,15 @@ ResourceChainResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCu } + @FunctionalInterface + interface WelcomePageHandlerMappingFactory { + + T create(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, + Resource indexHtmlResource, String staticPathPattern); + + } + + @FunctionalInterface interface ResourceHandlerRegistrationCustomizer { void customize(ResourceHandlerRegistration registration); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePage.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePage.java new file mode 100644 index 000000000000..1adad3cea9bf --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePage.java @@ -0,0 +1,79 @@ +/* + * Copyright 2012-2023 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.autoconfigure.web.servlet; + +import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders; +import org.springframework.context.ApplicationContext; +import org.springframework.core.io.Resource; + +/** + * Details for a welcome page resolved from a resource or a template. + * + * @author Phillip Webb + */ +final class WelcomePage { + + /** + * Value used for an unresolved welcome page. + */ + static final WelcomePage UNRESOLVED = new WelcomePage(null, false); + + private final String viewName; + + private final boolean templated; + + private WelcomePage(String viewName, boolean templated) { + this.viewName = viewName; + this.templated = templated; + } + + /** + * Return the view name of the welcome page. + * @return the view name + */ + String getViewName() { + return this.viewName; + } + + /** + * Return if the welcome page is from a template. + * @return if the welcome page is templated + */ + boolean isTemplated() { + return this.templated; + } + + /** + * Resolve the {@link WelcomePage} to use. + * @param templateAvailabilityProviders the template availability providers + * @param applicationContext the application context + * @param indexHtmlResource the index HTML resource to use or {@code null} + * @param staticPathPattern the static path pattern being used + * @return a resolved {@link WelcomePage} instance or {@link #UNRESOLVED} + */ + static WelcomePage resolve(TemplateAvailabilityProviders templateAvailabilityProviders, + ApplicationContext applicationContext, Resource indexHtmlResource, String staticPathPattern) { + if (indexHtmlResource != null && "/**".equals(staticPathPattern)) { + return new WelcomePage("forward:index.html", false); + } + if (templateAvailabilityProviders.getProvider("index", applicationContext) != null) { + return new WelcomePage("index", true); + } + return UNRESOLVED; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageHandlerMapping.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageHandlerMapping.java index 2f33cbb3e9dc..8872c5f8a411 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageHandlerMapping.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2023 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. @@ -27,6 +27,7 @@ import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders; import org.springframework.context.ApplicationContext; import org.springframework.core.io.Resource; +import org.springframework.core.log.LogMessage; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.util.StringUtils; @@ -34,12 +35,13 @@ import org.springframework.web.servlet.mvc.ParameterizableViewController; /** - * An {@link AbstractUrlHandlerMapping} for an application's welcome page. Supports both - * static and templated files. If both a static and templated index page are available, - * the static page is preferred. + * An {@link AbstractUrlHandlerMapping} for an application's HTML welcome page. Supports + * both static and templated files. If both a static and templated index page are + * available, the static page is preferred. * * @author Andy Wilkinson * @author Bruce Brouwer + * @see WelcomePageNotAcceptableHandlerMapping */ final class WelcomePageHandlerMapping extends AbstractUrlHandlerMapping { @@ -48,37 +50,31 @@ final class WelcomePageHandlerMapping extends AbstractUrlHandlerMapping { private static final List MEDIA_TYPES_ALL = Collections.singletonList(MediaType.ALL); WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, - ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) { - if (welcomePage != null && "/**".equals(staticPathPattern)) { - logger.info("Adding welcome page: " + welcomePage); - setRootViewName("forward:index.html"); - } - else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) { - logger.info("Adding welcome page template: index"); - setRootViewName("index"); - } - } - - private boolean welcomeTemplateExists(TemplateAvailabilityProviders templateAvailabilityProviders, - ApplicationContext applicationContext) { - return templateAvailabilityProviders.getProvider("index", applicationContext) != null; - } - - private void setRootViewName(String viewName) { - ParameterizableViewController controller = new ParameterizableViewController(); - controller.setViewName(viewName); - setRootHandler(controller); + ApplicationContext applicationContext, Resource indexHtmlResource, String staticPathPattern) { setOrder(2); + WelcomePage welcomePage = WelcomePage.resolve(templateAvailabilityProviders, applicationContext, + indexHtmlResource, staticPathPattern); + if (welcomePage != WelcomePage.UNRESOLVED) { + logger.info(LogMessage.of(() -> (!welcomePage.isTemplated()) ? "Adding welcome page: " + indexHtmlResource + : "Adding welcome page template: index")); + ParameterizableViewController controller = new ParameterizableViewController(); + controller.setViewName(welcomePage.getViewName()); + setRootHandler(controller); + } } @Override public Object getHandlerInternal(HttpServletRequest request) throws Exception { + return (!isHtmlTextAccepted(request)) ? null : super.getHandlerInternal(request); + } + + private boolean isHtmlTextAccepted(HttpServletRequest request) { for (MediaType mediaType : getAcceptedMediaTypes(request)) { if (mediaType.includes(MediaType.TEXT_HTML)) { - return super.getHandlerInternal(request); + return true; } } - return null; + return false; } private List getAcceptedMediaTypes(HttpServletRequest request) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageNotAcceptableHandlerMapping.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageNotAcceptableHandlerMapping.java new file mode 100644 index 000000000000..7b7154df9dad --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageNotAcceptableHandlerMapping.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2023 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.autoconfigure.web.servlet; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders; +import org.springframework.context.ApplicationContext; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpStatus; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping; +import org.springframework.web.servlet.mvc.Controller; + +/** + * An {@link AbstractUrlHandlerMapping} for an application's welcome page that was + * ultimately not accepted. + * + * @author Phillip Webb + */ +class WelcomePageNotAcceptableHandlerMapping extends AbstractUrlHandlerMapping { + + WelcomePageNotAcceptableHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, + ApplicationContext applicationContext, Resource indexHtmlResource, String staticPathPattern) { + setOrder(LOWEST_PRECEDENCE - 10); // Before ResourceHandlerRegistry + WelcomePage welcomePage = WelcomePage.resolve(templateAvailabilityProviders, applicationContext, + indexHtmlResource, staticPathPattern); + if (welcomePage != WelcomePage.UNRESOLVED) { + setRootHandler((Controller) this::handleRequest); + } + } + + private ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) { + response.setStatus(HttpStatus.NOT_ACCEPTABLE.value()); + return null; + } + + @Override + protected Object getHandlerInternal(HttpServletRequest request) throws Exception { + return super.getHandlerInternal(request); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/availability/ApplicationAvailabilityAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/availability/ApplicationAvailabilityAutoConfigurationTests.java index 225ce98f5eb4..c18b0bd01d55 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/availability/ApplicationAvailabilityAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/availability/ApplicationAvailabilityAutoConfigurationTests.java @@ -18,8 +18,11 @@ import org.junit.jupiter.api.Test; +import org.springframework.boot.LazyInitializationBeanFactoryPostProcessor; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.availability.ApplicationAvailability; +import org.springframework.boot.availability.AvailabilityChangeEvent; +import org.springframework.boot.availability.ReadinessState; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; @@ -51,4 +54,13 @@ void providerIsNotConfiguredWhenCustomOneIsPresent() { .hasBean("customApplicationAvailability"))); } + @Test + void whenLazyInitializationIsEnabledApplicationAvailabilityBeanShouldStillReceiveAvailabilityChangeEvents() { + this.contextRunner.withBean(LazyInitializationBeanFactoryPostProcessor.class).run((context) -> { + AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC); + ApplicationAvailability applicationAvailability = context.getBean(ApplicationAvailability.class); + assertThat(applicationAvailability.getLastChangeEvent(ReadinessState.class)).isNotNull(); + }); + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchRepositoriesAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchRepositoriesAutoConfigurationTests.java index ba6922202c36..20da516ec7a8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchRepositoriesAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchRepositoriesAutoConfigurationTests.java @@ -50,6 +50,7 @@ class ElasticsearchRepositoriesAutoConfigurationTests { @Container static final ElasticsearchContainer elasticsearch = new ElasticsearchContainer(DockerImageNames.elasticsearch()) + .withEnv("ES_JAVA_OPTS", "-Xms32m -Xmx512m") .withStartupAttempts(5) .withStartupTimeout(Duration.ofMinutes(10)); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ReactiveElasticsearchRepositoriesAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ReactiveElasticsearchRepositoriesAutoConfigurationTests.java index d85166aeb14e..4bff531dffed 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ReactiveElasticsearchRepositoriesAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ReactiveElasticsearchRepositoriesAutoConfigurationTests.java @@ -49,6 +49,7 @@ class ReactiveElasticsearchRepositoriesAutoConfigurationTests { @Container static ElasticsearchContainer elasticsearch = new ElasticsearchContainer(DockerImageNames.elasticsearch()) + .withEnv("ES_JAVA_OPTS", "-Xms32m -Xmx512m") .withStartupAttempts(5) .withStartupTimeout(Duration.ofMinutes(10)); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ReactiveElasticsearchRestClientAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ReactiveElasticsearchRestClientAutoConfigurationIntegrationTests.java index c78e1eb48786..ed9b8b3a71bd 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ReactiveElasticsearchRestClientAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ReactiveElasticsearchRestClientAutoConfigurationIntegrationTests.java @@ -45,6 +45,7 @@ class ReactiveElasticsearchRestClientAutoConfigurationIntegrationTests { @Container static ElasticsearchContainer elasticsearch = new ElasticsearchContainer(DockerImageNames.elasticsearch()) + .withEnv("ES_JAVA_OPTS", "-Xms32m -Xmx512m") .withStartupAttempts(5) .withStartupTimeout(Duration.ofMinutes(10)); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigurationIntegrationTests.java index b32f0dddccb5..70ada3a203c5 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigurationIntegrationTests.java @@ -19,6 +19,7 @@ import java.time.Duration; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.OS; import org.testcontainers.containers.Neo4jContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -28,6 +29,7 @@ import org.springframework.boot.autoconfigure.data.neo4j.country.CountryRepository; import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.testsupport.junit.DisabledOnOs; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.context.annotation.Configuration; import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; @@ -43,6 +45,8 @@ */ @SpringBootTest @Testcontainers(disabledWithoutDocker = true) +@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", + disabledReason = "The Neo4j image has no ARM support") class Neo4jRepositoriesAutoConfigurationIntegrationTests { @Container diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java index 1703575e2bd4..04435b273eb0 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java @@ -239,7 +239,7 @@ JedisClientConfigurationBuilderCustomizer customizer() { static class JedisConnectionFactoryCaptorConfiguration { @Bean - JedisConnectionFactoryCaptor jedisConnectionFactoryCaptor() { + static JedisConnectionFactoryCaptor jedisConnectionFactoryCaptor() { return new JedisConnectionFactoryCaptor(); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationIntegrationTests.java index 4c1c29488fa8..e133b8d9a980 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationIntegrationTests.java @@ -53,6 +53,7 @@ class ElasticsearchRestClientAutoConfigurationIntegrationTests { @Container static final ElasticsearchContainer elasticsearch = new ElasticsearchContainer(DockerImageNames.elasticsearch()) + .withEnv("ES_JAVA_OPTS", "-Xms32m -Xmx512m") .withStartupAttempts(5) .withStartupTimeout(Duration.ofMinutes(10)); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java index f6cd5f2f14c9..e53ef5277a50 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java @@ -154,7 +154,7 @@ void createDataSourceDoesNotFallbackToEmbeddedProperties() { DataSource dataSource = context.getBean(Flyway.class).getConfiguration().getDataSource(); assertThat(dataSource).isNotNull(); assertThat(dataSource).hasFieldOrPropertyWithValue("username", null); - assertThat(dataSource).hasFieldOrPropertyWithValue("password", ""); + assertThat(dataSource).hasFieldOrPropertyWithValue("password", null); }); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceTransactionManagerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceTransactionManagerAutoConfigurationTests.java index a469c0495ae2..90044440432a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceTransactionManagerAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceTransactionManagerAutoConfigurationTests.java @@ -38,6 +38,7 @@ * @author Dave Syer * @author Stephane Nicoll * @author Kazuki Shimizu + * @author Davin Byeon */ class DataSourceTransactionManagerAutoConfigurationTests { @@ -76,9 +77,10 @@ void transactionManagerWithCustomizationIsConfigured() { @Test void transactionManagerWithExistingTransactionManagerIsNotOverridden() { - this.contextRunner + this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class)) .withBean("myTransactionManager", TransactionManager.class, () -> mock(TransactionManager.class)) - .run((context) -> assertThat(context).hasSingleBean(TransactionManager.class) + .run((context) -> assertThat(context).hasSingleBean(DataSource.class) + .hasSingleBean(TransactionManager.class) .hasBean("myTransactionManager")); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationIntegrationTests.java index c8febbbe6056..bb03bea7bfdb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationIntegrationTests.java @@ -19,6 +19,7 @@ import java.time.Duration; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.OS; import org.neo4j.driver.Driver; import org.neo4j.driver.Result; import org.neo4j.driver.Session; @@ -30,6 +31,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.testsupport.junit.DisabledOnOs; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.DynamicPropertyRegistry; @@ -45,6 +47,8 @@ */ @SpringBootTest @Testcontainers(disabledWithoutDocker = true) +@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", + disabledReason = "The Neo4j image has no ARM support") class Neo4jAutoConfigurationIntegrationTests { @Container diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcAutoConfigurationTests.java index 8c475362c1d9..87ae445521f3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcAutoConfigurationTests.java @@ -77,15 +77,25 @@ void configureWithUrlCreateConnectionPoolByDefault() { void configureWithUrlAndPoolPropertiesApplyProperties() { this.contextRunner .withPropertyValues("spring.r2dbc.url:r2dbc:h2:mem:///" + randomDatabaseName(), - "spring.r2dbc.pool.max-size=15", "spring.r2dbc.pool.max-acquire-time=3m") + "spring.r2dbc.pool.max-size=15", "spring.r2dbc.pool.max-acquire-time=3m", + "spring.r2dbc.pool.min-idle=1", "spring.r2dbc.pool.max-validation-time=1s", + "spring.r2dbc.pool.initial-size=0") .run((context) -> { assertThat(context).hasSingleBean(ConnectionFactory.class) .hasSingleBean(ConnectionPool.class) .hasSingleBean(R2dbcProperties.class); ConnectionPool connectionPool = context.getBean(ConnectionPool.class); - PoolMetrics poolMetrics = connectionPool.getMetrics().get(); - assertThat(poolMetrics.getMaxAllocatedSize()).isEqualTo(15); - assertThat(connectionPool).hasFieldOrPropertyWithValue("maxAcquireTime", Duration.ofMinutes(3)); + connectionPool.warmup().block(); + try { + PoolMetrics poolMetrics = connectionPool.getMetrics().get(); + assertThat(poolMetrics.idleSize()).isEqualTo(1); + assertThat(poolMetrics.getMaxAllocatedSize()).isEqualTo(15); + assertThat(connectionPool).hasFieldOrPropertyWithValue("maxAcquireTime", Duration.ofMinutes(3)); + assertThat(connectionPool).hasFieldOrPropertyWithValue("maxValidationTime", Duration.ofSeconds(1)); + } + finally { + connectionPool.close().block(); + } }); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationEarlyInitializationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationEarlyInitializationIntegrationTests.java new file mode 100644 index 000000000000..6d449f83a4dd --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationEarlyInitializationIntegrationTests.java @@ -0,0 +1,66 @@ +/* + * Copyright 2012-2023 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.autoconfigure.session; + +import java.util.LinkedHashMap; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.session.MapSessionRepository; +import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession; +import org.springframework.util.Assert; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests to ensure {@link SessionAutoConfiguration} and + * {@link SessionRepositoryFilterConfiguration} does not cause early initialization. + * + * @author Phillip Webb + */ +public class SessionAutoConfigurationEarlyInitializationIntegrationTests { + + @Test + void configurationIsFrozenWhenSessionRepositoryAccessed() { + new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new) + .withSystemProperties("spring.jndi.ignore=true") + .withPropertyValues("server.port=0") + .withUserConfiguration(TestConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(MapSessionRepository.class)); + } + + @Configuration(proxyBeanMethods = false) + @EnableSpringHttpSession + @ImportAutoConfiguration({ ServletWebServerFactoryAutoConfiguration.class, SessionAutoConfiguration.class }) + static class TestConfiguration { + + @Bean + MapSessionRepository mapSessionRepository(ConfigurableApplicationContext context) { + Assert.isTrue(context.getBeanFactory().isConfigurationFrozen(), "Context should be frozen"); + return new MapSessionRepository(new LinkedHashMap<>()); + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationJdbcTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationJdbcTests.java index 508e3ad3bf8a..3643eff1176e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationJdbcTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationJdbcTests.java @@ -36,7 +36,7 @@ import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; -import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.boot.web.servlet.AbstractFilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; @@ -100,7 +100,7 @@ void filterOrderCanBeCustomized() { this.contextRunner .withPropertyValues("spring.session.store-type=jdbc", "spring.session.servlet.filter-order=123") .run((context) -> { - FilterRegistrationBean registration = context.getBean(FilterRegistrationBean.class); + AbstractFilterRegistrationBean registration = context.getBean(AbstractFilterRegistrationBean.class); assertThat(registration.getOrder()).isEqualTo(123); }); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationTests.java index 80e8b85db72d..ec4a96f0e1cf 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationTests.java @@ -28,7 +28,7 @@ import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; -import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.boot.web.servlet.AbstractFilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; @@ -41,6 +41,7 @@ import org.springframework.session.web.http.HeaderHttpSessionIdResolver; import org.springframework.session.web.http.HttpSessionIdResolver; import org.springframework.session.web.http.SessionRepositoryFilter; +import org.springframework.web.filter.DelegatingFilterProxy; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -99,8 +100,16 @@ void backOffIfSessionRepositoryIsPresent() { @Test void filterIsRegisteredWithAsyncErrorAndRequestDispatcherTypes() { this.contextRunner.withUserConfiguration(SessionRepositoryConfiguration.class).run((context) -> { - FilterRegistrationBean registration = context.getBean(FilterRegistrationBean.class); - assertThat(registration.getFilter()).isSameAs(context.getBean(SessionRepositoryFilter.class)); + AbstractFilterRegistrationBean registration = context.getBean(AbstractFilterRegistrationBean.class); + DelegatingFilterProxy delegatingFilterProxy = (DelegatingFilterProxy) registration.getFilter(); + try { + // Trigger actual initialization + delegatingFilterProxy.doFilter(null, null, null); + } + catch (Exception ex) { + } + assertThat(delegatingFilterProxy).extracting("delegate") + .isSameAs(context.getBean(SessionRepositoryFilter.class)); assertThat(registration) .extracting("dispatcherTypes", InstanceOfAssertFactories.iterable(DispatcherType.class)) .containsOnly(DispatcherType.ASYNC, DispatcherType.ERROR, DispatcherType.REQUEST); @@ -112,7 +121,7 @@ void filterOrderCanBeCustomizedWithCustomStore() { this.contextRunner.withUserConfiguration(SessionRepositoryConfiguration.class) .withPropertyValues("spring.session.servlet.filter-order=123") .run((context) -> { - FilterRegistrationBean registration = context.getBean(FilterRegistrationBean.class); + AbstractFilterRegistrationBean registration = context.getBean(AbstractFilterRegistrationBean.class); assertThat(registration.getOrder()).isEqualTo(123); }); } @@ -122,7 +131,7 @@ void filterDispatcherTypesCanBeCustomized() { this.contextRunner.withUserConfiguration(SessionRepositoryConfiguration.class) .withPropertyValues("spring.session.servlet.filter-dispatcher-types=error, request") .run((context) -> { - FilterRegistrationBean registration = context.getBean(FilterRegistrationBean.class); + AbstractFilterRegistrationBean registration = context.getBean(AbstractFilterRegistrationBean.class); assertThat(registration) .extracting("dispatcherTypes", InstanceOfAssertFactories.iterable(DispatcherType.class)) .containsOnly(DispatcherType.ERROR, DispatcherType.REQUEST); @@ -134,7 +143,7 @@ void emptyFilterDispatcherTypesDoNotThrowException() { this.contextRunner.withUserConfiguration(SessionRepositoryConfiguration.class) .withPropertyValues("spring.session.servlet.filter-dispatcher-types=") .run((context) -> { - FilterRegistrationBean registration = context.getBean(FilterRegistrationBean.class); + AbstractFilterRegistrationBean registration = context.getBean(AbstractFilterRegistrationBean.class); assertThat(registration) .extracting("dispatcherTypes", InstanceOfAssertFactories.iterable(DispatcherType.class)) .isEmpty(); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java index 59d4d7583d62..ffc3278f6c05 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java @@ -368,7 +368,7 @@ AnotherSampleService anotherSampleService() { static class SampleConfiguration { @Bean - MethodValidationPostProcessor testMethodValidationPostProcessor() { + static MethodValidationPostProcessor testMethodValidationPostProcessor() { return new MethodValidationPostProcessor(); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizerTests.java index 926a654ee382..1fe8acac2d98 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizerTests.java @@ -365,6 +365,8 @@ void disableRemoteIpValve() { } @Test + @Deprecated + @SuppressWarnings("deprecation") void testCustomizeRejectIllegalHeader() { bind("server.tomcat.reject-illegal-header=false"); customizeAndRunServer((server) -> assertThat( diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorConfigurationTests.java index 5e25714e1863..9314605c18bd 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorConfigurationTests.java @@ -24,8 +24,10 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.context.annotation.Bean; +import org.springframework.http.client.reactive.HttpComponentsClientHttpConnector; import org.springframework.http.client.reactive.JettyClientHttpConnector; import org.springframework.http.client.reactive.JettyResourceFactory; import org.springframework.test.util.ReflectionTestUtils; @@ -38,6 +40,7 @@ * * @author Phillip Webb * @author Brian Clozel + * @author Moritz Halbritter */ class ClientHttpConnectorConfigurationTests { @@ -83,6 +86,14 @@ void shouldApplyHttpClientMapper() { }); } + @Test + void shouldNotConfigureReactiveHttpClient5WhenHttpCore5ReactiveJarIsMissing() { + new ReactiveWebApplicationContextRunner() + .withClassLoader(new FilteredClassLoader("org.apache.hc.core5.reactive")) + .withConfiguration(AutoConfigurations.of(ClientHttpConnectorConfiguration.HttpClient5.class)) + .run((context) -> assertThat(context).doesNotHaveBean(HttpComponentsClientHttpConnector.class)); + } + static class CustomHttpClientMapper { static boolean called = false; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/FilterOrderingIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/FilterOrderingIntegrationTests.java index e13e55b4a0a4..1a62aa822a20 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/FilterOrderingIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/FilterOrderingIntegrationTests.java @@ -16,8 +16,6 @@ package org.springframework.boot.autoconfigure.web.servlet; -import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import java.util.concurrent.ConcurrentHashMap; @@ -44,7 +42,7 @@ import org.springframework.security.web.FilterChainProxy; import org.springframework.session.MapSessionRepository; import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession; -import org.springframework.session.web.http.SessionRepositoryFilter; +import org.springframework.web.filter.DelegatingFilterProxy; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; @@ -73,18 +71,19 @@ void testFilterOrdering() { List registeredFilters = this.context.getBean(MockServletWebServerFactory.class) .getWebServer() .getRegisteredFilters(); - List filters = new ArrayList<>(registeredFilters.size()); - for (RegisteredFilter registeredFilter : registeredFilters) { - filters.add(registeredFilter.getFilter()); - } - Iterator iterator = filters.iterator(); - assertThat(iterator.next()).isInstanceOf(OrderedCharacterEncodingFilter.class); - assertThat(iterator.next()).isInstanceOf(SessionRepositoryFilter.class); - assertThat(iterator.next()).isInstanceOf(Filter.class); - assertThat(iterator.next()).isInstanceOf(Filter.class); - assertThat(iterator.next()).isInstanceOf(OrderedRequestContextFilter.class); - assertThat(iterator.next()).isInstanceOf(ErrorPageSecurityFilter.class); - assertThat(iterator.next()).isInstanceOf(FilterChainProxy.class); + assertThat(registeredFilters.get(0).getFilter()).isInstanceOf(OrderedCharacterEncodingFilter.class); + assertThat(registeredFilters.get(1).getFilter()).isInstanceOf(DelegatingFilterProxy.class) + .extracting("targetBeanName") + .isEqualTo("springSessionRepositoryFilter"); + assertThat(registeredFilters.get(2).getFilter()).isInstanceOf(Filter.class) + .extracting("beanName") + .isEqualTo("hiddenHttpMethodFilter"); + assertThat(registeredFilters.get(3).getFilter()).isInstanceOf(Filter.class) + .extracting("beanName") + .isEqualTo("formContentFilter"); + assertThat(registeredFilters.get(4).getFilter()).isInstanceOf(OrderedRequestContextFilter.class); + assertThat(registeredFilters.get(5).getFilter()).isInstanceOf(ErrorPageSecurityFilter.class); + assertThat(registeredFilters.get(6).getFilter()).isInstanceOf(FilterChainProxy.class); } private void load() { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java index c68b98837ee5..613acf51cc2a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java @@ -169,7 +169,7 @@ void handlerAdaptersCreated() { @Test void handlerMappingsCreated() { - this.contextRunner.run((context) -> assertThat(context).getBeans(HandlerMapping.class).hasSize(5)); + this.contextRunner.run((context) -> assertThat(context).getBeans(HandlerMapping.class).hasSize(6)); } @Test @@ -687,8 +687,8 @@ void welcomePageHandlerMappingIsAutoConfigured() { this.contextRunner.withPropertyValues("spring.web.resources.static-locations:classpath:/welcome-page/") .run((context) -> { assertThat(context).hasSingleBean(WelcomePageHandlerMapping.class); - WelcomePageHandlerMapping bean = context.getBean(WelcomePageHandlerMapping.class); - assertThat(bean.getRootHandler()).isNotNull(); + assertThat(context.getBean(WelcomePageHandlerMapping.class).getRootHandler()).isNotNull(); + assertThat(context.getBean(WelcomePageNotAcceptableHandlerMapping.class).getRootHandler()).isNotNull(); }); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageHandlerMappingTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageHandlerMappingTests.java index fbc3f1ea483d..f5c249e6a8e5 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageHandlerMappingTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageHandlerMappingTests.java @@ -116,7 +116,6 @@ void handlesRequestWithEmptyAcceptHeader() { .perform(get("/").header(HttpHeaders.ACCEPT, "")) .andExpect(status().isOk()) .andExpect(forwardedUrl("index.html"))); - } @Test diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageIntegrationTests.java index 6a894afbdb5a..327f29d708a6 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageIntegrationTests.java @@ -29,6 +29,7 @@ import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; @@ -57,6 +58,16 @@ void contentStrategyWithWelcomePage() throws Exception { .build(); ResponseEntity content = this.template.exchange(entity, String.class); assertThat(content.getBody()).contains("/custom-"); + assertThat(content.getStatusCode()).isEqualTo(HttpStatus.OK); + } + + @Test + void notAcceptableWelcomePage() throws Exception { + RequestEntity entity = RequestEntity.get(new URI("http://localhost:" + this.port + "/")) + .header("Accept", "spring/boot") + .build(); + ResponseEntity content = this.template.exchange(entity, String.class); + assertThat(content.getStatusCode()).isEqualTo(HttpStatus.NOT_ACCEPTABLE); } @Configuration diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageNotAcceptableHandlerMappingTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageNotAcceptableHandlerMappingTests.java new file mode 100644 index 000000000000..3011f29b4a90 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WelcomePageNotAcceptableHandlerMappingTests.java @@ -0,0 +1,144 @@ +/* + * Copyright 2012-2023 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.autoconfigure.web.servlet; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Tests for {@link WelcomePageNotAcceptableHandlerMapping}. + * + * @author Phillip Webb + */ +class WelcomePageNotAcceptableHandlerMappingTests { + + private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() + .withUserConfiguration(HandlerMappingConfiguration.class) + .withConfiguration(AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class)); + + @Test + void isOrderedAtLowPriorityButAboveResourceHandlerRegistry() { + this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class).run((context) -> { + WelcomePageNotAcceptableHandlerMapping handler = context + .getBean(WelcomePageNotAcceptableHandlerMapping.class); + ResourceHandlerRegistry registry = new ResourceHandlerRegistry(context, null); + Integer resourceOrder = (Integer) ReflectionTestUtils.getField(registry, "order"); + assertThat(handler.getOrder()).isEqualTo(Ordered.LOWEST_PRECEDENCE - 10); + assertThat(handler.getOrder()).isLessThan(resourceOrder); + }); + } + + @Test + void handlesRequestForStaticPageThatAcceptsTextHtml() { + this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class) + .run((context) -> MockMvcBuilders.webAppContextSetup(context) + .build() + .perform(get("/").accept(MediaType.TEXT_HTML)) + .andExpect(status().isNotAcceptable())); + } + + @Test + void handlesRequestForStaticPagetThatDoesNotAcceptTextHtml() { + this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class) + .run((context) -> MockMvcBuilders.webAppContextSetup(context) + .build() + .perform(get("/").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotAcceptable())); + } + + @Test + void handlesRequestWithNoAcceptHeader() { + this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class) + .run((context) -> MockMvcBuilders.webAppContextSetup(context) + .build() + .perform(get("/")) + .andExpect(status().isNotAcceptable())); + } + + @Test + void handlesRequestWithEmptyAcceptHeader() { + this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class) + .run((context) -> MockMvcBuilders.webAppContextSetup(context) + .build() + .perform(get("/").header(HttpHeaders.ACCEPT, "")) + .andExpect(status().isNotAcceptable())); + } + + @Test + void rootHandlerIsNotRegisteredWhenStaticPathPatternIsNotSlashStarStar() { + this.contextRunner.withUserConfiguration(StaticResourceConfiguration.class) + .withPropertyValues("static-path-pattern=/foo/**") + .run((context) -> assertThat(context.getBean(WelcomePageNotAcceptableHandlerMapping.class).getRootHandler()) + .isNull()); + } + + @Test + void producesNotFoundResponseWhenThereIsNoWelcomePage() { + this.contextRunner.run((context) -> MockMvcBuilders.webAppContextSetup(context) + .build() + .perform(get("/").accept(MediaType.TEXT_HTML)) + .andExpect(status().isNotFound())); + } + + @Configuration(proxyBeanMethods = false) + static class HandlerMappingConfiguration { + + @Bean + WelcomePageNotAcceptableHandlerMapping handlerMapping(ApplicationContext applicationContext, + ObjectProvider templateAvailabilityProviders, + ObjectProvider staticIndexPage, + @Value("${static-path-pattern:/**}") String staticPathPattern) { + return new WelcomePageNotAcceptableHandlerMapping( + templateAvailabilityProviders + .getIfAvailable(() -> new TemplateAvailabilityProviders(applicationContext)), + applicationContext, staticIndexPage.getIfAvailable(), staticPathPattern); + } + + } + + @Configuration(proxyBeanMethods = false) + static class StaticResourceConfiguration { + + @Bean + Resource staticIndexPage() { + return new FileSystemResource("src/test/resources/welcome-page/index.html"); + } + + } + +} diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index b3ff6d72a25b..2c0583655e3a 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -306,7 +306,7 @@ bom { ] } } - library("Elasticsearch", "7.17.9") { + library("Elasticsearch", "7.17.10") { group("org.elasticsearch") { modules = [ "elasticsearch" @@ -452,7 +452,7 @@ bom { ] } } - library("Hazelcast", "5.1.5") { + library("Hazelcast", "5.1.6") { group("com.hazelcast") { modules = [ "hazelcast", @@ -1002,7 +1002,7 @@ bom { ] } } - library("Johnzon", "1.2.19") { + library("Johnzon", "1.2.20") { group("org.apache.johnzon") { modules = [ "johnzon-core", @@ -1050,7 +1050,7 @@ bom { ] } } - library("Json-smart", "2.4.10") { + library("Json-smart", "2.4.11") { group("net.minidev") { modules = [ "json-smart" @@ -1327,7 +1327,7 @@ bom { ] } } - library("Micrometer", "1.9.10") { + library("Micrometer", "1.9.11") { group("io.micrometer") { modules = [ "micrometer-registry-stackdriver" { @@ -1394,7 +1394,7 @@ bom { ] } } - library("Netty", "4.1.91.Final") { + library("Netty", "4.1.92.Final") { group("io.netty") { imports = [ "netty-bom" @@ -1486,7 +1486,7 @@ bom { ] } } - library("Reactor Bom", "2020.0.31") { + library("Reactor Bom", "2020.0.32") { group("io.projectreactor") { imports = [ "reactor-bom" @@ -1739,7 +1739,7 @@ bom { ] } } - library("Spring Data Bom", "2021.2.11") { + library("Spring Data Bom", "2021.2.12") { prohibit { versionRange "[2022.0.0-M1,)" because "it uses Spring Framework 6" @@ -1750,7 +1750,7 @@ bom { ] } } - library("Spring Framework", "5.3.27") { + library("Spring Framework", "${springFrameworkVersion}") { prohibit { versionRange "[6.0.0-M1,)" because "we upgrade in Spring Boot 3.x" @@ -1780,7 +1780,7 @@ bom { ] } } - library("Spring Integration", "5.5.17") { + library("Spring Integration", "5.5.18") { prohibit { versionRange "[6.0.0-M1,)" because "it uses Spring Framework 6" diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/MainMethod.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/MainMethod.java index 5c1c4570fc2f..a2435453783c 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/MainMethod.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/MainMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2023 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. @@ -40,7 +40,9 @@ class MainMethod { } private Method getMainMethod(Thread thread) { - for (StackTraceElement element : thread.getStackTrace()) { + StackTraceElement[] stackTrace = thread.getStackTrace(); + for (int i = stackTrace.length - 1; i >= 0; i--) { + StackTraceElement element = stackTrace[i]; if ("main".equals(element.getMethodName())) { Method method = getMainMethod(element); if (method != null) { diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/AbstractDevToolsDataSourceAutoConfigurationTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/AbstractDevToolsDataSourceAutoConfigurationTests.java index 432298e17e2a..76987fe76e68 100644 --- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/AbstractDevToolsDataSourceAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/AbstractDevToolsDataSourceAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -151,7 +151,7 @@ DataSource dataSourceTwo() { static class DataSourceSpyConfiguration { @Bean - DataSourceSpyBeanPostProcessor dataSourceSpyBeanPostProcessor() { + static DataSourceSpyBeanPostProcessor dataSourceSpyBeanPostProcessor() { return new DataSourceSpyBeanPostProcessor(); } diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/MainMethodTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/MainMethodTests.java index 5fe407a427a0..3995241ab41a 100644 --- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/MainMethodTests.java +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/MainMethodTests.java @@ -56,6 +56,14 @@ void validMainMethod() throws Exception { assertThat(method.getDeclaringClassName()).isEqualTo(this.actualMain.getDeclaringClass().getName()); } + @Test // gh-35214 + void nestedMainMethod() throws Exception { + MainMethod method = new TestThread(Nested::main).test(); + Method nestedMain = Nested.class.getMethod("main", String[].class); + assertThat(method.getMethod()).isEqualTo(nestedMain); + assertThat(method.getDeclaringClassName()).isEqualTo(nestedMain.getDeclaringClass().getName()); + } + @Test void missingArgsMainMethod() { assertThatIllegalStateException().isThrownBy(() -> new TestThread(MissingArgs::main).test()) @@ -114,6 +122,15 @@ private static void someOtherMethod() { } + public static class Nested { + + public static void main(String... args) { + mainMethod.set(new MainMethod()); + Valid.main(args); + } + + } + public static class MissingArgs { public static void main() { diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/application-properties.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/application-properties.adoc index 74adc6cc4152..03f81f56fc64 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/application-properties.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/application-properties.adoc @@ -5,7 +5,7 @@ include::attributes.adoc[] -Various properties can be specified inside your `application.properties` file, inside your `application.yml` file, or as command line switches. +Various properties can be specified inside your `application.properties` file, inside your `application.yaml` file, or as command line switches. This appendix provides a list of common Spring Boot properties and references to the underlying classes that consume them. TIP: Spring Boot provides various conversion mechanism with advanced value formatting, make sure to review <>. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/configuration-metadata.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/configuration-metadata.adoc index 5a5fb7b43544..193fd065b504 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/configuration-metadata.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/configuration-metadata.adoc @@ -6,7 +6,7 @@ include::attributes.adoc[] Spring Boot jars include metadata files that provide details of all supported configuration properties. -The files are designed to let IDE developers offer contextual help and "`code completion`" as users are working with `application.properties` or `application.yml` files. +The files are designed to let IDE developers offer contextual help and "`code completion`" as users are working with `application.properties` or `application.yaml` files. The majority of the metadata file is generated automatically at compile time by processing all items annotated with `@ConfigurationProperties`. However, it is possible to <> for corner cases or more advanced use cases. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/external-config.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/external-config.adoc index ac88cd5d4542..6d8f23bdab53 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/external-config.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/external-config.adoc @@ -35,7 +35,7 @@ Config data files are considered in the following order: . <> outside of your packaged jar (`application-\{profile}.properties` and YAML variants). NOTE: It is recommended to stick with one format for your entire application. -If you have configuration files with both `.properties` and `.yml` format in the same location, `.properties` takes precedence. +If you have configuration files with both `.properties` and YAML format in the same location, `.properties` takes precedence. To provide a concrete example, suppose you develop a `@Component` that uses a `name` property, as shown in the following example: @@ -220,7 +220,7 @@ You cannot use a wildcard in a `classpath:` location. [[features.external-config.files.profile-specific]] ==== Profile Specific Files As well as `application` property files, Spring Boot will also attempt to load profile-specific files using the naming convention `application-\{profile}`. -For example, if your application activates a profile named `prod` and uses YAML files, then both `application.yml` and `application-prod.yml` will be considered. +For example, if your application activates a profile named `prod` and uses YAML files, then both `application.yaml` and `application-prod.yaml` will be considered. Profile-specific properties are loaded from the same locations as standard `application.properties`, with profile-specific files always overriding the non-specific ones. If several profiles are specified, a last-wins strategy applies. @@ -441,7 +441,7 @@ For example, if a secret named `db.password` is mounted at location `/run/secret [[features.external-config.files.property-placeholders]] ==== Property Placeholders -The values in `application.properties` and `application.yml` are filtered through the existing `Environment` when they are used, so you can refer back to previously defined values (for example, from System properties or environment variables). +The values in `application.properties` and `application.yaml` are filtered through the existing `Environment` when they are used, so you can refer back to previously defined values (for example, from System properties or environment variables). The standard `$\{name}` property-placeholder syntax can be used anywhere within a value. Property placeholders can also specify a default value using a `:` to separate the default value from the property name, for example `${name:default}`. @@ -476,7 +476,7 @@ Spring Boot allows you to split a single physical file into multiple logical doc Documents are processed in order, from top to bottom. Later documents can override the properties defined in earlier ones. -For `application.yml` files, the standard YAML multi-document syntax is used. +For `application.yaml` files, the standard YAML multi-document syntax is used. Three consecutive hyphens represent the end of one document, and the start of the next. For example, the following file has two logical documents: @@ -834,13 +834,13 @@ With the preceding code, the following properties names can all be used: | Property | Note | `my.main-project.person.first-name` -| Kebab case, which is recommended for use in `.properties` and `.yml` files. +| Kebab case, which is recommended for use in `.properties` and YAML files. | `my.main-project.person.firstName` | Standard camel case syntax. | `my.main-project.person.first_name` -| Underscore notation, which is an alternative format for use in `.properties` and `.yml` files. +| Underscore notation, which is an alternative format for use in `.properties` and YAML files. | `MY_MAINPROJECT_PERSON_FIRSTNAME` | Upper case format, which is recommended when using system environment variables. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/profiles.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/profiles.adoc index cea00d60231f..f00af692b1ca 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/profiles.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/profiles.adoc @@ -115,5 +115,5 @@ It is also possible to activate profiles by using Spring's `ConfigurableEnvironm [[features.profiles.profile-specific-configuration-files]] === Profile-specific Configuration Files -Profile-specific variants of both `application.properties` (or `application.yml`) and files referenced through `@ConfigurationProperties` are considered as files and loaded. +Profile-specific variants of both `application.properties` (or `application.yaml`) and files referenced through `@ConfigurationProperties` are considered as files and loaded. See "<>" for details. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/build.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/build.adoc index b4dc9b030c4a..c176b8534b25 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/build.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/build.adoc @@ -212,7 +212,7 @@ For example, to indicate that JRuby should be flagged for unpacking by using the [[howto.build.create-a-nonexecutable-jar]] === Create a Non-executable JAR with Exclusions Often, if you have an executable and a non-executable jar as two separate build products, the executable version has additional configuration files that are not needed in a library jar. -For example, the `application.yml` configuration file might be excluded from the non-executable JAR. +For example, the `application.yaml` configuration file might be excluded from the non-executable JAR. In Maven, the executable jar must be the main artifact and you can add a classified jar for the library, as follows: @@ -236,7 +236,7 @@ In Maven, the executable jar must be the main artifact and you can add a classif lib - application.yml + application.yaml diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/properties-and-configuration.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/properties-and-configuration.adoc index 5e3e45622be6..208b3504a2f5 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/properties-and-configuration.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/properties-and-configuration.adoc @@ -146,7 +146,7 @@ You can also provide the following System properties (or environment variables) A separate `Environment` property source is set up for this document and it can be overridden by system properties, environment variables, or the command line. No matter what you set in the environment, Spring Boot always loads `application.properties` as described above. -By default, if YAML is used, then files with the '`.yml`' extension are also added to the list. +By default, if YAML is used, then files with the '`.yaml`' and '`.yml`' extension are also added to the list. TIP: If you want detailed information about the files that are being loaded you can <> of `org.springframework.boot.context.config` to `trace`. @@ -187,7 +187,7 @@ YAML is a superset of JSON and, as such, is a convenient syntax for storing exte port: 9000 ---- -Create a file called `application.yml` and put it in the root of your classpath. +Create a file called `application.yaml` and put it in the root of your classpath. Then add `snakeyaml` to your dependencies (Maven coordinates `org.yaml:snakeyaml`, already included if you use the `spring-boot-starter`). A YAML file is parsed to a Java `Map` (like a JSON object), and Spring Boot flattens the map so that it is one level deep and has period-separated keys, as many people are used to with `Properties` files in Java. @@ -287,7 +287,7 @@ Later values override earlier values. [[howto.properties-and-configuration.discover-build-in-options-for-external-properties]] === Discover Built-in Options for External Properties -Spring Boot binds external properties from `application.properties` (or `.yml` files and other places) into an application at runtime. +Spring Boot binds external properties from `application.properties` (or YAML files and other places) into an application at runtime. There is not (and technically cannot be) an exhaustive list of all supported properties in a single location, because contributions can come from additional jar files on your classpath. A running application with the Actuator features has a `configprops` endpoint that shows all the bound and bindable properties available through `@ConfigurationProperties`. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/webserver.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/webserver.adoc index 9843d9de1d48..11f53e384ddb 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/webserver.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/webserver.adoc @@ -182,7 +182,7 @@ You can configure this behavior by setting the configprop:server.compression.mim [[howto.webserver.configure-ssl]] === Configure SSL -SSL can be configured declaratively by setting the various `+server.ssl.*+` properties, typically in `application.properties` or `application.yml`. +SSL can be configured declaratively by setting the various `+server.ssl.*+` properties, typically in `application.properties` or `application.yaml`. The following example shows setting SSL properties using a Java KeyStore file: [source,yaml,indent=0,subs="verbatim",configprops,configblocks] @@ -280,7 +280,7 @@ As of Undertow 1.4.0+, both `h2` and `h2c` are supported on JDK 8 without any ad [[howto.webserver.configure]] === Configure the Web Server -Generally, you should first consider using one of the many available configuration keys and customize your web server by adding new entries in your `application.properties` or `application.yml` file. +Generally, you should first consider using one of the many available configuration keys and customize your web server by adding new entries in your `application.properties` or `application.yaml` file. See "`<>`"). The `server.{asterisk}` namespace is quite useful here, and it includes namespaces like `server.tomcat.{asterisk}`, `server.jetty.{asterisk}` and others, for server-specific features. See the list of <>. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/hazelcast.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/hazelcast.adoc index baee78ae1178..8b346c9f9f99 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/hazelcast.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/hazelcast.adoc @@ -26,7 +26,7 @@ You could also specify the Hazelcast configuration file to use through configura config: "classpath:config/my-hazelcast.xml" ---- -Otherwise, Spring Boot tries to find the Hazelcast configuration from the default locations: `hazelcast.xml` in the working directory or at the root of the classpath, or a `.yaml`/`.yml` counterpart in the same locations. +Otherwise, Spring Boot tries to find the Hazelcast configuration from the default locations: `hazelcast.xml` in the working directory or at the root of the classpath, or a YAML counterpart in the same locations. We also check if the `hazelcast.config` system property is set. See the https://docs.hazelcast.org/docs/latest/manual/html-single/[Hazelcast documentation] for more details. diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/actuator/endpoints/security/exposeall/MySecurityConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/actuator/endpoints/security/exposeall/MySecurityConfiguration.kt index f73ef895ee89..e795b681b00c 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/actuator/endpoints/security/exposeall/MySecurityConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/actuator/endpoints/security/exposeall/MySecurityConfiguration.kt @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -27,8 +27,9 @@ class MySecurityConfiguration { @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { - http.requestMatcher(EndpointRequest.toAnyEndpoint()).authorizeRequests { - requests -> requests.anyRequest().permitAll() } + http.requestMatcher(EndpointRequest.toAnyEndpoint()).authorizeRequests { requests -> + requests.anyRequest().permitAll() + } return http.build() } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestIntegrationTests.java index e57852a4e0e1..70b486b7ff6a 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestIntegrationTests.java @@ -46,6 +46,7 @@ class DataElasticsearchTestIntegrationTests { @Container static final ElasticsearchContainer elasticsearch = new ElasticsearchContainer(DockerImageNames.elasticsearch()) + .withEnv("ES_JAVA_OPTS", "-Xms32m -Xmx512m") .withStartupAttempts(5) .withStartupTimeout(Duration.ofMinutes(10)); diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestPropertiesIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestPropertiesIntegrationTests.java index 41cd78114180..0020e88b0d44 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestPropertiesIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestPropertiesIntegrationTests.java @@ -44,6 +44,7 @@ class DataElasticsearchTestPropertiesIntegrationTests { @Container static final ElasticsearchContainer elasticsearch = new ElasticsearchContainer(DockerImageNames.elasticsearch()) + .withEnv("ES_JAVA_OPTS", "-Xms32m -Xmx512m") .withStartupAttempts(5) .withStartupTimeout(Duration.ofMinutes(10)); diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestReactiveIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestReactiveIntegrationTests.java index 99e6b7fb69c1..98b061a05f7f 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestReactiveIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestReactiveIntegrationTests.java @@ -43,6 +43,7 @@ class DataElasticsearchTestReactiveIntegrationTests { @Container static final ElasticsearchContainer elasticsearch = new ElasticsearchContainer(DockerImageNames.elasticsearch()) + .withEnv("ES_JAVA_OPTS", "-Xms32m -Xmx512m") .withStartupAttempts(5) .withStartupTimeout(Duration.ofMinutes(10)); diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestWithIncludeFilterIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestWithIncludeFilterIntegrationTests.java index 18ebfef08852..08636af63852 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestWithIncludeFilterIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestWithIncludeFilterIntegrationTests.java @@ -45,6 +45,7 @@ class DataElasticsearchTestWithIncludeFilterIntegrationTests { @Container static final ElasticsearchContainer elasticsearch = new ElasticsearchContainer(DockerImageNames.elasticsearch()) + .withEnv("ES_JAVA_OPTS", "-Xms32m -Xmx512m") .withStartupAttempts(5) .withStartupTimeout(Duration.ofMinutes(10)); diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestIntegrationTests.java index b0e882603a42..bcc170381fd9 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestIntegrationTests.java @@ -19,12 +19,14 @@ import java.time.Duration; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.OS; import org.testcontainers.containers.Neo4jContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.testsupport.junit.DisabledOnOs; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.context.ApplicationContext; import org.springframework.data.neo4j.core.Neo4jTemplate; @@ -43,6 +45,8 @@ */ @DataNeo4jTest @Testcontainers(disabledWithoutDocker = true) +@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", + disabledReason = "The Neo4j image has no ARM support") class DataNeo4jTestIntegrationTests { @Container diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestPropertiesIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestPropertiesIntegrationTests.java index 272402cf5e26..7ddabaae3b5a 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestPropertiesIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestPropertiesIntegrationTests.java @@ -20,11 +20,13 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.OS; import org.testcontainers.containers.Neo4jContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.testsupport.junit.DisabledOnOs; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.core.env.Environment; import org.springframework.test.context.DynamicPropertyRegistry; @@ -40,6 +42,8 @@ */ @Testcontainers(disabledWithoutDocker = true) @DataNeo4jTest(properties = "spring.profiles.active=test") +@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", + disabledReason = "The Neo4j image has no ARM support") class DataNeo4jTestPropertiesIntegrationTests { @Container diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestReactiveIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestReactiveIntegrationTests.java index 64ba554c95ac..5ac5106ae16c 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestReactiveIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestReactiveIntegrationTests.java @@ -19,6 +19,7 @@ import java.time.Duration; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.OS; import org.neo4j.driver.Driver; import org.testcontainers.containers.Neo4jContainer; import org.testcontainers.junit.jupiter.Container; @@ -29,6 +30,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.testsupport.junit.DisabledOnOs; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -52,6 +54,8 @@ @DataNeo4jTest @Transactional(propagation = Propagation.NOT_SUPPORTED) @Testcontainers(disabledWithoutDocker = true) +@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", + disabledReason = "The Neo4j image has no ARM support") class DataNeo4jTestReactiveIntegrationTests { @Container diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestWithIncludeFilterIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestWithIncludeFilterIntegrationTests.java index 85b46d43ae1a..003212a8cb1f 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestWithIncludeFilterIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestWithIncludeFilterIntegrationTests.java @@ -19,11 +19,13 @@ import java.time.Duration; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.OS; import org.testcontainers.containers.Neo4jContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.testsupport.junit.DisabledOnOs; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.stereotype.Service; @@ -40,6 +42,8 @@ */ @Testcontainers(disabledWithoutDocker = true) @DataNeo4jTest(includeFilters = @Filter(Service.class)) +@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", + disabledReason = "The Neo4j image has no ARM support") class DataNeo4jTestWithIncludeFilterIntegrationTests { @Container diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/ResetMocksTestExecutionListener.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/ResetMocksTestExecutionListener.java index d3157918e099..0dc7b55c1199 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/ResetMocksTestExecutionListener.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/ResetMocksTestExecutionListener.java @@ -22,6 +22,8 @@ import org.mockito.Mockito; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @@ -79,7 +81,7 @@ private void resetMocks(ConfigurableApplicationContext applicationContext, MockR BeanDefinition definition = beanFactory.getBeanDefinition(name); if (definition.isSingleton() && instantiatedSingletons.contains(name)) { Object bean = getBean(beanFactory, name); - if (reset.equals(MockReset.get(bean))) { + if (bean != null && reset.equals(MockReset.get(bean))) { Mockito.reset(bean); } } @@ -102,11 +104,25 @@ private void resetMocks(ConfigurableApplicationContext applicationContext, MockR private Object getBean(ConfigurableListableBeanFactory beanFactory, String name) { try { - return beanFactory.getBean(name); + if (isStandardBeanOrSingletonFactoryBean(beanFactory, name)) { + return beanFactory.getBean(name); + } } catch (Exception ex) { - return beanFactory.getSingleton(name); + // Continue + } + return beanFactory.getSingleton(name); + } + + private boolean isStandardBeanOrSingletonFactoryBean(ConfigurableListableBeanFactory beanFactory, String name) { + String factoryBeanName = BeanFactory.FACTORY_BEAN_PREFIX + name; + if (beanFactory.containsBean(factoryBeanName)) { + FactoryBean factoryBean = (FactoryBean) beanFactory.getBean(factoryBeanName); + if (!factoryBean.isSingleton()) { + return false; + } } + return true; } } diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/ResetMocksTestExecutionListenerTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/ResetMocksTestExecutionListenerTests.java index 1c51cedf9443..5735f4a3ffb5 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/ResetMocksTestExecutionListenerTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/ResetMocksTestExecutionListenerTests.java @@ -53,6 +53,7 @@ void test001() { given(getMock("before").greeting()).willReturn("before"); given(getMock("after").greeting()).willReturn("after"); given(getMock("fromFactoryBean").greeting()).willReturn("fromFactoryBean"); + assertThat(this.context.getBean(NonSingletonFactoryBean.class).getObjectInvocations).isEqualTo(0); } @Test @@ -61,6 +62,7 @@ void test002() { assertThat(getMock("before").greeting()).isNull(); assertThat(getMock("after").greeting()).isNull(); assertThat(getMock("fromFactoryBean").greeting()).isNull(); + assertThat(this.context.getBean(NonSingletonFactoryBean.class).getObjectInvocations).isEqualTo(0); } ExampleService getMock(String name) { @@ -109,6 +111,11 @@ WorkingFactoryBean fromFactoryBean() { return new WorkingFactoryBean(); } + @Bean + NonSingletonFactoryBean nonSingletonFactoryBean() { + return new NonSingletonFactoryBean(); + } + } static class BrokenFactoryBean implements FactoryBean { @@ -132,9 +139,11 @@ public boolean isSingleton() { static class WorkingFactoryBean implements FactoryBean { + private final ExampleService service = mock(ExampleService.class, MockReset.before()); + @Override public ExampleService getObject() { - return mock(ExampleService.class, MockReset.before()); + return this.service; } @Override @@ -149,4 +158,26 @@ public boolean isSingleton() { } + static class NonSingletonFactoryBean implements FactoryBean { + + private int getObjectInvocations = 0; + + @Override + public ExampleService getObject() { + this.getObjectInvocations++; + return mock(ExampleService.class, MockReset.before()); + } + + @Override + public Class getObjectType() { + return ExampleService.class; + } + + @Override + public boolean isSingleton() { + return false; + } + + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java index eee724006b96..8073cba7956f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java @@ -33,6 +33,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; @@ -65,6 +66,7 @@ * @author Phillip Webb * @author Scott Frederick * @author Rafael Ceccone + * @author Moritz Halbritter * @since 2.3.0 */ public class DockerApi { @@ -120,16 +122,16 @@ private JsonStream jsonStream() { return this.jsonStream; } - private URI buildUrl(String path, Collection params) { - return buildUrl(path, StringUtils.toStringArray(params)); + private URI buildUrl(String path, Collection params) { + return buildUrl(path, (params != null) ? params.toArray() : null); } - private URI buildUrl(String path, String... params) { + private URI buildUrl(String path, Object... params) { try { URIBuilder builder = new URIBuilder("/" + API_VERSION + path); int param = 0; while (param < params.length) { - builder.addParameter(params[param++], params[param++]); + builder.addParameter(Objects.toString(params[param++]), Objects.toString(params[param++])); } return builder.build(); } @@ -189,7 +191,7 @@ public Image pull(ImageReference reference, UpdateListener throws IOException { Assert.notNull(reference, "Reference must not be null"); Assert.notNull(listener, "Listener must not be null"); - URI createUri = buildUrl("/images/create", "fromImage", reference.toString()); + URI createUri = buildUrl("/images/create", "fromImage", reference); DigestCaptureUpdateListener digestCapture = new DigestCaptureUpdateListener(); listener.onStart(); try { @@ -346,7 +348,10 @@ public Image inspect(ImageReference reference) throws IOException { public void tag(ImageReference sourceReference, ImageReference targetReference) throws IOException { Assert.notNull(sourceReference, "SourceReference must not be null"); Assert.notNull(targetReference, "TargetReference must not be null"); - URI uri = buildUrl("/images/" + sourceReference + "/tag", "repo", targetReference.toString()); + String tag = targetReference.getTag(); + String path = "/images/" + sourceReference + "/tag"; + URI uri = (tag != null) ? buildUrl(path, "repo", targetReference.inTaglessForm(), "tag", tag) + : buildUrl(path, "repo", targetReference); http().post(uri).close(); } @@ -428,7 +433,7 @@ public void start(ContainerReference reference) throws IOException { public void logs(ContainerReference reference, UpdateListener listener) throws IOException { Assert.notNull(reference, "Reference must not be null"); Assert.notNull(listener, "Listener must not be null"); - String[] params = { "stdout", "1", "stderr", "1", "follow", "1" }; + Object[] params = { "stdout", "1", "stderr", "1", "follow", "1" }; URI uri = buildUrl("/containers/" + reference + "/logs", params); listener.onStart(); try { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/ssl/PrivateKeyParser.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/ssl/PrivateKeyParser.java index 002791a88de9..df2e5ce1c25f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/ssl/PrivateKeyParser.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/ssl/PrivateKeyParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -24,6 +24,7 @@ import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.PrivateKey; +import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.util.ArrayList; import java.util.Collections; @@ -39,6 +40,7 @@ * * @author Scott Frederick * @author Phillip Webb + * @author Moritz Halbritter */ final class PrivateKeyParser { @@ -59,9 +61,9 @@ final class PrivateKeyParser { private static final List PEM_PARSERS; static { List parsers = new ArrayList<>(); - parsers.add(new PemParser(PKCS1_HEADER, PKCS1_FOOTER, "RSA", PrivateKeyParser::createKeySpecForPkcs1)); - parsers.add(new PemParser(EC_HEADER, EC_FOOTER, "EC", PrivateKeyParser::createKeySpecForEc)); - parsers.add(new PemParser(PKCS8_HEADER, PKCS8_FOOTER, "RSA", PKCS8EncodedKeySpec::new)); + parsers.add(new PemParser(PKCS1_HEADER, PKCS1_FOOTER, PrivateKeyParser::createKeySpecForPkcs1, "RSA")); + parsers.add(new PemParser(EC_HEADER, EC_FOOTER, PrivateKeyParser::createKeySpecForEc, "EC")); + parsers.add(new PemParser(PKCS8_HEADER, PKCS8_FOOTER, PKCS8EncodedKeySpec::new, "RSA", "EC", "DSA")); PEM_PARSERS = Collections.unmodifiableList(parsers); } @@ -141,14 +143,14 @@ private static class PemParser { private final Pattern pattern; - private final String algorithm; - private final Function keySpecFactory; - PemParser(String header, String footer, String algorithm, - Function keySpecFactory) { + private final String[] algorithms; + + PemParser(String header, String footer, Function keySpecFactory, + String... algorithms) { this.pattern = Pattern.compile(header + BASE64_TEXT + footer, Pattern.CASE_INSENSITIVE); - this.algorithm = algorithm; + this.algorithms = algorithms; this.keySpecFactory = keySpecFactory; } @@ -165,8 +167,15 @@ private static byte[] decodeBase64(String content) { private PrivateKey parse(byte[] bytes) { try { PKCS8EncodedKeySpec keySpec = this.keySpecFactory.apply(bytes); - KeyFactory keyFactory = KeyFactory.getInstance(this.algorithm); - return keyFactory.generatePrivate(keySpec); + for (String algorithm : this.algorithms) { + KeyFactory keyFactory = KeyFactory.getInstance(algorithm); + try { + return keyFactory.generatePrivate(keySpec); + } + catch (InvalidKeySpecException ex) { + } + } + return null; } catch (GeneralSecurityException ex) { throw new IllegalArgumentException("Unexpected key format", ex); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageReference.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageReference.java index c2a3d7aae08d..3f07f53166e1 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageReference.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageReference.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -28,6 +28,7 @@ * * @author Phillip Webb * @author Scott Frederick + * @author Moritz Halbritter * @since 2.3.0 * @see ImageName */ @@ -153,6 +154,17 @@ public ImageReference inTaggedForm() { return new ImageReference(this.name, (this.tag != null) ? this.tag : LATEST, null); } + /** + * Return an {@link ImageReference} without the tag. + * @return the image reference in tagless form + */ + public ImageReference inTaglessForm() { + if (this.tag == null) { + return this; + } + return new ImageReference(this.name, null, this.digest); + } + /** * Return an {@link ImageReference} containing either a tag or a digest. If neither * the digest nor the tag has been defined then tag {@code latest} is used. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiTests.java index 8410e401b7e4..f974129c89f0 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiTests.java @@ -75,6 +75,7 @@ * @author Phillip Webb * @author Scott Frederick * @author Rafael Ceccone + * @author Moritz Halbritter */ @ExtendWith(MockitoExtension.class) class DockerApiTests { @@ -419,7 +420,17 @@ void tagWhenTargetIsNullThrowsException() { void tagTagsImage() throws Exception { ImageReference sourceReference = ImageReference.of("localhost:5000/ubuntu"); ImageReference targetReference = ImageReference.of("localhost:5000/ubuntu:tagged"); - URI tagURI = new URI(IMAGES_URL + "/localhost:5000/ubuntu/tag?repo=localhost%3A5000%2Fubuntu%3Atagged"); + URI tagURI = new URI(IMAGES_URL + "/localhost:5000/ubuntu/tag?repo=localhost%3A5000%2Fubuntu&tag=tagged"); + given(http().post(tagURI)).willReturn(emptyResponse()); + this.api.tag(sourceReference, targetReference); + then(http()).should().post(tagURI); + } + + @Test + void tagRenamesImage() throws Exception { + ImageReference sourceReference = ImageReference.of("localhost:5000/ubuntu"); + ImageReference targetReference = ImageReference.of("localhost:5000/ubuntu-2"); + URI tagURI = new URI(IMAGES_URL + "/localhost:5000/ubuntu/tag?repo=localhost%3A5000%2Fubuntu-2"); given(http().post(tagURI)).willReturn(emptyResponse()); this.api.tag(sourceReference, targetReference); then(http()).should().post(tagURI); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/ssl/PemFileWriter.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/ssl/PemFileWriter.java index 05888b14401f..b40c9c9d1a1b 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/ssl/PemFileWriter.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/ssl/PemFileWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -28,6 +28,7 @@ * Utility to write certificate and key PEM files for testing. * * @author Scott Frederick + * @author Moritz Halbritter */ public class PemFileWriter { @@ -49,22 +50,6 @@ public class PemFileWriter { + "zLEfeu6JPugAR71JYbc2CqGrMneSk1zT91EH6ohIz8OR5VNvzB7N7q65Ci7OFMPl\n" + "ly6k3rHpMCBtHoyNFhNVfPLxGJ9VlWFKLgIAbCmL4OIQm1l6Fr1MSM38Zw==\n" + "-----END TRUSTED CERTIFICATE-----\n"; - public static final String CA_PRIVATE_KEY = EXAMPLE_SECRET_QUALIFIER + "-----BEGIN PRIVATE KEY-----\n" - + "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANWM4sVhKMs63dtA\n" - + "tkdYI8jwqD3xS5RulGE8k9Lbnjcuso7+qmyfJXGYO+tejtyPS7jh4WYfVPnwgwoD\n" - + "34g3ciXwxFvz/nbDjBcjFFz6yT83tsj6Tp71fIieQzPd7nxFYQgssy3WLtD9j94V\n" - + "Nlvk9/yJvQwOxTjwNrxdUqyCdqrVAgMBAAECgYEAyJTlZ8nj3Eg1nLxCue6C5jmN\n" - + "fWkIuanH+zFAE/0utdxJ4WA4yYAOVo1MMr8FZwu9bzHTWe2yDnWnT5/ltPeHYX2X\n" - + "9Pg5cY0tjq07utaMwLKWgJ0Xoh2UpVM799t/rSvMWmLaZ2c8nipX+gQfYJFpX8Vg\n" - + "mR3QPxwdmNyFo13qif0CQQD4z2SqCfARuxscTCJDZ6wReikMQxaJvq74lPEtT26L\n" - + "rBr/bN+mG7+rMEHxs5wtU47aNjUKuVVC0Qfhsf95ahvHAkEA27inSlxrwGvhvFsD\n" - + "FWdgDsfYpPZdL4YgpVSEvcoypRGg2suJw2omcKcY56XpkmWUqZc06QirumtnEC0P\n" - + "HfnsgwJBAMVhEURrOc13FxytsQiz96atuF6H4htH79o3ndQKDXI0B/7VSd6maLjP\n" - + "QaESkTTL8qldE1r8h4zH8m6zHC4fZQUCQFWJ+8bdWC2fUlBr9jVc+26Fqvf92aVo\n" - + "yEjVMKBamYDd7gt/9fAX4UM2KmH0m4wc89VaQoT+lSyMJ6GKiToYVFUCQEXcyoeO\n" - + "zWqtSgEX/eXQXzmMKxYnjv1O//ba3Q7UiHd/XO5j4QXAJpcB6h0h00uC5KY2d0Zy\n" + "JQ1kB1C2l6l9tyc=\n" - + "-----END PRIVATE KEY-----"; - public static final String CERTIFICATE = "-----BEGIN CERTIFICATE-----\n" + "MIICjzCCAfgCAQEwDQYJKoZIhvcNAQEFBQAwgY8xCzAJBgNVBAYTAlVTMRMwEQYD\n" + "VQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMQ0wCwYDVQQK\n" @@ -101,6 +86,74 @@ public class PemFileWriter { + "AwEHoUQDQgAE8y28khug747bA68M90IAMCPHAYyen+RsN6i84LORpNDUhv00QZWd\n" + "hOhjWFCQjnewR98Y8pEb1fnORll4LhHPlQ==\n" + "-----END EC PRIVATE KEY-----"; + public static final String PRIVATE_DSA_KEY = EXAMPLE_SECRET_QUALIFIER + "-----BEGIN PRIVATE KEY-----\n" + + "MIICXAIBADCCAjUGByqGSM44BAEwggIoAoIBAQCPeTXZuarpv6vtiHrPSVG28y7F\n" + + "njuvNxjo6sSWHz79NgbnQ1GpxBgzObgJ58KuHFObp0dbhdARrbi0eYd1SYRpXKwO\n" + + "jxSzNggooi/6JxEKPWKpk0U0CaD+aWxGWPhL3SCBnDcJoBBXsZWtzQAjPbpUhLYp\n" + + "H51kjviDRIZ3l5zsBLQ0pqwudemYXeI9sCkvwRGMn/qdgYHnM423krcw17njSVkv\n" + + "aAmYchU5Feo9a4tGU8YzRY+AOzKkwuDycpAlbk4/ijsIOKHEUOThjBopo33fXqFD\n" + + "3ktm/wSQPtXPFiPhWNSHxgjpfyEc2B3KI8tuOAdl+CLjQr5ITAV2OTlgHNZnAh0A\n" + + "uvaWpoV499/e5/pnyXfHhe8ysjO65YDAvNVpXQKCAQAWplxYIEhQcE51AqOXVwQN\n" + + "NNo6NHjBVNTkpcAtJC7gT5bmHkvQkEq9rI837rHgnzGC0jyQQ8tkL4gAQWDt+coJ\n" + + "syB2p5wypifyRz6Rh5uixOdEvSCBVEy1W4AsNo0fqD7UielOD6BojjJCilx4xHjG\n" + + "jQUntxyaOrsLC+EsRGiWOefTznTbEBplqiuH9kxoJts+xy9LVZmDS7TtsC98kOmk\n" + + "ltOlXVNb6/xF1PYZ9j897buHOSXC8iTgdzEpbaiH7B5HSPh++1/et1SEMWsiMt7l\n" + + "U92vAhErDR8C2jCXMiT+J67ai51LKSLZuovjntnhA6Y8UoELxoi34u1DFuHvF9ve\n" + + "BB4CHHBQgJ3ST6U8rIxoTqGe42TiVckPf1PoSiJy8GY=\n" + "-----END PRIVATE KEY-----\n"; + + public static final String PKCS8_PRIVATE_EC_NIST_P256_KEY = EXAMPLE_SECRET_QUALIFIER + + "-----BEGIN PRIVATE KEY-----\n" + "MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgd6SePFfpaTKFd1Gm\n" + + "+WeHZNkORkot5hx6X9elPdICL9ygCgYIKoZIzj0DAQehRANCAASnMAMgeFBv9ks0\n" + + "d0jP+utQ3mohwmxY93xljfaBofdg1IeHgDd4I4pBzPxEnvXrU3kcz+SgPZyH1ybl\n" + "P6mSXDXu\n" + + "-----END PRIVATE KEY-----\n"; + + public static final String PKCS8_PRIVATE_EC_NIST_P384_KEY = EXAMPLE_SECRET_QUALIFIER + + "-----BEGIN PRIVATE KEY-----\n" + "MIG/AgEAMBAGByqGSM49AgEGBSuBBAAiBIGnMIGkAgEBBDCexXiWKrtrqV1+d1Tv\n" + + "t1n5huuw2A+204mQHRuPL9UC8l0XniJjx/PVELCciyJM/7+gBwYFK4EEACKhZANi\n" + + "AASHEELZSdrHiSXqU1B+/jrOCr6yjxCMqQsetTb0q5WZdCXOhggGXfbzlRynqphQ\n" + + "i4G7azBUklgLaXfxN5eFk6C+E38SYOR7iippcQsSR2ZsCiTk7rnur4b40gQ7IgLA\n" + "/sU=\n" + + "-----END PRIVATE KEY-----\n"; + + public static final String PKCS8_PRIVATE_EC_PRIME256V1_KEY = EXAMPLE_SECRET_QUALIFIER + + "-----BEGIN PRIVATE KEY-----\n" + "MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg4dVuddgQ6enDvPPw\n" + + "Dd1mmS6FMm/kzTJjDVsltrNmRuSgCgYIKoZIzj0DAQehRANCAAR1WMrRADEaVj9m\n" + + "uoUfPhUefJK+lS89NHikQ0ZdkHkybyVKLFMLe1hCynhzpKQmnpgud3E10F0P2PZQ\n" + "L9RCEpGf\n" + + "-----END PRIVATE KEY-----\n"; + + public static final String PKCS8_PRIVATE_EC_SECP256R1_KEY = EXAMPLE_SECRET_QUALIFIER + + "-----BEGIN PRIVATE KEY-----\n" + "MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgU9+v5hUNnTKix8fe\n" + + "Pfz+NfXFlGxQZMReSCT2Id9PfKagCgYIKoZIzj0DAQehRANCAATeJg+YS4BrJ35A\n" + + "KgRlZ59yKLDpmENCMoaYUuWbQ9hqHzdybQGzQsrNJqgH0nzWghPwP4nFaLPN+pgB\n" + "bqiRgbjG\n" + + "-----END PRIVATE KEY-----\n"; + + public static final String PKCS8_PRIVATE_RSA_KEY = EXAMPLE_SECRET_QUALIFIER + "-----BEGIN PRIVATE KEY-----\n" + + "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDR0KfxUw7MF/8R\n" + + "B5/YXOM7yLnoHYb/M/6dyoulMbtEdKKhQhU28o5FiDkHcEG9PJQLgqrRgAjl3VmC\n" + + "C9omtfZJQ2EpfkTttkJjnKOOroXhYE51/CYSckapBYCVh8GkjUEJuEfnp07cTfYZ\n" + + "FqViIgIWPZyjkzl3w4girS7kCuzNdDntVJVx5F/EsFwMA8n3C0QazHQoM5s00Fer\n" + + "6aTwd6AW0JD5QkADavpfzZ554e4HrVGwHlM28WKQQkFzzGu44FFXyVuEF3HeyVPu\n" + + "g8GRHAc8UU7ijVgJB5TmbvRGYowIErD5i4VvGLuOv9mgR3aVyN0SdJ1N7aJnXpeS\n" + + "QjAgf03jAgMBAAECggEBAIhQyzwj3WJGWOZkkLqOpufJotcmj/Wwf0VfOdkq9WMl\n" + + "cB/bAlN/xWVxerPVgDCFch4EWBzi1WUaqbOvJZ2u7QNubmr56aiTmJCFTVI/GyZx\n" + + "XqiTGN01N6lKtN7xo6LYTyAUhUsBTWAemrx0FSErvTVb9C/mUBj6hbEZ2XQ5kN5t\n" + + "7qYX4Lu0zyn7s1kX5SLtm5I+YRq7HSwB6wLy+DSroO71izZ/VPwME3SwT5SN+c87\n" + + "3dkklR7fumNd9dOpSWKrLPnq4aMko00rvIGc63xD1HrEpXUkB5v24YEn7HwCLEH7\n" + + "b8jrp79j2nCvvR47inpf+BR8FIWAHEOUUqCEzjQkdiECgYEA6ifjMM0f02KPeIs7\n" + + "zXd1lI7CUmJmzkcklCIpEbKWf/t/PHv3QgqIkJzERzRaJ8b+GhQ4zrSwAhrGUmI8\n" + + "kDkXIqe2/2ONgIOX2UOHYHyTDQZHnlXyDecvHUTqs2JQZCGBZkXyZ9i0j3BnTymC\n" + + "iZ8DvEa0nxsbP+U3rgzPQmXiQVMCgYEA5WN2Y/RndbriNsNrsHYRldbPO5nfV9rp\n" + + "cDzcQU66HRdK5VIdbXT9tlMYCJIZsSqE0tkOwTgEB/sFvF/tIHSCY5iO6hpIyk6g\n" + + "kkUzPcld4eM0dEPAge7SYUbakB9CMvA7MkDQSXQNFyZ0mH83+UikwT6uYHFh7+ox\n" + + "N1P+psDhXzECgYEA1gXLVQnIcy/9LxMkgDMWV8j8uMyUZysDthpbK3/uq+A2dhRg\n" + + "9g4msPd5OBQT65OpIjElk1n4HpRWfWqpLLHiAZ0GWPynk7W0D7P3gyuaRSdeQs0P\n" + + "x8FtgPVDCN9t13gAjHiWjnC26Py2kNbCKAQeJ/MAmQTvrUFX2VCACJKTcV0CgYAj\n" + + "xJWSUmrLfb+GQISLOG3Xim434e9keJsLyEGj4U29+YLRLTOvfJ2PD3fg5j8hU/rw\n" + + "Ea5uTHi8cdTcIa0M8X3fX8txD3YoLYh2JlouGTcNYOst8d6TpBSj3HN6I5Wj8beZ\n" + + "R2fy/CiKYpGtsbCdq0kdZNO18BgQW9kewncjs1GxEQKBgQCf8q34h6KuHpHSDh9h\n" + + "YkDTypk0FReWBAVJCzDNDUMhVLFivjcwtaMd2LiC3FMKZYodr52iKg60cj43vbYI\n" + + "frmFFxoL37rTmUocCTBKc0LhWj6MicI+rcvQYe1uwTrpWdFf1aZJMYRLRczeKtev\n" + "OWaE/9hVZ5+9pild1NukGpOydw==\n" + + "-----END PRIVATE KEY-----\n"; + private final Path tempDir; public PemFileWriter() throws IOException { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/ssl/PrivateKeyParserTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/ssl/PrivateKeyParserTests.java index 8ba45fa51398..6d1109ad9656 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/ssl/PrivateKeyParserTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/ssl/PrivateKeyParserTests.java @@ -28,6 +28,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.boot.buildpack.platform.docker.ssl.PrivateKeyParser.DerEncoder; @@ -39,6 +41,7 @@ * * @author Scott Frederick * @author Phillip Webb + * @author Moritz Halbritter */ class PrivateKeyParserTests { @@ -55,14 +58,34 @@ void tearDown() throws IOException { } @Test - void parsePkcs8KeyFile() throws IOException { - Path path = this.fileWriter.writeFile("key.pem", PemFileWriter.CA_PRIVATE_KEY); + void parsePkcs8RsaKeyFile() throws IOException { + Path path = this.fileWriter.writeFile("key.pem", PemFileWriter.PKCS8_PRIVATE_RSA_KEY); PrivateKey privateKey = PrivateKeyParser.parse(path); assertThat(privateKey).isNotNull(); assertThat(privateKey.getFormat()).isEqualTo("PKCS#8"); Files.delete(path); } + @ParameterizedTest + @ValueSource(strings = { PemFileWriter.PKCS8_PRIVATE_EC_NIST_P256_KEY, PemFileWriter.PKCS8_PRIVATE_EC_NIST_P384_KEY, + PemFileWriter.PKCS8_PRIVATE_EC_PRIME256V1_KEY, PemFileWriter.PKCS8_PRIVATE_EC_SECP256R1_KEY }) + void parsePkcs8EcKeyFile(String contents) throws IOException { + Path path = this.fileWriter.writeFile("key.pem", contents); + PrivateKey privateKey = PrivateKeyParser.parse(path); + assertThat(privateKey).isNotNull(); + assertThat(privateKey.getFormat()).isEqualTo("PKCS#8"); + assertThat(privateKey.getAlgorithm()).isEqualTo("EC"); + } + + @Test + void parsePkcs8DsaKeyFile() throws IOException { + Path path = this.fileWriter.writeFile("key.pem", PemFileWriter.PRIVATE_DSA_KEY); + PrivateKey privateKey = PrivateKeyParser.parse(path); + assertThat(privateKey).isNotNull(); + assertThat(privateKey.getFormat()).isEqualTo("PKCS#8"); + assertThat(privateKey.getAlgorithm()).isEqualTo("DSA"); + } + @Test void parsePkcs1RsaKeyFile() throws IOException { Path path = this.fileWriter.writeFile("key.pem", PemFileWriter.PRIVATE_RSA_KEY); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageReferenceTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageReferenceTests.java index b5ad72e1ef7d..7c0c7918e827 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageReferenceTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageReferenceTests.java @@ -29,6 +29,7 @@ * * @author Phillip Webb * @author Scott Frederick + * @author Moritz Halbritter */ class ImageReferenceTests { @@ -272,4 +273,29 @@ void equalsAndHashCode() { assertThat(r1).isEqualTo(r1).isEqualTo(r2).isNotEqualTo(r3); } + @Test + void withDigest() { + ImageReference reference = ImageReference.of("docker.io/library/ubuntu:bionic"); + ImageReference updated = reference + .withDigest("sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); + assertThat(updated).hasToString( + "docker.io/library/ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); + } + + @Test + void inTaglessFormWithDigest() { + ImageReference reference = ImageReference + .of("docker.io/library/ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); + ImageReference updated = reference.inTaglessForm(); + assertThat(updated).hasToString( + "docker.io/library/ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); + } + + @Test + void inTaglessForm() { + ImageReference reference = ImageReference.of("docker.io/library/ubuntu:bionic"); + ImageReference updated = reference.inTaglessForm(); + assertThat(updated).hasToString("docker.io/library/ubuntu"); + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java index 7fbcdee652ce..7aacc87d4f7a 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java @@ -36,7 +36,6 @@ import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.TaskOutcome; import org.junit.jupiter.api.TestTemplate; -import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.OS; import org.springframework.boot.buildpack.platform.docker.DockerApi; @@ -47,6 +46,7 @@ import org.springframework.boot.buildpack.platform.io.FilePermissions; import org.springframework.boot.gradle.junit.GradleCompatibility; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; +import org.springframework.boot.testsupport.junit.DisabledOnOs; import org.springframework.boot.testsupport.testcontainers.DisabledIfDockerUnavailable; import static org.assertj.core.api.Assertions.assertThat; @@ -60,6 +60,8 @@ */ @GradleCompatibility(configurationCache = true) @DisabledIfDockerUnavailable +@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", + disabledReason = "The builder image has no ARM support") class BootBuildImageIntegrationTests { GradleBuild gradleBuild; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/src/main/java/org/springframework/boot/testsupport/gradle/testkit/GradleVersions.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/src/main/java/org/springframework/boot/testsupport/gradle/testkit/GradleVersions.java index 0b84fbadc6e9..21a1ff6af390 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/src/main/java/org/springframework/boot/testsupport/gradle/testkit/GradleVersions.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/src/main/java/org/springframework/boot/testsupport/gradle/testkit/GradleVersions.java @@ -34,15 +34,15 @@ private GradleVersions() { public static List allCompatible() { if (isJava18()) { - return Arrays.asList("7.3.3", GradleVersion.current().getVersion(), "8.0.2", "8.1"); + return Arrays.asList("7.3.3", GradleVersion.current().getVersion(), "8.0.2", "8.1.1"); } if (isJava17()) { - return Arrays.asList("7.2", GradleVersion.current().getVersion(), "8.0.2", "8.1"); + return Arrays.asList("7.2", GradleVersion.current().getVersion(), "8.0.2", "8.1.1"); } if (isJava16()) { - return Arrays.asList("7.0.2", GradleVersion.current().getVersion(), "8.0.2", "8.1"); + return Arrays.asList("7.0.2", GradleVersion.current().getVersion(), "8.0.2", "8.1.1"); } - return Arrays.asList("6.8.3", "6.9.4", "7.0.2", GradleVersion.current().getVersion(), "8.0.2", "8.1"); + return Arrays.asList("6.8.3", "6.9.4", "7.0.2", GradleVersion.current().getVersion(), "8.0.2", "8.1.1"); } public static String minimumCompatible() { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/anchor-rewrite.properties b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/anchor-rewrite.properties index af45d43d6838..0cea3c652e57 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/anchor-rewrite.properties +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/anchor-rewrite.properties @@ -1,17 +1,8 @@ - -build-info=? -getting-started=? -goals=? -help=? -spring-boot-maven-plugin-documentation=? -introduction=?.? -integration-tests=integration-tests integration-tests-no-starter-parent=integration-tests.no-starter-parent integration-tests-example=integration-tests.examples integration-tests-example-random-port=integration-tests.examples.random-port integration-tests-example-jmx-port=integration-tests.examples.jmx-port integration-tests-example-skip=integration-tests.examples.skip -build-image=build-image build-image-docker-daemon=build-image.docker-daemon build-image-docker-registry=build-image.docker-registry build-image-customization=build-image.customization @@ -33,14 +24,12 @@ repackage-example-custom-layout=packaging.examples.custom-layout repackage-example-exclude-dependency=packaging.examples.exclude-dependency repackage-layered-archive-tools=packaging.examples.layered-archive-tools repackage-layered-archive-additional-layers=packaging.examples.custom-layers-configuration -run=run run-examples=run.examples run-example-debug=run.examples.debug run-example-system-properties=run.examples.system-properties run-example-environment-variables=run.examples.environment-variables run-example-application-arguments=run.examples.using-application-arguments run-example-active-profiles=run.examples.specify-active-profiles -using=using using-parent-pom=using.parent-pom using-import=using.import using-overriding-command-line=using.overriding-command-line diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/BuildImageTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/BuildImageTests.java index 343e84114ca6..7d6d83a67355 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/BuildImageTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/BuildImageTests.java @@ -25,6 +25,7 @@ import java.util.stream.IntStream; import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.buildpack.platform.docker.DockerApi; @@ -32,6 +33,7 @@ import org.springframework.boot.buildpack.platform.docker.type.ImageName; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.VolumeName; +import org.springframework.boot.testsupport.junit.DisabledOnOs; import org.springframework.boot.testsupport.testcontainers.DisabledIfDockerUnavailable; import static org.assertj.core.api.Assertions.assertThat; @@ -45,6 +47,8 @@ */ @ExtendWith(MavenBuildExtension.class) @DisabledIfDockerUnavailable +@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", + disabledReason = "The builder image has no ARM support") class BuildImageTests extends AbstractArchiveIntegrationTests { @TestTemplate diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractPackagerMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractPackagerMojo.java index 82ff6455e40f..e51f9700884f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractPackagerMojo.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractPackagerMojo.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/CustomLayersProvider.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/CustomLayersProvider.java index 2889520bd400..4bd71c33b7d8 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/CustomLayersProvider.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/CustomLayersProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/junit/DisabledOnOs.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/junit/DisabledOnOs.java index 0b0dd15b6940..c7c2763cb27c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/junit/DisabledOnOs.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/junit/DisabledOnOs.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -37,16 +37,22 @@ public @interface DisabledOnOs { /** - * See {@link org.junit.jupiter.api.condition.DisabledOnOs#value()}. - * @return os + * The operating systems on which the annotated class or method should be disabled. + * @return the operating systems where the test is disabled */ - OS[] os(); + OS[] value() default {}; /** - * Architecture of the operating system. - * @return architecture + * The operating systems on which the annotated class or method should be disabled. + * @return the operating systems where the test is disabled */ - String architecture(); + OS[] os() default {}; + + /** + * The architectures on which the annotated class or method should be disabled. + * @return the architectures where the test is disabled + */ + String[] architecture() default {}; /** * See {@link org.junit.jupiter.api.condition.DisabledOnOs#disabledReason()}. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/junit/DisabledOnOsCondition.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/junit/DisabledOnOsCondition.java index 855bcde7a095..32a08b0e88e8 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/junit/DisabledOnOsCondition.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/junit/DisabledOnOsCondition.java @@ -16,42 +16,49 @@ package org.springframework.boot.testsupport.junit; -import java.util.Optional; +import java.util.Arrays; import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.platform.commons.util.AnnotationUtils; + +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; /** * Evaluates {@link DisabledOnOs}. * * @author Moritz Halbritter + * @author Phillip Webb */ class DisabledOnOsCondition implements ExecutionCondition { @Override public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { - Optional annotation = AnnotationUtils.findAnnotation(context.getElement(), DisabledOnOs.class); + if (!context.getElement().isPresent()) { + return ConditionEvaluationResult.enabled("No element for @DisabledOnOs found"); + } + MergedAnnotation annotation = MergedAnnotations + .from(context.getElement().get(), SearchStrategy.TYPE_HIERARCHY) + .get(DisabledOnOs.class); if (!annotation.isPresent()) { return ConditionEvaluationResult.enabled("No @DisabledOnOs found"); } - return evaluate(annotation.get()); + return evaluate(annotation.synthesize()); } private ConditionEvaluationResult evaluate(DisabledOnOs annotation) { String architecture = System.getProperty("os.arch"); String os = System.getProperty("os.name"); - if (annotation.architecture().equals(architecture)) { - for (OS targetOs : annotation.os()) { - if (targetOs.isCurrentOs()) { - String reason = annotation.disabledReason().isEmpty() - ? String.format("Disabled on OS = %s, architecture = %s", os, architecture) - : annotation.disabledReason(); - return ConditionEvaluationResult.disabled(reason); - } - } + boolean onDisabledOs = Arrays.stream(annotation.os()).anyMatch(OS::isCurrentOs); + boolean onDisabledArchitecture = Arrays.stream(annotation.architecture()).anyMatch(architecture::equals); + if (onDisabledOs && onDisabledArchitecture) { + String reason = annotation.disabledReason().isEmpty() + ? String.format("Disabled on OS = %s, architecture = %s", os, architecture) + : annotation.disabledReason(); + return ConditionEvaluationResult.disabled(reason); } return ConditionEvaluationResult .enabled(String.format("Enabled on OS = %s, architecture = %s", os, architecture)); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/testcontainers/DockerImageNames.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/testcontainers/DockerImageNames.java index 36803822c387..44da467557d9 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/testcontainers/DockerImageNames.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/testcontainers/DockerImageNames.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2023 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. @@ -28,7 +28,7 @@ public final class DockerImageNames { private static final String CASSANDRA_VERSION = "3.11.10"; - private static final String COUCHBASE_VERSION = "6.5.1"; + private static final String COUCHBASE_VERSION = "7.1.4"; private static final String MONGO_VERSION = "4.0.23"; diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ResourceBanner.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ResourceBanner.java index 46108497dfaf..68867dec2c4f 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ResourceBanner.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ResourceBanner.java @@ -19,6 +19,7 @@ import java.io.PrintStream; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -65,7 +66,6 @@ public void printBanner(Environment environment, Class sourceClass, PrintStre try { String banner = StreamUtils.copyToString(this.resource.getInputStream(), environment.getProperty("spring.banner.charset", Charset.class, StandardCharsets.UTF_8)); - for (PropertyResolver resolver : getPropertyResolvers(environment, sourceClass)) { banner = resolver.resolvePlaceholders(banner); } @@ -77,15 +77,46 @@ public void printBanner(Environment environment, Class sourceClass, PrintStre } } + /** + * Return a mutable list of the {@link PropertyResolver} instances that will be used + * to resolve placeholders. + * @param environment the environment + * @param sourceClass the source class + * @return a mutable list of property resolvers + */ protected List getPropertyResolvers(Environment environment, Class sourceClass) { - MutablePropertySources propertySources = new MutablePropertySources(); + MutablePropertySources sources = new MutablePropertySources(); if (environment instanceof ConfigurableEnvironment) { - ((ConfigurableEnvironment) environment).getPropertySources().forEach(propertySources::addLast); + ((ConfigurableEnvironment) environment).getPropertySources().forEach(sources::addLast); } - propertySources.addLast(getTitleSource(sourceClass)); - propertySources.addLast(getAnsiSource()); - propertySources.addLast(getVersionSource(sourceClass)); - return Collections.singletonList(new PropertySourcesPropertyResolver(propertySources)); + sources.addLast(getTitleSource(sourceClass)); + sources.addLast(getAnsiSource()); + sources.addLast(getVersionSource(sourceClass)); + List resolvers = new ArrayList<>(); + resolvers.add(new PropertySourcesPropertyResolver(sources)); + return resolvers; + } + + private MapPropertySource getTitleSource(Class sourceClass) { + String applicationTitle = getApplicationTitle(sourceClass); + Map titleMap = Collections.singletonMap("application.title", + (applicationTitle != null) ? applicationTitle : ""); + return new MapPropertySource("title", titleMap); + } + + /** + * Return the application title that should be used for the source class. By default + * will use {@link Package#getImplementationTitle()}. + * @param sourceClass the source class + * @return the application title + */ + protected String getApplicationTitle(Class sourceClass) { + Package sourcePackage = (sourceClass != null) ? sourceClass.getPackage() : null; + return (sourcePackage != null) ? sourcePackage.getImplementationTitle() : null; + } + + private AnsiPropertySource getAnsiSource() { + return new AnsiPropertySource("ansi", true); } private MapPropertySource getVersionSource(Class sourceClass) { @@ -119,20 +150,4 @@ private String getVersionString(String version, boolean format) { return format ? " (v" + version + ")" : version; } - private AnsiPropertySource getAnsiSource() { - return new AnsiPropertySource("ansi", true); - } - - private MapPropertySource getTitleSource(Class sourceClass) { - String applicationTitle = getApplicationTitle(sourceClass); - Map titleMap = Collections.singletonMap("application.title", - (applicationTitle != null) ? applicationTitle : ""); - return new MapPropertySource("title", titleMap); - } - - protected String getApplicationTitle(Class sourceClass) { - Package sourcePackage = (sourceClass != null) ? sourceClass.getPackage() : null; - return (sourcePackage != null) ? sourcePackage.getImplementationTitle() : null; - } - } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/GracefulShutdown.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/GracefulShutdown.java index 12a37e2c184a..a67158e1632c 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/GracefulShutdown.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/GracefulShutdown.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2023 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. @@ -78,7 +78,7 @@ void abort() { while (!this.shuttingDown) { sleep(50); } - this.shutdownThread.interrupt(); + shutdownThread.interrupt(); } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/error/ErrorAttributeOptions.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/error/ErrorAttributeOptions.java index 707a889be10d..c32f683276e3 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/error/ErrorAttributeOptions.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/error/ErrorAttributeOptions.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2023 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. @@ -75,7 +75,7 @@ public ErrorAttributeOptions including(Include... includes) { */ public ErrorAttributeOptions excluding(Include... excludes) { EnumSet updated = copyIncludes(); - updated.removeAll(Arrays.asList(excludes)); + Arrays.stream(excludes).forEach(updated::remove); return new ErrorAttributeOptions(Collections.unmodifiableSet(updated)); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/PrivateKeyParser.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/PrivateKeyParser.java index 93df27cb1c6c..a95f6e72a410 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/PrivateKeyParser.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/PrivateKeyParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -24,6 +24,7 @@ import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.PrivateKey; +import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.util.ArrayList; import java.util.Collections; @@ -41,6 +42,7 @@ * * @author Scott Frederick * @author Phillip Webb + * @author Moritz Halbritter */ final class PrivateKeyParser { @@ -61,9 +63,9 @@ final class PrivateKeyParser { private static final List PEM_PARSERS; static { List parsers = new ArrayList<>(); - parsers.add(new PemParser(PKCS1_HEADER, PKCS1_FOOTER, "RSA", PrivateKeyParser::createKeySpecForPkcs1)); - parsers.add(new PemParser(EC_HEADER, EC_FOOTER, "EC", PrivateKeyParser::createKeySpecForEc)); - parsers.add(new PemParser(PKCS8_HEADER, PKCS8_FOOTER, "RSA", PKCS8EncodedKeySpec::new)); + parsers.add(new PemParser(PKCS1_HEADER, PKCS1_FOOTER, PrivateKeyParser::createKeySpecForPkcs1, "RSA")); + parsers.add(new PemParser(EC_HEADER, EC_FOOTER, PrivateKeyParser::createKeySpecForEc, "EC")); + parsers.add(new PemParser(PKCS8_HEADER, PKCS8_FOOTER, PKCS8EncodedKeySpec::new, "RSA", "EC", "DSA")); PEM_PARSERS = Collections.unmodifiableList(parsers); } @@ -145,14 +147,14 @@ private static class PemParser { private final Pattern pattern; - private final String algorithm; - private final Function keySpecFactory; - PemParser(String header, String footer, String algorithm, - Function keySpecFactory) { + private final String[] algorithms; + + PemParser(String header, String footer, Function keySpecFactory, + String... algorithms) { this.pattern = Pattern.compile(header + BASE64_TEXT + footer, Pattern.CASE_INSENSITIVE); - this.algorithm = algorithm; + this.algorithms = algorithms; this.keySpecFactory = keySpecFactory; } @@ -169,8 +171,15 @@ private static byte[] decodeBase64(String content) { private PrivateKey parse(byte[] bytes) { try { PKCS8EncodedKeySpec keySpec = this.keySpecFactory.apply(bytes); - KeyFactory keyFactory = KeyFactory.getInstance(this.algorithm); - return keyFactory.generatePrivate(keySpec); + for (String algorithm : this.algorithms) { + KeyFactory keyFactory = KeyFactory.getInstance(algorithm); + try { + return keyFactory.generatePrivate(keySpec); + } + catch (InvalidKeySpecException ex) { + } + } + return null; } catch (GeneralSecurityException ex) { throw new IllegalArgumentException("Unexpected key format", ex); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ResourceBannerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ResourceBannerTests.java index 9960e78c9b88..8ba272032d46 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ResourceBannerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ResourceBannerTests.java @@ -19,6 +19,7 @@ import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.Collections; +import java.util.List; import java.util.Map; import org.junit.jupiter.api.AfterEach; @@ -26,8 +27,11 @@ import org.springframework.boot.ansi.AnsiOutput; import org.springframework.boot.ansi.AnsiOutput.Enabled; +import org.springframework.core.env.AbstractPropertyResolver; import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.PropertyResolver; import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.Resource; import org.springframework.mock.env.MockEnvironment; @@ -136,9 +140,20 @@ void renderWithDefaultValues() { assertThat(banner).startsWith("banner 1 default-b 10.2 1.0"); } + @Test + void renderWithMutation() { + Resource resource = new ByteArrayResource("banner ${foo}".getBytes()); + String banner = printBanner(new MutatingResourceBanner(resource, "1", "2", null)); + assertThat(banner).startsWith("banner bar"); + + } + private String printBanner(Resource resource, String bootVersion, String applicationVersion, String applicationTitle) { - ResourceBanner banner = new MockResourceBanner(resource, bootVersion, applicationVersion, applicationTitle); + return printBanner(new MockResourceBanner(resource, bootVersion, applicationVersion, applicationTitle)); + } + + private String printBanner(ResourceBanner banner) { ConfigurableEnvironment environment = new MockEnvironment(); Map source = Collections.singletonMap("a", "1"); environment.getPropertySources().addLast(new MapPropertySource("map", source)); @@ -179,4 +194,34 @@ protected String getApplicationTitle(Class sourceClass) { } + static class MutatingResourceBanner extends MockResourceBanner { + + MutatingResourceBanner(Resource resource, String bootVersion, String applicationVersion, + String applicationTitle) { + super(resource, bootVersion, applicationVersion, applicationTitle); + } + + @Override + protected List getPropertyResolvers(Environment environment, Class sourceClass) { + List resolvers = super.getPropertyResolvers(environment, sourceClass); + PropertyResolver resolver = new AbstractPropertyResolver() { + + @Override + @SuppressWarnings("unchecked") + public T getProperty(String key, Class targetType) { + return String.class.equals(targetType) ? (T) getPropertyAsRawString(key) : null; + } + + @Override + protected String getPropertyAsRawString(String key) { + return ("foo".equals(key)) ? "bar" : null; + } + + }; + resolvers.add(resolver); + return resolvers; + } + + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/PropertySourcesDeducerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/PropertySourcesDeducerTests.java index 6cb182a36f76..25738886289d 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/PropertySourcesDeducerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/PropertySourcesDeducerTests.java @@ -87,7 +87,7 @@ void getPropertySourcesWhenUnavailableThrowsException() { static class PropertySourcesPlaceholderConfigurerConfiguration { @Bean - PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { + static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer(); MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new TestPropertySource()); @@ -106,12 +106,12 @@ static class EmptyConfiguration { static class MultiplePropertySourcesPlaceholderConfigurerConfiguration { @Bean - PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer1() { + static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer1() { return new PropertySourcesPlaceholderConfigurer(); } @Bean - PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer2() { + static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer2() { return new PropertySourcesPlaceholderConfigurer(); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/YamlPropertySourceLoaderSnakeYaml132Tests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/YamlPropertySourceLoaderSnakeYaml132Tests.java new file mode 100644 index 000000000000..70d5b86ef8cf --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/YamlPropertySourceLoaderSnakeYaml132Tests.java @@ -0,0 +1,29 @@ +/* + * Copyright 2012-2022 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.env; + +import org.springframework.boot.testsupport.classpath.ClassPathOverrides; + +/** + * Tests for {@link YamlPropertySourceLoader} with SnakeYAML 1.33. + * + * @author Andy Wilkinson + */ +@ClassPathOverrides("org.yaml:snakeyaml:1.32") +class YamlPropertySourceLoaderSnakeYaml132Tests extends YamlPropertySourceLoaderTests { + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/YamlPropertySourceLoaderSnakeYaml133Tests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/YamlPropertySourceLoaderSnakeYaml133Tests.java new file mode 100644 index 000000000000..5c796c0ac311 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/YamlPropertySourceLoaderSnakeYaml133Tests.java @@ -0,0 +1,29 @@ +/* + * Copyright 2012-2022 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.env; + +import org.springframework.boot.testsupport.classpath.ClassPathOverrides; + +/** + * Tests for {@link YamlPropertySourceLoader} with SnakeYAML 1.33. + * + * @author Andy Wilkinson + */ +@ClassPathOverrides("org.yaml:snakeyaml:1.33") +class YamlPropertySourceLoaderSnakeYaml133Tests extends YamlPropertySourceLoaderTests { + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/server/PrivateKeyParserTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/server/PrivateKeyParserTests.java index 390fb1b2b68d..926e5ac12b7b 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/server/PrivateKeyParserTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/server/PrivateKeyParserTests.java @@ -19,6 +19,8 @@ import java.security.PrivateKey; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; @@ -27,17 +29,36 @@ * Tests for {@link PrivateKeyParser}. * * @author Scott Frederick + * @author Moritz Halbritter */ class PrivateKeyParserTests { @Test - void parsePkcs8KeyFile() { - PrivateKey privateKey = PrivateKeyParser.parse("classpath:test-key.pem"); + void parsePkcs8RsaKeyFile() { + PrivateKey privateKey = PrivateKeyParser.parse("classpath:ssl/pkcs8/key-rsa.pem"); assertThat(privateKey).isNotNull(); assertThat(privateKey.getFormat()).isEqualTo("PKCS#8"); assertThat(privateKey.getAlgorithm()).isEqualTo("RSA"); } + @ParameterizedTest + @ValueSource(strings = { "key-ec-nist-p256.pem", "key-ec-nist-p384.pem", "key-ec-prime256v1.pem", + "key-ec-secp256r1.pem" }) + void parsePkcs8EcKeyFile(String fileName) { + PrivateKey privateKey = PrivateKeyParser.parse("classpath:ssl/pkcs8/" + fileName); + assertThat(privateKey).isNotNull(); + assertThat(privateKey.getFormat()).isEqualTo("PKCS#8"); + assertThat(privateKey.getAlgorithm()).isEqualTo("EC"); + } + + @Test + void parsePkcs8DsaKeyFile() { + PrivateKey privateKey = PrivateKeyParser.parse("classpath:ssl/pkcs8/key-dsa.pem"); + assertThat(privateKey).isNotNull(); + assertThat(privateKey.getFormat()).isEqualTo("PKCS#8"); + assertThat(privateKey.getAlgorithm()).isEqualTo("DSA"); + } + @Test void parsePkcs8KeyFileWithEcdsa() { PrivateKey privateKey = PrivateKeyParser.parse("classpath:test-ec-key.pem"); diff --git a/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-dsa.pem b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-dsa.pem new file mode 100644 index 000000000000..1aa27e11c20f --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-dsa.pem @@ -0,0 +1,15 @@ +-----BEGIN PRIVATE KEY----- +MIICXAIBADCCAjUGByqGSM44BAEwggIoAoIBAQCPeTXZuarpv6vtiHrPSVG28y7F +njuvNxjo6sSWHz79NgbnQ1GpxBgzObgJ58KuHFObp0dbhdARrbi0eYd1SYRpXKwO +jxSzNggooi/6JxEKPWKpk0U0CaD+aWxGWPhL3SCBnDcJoBBXsZWtzQAjPbpUhLYp +H51kjviDRIZ3l5zsBLQ0pqwudemYXeI9sCkvwRGMn/qdgYHnM423krcw17njSVkv +aAmYchU5Feo9a4tGU8YzRY+AOzKkwuDycpAlbk4/ijsIOKHEUOThjBopo33fXqFD +3ktm/wSQPtXPFiPhWNSHxgjpfyEc2B3KI8tuOAdl+CLjQr5ITAV2OTlgHNZnAh0A +uvaWpoV499/e5/pnyXfHhe8ysjO65YDAvNVpXQKCAQAWplxYIEhQcE51AqOXVwQN +NNo6NHjBVNTkpcAtJC7gT5bmHkvQkEq9rI837rHgnzGC0jyQQ8tkL4gAQWDt+coJ +syB2p5wypifyRz6Rh5uixOdEvSCBVEy1W4AsNo0fqD7UielOD6BojjJCilx4xHjG +jQUntxyaOrsLC+EsRGiWOefTznTbEBplqiuH9kxoJts+xy9LVZmDS7TtsC98kOmk +ltOlXVNb6/xF1PYZ9j897buHOSXC8iTgdzEpbaiH7B5HSPh++1/et1SEMWsiMt7l +U92vAhErDR8C2jCXMiT+J67ai51LKSLZuovjntnhA6Y8UoELxoi34u1DFuHvF9ve +BB4CHHBQgJ3ST6U8rIxoTqGe42TiVckPf1PoSiJy8GY= +-----END PRIVATE KEY----- diff --git a/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-nist-p256.pem b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-nist-p256.pem new file mode 100644 index 000000000000..8cd5d39294cf --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-nist-p256.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgd6SePFfpaTKFd1Gm ++WeHZNkORkot5hx6X9elPdICL9ygCgYIKoZIzj0DAQehRANCAASnMAMgeFBv9ks0 +d0jP+utQ3mohwmxY93xljfaBofdg1IeHgDd4I4pBzPxEnvXrU3kcz+SgPZyH1ybl +P6mSXDXu +-----END PRIVATE KEY----- diff --git a/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-nist-p384.pem b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-nist-p384.pem new file mode 100644 index 000000000000..563b519588b7 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-nist-p384.pem @@ -0,0 +1,7 @@ +-----BEGIN PRIVATE KEY----- +MIG/AgEAMBAGByqGSM49AgEGBSuBBAAiBIGnMIGkAgEBBDCexXiWKrtrqV1+d1Tv +t1n5huuw2A+204mQHRuPL9UC8l0XniJjx/PVELCciyJM/7+gBwYFK4EEACKhZANi +AASHEELZSdrHiSXqU1B+/jrOCr6yjxCMqQsetTb0q5WZdCXOhggGXfbzlRynqphQ +i4G7azBUklgLaXfxN5eFk6C+E38SYOR7iippcQsSR2ZsCiTk7rnur4b40gQ7IgLA +/sU= +-----END PRIVATE KEY----- diff --git a/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-prime256v1.pem b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-prime256v1.pem new file mode 100644 index 000000000000..66c626d622ef --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-prime256v1.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg4dVuddgQ6enDvPPw +Dd1mmS6FMm/kzTJjDVsltrNmRuSgCgYIKoZIzj0DAQehRANCAAR1WMrRADEaVj9m +uoUfPhUefJK+lS89NHikQ0ZdkHkybyVKLFMLe1hCynhzpKQmnpgud3E10F0P2PZQ +L9RCEpGf +-----END PRIVATE KEY----- diff --git a/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-secp256r1.pem b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-secp256r1.pem new file mode 100644 index 000000000000..adffc64637e7 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-secp256r1.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgU9+v5hUNnTKix8fe +Pfz+NfXFlGxQZMReSCT2Id9PfKagCgYIKoZIzj0DAQehRANCAATeJg+YS4BrJ35A +KgRlZ59yKLDpmENCMoaYUuWbQ9hqHzdybQGzQsrNJqgH0nzWghPwP4nFaLPN+pgB +bqiRgbjG +-----END PRIVATE KEY----- diff --git a/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-rsa.pem b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-rsa.pem new file mode 100644 index 000000000000..00d439edc6b0 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-rsa.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDR0KfxUw7MF/8R +B5/YXOM7yLnoHYb/M/6dyoulMbtEdKKhQhU28o5FiDkHcEG9PJQLgqrRgAjl3VmC +C9omtfZJQ2EpfkTttkJjnKOOroXhYE51/CYSckapBYCVh8GkjUEJuEfnp07cTfYZ +FqViIgIWPZyjkzl3w4girS7kCuzNdDntVJVx5F/EsFwMA8n3C0QazHQoM5s00Fer +6aTwd6AW0JD5QkADavpfzZ554e4HrVGwHlM28WKQQkFzzGu44FFXyVuEF3HeyVPu +g8GRHAc8UU7ijVgJB5TmbvRGYowIErD5i4VvGLuOv9mgR3aVyN0SdJ1N7aJnXpeS +QjAgf03jAgMBAAECggEBAIhQyzwj3WJGWOZkkLqOpufJotcmj/Wwf0VfOdkq9WMl +cB/bAlN/xWVxerPVgDCFch4EWBzi1WUaqbOvJZ2u7QNubmr56aiTmJCFTVI/GyZx +XqiTGN01N6lKtN7xo6LYTyAUhUsBTWAemrx0FSErvTVb9C/mUBj6hbEZ2XQ5kN5t +7qYX4Lu0zyn7s1kX5SLtm5I+YRq7HSwB6wLy+DSroO71izZ/VPwME3SwT5SN+c87 +3dkklR7fumNd9dOpSWKrLPnq4aMko00rvIGc63xD1HrEpXUkB5v24YEn7HwCLEH7 +b8jrp79j2nCvvR47inpf+BR8FIWAHEOUUqCEzjQkdiECgYEA6ifjMM0f02KPeIs7 +zXd1lI7CUmJmzkcklCIpEbKWf/t/PHv3QgqIkJzERzRaJ8b+GhQ4zrSwAhrGUmI8 +kDkXIqe2/2ONgIOX2UOHYHyTDQZHnlXyDecvHUTqs2JQZCGBZkXyZ9i0j3BnTymC +iZ8DvEa0nxsbP+U3rgzPQmXiQVMCgYEA5WN2Y/RndbriNsNrsHYRldbPO5nfV9rp +cDzcQU66HRdK5VIdbXT9tlMYCJIZsSqE0tkOwTgEB/sFvF/tIHSCY5iO6hpIyk6g +kkUzPcld4eM0dEPAge7SYUbakB9CMvA7MkDQSXQNFyZ0mH83+UikwT6uYHFh7+ox +N1P+psDhXzECgYEA1gXLVQnIcy/9LxMkgDMWV8j8uMyUZysDthpbK3/uq+A2dhRg +9g4msPd5OBQT65OpIjElk1n4HpRWfWqpLLHiAZ0GWPynk7W0D7P3gyuaRSdeQs0P +x8FtgPVDCN9t13gAjHiWjnC26Py2kNbCKAQeJ/MAmQTvrUFX2VCACJKTcV0CgYAj +xJWSUmrLfb+GQISLOG3Xim434e9keJsLyEGj4U29+YLRLTOvfJ2PD3fg5j8hU/rw +Ea5uTHi8cdTcIa0M8X3fX8txD3YoLYh2JlouGTcNYOst8d6TpBSj3HN6I5Wj8beZ +R2fy/CiKYpGtsbCdq0kdZNO18BgQW9kewncjs1GxEQKBgQCf8q34h6KuHpHSDh9h +YkDTypk0FReWBAVJCzDNDUMhVLFivjcwtaMd2LiC3FMKZYodr52iKg60cj43vbYI +frmFFxoL37rTmUocCTBKc0LhWj6MicI+rcvQYe1uwTrpWdFf1aZJMYRLRczeKtev +OWaE/9hVZ5+9pild1NukGpOydw== +-----END PRIVATE KEY----- diff --git a/spring-boot-system-tests/spring-boot-image-tests/src/systemTest/java/org/springframework/boot/image/paketo/PaketoBuilderTests.java b/spring-boot-system-tests/spring-boot-image-tests/src/systemTest/java/org/springframework/boot/image/paketo/PaketoBuilderTests.java index 86ce995ded65..14131fce1c07 100644 --- a/spring-boot-system-tests/spring-boot-image-tests/src/systemTest/java/org/springframework/boot/image/paketo/PaketoBuilderTests.java +++ b/spring-boot-system-tests/spring-boot-image-tests/src/systemTest/java/org/springframework/boot/image/paketo/PaketoBuilderTests.java @@ -21,7 +21,6 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.function.Consumer; @@ -276,10 +275,16 @@ void plainWarApp() throws Exception { "paketo-buildpacks/spring-boot"); metadata.processOfType("web") .extracting("command", "args") - .containsExactly("bash", Arrays.asList("catalina.sh", "run")); + .satisfiesExactly((command) -> assertThat(command).asString().endsWith("sh"), + (args) -> assertThat(args).asList() + .satisfiesExactly((arg) -> assertThat(arg).asString().endsWith("catalina.sh"), + (arg) -> assertThat(arg).asString().isEqualTo("run"))); metadata.processOfType("tomcat") .extracting("command", "args") - .containsExactly("bash", Arrays.asList("catalina.sh", "run")); + .satisfiesExactly((command) -> assertThat(command).asString().endsWith("sh"), + (args) -> assertThat(args).asList() + .satisfiesExactly((arg) -> assertThat(arg).asString().endsWith("catalina.sh"), + (arg) -> assertThat(arg).asString().isEqualTo("run"))); }); assertImageHasSbomLayer(imageReference, config, "apache-tomcat"); DigestCapturingCondition digest = new DigestCapturingCondition(); diff --git a/spring-boot-tests/spring-boot-integration-tests/spring-boot-launch-script-tests/src/intTest/java/org/springframework/boot/launchscript/JarLaunchScriptIntegrationTests.java b/spring-boot-tests/spring-boot-integration-tests/spring-boot-launch-script-tests/src/intTest/java/org/springframework/boot/launchscript/JarLaunchScriptIntegrationTests.java index c8ad70eea8eb..7a6270b7f423 100644 --- a/spring-boot-tests/spring-boot-integration-tests/spring-boot-launch-script-tests/src/intTest/java/org/springframework/boot/launchscript/JarLaunchScriptIntegrationTests.java +++ b/spring-boot-tests/spring-boot-integration-tests/spring-boot-launch-script-tests/src/intTest/java/org/springframework/boot/launchscript/JarLaunchScriptIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2023 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. @@ -18,9 +18,11 @@ import java.util.List; +import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.boot.testsupport.junit.DisabledOnOs; import org.springframework.boot.testsupport.testcontainers.DisabledIfDockerUnavailable; import static org.assertj.core.api.Assertions.assertThat; @@ -32,6 +34,8 @@ * @author Andy Wilkinson */ @DisabledIfDockerUnavailable +@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", + disabledReason = "The docker images have no ARM support") class JarLaunchScriptIntegrationTests extends AbstractLaunchScriptIntegrationTests { JarLaunchScriptIntegrationTests() { diff --git a/spring-boot-tests/spring-boot-integration-tests/spring-boot-launch-script-tests/src/intTest/java/org/springframework/boot/launchscript/SysVinitLaunchScriptIntegrationTests.java b/spring-boot-tests/spring-boot-integration-tests/spring-boot-launch-script-tests/src/intTest/java/org/springframework/boot/launchscript/SysVinitLaunchScriptIntegrationTests.java index 0275aec3254f..513e3527dc9b 100644 --- a/spring-boot-tests/spring-boot-integration-tests/spring-boot-launch-script-tests/src/intTest/java/org/springframework/boot/launchscript/SysVinitLaunchScriptIntegrationTests.java +++ b/spring-boot-tests/spring-boot-integration-tests/spring-boot-launch-script-tests/src/intTest/java/org/springframework/boot/launchscript/SysVinitLaunchScriptIntegrationTests.java @@ -20,10 +20,12 @@ import java.util.regex.Pattern; import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.springframework.boot.ansi.AnsiColor; +import org.springframework.boot.testsupport.junit.DisabledOnOs; import org.springframework.boot.testsupport.testcontainers.DisabledIfDockerUnavailable; import static org.assertj.core.api.Assertions.assertThat; @@ -36,6 +38,8 @@ * @author Alexey Vinogradov */ @DisabledIfDockerUnavailable +@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", + disabledReason = "The docker images have no ARM support") class SysVinitLaunchScriptIntegrationTests extends AbstractLaunchScriptIntegrationTests { SysVinitLaunchScriptIntegrationTests() { diff --git a/spring-boot-tests/spring-boot-integration-tests/spring-boot-loader-tests/src/intTest/java/org/springframework/boot/loader/LoaderIntegrationTests.java b/spring-boot-tests/spring-boot-integration-tests/spring-boot-loader-tests/src/intTest/java/org/springframework/boot/loader/LoaderIntegrationTests.java index afcdb9975c4a..35ed48e0c326 100644 --- a/spring-boot-tests/spring-boot-integration-tests/spring-boot-loader-tests/src/intTest/java/org/springframework/boot/loader/LoaderIntegrationTests.java +++ b/spring-boot-tests/spring-boot-integration-tests/spring-boot-loader-tests/src/intTest/java/org/springframework/boot/loader/LoaderIntegrationTests.java @@ -23,6 +23,7 @@ import java.util.function.Supplier; import java.util.stream.Stream; +import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.testcontainers.containers.GenericContainer; @@ -33,6 +34,7 @@ import org.testcontainers.utility.MountableFile; import org.springframework.boot.system.JavaVersion; +import org.springframework.boot.testsupport.junit.DisabledOnOs; import org.springframework.boot.testsupport.testcontainers.DisabledIfDockerUnavailable; import org.springframework.util.Assert; @@ -44,6 +46,8 @@ * @author Phillip Webb */ @DisabledIfDockerUnavailable +@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", + disabledReason = "Not all docker images have ARM support") class LoaderIntegrationTests { private final ToStringConsumer output = new ToStringConsumer();