diff --git a/buildSrc/src/main/java/org/springframework/boot/build/ConventionsPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/ConventionsPlugin.java
index 3aef095deca8..e6c02690e727 100644
--- a/buildSrc/src/main/java/org/springframework/boot/build/ConventionsPlugin.java
+++ b/buildSrc/src/main/java/org/springframework/boot/build/ConventionsPlugin.java
@@ -47,7 +47,6 @@ public void apply(Project project) {
new MavenPublishingConventions().apply(project);
new AsciidoctorConventions().apply(project);
new KotlinConventions().apply(project);
- new EclipseConventions().apply(project);
}
}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/EclipseConventions.java b/buildSrc/src/main/java/org/springframework/boot/build/EclipseConventions.java
deleted file mode 100644
index 68258a3bab12..000000000000
--- a/buildSrc/src/main/java/org/springframework/boot/build/EclipseConventions.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2012-2021 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;
-
-import org.gradle.api.Project;
-import org.gradle.api.plugins.JavaBasePlugin;
-import org.gradle.plugins.ide.eclipse.EclipsePlugin;
-import org.gradle.plugins.ide.eclipse.model.EclipseModel;
-
-/**
- * Conventions that are applied in the presence of the {@link JavaBasePlugin} in order to
- * correctly configure Eclipse.
- *
- * @author Phillip Webb
- */
-class EclipseConventions {
-
- void apply(Project project) {
- project.getPlugins().withType(JavaBasePlugin.class, (java) -> {
- project.getPlugins().apply(EclipsePlugin.class);
- EclipseModel eclipse = project.getExtensions().getByType(EclipseModel.class);
- eclipse.getJdt().setJavaRuntimeName("JavaSE-1.8");
- });
- }
-
-}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java b/buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java
index fbf12d8e6dd9..a6007a67dce0 100644
--- a/buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java
+++ b/buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java
@@ -43,6 +43,7 @@
import org.gradle.api.artifacts.DependencySet;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.plugins.JavaPlugin;
+import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.plugins.quality.Checkstyle;
import org.gradle.api.plugins.quality.CheckstyleExtension;
import org.gradle.api.plugins.quality.CheckstylePlugin;
@@ -64,6 +65,7 @@
* plugin is applied:
*
*
+ *
The project is configered with source and target compatibility of 1.8
*
{@link SpringJavaFormatPlugin Spring Java Format}, {@link CheckstylePlugin
* Checkstyle}, {@link TestFailuresPlugin Test Failures}, and {@link TestRetryPlugin Test
* Retry} plugins are applied
@@ -78,11 +80,7 @@
* {@link JavaPlugin} applied
*
{@link JavaCompile}, {@link Javadoc}, and {@link FormatTask} tasks are configured
* to use UTF-8 encoding
- *
{@link JavaCompile} tasks are configured:
- *
- *
to use {@code -parameters}
- *
with source and target compatibility of 1.8
- *
+ *
{@link JavaCompile} tasks are configured to use {@code -parameters}.
*
When building with Java 8, {@link JavaCompile} tasks are also configured to:
*
*
Treat warnings as errors
@@ -116,7 +114,7 @@ void apply(Project project) {
project.getPlugins().withType(JavaBasePlugin.class, (java) -> {
project.getPlugins().apply(TestFailuresPlugin.class);
configureSpringJavaFormat(project);
- configureJavaCompileConventions(project);
+ configureJavaConventions(project);
configureJavadocConventions(project);
configureTestConventions(project);
configureJarManifestConventions(project);
@@ -195,16 +193,22 @@ private void configureJavadocConventions(Project project) {
project.getTasks().withType(Javadoc.class, (javadoc) -> javadoc.getOptions().source("1.8").encoding("UTF-8"));
}
- private void configureJavaCompileConventions(Project project) {
+ private void configureJavaConventions(Project project) {
+ if (!project.hasProperty("toolchainVersion")) {
+ JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class);
+ javaPluginExtension.setSourceCompatibility(JavaVersion.toVersion(SOURCE_AND_TARGET_COMPATIBILITY));
+ }
project.getTasks().withType(JavaCompile.class, (compile) -> {
compile.getOptions().setEncoding("UTF-8");
- compile.setSourceCompatibility(SOURCE_AND_TARGET_COMPATIBILITY);
- compile.setTargetCompatibility(SOURCE_AND_TARGET_COMPATIBILITY);
List args = compile.getOptions().getCompilerArgs();
if (!args.contains("-parameters")) {
args.add("-parameters");
}
- if (buildingWithJava8(project)) {
+ if (project.hasProperty("toolchainVersion")) {
+ compile.setSourceCompatibility(SOURCE_AND_TARGET_COMPATIBILITY);
+ compile.setTargetCompatibility(SOURCE_AND_TARGET_COMPATIBILITY);
+ }
+ else if (buildingWithJava8(project)) {
args.addAll(Arrays.asList("-Werror", "-Xlint:unchecked", "-Xlint:deprecation", "-Xlint:rawtypes",
"-Xlint:varargs"));
}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java
index 95f87bd498ce..3979a0014414 100644
--- a/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -56,10 +56,15 @@
import org.w3c.dom.NodeList;
import org.springframework.boot.build.DeployedPlugin;
+import org.springframework.boot.build.bom.Library.DependencyConstraintsDependencyVersions;
+import org.springframework.boot.build.bom.Library.DependencyLockDependencyVersions;
+import org.springframework.boot.build.bom.Library.DependencyVersions;
import org.springframework.boot.build.bom.Library.Exclusion;
import org.springframework.boot.build.bom.Library.Group;
+import org.springframework.boot.build.bom.Library.LibraryVersion;
import org.springframework.boot.build.bom.Library.Module;
import org.springframework.boot.build.bom.Library.ProhibitedVersion;
+import org.springframework.boot.build.bom.Library.VersionAlignment;
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
import org.springframework.boot.build.mavenplugin.MavenExec;
import org.springframework.util.FileCopyUtils;
@@ -101,11 +106,17 @@ public Upgrade getUpgrade() {
this.upgradeHandler.gitHub.repository, this.upgradeHandler.gitHub.issueLabels));
}
+ public void library(String name, Closure> closure) {
+ this.library(name, null, closure);
+ }
+
public void library(String name, String version, Closure> closure) {
- LibraryHandler libraryHandler = new LibraryHandler();
+ LibraryHandler libraryHandler = new LibraryHandler(version);
ConfigureUtil.configure(closure, libraryHandler);
- addLibrary(new Library(name, DependencyVersion.parse(version), libraryHandler.groups,
- libraryHandler.prohibitedVersions));
+ LibraryVersion libraryVersion = new LibraryVersion(DependencyVersion.parse(libraryHandler.version),
+ libraryHandler.versionAlignment);
+ addLibrary(new Library(name, libraryVersion, libraryHandler.groups, libraryHandler.prohibitedVersions,
+ libraryHandler.dependencyVersions));
}
public void effectiveBomArtifact() {
@@ -171,17 +182,18 @@ private void addLibrary(Library library) {
this.libraries.add(library);
String versionProperty = library.getVersionProperty();
if (versionProperty != null) {
- this.properties.put(versionProperty, library.getVersion());
+ this.properties.put(versionProperty, library.getVersion().getVersion());
}
for (Group group : library.getGroups()) {
for (Module module : group.getModules()) {
putArtifactVersionProperty(group.getId(), module.getName(), versionProperty);
this.dependencyHandler.getConstraints().add(JavaPlatformPlugin.API_CONFIGURATION_NAME,
- createDependencyNotation(group.getId(), module.getName(), library.getVersion()));
+ createDependencyNotation(group.getId(), module.getName(), library.getVersion().getVersion()));
}
for (String bomImport : group.getBoms()) {
putArtifactVersionProperty(group.getId(), bomImport, versionProperty);
- String bomDependency = createDependencyNotation(group.getId(), bomImport, library.getVersion());
+ String bomDependency = createDependencyNotation(group.getId(), bomImport,
+ library.getVersion().getVersion());
this.dependencyHandler.add(JavaPlatformPlugin.API_CONFIGURATION_NAME,
this.dependencyHandler.platform(bomDependency));
this.dependencyHandler.add(BomPlugin.API_ENFORCED_CONFIGURATION_NAME,
@@ -196,6 +208,23 @@ public static class LibraryHandler {
private final List prohibitedVersions = new ArrayList<>();
+ private String version;
+
+ private VersionAlignment versionAlignment;
+
+ private DependencyVersions dependencyVersions;
+
+ public LibraryHandler(String version) {
+ this.version = version;
+ }
+
+ public void version(String version, Closure> closure) {
+ this.version = version;
+ VersionHandler versionHandler = new VersionHandler();
+ ConfigureUtil.configure(closure, versionHandler);
+ this.versionAlignment = new VersionAlignment(versionHandler.libraryName);
+ }
+
public void group(String id, Closure> closure) {
GroupHandler groupHandler = new GroupHandler(id);
ConfigureUtil.configure(closure, groupHandler);
@@ -215,6 +244,21 @@ public void prohibit(String range, Closure> closure) {
}
}
+ public void dependencyVersions(Closure> closure) {
+ DependencyVersionsHandler dependencyVersionsHandler = new DependencyVersionsHandler();
+ ConfigureUtil.configure(closure, dependencyVersionsHandler);
+ }
+
+ public static class VersionHandler {
+
+ private String libraryName;
+
+ public void shouldAlignWithVersionFrom(String libraryName) {
+ this.libraryName = libraryName;
+ }
+
+ }
+
public static class ProhibitedVersionHandler {
private String reason;
@@ -277,6 +321,29 @@ public void exclude(Map exclusion) {
}
+ public class DependencyVersionsHandler {
+
+ public void extractFrom(Closure> closure) {
+ ExtractFromHandler extractFromHandler = new ExtractFromHandler();
+ ConfigureUtil.configure(closure, extractFromHandler);
+ }
+
+ public class ExtractFromHandler {
+
+ public void dependencyLock(String location) {
+ LibraryHandler.this.dependencyVersions = new DependencyLockDependencyVersions(location,
+ LibraryHandler.this.version);
+ }
+
+ public void dependencyConstraints(String location) {
+ LibraryHandler.this.dependencyVersions = new DependencyConstraintsDependencyVersions(location,
+ LibraryHandler.this.version);
+ }
+
+ }
+
+ }
+
}
public static class UpgradeHandler {
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/BomPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomPlugin.java
index a8a961f01d66..4bf1d5b2ee81 100644
--- a/buildSrc/src/main/java/org/springframework/boot/build/bom/BomPlugin.java
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomPlugin.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -170,7 +170,7 @@ private void addPluginManagement(Node projectNode) {
plugin.appendNode("artifactId", pluginName);
String versionProperty = library.getVersionProperty();
String value = (versionProperty != null) ? "${" + versionProperty + "}"
- : library.getVersion().toString();
+ : library.getVersion().getVersion().toString();
plugin.appendNode("version", value);
}
}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckBom.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckBom.java
index 876fe8eadab4..410aebb09d38 100644
--- a/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckBom.java
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckBom.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -50,7 +50,7 @@ void checkBom() {
for (Group group : library.getGroups()) {
for (Module module : group.getModules()) {
if (!module.getExclusions().isEmpty()) {
- checkExclusions(group.getId(), module, library.getVersion());
+ checkExclusions(group.getId(), module, library.getVersion().getVersion());
}
}
}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/Library.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/Library.java
index 80b510e2ad49..abb0fb371f2c 100644
--- a/buildSrc/src/main/java/org/springframework/boot/build/bom/Library.java
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/Library.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -16,11 +16,20 @@
package org.springframework.boot.build.bom;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URI;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import org.apache.maven.artifact.versioning.VersionRange;
+import org.gradle.api.GradleException;
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
@@ -34,7 +43,7 @@ public class Library {
private final String name;
- private final DependencyVersion version;
+ private final LibraryVersion version;
private final List groups;
@@ -42,6 +51,8 @@ public class Library {
private final List prohibitedVersions;
+ private final DependencyVersions dependencyVersions;
+
/**
* Create a new {@code Library} with the given {@code name}, {@code version}, and
* {@code groups}.
@@ -49,22 +60,24 @@ public class Library {
* @param version version of the library
* @param groups groups in the library
* @param prohibitedVersions version of the library that are prohibited
+ * @param dependencyVersions the library's dependency versions
*/
- public Library(String name, DependencyVersion version, List groups,
- List prohibitedVersions) {
+ public Library(String name, LibraryVersion version, List groups, List prohibitedVersions,
+ DependencyVersions dependencyVersions) {
this.name = name;
this.version = version;
this.groups = groups;
this.versionProperty = "Spring Boot".equals(name) ? null
: name.toLowerCase(Locale.ENGLISH).replace(' ', '-') + ".version";
this.prohibitedVersions = prohibitedVersions;
+ this.dependencyVersions = dependencyVersions;
}
public String getName() {
return this.name;
}
- public DependencyVersion getVersion() {
+ public LibraryVersion getVersion() {
return this.version;
}
@@ -80,6 +93,10 @@ public List getProhibitedVersions() {
return this.prohibitedVersions;
}
+ public DependencyVersions getDependencyVersions() {
+ return this.dependencyVersions;
+ }
+
/**
* A version or range of versions that are prohibited from being used in a bom.
*/
@@ -104,6 +121,27 @@ public String getReason() {
}
+ public static class LibraryVersion {
+
+ private final DependencyVersion version;
+
+ private final VersionAlignment versionAlignment;
+
+ public LibraryVersion(DependencyVersion version, VersionAlignment versionAlignment) {
+ this.version = version;
+ this.versionAlignment = versionAlignment;
+ }
+
+ public DependencyVersion getVersion() {
+ return this.version;
+ }
+
+ public VersionAlignment getVersionAlignment() {
+ return this.versionAlignment;
+ }
+
+ }
+
/**
* A collection of modules, Maven plugins, and Maven boms with the same group ID.
*/
@@ -194,4 +232,128 @@ public String getArtifactId() {
}
+ public interface DependencyVersions {
+
+ String getVersion(String groupId, String artifactId);
+
+ default boolean available() {
+ return true;
+ }
+
+ }
+
+ public static class DependencyLockDependencyVersions implements DependencyVersions {
+
+ private final Map> dependencyVersions = new HashMap<>();
+
+ private final String sourceTemplate;
+
+ private final String libraryVersion;
+
+ public DependencyLockDependencyVersions(String sourceTemplate, String libraryVersion) {
+ this.sourceTemplate = sourceTemplate;
+ this.libraryVersion = libraryVersion;
+ }
+
+ @Override
+ public boolean available() {
+ return !this.libraryVersion.contains("-SNAPSHOT");
+ }
+
+ @Override
+ public String getVersion(String groupId, String artifactId) {
+ if (this.dependencyVersions.isEmpty()) {
+ loadVersions();
+ }
+ return this.dependencyVersions.computeIfAbsent(groupId, (key) -> Collections.emptyMap()).get(artifactId);
+ }
+
+ private void loadVersions() {
+ String source = this.sourceTemplate.replace("", this.libraryVersion);
+ try {
+ try (BufferedReader reader = new BufferedReader(
+ new InputStreamReader(URI.create(source).toURL().openStream()))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (!line.startsWith("#")) {
+ String[] components = line.split(":");
+ Map groupDependencies = this.dependencyVersions
+ .computeIfAbsent(components[0], (key) -> new HashMap<>());
+ groupDependencies.put(components[1], components[2]);
+ }
+ }
+ }
+ }
+ catch (IOException ex) {
+ throw new GradleException("Failed to load versions from dependency lock file '" + source + "'", ex);
+ }
+ }
+
+ }
+
+ public static class DependencyConstraintsDependencyVersions implements DependencyVersions {
+
+ private static final Pattern CONSTRAINT_PATTERN = Pattern.compile("api \"(.+):(.+):(.+)\"");
+
+ private final Map> dependencyVersions = new HashMap<>();
+
+ private final String sourceTemplate;
+
+ private final String libraryVersion;
+
+ public DependencyConstraintsDependencyVersions(String sourceTemplate, String libraryVersion) {
+ this.sourceTemplate = sourceTemplate;
+ this.libraryVersion = libraryVersion;
+ }
+
+ @Override
+ public String getVersion(String groupId, String artifactId) {
+ if (this.dependencyVersions.isEmpty()) {
+ loadVersions();
+ }
+ return this.dependencyVersions.computeIfAbsent(groupId, (key) -> Collections.emptyMap()).get(artifactId);
+ }
+
+ private void loadVersions() {
+ String version = this.libraryVersion;
+ if (version.endsWith("-SNAPSHOT")) {
+ version = version.substring(0, version.lastIndexOf('.')) + ".x";
+ }
+ String source = this.sourceTemplate.replace("", version);
+ try {
+ try (BufferedReader reader = new BufferedReader(
+ new InputStreamReader(URI.create(source).toURL().openStream()))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ Matcher matcher = CONSTRAINT_PATTERN.matcher(line.trim());
+ if (matcher.matches()) {
+ Map groupDependencies = this.dependencyVersions
+ .computeIfAbsent(matcher.group(1), (key) -> new HashMap<>());
+ groupDependencies.put(matcher.group(2), matcher.group(3));
+ }
+ }
+ }
+ }
+ catch (IOException ex) {
+ throw new GradleException(
+ "Failed to load versions from dependency constraints declared in '" + source + "'", ex);
+ }
+ }
+
+ }
+
+ public static class VersionAlignment {
+
+ private final String libraryName;
+
+ public VersionAlignment(String libraryName) {
+ this.libraryName = libraryName;
+ }
+
+ public String getLibraryName() {
+ return this.libraryName;
+ }
+
+ }
+
}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/InteractiveUpgradeResolver.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/InteractiveUpgradeResolver.java
index ff71a617cd12..d56525412b64 100644
--- a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/InteractiveUpgradeResolver.java
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/InteractiveUpgradeResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -19,19 +19,25 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.SortedSet;
import java.util.stream.Collectors;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
+import org.gradle.api.InvalidUserDataException;
import org.gradle.api.internal.tasks.userinput.UserInputHandler;
import org.springframework.boot.build.bom.Library;
+import org.springframework.boot.build.bom.Library.DependencyVersions;
import org.springframework.boot.build.bom.Library.Group;
import org.springframework.boot.build.bom.Library.Module;
import org.springframework.boot.build.bom.Library.ProhibitedVersion;
+import org.springframework.boot.build.bom.Library.VersionAlignment;
import org.springframework.boot.build.bom.UpgradePolicy;
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
import org.springframework.util.StringUtils;
@@ -59,39 +65,103 @@ public final class InteractiveUpgradeResolver implements UpgradeResolver {
@Override
public List resolveUpgrades(Collection libraries) {
- return libraries.stream().map(this::resolveUpgrade).filter((upgrade) -> upgrade != null)
- .collect(Collectors.toList());
+ Map librariesByName = new HashMap<>();
+ for (Library library : libraries) {
+ librariesByName.put(library.getName(), library);
+ }
+ return libraries.stream().map((library) -> resolveUpgrade(library, librariesByName))
+ .filter((upgrade) -> upgrade != null).collect(Collectors.toList());
+ }
+
+ private Upgrade resolveUpgrade(Library library, Map libraries) {
+ List versionOptions = getVersionOptions(library, libraries);
+ if (versionOptions.isEmpty()) {
+ return null;
+ }
+ VersionOption current = new VersionOption(library.getVersion().getVersion());
+ VersionOption selected = this.userInputHandler
+ .selectOption(library.getName() + " " + library.getVersion().getVersion(), versionOptions, current);
+ return (selected.equals(current)) ? null : new Upgrade(library, selected.version);
+ }
+
+ private List getVersionOptions(Library library, Map libraries) {
+ if (library.getVersion().getVersionAlignment() != null) {
+ return determineAlignedVersionOption(library, libraries);
+ }
+ return determineResolvedVersionOptions(library);
}
- private Upgrade resolveUpgrade(Library library) {
+ private List determineResolvedVersionOptions(Library library) {
Map> moduleVersions = new LinkedHashMap<>();
+ DependencyVersion libraryVersion = library.getVersion().getVersion();
for (Group group : library.getGroups()) {
for (Module module : group.getModules()) {
moduleVersions.put(group.getId() + ":" + module.getName(),
- getLaterVersionsForModule(group.getId(), module.getName(), library.getVersion()));
+ getLaterVersionsForModule(group.getId(), module.getName(), libraryVersion));
}
for (String bom : group.getBoms()) {
moduleVersions.put(group.getId() + ":" + bom,
- getLaterVersionsForModule(group.getId(), bom, library.getVersion()));
+ getLaterVersionsForModule(group.getId(), bom, libraryVersion));
}
for (String plugin : group.getPlugins()) {
moduleVersions.put(group.getId() + ":" + plugin,
- getLaterVersionsForModule(group.getId(), plugin, library.getVersion()));
+ getLaterVersionsForModule(group.getId(), plugin, libraryVersion));
}
}
List allVersions = moduleVersions.values().stream().flatMap(SortedSet::stream).distinct()
.filter((dependencyVersion) -> isPermitted(dependencyVersion, library.getProhibitedVersions()))
.collect(Collectors.toList());
if (allVersions.isEmpty()) {
- return null;
+ return Collections.emptyList();
}
- List versionOptions = allVersions.stream()
- .map((version) -> new VersionOption(version, getMissingModules(moduleVersions, version)))
+ return allVersions.stream()
+ .map((version) -> new ResolvedVersionOption(version, getMissingModules(moduleVersions, version)))
.collect(Collectors.toList());
- VersionOption current = new VersionOption(library.getVersion(), Collections.emptyList());
- VersionOption selected = this.userInputHandler.selectOption(library.getName() + " " + library.getVersion(),
- versionOptions, current);
- return (selected.equals(current)) ? null : new Upgrade(library, selected.version);
+ }
+
+ private List determineAlignedVersionOption(Library library, Map libraries) {
+ VersionOption alignedVersionOption = alignedVersionOption(library, libraries);
+ if (alignedVersionOption == null) {
+ return Collections.emptyList();
+ }
+ if (!isPermitted(alignedVersionOption.version, library.getProhibitedVersions())) {
+ throw new InvalidUserDataException("Version alignment failed. Version " + alignedVersionOption.version
+ + " from " + library.getName() + " is prohibited");
+ }
+ return Collections.singletonList(alignedVersionOption);
+ }
+
+ private VersionOption alignedVersionOption(Library library, Map libraries) {
+ VersionAlignment versionAlignment = library.getVersion().getVersionAlignment();
+ Library alignmentLibrary = libraries.get(versionAlignment.getLibraryName());
+ DependencyVersions dependencyVersions = alignmentLibrary.getDependencyVersions();
+ if (dependencyVersions == null) {
+ throw new InvalidUserDataException("Cannot align with library '" + versionAlignment.getLibraryName()
+ + "' as it does not define any dependency versions");
+ }
+ if (!dependencyVersions.available()) {
+ return null;
+ }
+ Set versions = new HashSet<>();
+ for (Group group : library.getGroups()) {
+ for (Module module : group.getModules()) {
+ String version = dependencyVersions.getVersion(group.getId(), module.getName());
+ if (version != null) {
+ versions.add(version);
+ }
+ }
+ }
+ if (versions.isEmpty()) {
+ throw new InvalidUserDataException("Cannot align with library '" + versionAlignment.getLibraryName()
+ + "' as its dependency versions do not include any of this library's modules");
+ }
+ if (versions.size() > 1) {
+ throw new InvalidUserDataException("Cannot align with library '" + versionAlignment.getLibraryName()
+ + "' as it uses multiple different versions of this library's modules");
+ }
+ DependencyVersion version = DependencyVersion.parse(versions.iterator().next());
+ return library.getVersion().getVersion().equals(version) ? null
+ : new AlignedVersionOption(version, alignmentLibrary);
}
private boolean isPermitted(DependencyVersion dependencyVersion, List prohibitedVersions) {
@@ -125,23 +195,53 @@ private SortedSet getLaterVersionsForModule(String groupId, S
return versions;
}
- private static final class VersionOption {
+ private static class VersionOption {
private final DependencyVersion version;
+ protected VersionOption(DependencyVersion version) {
+ this.version = version;
+ }
+
+ @Override
+ public String toString() {
+ return this.version.toString();
+ }
+
+ }
+
+ private static final class AlignedVersionOption extends VersionOption {
+
+ private final Library alignedWith;
+
+ private AlignedVersionOption(DependencyVersion version, Library alignedWith) {
+ super(version);
+ this.alignedWith = alignedWith;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + " (aligned with " + this.alignedWith.getName() + " "
+ + this.alignedWith.getVersion().getVersion() + ")";
+ }
+
+ }
+
+ private static final class ResolvedVersionOption extends VersionOption {
+
private final List missingModules;
- private VersionOption(DependencyVersion version, List missingModules) {
- this.version = version;
+ private ResolvedVersionOption(DependencyVersion version, List missingModules) {
+ super(version);
this.missingModules = missingModules;
}
@Override
public String toString() {
if (this.missingModules.isEmpty()) {
- return this.version.toString();
+ return super.toString();
}
- return this.version + " (some modules are missing: "
+ return super.toString() + " (some modules are missing: "
+ StringUtils.collectionToDelimitedString(this.missingModules, ", ") + ")";
}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeApplicator.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeApplicator.java
index 2f47151f183a..1588ed7b5bd9 100644
--- a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeApplicator.java
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeApplicator.java
@@ -46,8 +46,14 @@ Path apply(Upgrade upgrade) throws IOException {
Matcher matcher = Pattern.compile("library\\(\"" + upgrade.getLibrary().getName() + "\", \"(.+)\"\\)")
.matcher(buildFileContents);
if (!matcher.find()) {
- throw new IllegalStateException("Failed to find definition for library '" + upgrade.getLibrary().getName()
- + "' in bom '" + this.buildFile + "'");
+ matcher = Pattern
+ .compile("library\\(\"" + upgrade.getLibrary().getName() + "\"\\) \\{\\s+version\\(\"(.+)\"\\)",
+ Pattern.MULTILINE)
+ .matcher(buildFileContents);
+ if (!matcher.find()) {
+ throw new IllegalStateException("Failed to find definition for library '"
+ + upgrade.getLibrary().getName() + "' in bom '" + this.buildFile + "'");
+ }
}
String version = matcher.group(1);
if (version.startsWith("${") && version.endsWith("}")) {
@@ -55,7 +61,7 @@ Path apply(Upgrade upgrade) throws IOException {
return this.gradleProperties;
}
else {
- updateBuildFile(upgrade, buildFileContents);
+ updateBuildFile(upgrade, buildFileContents, matcher.start(1), matcher.end(1));
return this.buildFile;
}
}
@@ -63,15 +69,15 @@ Path apply(Upgrade upgrade) throws IOException {
private void updateGradleProperties(Upgrade upgrade, String version) throws IOException {
String property = version.substring(2, version.length() - 1);
String gradlePropertiesContents = new String(Files.readAllBytes(this.gradleProperties), StandardCharsets.UTF_8);
- String modified = gradlePropertiesContents.replace(property + "=" + upgrade.getLibrary().getVersion(),
- property + "=" + upgrade.getVersion());
+ String modified = gradlePropertiesContents.replace(
+ property + "=" + upgrade.getLibrary().getVersion().getVersion(), property + "=" + upgrade.getVersion());
overwrite(this.gradleProperties, modified);
}
- private void updateBuildFile(Upgrade upgrade, String buildFileContents) throws IOException {
- String modified = buildFileContents.replace(
- "library(\"" + upgrade.getLibrary().getName() + "\", \"" + upgrade.getLibrary().getVersion() + "\")",
- "library(\"" + upgrade.getLibrary().getName() + "\", \"" + upgrade.getVersion() + "\")");
+ private void updateBuildFile(Upgrade upgrade, String buildFileContents, int versionStart, int versionEnd)
+ throws IOException {
+ String modified = buildFileContents.substring(0, versionStart) + upgrade.getVersion()
+ + buildFileContents.substring(versionEnd);
overwrite(this.buildFile, modified);
}
diff --git a/buildSrc/src/test/java/org/springframework/boot/build/bom/BomPluginIntegrationTests.java b/buildSrc/src/test/java/org/springframework/boot/build/bom/BomPluginIntegrationTests.java
index 42f96bb074d7..268744ee5eae 100644
--- a/buildSrc/src/test/java/org/springframework/boot/build/bom/BomPluginIntegrationTests.java
+++ b/buildSrc/src/test/java/org/springframework/boot/build/bom/BomPluginIntegrationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -197,6 +197,34 @@ void libraryNamedSpringBootHasNoVersionProperty() throws IOException {
});
}
+ // @Test
+ // void versionAlignmentIsVerified() throws IOException {
+ // try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) {
+ // out.println("plugins {");
+ // out.println(" id 'org.springframework.boot.bom'");
+ // out.println("}");
+ // out.println("bom {");
+ // out.println(" library('OAuth2 OIDC SDK', '8.36.1') {");
+ // out.println(" alignedWith('Spring Security') {");
+ // out.println(
+ // "
+ // source('https://github.com/spring-projects/spring-security/blob/${libraryVersion}/config/gradle/dependency-locks/optional.lockfile')");
+ // out.println(" pattern('com.nimbusds:oauth2-oidc-sdk:(.+)')");
+ // out.println(" }");
+ // out.println(" group('com.nimbusds') {");
+ // out.println(" modules = [");
+ // out.println(" 'oauth2-oidc-sdk'");
+ // out.println(" ]");
+ // out.println(" }");
+ // out.println(" }");
+ // out.println(" library('Spring Security', '5.4.7') {");
+ // out.println(" }");
+ // out.println("}");
+ // }
+ // System.out.println(runGradle(DeployedPlugin.GENERATE_POM_TASK_NAME,
+ // "-s").getOutput());
+ // }
+
private BuildResult runGradle(String... args) {
return GradleRunner.create().withDebug(true).withProjectDir(this.projectDir).withArguments(args)
.withPluginClasspath().build();
diff --git a/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/UpgradeApplicatorTests.java b/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/UpgradeApplicatorTests.java
index aef6b32ff523..c1c2c9c85d5e 100644
--- a/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/UpgradeApplicatorTests.java
+++ b/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/UpgradeApplicatorTests.java
@@ -28,6 +28,7 @@
import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.build.bom.Library;
+import org.springframework.boot.build.bom.Library.LibraryVersion;
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
import org.springframework.util.FileCopyUtils;
@@ -51,13 +52,28 @@ void whenUpgradeIsAppliedToLibraryWithVersionThenBomIsUpdated() throws IOExcepti
String originalContents = new String(Files.readAllBytes(bom.toPath()), StandardCharsets.UTF_8);
File gradleProperties = new File(this.temp, "gradle.properties");
FileCopyUtils.copy(new File("src/test/resources/gradle.properties"), gradleProperties);
- new UpgradeApplicator(bom.toPath(), gradleProperties.toPath())
- .apply(new Upgrade(new Library("ActiveMQ", DependencyVersion.parse("5.15.11"), null, null),
- DependencyVersion.parse("5.16")));
+ new UpgradeApplicator(bom.toPath(), gradleProperties.toPath()).apply(new Upgrade(
+ new Library("ActiveMQ", new LibraryVersion(DependencyVersion.parse("5.15.11"), null), null, null, null),
+ DependencyVersion.parse("5.16")));
String bomContents = new String(Files.readAllBytes(bom.toPath()), StandardCharsets.UTF_8);
assertThat(bomContents.length()).isEqualTo(originalContents.length() - 3);
}
+ @Test
+ void whenUpgradeIsAppliedToLibraryWithAlignedVersionThenBomIsUpdated() throws IOException {
+ File bom = new File(this.temp, "bom.gradle");
+ FileCopyUtils.copy(new File("src/test/resources/bom.gradle"), bom);
+ String originalContents = new String(Files.readAllBytes(bom.toPath()), StandardCharsets.UTF_8);
+ File gradleProperties = new File(this.temp, "gradle.properties");
+ FileCopyUtils.copy(new File("src/test/resources/gradle.properties"), gradleProperties);
+ new UpgradeApplicator(bom.toPath(), gradleProperties.toPath()).apply(
+ new Upgrade(new Library("OAuth2 OIDC SDK", new LibraryVersion(DependencyVersion.parse("8.36.1"), null),
+ null, null, null), DependencyVersion.parse("8.36.2")));
+ String bomContents = new String(Files.readAllBytes(bom.toPath()), StandardCharsets.UTF_8);
+ assertThat(bomContents.length()).isEqualTo(originalContents.length());
+ assertThat(bomContents).contains("version(\"8.36.2\")");
+ }
+
@Test
void whenUpgradeIsAppliedToLibraryWithVersionPropertyThenGradlePropertiesIsUpdated() throws IOException {
File bom = new File(this.temp, "bom.gradle");
@@ -65,7 +81,8 @@ void whenUpgradeIsAppliedToLibraryWithVersionPropertyThenGradlePropertiesIsUpdat
File gradleProperties = new File(this.temp, "gradle.properties");
FileCopyUtils.copy(new File("src/test/resources/gradle.properties"), gradleProperties);
new UpgradeApplicator(bom.toPath(), gradleProperties.toPath()).apply(new Upgrade(
- new Library("Kotlin", DependencyVersion.parse("1.3.70"), null, null), DependencyVersion.parse("1.4")));
+ new Library("Kotlin", new LibraryVersion(DependencyVersion.parse("1.3.70"), null), null, null, null),
+ DependencyVersion.parse("1.4")));
Properties properties = new Properties();
try (InputStream in = new FileInputStream(gradleProperties)) {
properties.load(in);
diff --git a/buildSrc/src/test/resources/bom.gradle b/buildSrc/src/test/resources/bom.gradle
index 1d36dc42dc7c..03d7288b1553 100644
--- a/buildSrc/src/test/resources/bom.gradle
+++ b/buildSrc/src/test/resources/bom.gradle
@@ -48,4 +48,14 @@ bom {
]
}
}
+ library("OAuth2 OIDC SDK") {
+ version("8.36.1") {
+ shouldAlignWithVersionFrom("Spring Security")
+ }
+ group("com.nimbusds") {
+ modules = [
+ "oauth2-oidc-sdk"
+ ]
+ }
+ }
}
diff --git a/ci/images/ci-image-jdk11/Dockerfile b/ci/images/ci-image-jdk11/Dockerfile
index 34466ebf5387..ce7452ce5f03 100644
--- a/ci/images/ci-image-jdk11/Dockerfile
+++ b/ci/images/ci-image-jdk11/Dockerfile
@@ -1,4 +1,4 @@
-FROM ubuntu:focal-20210416
+FROM ubuntu:focal-20210609
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
diff --git a/ci/images/ci-image-jdk16/Dockerfile b/ci/images/ci-image-jdk16/Dockerfile
index 379573286030..d3d12fd41512 100644
--- a/ci/images/ci-image-jdk16/Dockerfile
+++ b/ci/images/ci-image-jdk16/Dockerfile
@@ -1,4 +1,4 @@
-FROM ubuntu:focal-20210416
+FROM ubuntu:focal-20210609
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 1ffa630a5bff..ec2b2fa2856c 100644
--- a/ci/images/ci-image/Dockerfile
+++ b/ci/images/ci-image/Dockerfile
@@ -1,4 +1,4 @@
-FROM ubuntu:focal-20210416
+FROM ubuntu:focal-20210609
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
diff --git a/ci/images/releasescripts/src/main/java/io/spring/concourse/releasescripts/artifactory/ArtifactoryService.java b/ci/images/releasescripts/src/main/java/io/spring/concourse/releasescripts/artifactory/ArtifactoryService.java
index 7668c3b183c2..d01ba77b9538 100644
--- a/ci/images/releasescripts/src/main/java/io/spring/concourse/releasescripts/artifactory/ArtifactoryService.java
+++ b/ci/images/releasescripts/src/main/java/io/spring/concourse/releasescripts/artifactory/ArtifactoryService.java
@@ -93,7 +93,7 @@ public void promote(String targetRepo, ReleaseInfo releaseInfo) {
private boolean isAlreadyPromoted(String buildName, String buildNumber, String targetRepo) {
try {
- logger.debug("Checking if alreay promoted");
+ logger.debug("Checking if already promoted");
ResponseEntity entity = this.restTemplate
.getForEntity(BUILD_INFO_URL + buildName + "/" + buildNumber, BuildInfoResponse.class);
BuildInfoResponse.Status status = entity.getBody().getBuildInfo().getStatuses()[0];
diff --git a/ci/images/releasescripts/src/main/java/io/spring/concourse/releasescripts/command/PublishToCentralCommand.java b/ci/images/releasescripts/src/main/java/io/spring/concourse/releasescripts/command/PublishToCentralCommand.java
index 23ec5a319d37..2dccc1876e48 100644
--- a/ci/images/releasescripts/src/main/java/io/spring/concourse/releasescripts/command/PublishToCentralCommand.java
+++ b/ci/images/releasescripts/src/main/java/io/spring/concourse/releasescripts/command/PublishToCentralCommand.java
@@ -72,8 +72,9 @@ public void run(ApplicationArguments args) throws Exception {
byte[] content = Files.readAllBytes(new File(buildInfoLocation).toPath());
BuildInfoResponse buildInfoResponse = this.objectMapper.readValue(content, BuildInfoResponse.class);
BuildInfo buildInfo = buildInfoResponse.getBuildInfo();
+ ReleaseInfo releaseInfo = ReleaseInfo.from(buildInfo);
String artifactsLocation = nonOptionArgs.get(3);
- this.sonatype.publish(ReleaseInfo.from(buildInfo), new File(artifactsLocation).toPath());
+ this.sonatype.publish(releaseInfo, new File(artifactsLocation).toPath());
}
}
diff --git a/ci/images/releasescripts/src/main/java/io/spring/concourse/releasescripts/sonatype/SonatypeService.java b/ci/images/releasescripts/src/main/java/io/spring/concourse/releasescripts/sonatype/SonatypeService.java
index 1f17dd12609e..da92b0c207b8 100644
--- a/ci/images/releasescripts/src/main/java/io/spring/concourse/releasescripts/sonatype/SonatypeService.java
+++ b/ci/images/releasescripts/src/main/java/io/spring/concourse/releasescripts/sonatype/SonatypeService.java
@@ -88,27 +88,6 @@ public SonatypeService(RestTemplateBuilder builder, SonatypeProperties sonatypeP
this.artifactCollector = new ArtifactCollector(sonatypeProperties.getExclude());
}
- /**
- * Checks if artifacts are already published to Maven Central.
- * @return true if artifacts are published
- * @param releaseInfo the release information
- */
- public boolean artifactsPublished(ReleaseInfo releaseInfo) {
- try {
- ResponseEntity> entity = this.restTemplate
- .getForEntity(String.format(NEXUS_REPOSITORY_PATH + "%s/spring-boot-%s.jar.sha1",
- releaseInfo.getVersion(), releaseInfo.getVersion()), byte[].class);
- if (HttpStatus.OK.equals(entity.getStatusCode())) {
- logger.info("Already published to Sonatype.");
- return true;
- }
- }
- catch (HttpClientErrorException ex) {
-
- }
- return false;
- }
-
/**
* Publishes the release by creating a staging repository and deploying to it the
* artifacts at the given {@code artifactsRoot}. The repository is then closed and,
@@ -117,6 +96,9 @@ public boolean artifactsPublished(ReleaseInfo releaseInfo) {
* @param artifactsRoot the root directory of the artifacts to stage
*/
public void publish(ReleaseInfo releaseInfo, Path artifactsRoot) {
+ if (artifactsPublished(releaseInfo)) {
+ return;
+ }
logger.info("Creating staging repository");
String buildId = releaseInfo.getBuildNumber();
String repositoryId = createStagingRepository(buildId);
@@ -130,6 +112,22 @@ public void publish(ReleaseInfo releaseInfo, Path artifactsRoot) {
logger.info("Staging repository released");
}
+ private boolean artifactsPublished(ReleaseInfo releaseInfo) {
+ try {
+ ResponseEntity> entity = this.restTemplate
+ .getForEntity(String.format(NEXUS_REPOSITORY_PATH + "%s/spring-boot-%s.jar.sha1",
+ releaseInfo.getVersion(), releaseInfo.getVersion()), byte[].class);
+ if (HttpStatus.OK.equals(entity.getStatusCode())) {
+ logger.info("Already published to Sonatype.");
+ return true;
+ }
+ }
+ catch (HttpClientErrorException ex) {
+
+ }
+ return false;
+ }
+
private String createStagingRepository(String buildId) {
Map body = new HashMap<>();
body.put("data", Collections.singletonMap("description", buildId));
diff --git a/ci/images/releasescripts/src/test/java/io/spring/concourse/releasescripts/sonatype/SonatypeServiceTests.java b/ci/images/releasescripts/src/test/java/io/spring/concourse/releasescripts/sonatype/SonatypeServiceTests.java
index c8bd5e8e7dcd..6c9133c7c677 100644
--- a/ci/images/releasescripts/src/test/java/io/spring/concourse/releasescripts/sonatype/SonatypeServiceTests.java
+++ b/ci/images/releasescripts/src/test/java/io/spring/concourse/releasescripts/sonatype/SonatypeServiceTests.java
@@ -74,29 +74,23 @@ void tearDown() {
}
@Test
- void artifactsPublishedWhenPublishedShouldReturnTrue() {
+ void publishWhenAlreadyPublishedShouldNotPublish() {
this.server.expect(requestTo(String.format(
"/service/local/repositories/releases/content/org/springframework/boot/spring-boot/%s/spring-boot-%s.jar.sha1",
"1.1.0.RELEASE", "1.1.0.RELEASE"))).andExpect(method(HttpMethod.GET))
.andRespond(withSuccess().body("ce8d8b6838ecceb68962b9150b18682f4237ccf71".getBytes()));
- boolean published = this.service.artifactsPublished(getReleaseInfo());
- assertThat(published).isTrue();
+ Path artifactsRoot = new File("src/test/resources/io/spring/concourse/releasescripts/sonatype/artifactory-repo")
+ .toPath();
+ this.service.publish(getReleaseInfo(), artifactsRoot);
this.server.verify();
}
@Test
- void artifactsPublishedWhenNotPublishedShouldReturnFalse() {
+ void publishWithSuccessfulClose() throws IOException {
this.server.expect(requestTo(String.format(
"/service/local/repositories/releases/content/org/springframework/boot/spring-boot/%s/spring-boot-%s.jar.sha1",
"1.1.0.RELEASE", "1.1.0.RELEASE"))).andExpect(method(HttpMethod.GET))
.andRespond(withStatus(HttpStatus.NOT_FOUND));
- boolean published = this.service.artifactsPublished(getReleaseInfo());
- assertThat(published).isFalse();
- this.server.verify();
- }
-
- @Test
- void publishWithSuccessfulClose() throws IOException {
this.server.expect(requestTo("/service/local/staging/profiles/1a2b3c4d/start"))
.andExpect(method(HttpMethod.POST)).andExpect(header("Content-Type", "application/json"))
.andExpect(header("Accept", "application/json, application/*+json"))
@@ -145,6 +139,10 @@ void publishWithSuccessfulClose() throws IOException {
@Test
void publishWithCloseFailureDueToRuleViolations() throws IOException {
+ this.server.expect(requestTo(String.format(
+ "/service/local/repositories/releases/content/org/springframework/boot/spring-boot/%s/spring-boot-%s.jar.sha1",
+ "1.1.0.RELEASE", "1.1.0.RELEASE"))).andExpect(method(HttpMethod.GET))
+ .andRespond(withStatus(HttpStatus.NOT_FOUND));
this.server.expect(requestTo("/service/local/staging/profiles/1a2b3c4d/start"))
.andExpect(method(HttpMethod.POST)).andExpect(header("Content-Type", "application/json"))
.andExpect(header("Accept", "application/json, application/*+json"))
diff --git a/ci/parameters.yml b/ci/parameters.yml
index 827292f87723..6e2424434023 100644
--- a/ci/parameters.yml
+++ b/ci/parameters.yml
@@ -6,7 +6,7 @@ github-repo-name: "spring-projects/spring-boot"
homebrew-tap-repo: "https://github.com/spring-io/homebrew-tap.git"
docker-hub-organization: "springci"
artifactory-server: "https://repo.spring.io"
-branch: "main"
+branch: "2.5.x"
milestone: "2.5.x"
build-name: "spring-boot"
concourse-url: "https://ci.spring.io"
diff --git a/ci/pipeline.yml b/ci/pipeline.yml
index 4792e7bbc3fe..bf6ada8033c8 100644
--- a/ci/pipeline.yml
+++ b/ci/pipeline.yml
@@ -464,6 +464,8 @@ jobs:
params:
<<: *artifactory-repo-put-params
repo: libs-staging-local
+ get_params:
+ threads: 8
- put: git-repo
params:
repository: stage-git-repo
@@ -484,6 +486,8 @@ jobs:
params:
<<: *artifactory-repo-put-params
repo: libs-staging-local
+ get_params:
+ threads: 8
- put: git-repo
params:
repository: stage-git-repo
@@ -504,6 +508,8 @@ jobs:
params:
<<: *artifactory-repo-put-params
repo: libs-staging-local
+ get_params:
+ threads: 8
- put: git-repo
params:
repository: stage-git-repo
@@ -577,6 +583,7 @@ jobs:
params:
download_artifacts: true
save_build_info: true
+ threads: 8
- task: promote
image: ci-image
file: git-repo/ci/tasks/promote.yml
diff --git a/gradle.properties b/gradle.properties
index b790600ac1d9..620d83a71b1d 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,10 +1,10 @@
-version=2.5.1-SNAPSHOT
+version=2.5.2
org.gradle.caching=true
org.gradle.parallel=true
org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8
-kotlinVersion=1.5.10
-tomcatVersion=9.0.46
+kotlinVersion=1.5.20
+tomcatVersion=9.0.48
kotlin.stdlib.default.dependency=false
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/index.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/index.adoc
index c93694a76a77..0168b94a4a6b 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/index.adoc
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/index.adoc
@@ -1,6 +1,8 @@
[[spring-boot-actuator-web-api-documentation]]
= Spring Boot Actuator Web API Documentation
-Andy Wilkinson, Stephane Nicoll
+Andy Wilkinson; Stephane Nicoll
+v{gradle-project-version}
+:!version-label:
:doctype: book
:toc: left
:toclevels: 4
diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/MetricsClientHttpRequestInterceptor.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/MetricsClientHttpRequestInterceptor.java
index a5e4a540dbfe..8da5d69a4847 100644
--- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/MetricsClientHttpRequestInterceptor.java
+++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/MetricsClientHttpRequestInterceptor.java
@@ -77,7 +77,7 @@ class MetricsClientHttpRequestInterceptor implements ClientHttpRequestIntercepto
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
- if (!this.autoTimer.isEnabled()) {
+ if (!enabled()) {
return execution.execute(request, body);
}
long startTime = System.nanoTime();
@@ -100,6 +100,10 @@ public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttp
}
}
+ private boolean enabled() {
+ return this.autoTimer.isEnabled();
+ }
+
UriTemplateHandler createUriTemplateHandler(UriTemplateHandler delegate) {
if (delegate instanceof RootUriTemplateHandler) {
return ((RootUriTemplateHandler) delegate).withHandlerWrapper(CapturingUriTemplateHandler::new);
@@ -113,7 +117,7 @@ private Timer.Builder getTimeBuilder(HttpRequest request, ClientHttpResponse res
.description("Timer of RestTemplate operation");
}
- private static final class CapturingUriTemplateHandler implements UriTemplateHandler {
+ private final class CapturingUriTemplateHandler implements UriTemplateHandler {
private final UriTemplateHandler delegate;
@@ -123,13 +127,17 @@ private CapturingUriTemplateHandler(UriTemplateHandler delegate) {
@Override
public URI expand(String url, Map arguments) {
- urlTemplate.get().push(url);
+ if (enabled()) {
+ urlTemplate.get().push(url);
+ }
return this.delegate.expand(url, arguments);
}
@Override
public URI expand(String url, Object... arguments) {
- urlTemplate.get().push(url);
+ if (enabled()) {
+ urlTemplate.get().push(url);
+ }
return this.delegate.expand(url, arguments);
}
diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/client/MetricsRestTemplateCustomizerTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/client/MetricsRestTemplateCustomizerTests.java
index 693dfa2a4f7b..00c88bbe8a43 100644
--- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/client/MetricsRestTemplateCustomizerTests.java
+++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/client/MetricsRestTemplateCustomizerTests.java
@@ -19,10 +19,12 @@
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
+import java.util.concurrent.atomic.AtomicBoolean;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.MockClock;
import io.micrometer.core.instrument.Tag;
+import io.micrometer.core.instrument.Timer.Builder;
import io.micrometer.core.instrument.simple.SimpleConfig;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.assertj.core.api.InstanceOfAssertFactories;
@@ -153,6 +155,39 @@ void whenCustomizerAndLocalHostUriTemplateHandlerAreUsedTogetherThenRestTemplate
.extracting(RootUriTemplateHandler::getRootUri).isEqualTo("https://localhost:8443");
}
+ @Test
+ void whenAutoTimingIsDisabledUriTemplateHandlerDoesNotCaptureUris() {
+ AtomicBoolean enabled = new AtomicBoolean(false);
+ AutoTimer autoTimer = new AutoTimer() {
+
+ @Override
+ public boolean isEnabled() {
+ return enabled.get();
+ }
+
+ @Override
+ public void apply(Builder builder) {
+ }
+
+ };
+ RestTemplate restTemplate = new RestTemplateBuilder(new MetricsRestTemplateCustomizer(this.registry,
+ new DefaultRestTemplateExchangeTagsProvider(), "http.client.requests", autoTimer)).build();
+ MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);
+ mockServer.expect(MockRestRequestMatchers.requestTo("/first/123"))
+ .andExpect(MockRestRequestMatchers.method(HttpMethod.GET))
+ .andRespond(MockRestResponseCreators.withSuccess("OK", MediaType.APPLICATION_JSON));
+ mockServer.expect(MockRestRequestMatchers.requestTo("/second/456"))
+ .andExpect(MockRestRequestMatchers.method(HttpMethod.GET))
+ .andRespond(MockRestResponseCreators.withSuccess("OK", MediaType.APPLICATION_JSON));
+ assertThat(restTemplate.getForObject("/first/{id}", String.class, 123)).isEqualTo("OK");
+ assertThat(this.registry.find("http.client.requests").timer()).isNull();
+ enabled.set(true);
+ assertThat(restTemplate.getForObject(URI.create("/second/456"), String.class)).isEqualTo("OK");
+ this.registry.get("http.client.requests").tags("uri", "/second/456").timer();
+ this.mockServer.verify();
+
+ }
+
private static final class TestInterceptor implements ClientHttpRequestInterceptor {
private final RestTemplate restTemplate;
diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/client/MetricsWebClientFilterFunctionTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/client/MetricsWebClientFilterFunctionTests.java
index 4e10dfbaf406..b651560ec57c 100644
--- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/client/MetricsWebClientFilterFunctionTests.java
+++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/client/MetricsWebClientFilterFunctionTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -146,17 +146,17 @@ void filterWhenCancelAfterResponseThrownShouldNotRecordTimer() {
}
@Test
- void filterWhenExceptionAndRetryShouldNotCumulateRecordTime() {
+ void filterWhenExceptionAndRetryShouldNotAccumulateRecordTime() {
ClientRequest request = ClientRequest
.create(HttpMethod.GET, URI.create("https://example.com/projects/spring-boot")).build();
ExchangeFunction exchange = (r) -> Mono.error(new IllegalArgumentException())
- .delaySubscription(Duration.ofMillis(300)).cast(ClientResponse.class);
+ .delaySubscription(Duration.ofMillis(1000)).cast(ClientResponse.class);
this.filterFunction.filter(request, exchange).retry(1)
.onErrorResume(IllegalArgumentException.class, (t) -> Mono.empty()).block(Duration.ofSeconds(5));
Timer timer = this.registry.get("http.client.requests")
.tags("method", "GET", "uri", "/projects/spring-boot", "status", "CLIENT_ERROR").timer();
assertThat(timer.count()).isEqualTo(2);
- assertThat(timer.max(TimeUnit.MILLISECONDS)).isLessThan(600);
+ assertThat(timer.max(TimeUnit.MILLISECONDS)).isLessThan(2000);
}
}
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java
index 37b35946f5b3..4a342d0eb825 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java
@@ -103,6 +103,7 @@ public FlywaySchemaManagementProvider flywayDefaultDdlModeProvider(ObjectProvide
}
@Configuration(proxyBeanMethods = false)
+ @ConditionalOnClass(JdbcUtils.class)
@ConditionalOnMissingBean(Flyway.class)
@EnableConfigurationProperties(FlywayProperties.class)
public static class FlywayConfiguration {
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java
index d50622fc8fd7..84eeca6ad925 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java
@@ -54,14 +54,15 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
private ClassLoader classLoader;
/**
- * Name of the datasource. Default to "testdb" when using an embedded database.
+ * Whether to generate a random datasource name.
*/
- private String name;
+ private boolean generateUniqueName = true;
/**
- * Whether to generate a random datasource name.
+ * Datasource name to use if "generate-unique-name" is false. Defaults to "testdb"
+ * when using an embedded database, otherwise null.
*/
- private boolean generateUniqueName = true;
+ private String name;
/**
* Fully qualified name of the connection pool implementation to use. By default, it
@@ -194,14 +195,6 @@ public DataSourceBuilder> initializeDataSourceBuilder() {
.url(determineUrl()).username(determineUsername()).password(determinePassword());
}
- public String getName() {
- return this.name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
public boolean isGenerateUniqueName() {
return this.generateUniqueName;
}
@@ -210,6 +203,14 @@ public void setGenerateUniqueName(boolean generateUniqueName) {
this.generateUniqueName = generateUniqueName;
}
+ public String getName() {
+ return this.name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
public Class extends DataSource> getType() {
return this.type;
}
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java
index b5685af85856..1984d44fcb3a 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java
@@ -39,6 +39,7 @@
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
+import org.springframework.jdbc.core.ConnectionCallback;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -73,6 +74,7 @@ public LiquibaseSchemaManagementProvider liquibaseDefaultDdlModeProvider(
}
@Configuration(proxyBeanMethods = false)
+ @ConditionalOnClass(ConnectionCallback.class)
@ConditionalOnMissingBean(SpringLiquibase.class)
@EnableConfigurationProperties(LiquibaseProperties.class)
public static class LiquibaseConfiguration {
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/netty/NettyProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/netty/NettyProperties.java
index 10d859fe264b..1f381468a303 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/netty/NettyProperties.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/netty/NettyProperties.java
@@ -32,7 +32,7 @@ public class NettyProperties {
/**
* Level of leak detection for reference-counted buffers.
*/
- private LeakDetection leakDetection = LeakDetection.DISABLED;
+ private LeakDetection leakDetection = LeakDetection.SIMPLE;
public LeakDetection getLeakDetection() {
return this.leakDetection;
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/servlet/Jetty10WebSocketServletWebServerCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/servlet/Jetty10WebSocketServletWebServerCustomizer.java
new file mode 100644
index 000000000000..c378d6683d02
--- /dev/null
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/servlet/Jetty10WebSocketServletWebServerCustomizer.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2012-2021 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.websocket.servlet;
+
+import java.lang.reflect.Method;
+
+import javax.servlet.ServletContext;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.webapp.AbstractConfiguration;
+import org.eclipse.jetty.webapp.WebAppContext;
+
+import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
+import org.springframework.boot.web.server.WebServerFactoryCustomizer;
+import org.springframework.core.Ordered;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * WebSocket customizer for {@link JettyServletWebServerFactory} with Jetty 10.
+ *
+ * @author Andy Wilkinson
+ */
+class Jetty10WebSocketServletWebServerCustomizer
+ implements WebServerFactoryCustomizer, Ordered {
+
+ static final String JETTY_WEB_SOCKET_SERVER_CONTAINER = "org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer";
+
+ static final String JAVAX_WEB_SOCKET_SERVER_CONTAINER = "org.eclipse.jetty.websocket.javax.server.internal.JavaxWebSocketServerContainer";
+
+ @Override
+ public void customize(JettyServletWebServerFactory factory) {
+ factory.addConfigurations(new AbstractConfiguration() {
+
+ @Override
+ public void configure(WebAppContext context) throws Exception {
+ ServletContext servletContext = context.getServletContext();
+ Class> jettyContainer = ClassUtils.forName(JETTY_WEB_SOCKET_SERVER_CONTAINER, null);
+ Method getJettyContainer = ReflectionUtils.findMethod(jettyContainer, "getContainer",
+ ServletContext.class);
+ Server server = context.getServer();
+ if (ReflectionUtils.invokeMethod(getJettyContainer, null, servletContext) == null) {
+ ensureWebSocketComponents(server, servletContext);
+ ensureContainer(jettyContainer, servletContext);
+ }
+ Class> javaxContainer = ClassUtils.forName(JAVAX_WEB_SOCKET_SERVER_CONTAINER, null);
+ Method getJavaxContainer = ReflectionUtils.findMethod(javaxContainer, "getContainer",
+ ServletContext.class);
+ if (ReflectionUtils.invokeMethod(getJavaxContainer, "getContainer", servletContext) == null) {
+ ensureWebSocketComponents(server, servletContext);
+ ensureUpgradeFilter(servletContext);
+ ensureMappings(servletContext);
+ ensureContainer(javaxContainer, servletContext);
+ }
+ }
+
+ private void ensureWebSocketComponents(Server server, ServletContext servletContext)
+ throws ClassNotFoundException {
+ Class> webSocketServerComponents = ClassUtils
+ .forName("org.eclipse.jetty.websocket.core.server.WebSocketServerComponents", null);
+ Method ensureWebSocketComponents = ReflectionUtils.findMethod(webSocketServerComponents,
+ "ensureWebSocketComponents", Server.class, ServletContext.class);
+ ReflectionUtils.invokeMethod(ensureWebSocketComponents, null, server, servletContext);
+ }
+
+ private void ensureContainer(Class> container, ServletContext servletContext)
+ throws ClassNotFoundException {
+ Method ensureContainer = ReflectionUtils.findMethod(container, "ensureContainer", ServletContext.class);
+ ReflectionUtils.invokeMethod(ensureContainer, null, servletContext);
+ }
+
+ private void ensureUpgradeFilter(ServletContext servletContext) throws ClassNotFoundException {
+ Class> webSocketUpgradeFilter = ClassUtils
+ .forName("org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter", null);
+ Method ensureFilter = ReflectionUtils.findMethod(webSocketUpgradeFilter, "ensureFilter",
+ ServletContext.class);
+ ReflectionUtils.invokeMethod(ensureFilter, null, servletContext);
+ }
+
+ private void ensureMappings(ServletContext servletContext) throws ClassNotFoundException {
+ Class> webSocketMappings = ClassUtils
+ .forName("org.eclipse.jetty.websocket.core.server.WebSocketMappings", null);
+ Method ensureMappings = ReflectionUtils.findMethod(webSocketMappings, "ensureMappings",
+ ServletContext.class);
+ ReflectionUtils.invokeMethod(ensureMappings, null, servletContext);
+ }
+
+ });
+ }
+
+ @Override
+ public int getOrder() {
+ return 0;
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/servlet/WebSocketServletAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/servlet/WebSocketServletAutoConfiguration.java
index de8a544e0b01..70f0d2bf4e62 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/servlet/WebSocketServletAutoConfiguration.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/servlet/WebSocketServletAutoConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -83,6 +83,19 @@ JettyWebSocketServletWebServerCustomizer websocketServletWebServerCustomizer() {
}
+ @Configuration(proxyBeanMethods = false)
+ @ConditionalOnClass(name = { Jetty10WebSocketServletWebServerCustomizer.JAVAX_WEB_SOCKET_SERVER_CONTAINER,
+ Jetty10WebSocketServletWebServerCustomizer.JETTY_WEB_SOCKET_SERVER_CONTAINER })
+ static class Jetty10WebSocketConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean(name = "websocketServletWebServerCustomizer")
+ Jetty10WebSocketServletWebServerCustomizer websocketServletWebServerCustomizer() {
+ return new Jetty10WebSocketServletWebServerCustomizer();
+ }
+
+ }
+
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(io.undertow.websockets.jsr.Bootstrap.class)
static class UndertowWebSocketConfiguration {
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfigurationTests.java
index 597d79da6826..dba9917b9e68 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfigurationTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfigurationTests.java
@@ -20,20 +20,17 @@
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
-import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.TrustManager;
import com.rabbitmq.client.Address;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.JDKSaslConfig;
-import com.rabbitmq.client.SslContextFactory;
-import com.rabbitmq.client.TrustEverythingTrustManager;
import com.rabbitmq.client.impl.CredentialsProvider;
import com.rabbitmq.client.impl.CredentialsRefreshService;
import com.rabbitmq.client.impl.DefaultCredentialsProvider;
import org.aopalliance.aop.Advice;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InOrder;
import org.springframework.amqp.core.AcknowledgeMode;
@@ -59,6 +56,8 @@
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.boot.test.system.CapturedOutput;
+import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@@ -92,6 +91,7 @@
* @author HaiTao Zhang
* @author Franjo Zilic
*/
+@ExtendWith(OutputCaptureExtension.class)
class RabbitAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
@@ -722,24 +722,24 @@ void enableSslWithKeystoreTypeAndTrustStoreTypeShouldWork() {
}
@Test
- void enableSslWithValidateServerCertificateFalse() {
+ void enableSslWithValidateServerCertificateFalse(CapturedOutput output) {
this.contextRunner.withUserConfiguration(TestConfiguration.class)
.withPropertyValues("spring.rabbitmq.ssl.enabled:true",
"spring.rabbitmq.ssl.validateServerCertificate=false")
.run((context) -> {
com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory = getTargetConnectionFactory(context);
- TrustManager trustManager = getTrustManager(rabbitConnectionFactory);
- assertThat(trustManager).isInstanceOf(TrustEverythingTrustManager.class);
+ assertThat(rabbitConnectionFactory.isSSL()).isTrue();
+ assertThat(output).contains("TrustEverythingTrustManager", "SECURITY ALERT");
});
}
@Test
- void enableSslWithValidateServerCertificateDefault() {
+ void enableSslWithValidateServerCertificateDefault(CapturedOutput output) {
this.contextRunner.withUserConfiguration(TestConfiguration.class)
.withPropertyValues("spring.rabbitmq.ssl.enabled:true").run((context) -> {
com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory = getTargetConnectionFactory(context);
- TrustManager trustManager = getTrustManager(rabbitConnectionFactory);
- assertThat(trustManager).isNotInstanceOf(TrustEverythingTrustManager.class);
+ assertThat(rabbitConnectionFactory.isSSL()).isTrue();
+ assertThat(output).doesNotContain("TrustEverythingTrustManager", "SECURITY ALERT");
});
}
@@ -849,18 +849,6 @@ void whenMultipleConnectionFactoryCustomizersAreDefinedThenTheyAreCalledInOrder(
});
}
- private TrustManager getTrustManager(com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory) {
- SslContextFactory sslContextFactory = (SslContextFactory) ReflectionTestUtils.getField(rabbitConnectionFactory,
- "sslContextFactory");
- SSLContext sslContext = sslContextFactory.create("connection");
- Object spi = ReflectionTestUtils.getField(sslContext, "contextSpi");
- Object trustManager = ReflectionTestUtils.getField(spi, "trustManager");
- while (trustManager.getClass().getName().endsWith("Wrapper")) {
- trustManager = ReflectionTestUtils.getField(trustManager, "tm");
- }
- return (TrustManager) trustManager;
- }
-
private com.rabbitmq.client.ConnectionFactory getTargetConnectionFactory(AssertableApplicationContext context) {
CachingConnectionFactory connectionFactory = context.getBean(CachingConnectionFactory.class);
return connectionFactory.getRabbitConnectionFactory();
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 0a39f1ec2615..64d6c120c41d 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
@@ -50,6 +50,7 @@
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.jdbc.SchemaManagement;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
+import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ContextConsumer;
@@ -115,6 +116,13 @@ void createsDataSourceWithNoDataSourceBeanAndFlywayUrl() {
});
}
+ @Test
+ void backsOffWithFlywayUrlAndNoSpringJdbc() {
+ this.contextRunner.withPropertyValues("spring.flyway.url:jdbc:hsqldb:mem:" + UUID.randomUUID())
+ .withClassLoader(new FilteredClassLoader("org.springframework.jdbc"))
+ .run((context) -> assertThat(context).doesNotHaveBean(Flyway.class));
+ }
+
@Test
void createDataSourceWithUrl() {
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java
index 029a4aa0c122..d8243da408dd 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java
@@ -43,6 +43,7 @@
import org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.jdbc.DataSourceBuilder;
+import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ContextConsumer;
@@ -95,6 +96,13 @@ void createsDataSourceWithNoDataSourceBeanAndLiquibaseUrl() {
}));
}
+ @Test
+ void backsOffWithLiquibaseUrlAndNoSpringJdbc() {
+ this.contextRunner.withPropertyValues("spring.liquibase.url:jdbc:hsqldb:mem:" + UUID.randomUUID())
+ .withClassLoader(new FilteredClassLoader("org.springframework.jdbc"))
+ .run((context) -> assertThat(context).doesNotHaveBean(SpringLiquibase.class));
+ }
+
@Test
void defaultSpringLiquibase() {
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/netty/NettyPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/netty/NettyPropertiesTests.java
new file mode 100644
index 000000000000..df23a8ecb18d
--- /dev/null
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/netty/NettyPropertiesTests.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012-2021 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.netty;
+
+import io.netty.util.ResourceLeakDetector;
+import io.netty.util.ResourceLeakDetector.Level;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.test.util.ReflectionTestUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link NettyProperties}
+ *
+ * @author Brian Clozel
+ */
+class NettyPropertiesTests {
+
+ @Test
+ void defaultValueShouldMatchNettys() {
+ NettyProperties properties = new NettyProperties();
+ ResourceLeakDetector.Level defaultLevel = (Level) ReflectionTestUtils.getField(ResourceLeakDetector.class,
+ "DEFAULT_LEVEL");
+ assertThat(ResourceLeakDetector.Level.valueOf(properties.getLeakDetection().name())).isEqualTo(defaultLevel);
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-cli/build.gradle b/spring-boot-project/spring-boot-cli/build.gradle
index 8be85e7dde43..e2e9cef368cc 100644
--- a/spring-boot-project/spring-boot-cli/build.gradle
+++ b/spring-boot-project/spring-boot-cli/build.gradle
@@ -56,6 +56,7 @@ dependencies {
loader(project(":spring-boot-project:spring-boot-tools:spring-boot-loader"))
+ testCompileOnly("org.apache.tomcat.embed:tomcat-embed-core")
testImplementation(project(":spring-boot-project:spring-boot"))
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation(project(":spring-boot-project:spring-boot-test"))
diff --git a/spring-boot-project/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/archive/ArchiveCommand.java b/spring-boot-project/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/archive/ArchiveCommand.java
index 3ab712471293..d0732c163abd 100644
--- a/spring-boot-project/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/archive/ArchiveCommand.java
+++ b/spring-boot-project/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/archive/ArchiveCommand.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -198,7 +198,7 @@ private List createLibraries(List dependencies) throws URISyntaxEx
List libraries = new ArrayList<>();
for (URL dependency : dependencies) {
File file = new File(dependency.toURI());
- libraries.add(new Library(file, getLibraryScope(file)));
+ libraries.add(new Library(null, file, getLibraryScope(file), null, false, false, true));
}
return libraries;
}
@@ -256,7 +256,7 @@ private List addClasspathEntries(JarWriter writer, List libraries = new ArrayList<>();
for (MatchedResource entry : entries) {
if (entry.isRoot()) {
- libraries.add(new Library(entry.getFile(), LibraryScope.COMPILE));
+ libraries.add(new Library(null, entry.getFile(), LibraryScope.COMPILE, null, false, false, true));
}
else {
writeClasspathEntry(writer, entry);
diff --git a/spring-boot-project/spring-boot-cli/src/test/java/org/springframework/boot/cli/CliTester.java b/spring-boot-project/spring-boot-cli/src/test/java/org/springframework/boot/cli/CliTester.java
index 980ce55f9835..6e28f4dbc54e 100644
--- a/spring-boot-project/spring-boot-cli/src/test/java/org/springframework/boot/cli/CliTester.java
+++ b/spring-boot-project/spring-boot-cli/src/test/java/org/springframework/boot/cli/CliTester.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -22,9 +22,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
-import java.lang.reflect.Field;
import java.net.URI;
-import java.net.URL;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
@@ -127,7 +125,6 @@ public File getTemp() {
}
private Future submitCommand(T command, String... args) {
- clearUrlHandler();
final String[] sources = getSources(args);
return Executors.newSingleThreadExecutor().submit(() -> {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
@@ -152,21 +149,6 @@ private Future submitCommand(T command, Stri
});
}
- /**
- * The TomcatURLStreamHandlerFactory fails if the factory is already set, use
- * reflection to reset it.
- */
- private void clearUrlHandler() {
- try {
- Field field = URL.class.getDeclaredField("factory");
- field.setAccessible(true);
- field.set(null, null);
- }
- catch (Exception ex) {
- throw new IllegalStateException(ex);
- }
- }
-
protected String[] getSources(String... args) {
final String[] sources = new String[args.length];
for (int i = 0; i < args.length; i++) {
diff --git a/spring-boot-project/spring-boot-cli/src/test/java/org/springframework/boot/cli/CliTesterSpringApplication.java b/spring-boot-project/spring-boot-cli/src/test/java/org/springframework/boot/cli/CliTesterSpringApplication.java
index 18b600c40785..dd4a091a70f9 100644
--- a/spring-boot-project/spring-boot-cli/src/test/java/org/springframework/boot/cli/CliTesterSpringApplication.java
+++ b/spring-boot-project/spring-boot-cli/src/test/java/org/springframework/boot/cli/CliTesterSpringApplication.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -16,9 +16,12 @@
package org.springframework.boot.cli;
+import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory;
+
import org.springframework.boot.SpringApplication;
import org.springframework.boot.web.context.WebServerPortFileWriter;
import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.util.ClassUtils;
/**
* Custom {@link SpringApplication} used by {@link CliTester}.
@@ -27,6 +30,13 @@
*/
public class CliTesterSpringApplication extends SpringApplication {
+ static {
+ if (ClassUtils.isPresent("org.apache.catalina.webresources.TomcatURLStreamHandlerFactory",
+ CliTesterSpringApplication.class.getClassLoader())) {
+ TomcatURLStreamHandlerFactory.disable();
+ }
+ }
+
public CliTesterSpringApplication(Class>... sources) {
super(sources);
}
diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle
index 7111d31c38f9..2707f6832622 100644
--- a/spring-boot-project/spring-boot-dependencies/build.gradle
+++ b/spring-boot-project/spring-boot-dependencies/build.gradle
@@ -159,7 +159,7 @@ bom {
]
}
}
- library("Cassandra Driver", "4.11.1") {
+ library("Cassandra Driver", "4.11.2") {
group("com.datastax.oss") {
imports = [
"java-driver-bom"
@@ -247,7 +247,7 @@ bom {
]
}
}
- library("Dropwizard Metrics", "4.1.22") {
+ library("Dropwizard Metrics", "4.1.24") {
group("io.dropwizard.metrics") {
imports = [
"metrics-bom"
@@ -387,7 +387,7 @@ bom {
]
}
}
- library("Hazelcast", "4.1.3") {
+ library("Hazelcast", "4.1.4") {
group("com.hazelcast") {
modules = [
"hazelcast",
@@ -842,7 +842,7 @@ bom {
]
}
}
- library("Jedis", "3.6.0") {
+ library("Jedis", "3.6.1") {
group("redis.clients") {
modules = [
"jedis"
@@ -859,7 +859,7 @@ bom {
]
}
}
- library("Jetty EL", "9.0.29") {
+ library("Jetty EL", "9.0.48") {
group("org.mortbay.jasper") {
modules = [
"apache-el"
@@ -1024,7 +1024,7 @@ bom {
]
}
}
- library("Lettuce", "6.1.2.RELEASE") {
+ library("Lettuce", "6.1.3.RELEASE") {
group("io.lettuce") {
modules = [
"lettuce-core"
@@ -1200,7 +1200,7 @@ bom {
]
}
}
- library("Micrometer", "1.7.0") {
+ library("Micrometer", "1.7.1") {
group("io.micrometer") {
modules = [
"micrometer-registry-stackdriver" {
@@ -1262,7 +1262,7 @@ bom {
]
}
}
- library("Neo4j Java Driver", "4.2.6") {
+ library("Neo4j Java Driver", "4.2.7") {
group("org.neo4j.driver") {
modules = [
"neo4j-java-driver"
@@ -1276,7 +1276,7 @@ bom {
]
}
}
- library("Netty tcNative", "2.0.39.Final") {
+ library("Netty tcNative", "2.0.40.Final") {
group("io.netty") {
modules = [
"netty-tcnative",
@@ -1284,14 +1284,20 @@ bom {
]
}
}
- library("OAuth2 OIDC SDK", "9.3.3") {
+ library("OAuth2 OIDC SDK") {
+ version("9.9") {
+ shouldAlignWithVersionFrom("Spring Security")
+ }
group("com.nimbusds") {
modules = [
"oauth2-oidc-sdk"
]
}
}
- library("Nimbus JOSE JWT", "9.8.1") {
+ library("Nimbus JOSE JWT") {
+ version("9.10") {
+ shouldAlignWithVersionFrom("Spring Security")
+ }
group("com.nimbusds") {
modules = [
"nimbus-jose-jwt"
@@ -1351,7 +1357,7 @@ bom {
]
}
}
- library("Postgresql", "42.2.20") {
+ library("Postgresql", "42.2.22") {
group("org.postgresql") {
modules = [
"postgresql"
@@ -1410,7 +1416,7 @@ bom {
]
}
}
- library("Reactor Bom", "2020.0.7") {
+ library("Reactor Bom", "2020.0.8") {
group("io.projectreactor") {
imports = [
"reactor-bom"
@@ -1581,7 +1587,7 @@ bom {
]
}
}
- library("SLF4J", "1.7.30") {
+ library("SLF4J", "1.7.31") {
group("org.slf4j") {
modules = [
"jcl-over-slf4j",
@@ -1623,7 +1629,7 @@ bom {
]
}
}
- library("Spring AMQP", "2.3.8") {
+ library("Spring AMQP", "2.3.9") {
group("org.springframework.amqp") {
modules = [
"spring-amqp",
@@ -1643,7 +1649,7 @@ bom {
]
}
}
- library("Spring Data Bom", "2021.0.1") {
+ library("Spring Data Bom", "2021.0.2") {
group("org.springframework.data") {
imports = [
"spring-data-bom"
@@ -1657,21 +1663,21 @@ bom {
]
}
}
- library("Spring HATEOAS", "1.3.1") {
+ library("Spring HATEOAS", "1.3.2") {
group("org.springframework.hateoas") {
modules = [
"spring-hateoas"
]
}
}
- library("Spring Integration", "5.5.0") {
+ library("Spring Integration", "5.5.1") {
group("org.springframework.integration") {
imports = [
"spring-integration-bom"
]
}
}
- library("Spring Kafka", "2.7.2") {
+ library("Spring Kafka", "2.7.3") {
group("org.springframework.kafka") {
modules = [
"spring-kafka",
@@ -1709,14 +1715,19 @@ bom {
]
}
}
- library("Spring Security", "5.5.0") {
+ library("Spring Security", "5.5.1") {
group("org.springframework.security") {
imports = [
"spring-security-bom"
]
}
+ dependencyVersions {
+ extractFrom {
+ dependencyConstraints("https://raw.githubusercontent.com/spring-projects/spring-security//dependencies/spring-security-dependencies.gradle")
+ }
+ }
}
- library("Spring Session Bom", "2021.0.0") {
+ library("Spring Session Bom", "2021.0.1") {
group("org.springframework.session") {
imports = [
"spring-session-bom"
diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/livereload/Connection.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/livereload/Connection.java
index 21cdd21119c6..070dca8a4f5e 100644
--- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/livereload/Connection.java
+++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/livereload/Connection.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -23,6 +23,7 @@
import java.net.SocketTimeoutException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -30,18 +31,20 @@
import org.apache.commons.logging.LogFactory;
import org.springframework.core.log.LogMessage;
+import org.springframework.util.Assert;
import org.springframework.util.Base64Utils;
/**
* A {@link LiveReloadServer} connection.
*
* @author Phillip Webb
+ * @author Francis Lavoie
*/
class Connection {
private static final Log logger = LogFactory.getLog(Connection.class);
- private static final Pattern WEBSOCKET_KEY_PATTERN = Pattern.compile("^Sec-WebSocket-Key:(.*)$", Pattern.MULTILINE);
+ private static final Pattern WEBSOCKET_KEY_PATTERN = Pattern.compile("^sec-websocket-key:(.*)$", Pattern.MULTILINE);
public static final String WEBSOCKET_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
@@ -68,8 +71,9 @@ class Connection {
this.socket = socket;
this.inputStream = new ConnectionInputStream(inputStream);
this.outputStream = new ConnectionOutputStream(outputStream);
- this.header = this.inputStream.readHeader();
- logger.debug(LogMessage.format("Established livereload connection [%s]", this.header));
+ String header = this.inputStream.readHeader();
+ logger.debug(LogMessage.format("Established livereload connection [%s]", header));
+ this.header = header.toLowerCase(Locale.ENGLISH);
}
/**
@@ -77,10 +81,10 @@ class Connection {
* @throws Exception in case of errors
*/
void run() throws Exception {
- if (this.header.contains("Upgrade: websocket") && this.header.contains("Sec-WebSocket-Version: 13")) {
+ if (this.header.contains("upgrade: websocket") && this.header.contains("sec-websocket-version: 13")) {
runWebSocket();
}
- if (this.header.contains("GET /livereload.js")) {
+ if (this.header.contains("get /livereload.js")) {
this.outputStream.writeHttp(getClass().getResourceAsStream("livereload.js"), "text/javascript");
}
}
@@ -140,9 +144,7 @@ private void writeWebSocketFrame(Frame frame) throws IOException {
private String getWebsocketAcceptResponse() throws NoSuchAlgorithmException {
Matcher matcher = WEBSOCKET_KEY_PATTERN.matcher(this.header);
- if (!matcher.find()) {
- throw new IllegalStateException("No Sec-WebSocket-Key");
- }
+ Assert.state(matcher.find(), "No Sec-WebSocket-Key");
String response = matcher.group(1).trim() + WEBSOCKET_GUID;
MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
messageDigest.update(response.getBytes(), 0, response.length());
diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/livereload/LiveReloadServerTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/livereload/LiveReloadServerTests.java
index ea9787af9349..3008329338cf 100644
--- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/livereload/LiveReloadServerTests.java
+++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/livereload/LiveReloadServerTests.java
@@ -19,13 +19,27 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
import java.net.URI;
+import java.net.UnknownHostException;
import java.time.Duration;
import java.util.ArrayList;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
+import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.ClientEndpointConfig.Configurator;
+import javax.websocket.Endpoint;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.WebSocketContainer;
import org.apache.tomcat.websocket.WsWebSocketContainer;
import org.awaitility.Awaitility;
@@ -34,13 +48,20 @@
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
+import org.springframework.http.HttpHeaders;
+import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.PingMessage;
import org.springframework.web.socket.PongMessage;
import org.springframework.web.socket.TextMessage;
+import org.springframework.web.socket.WebSocketExtension;
+import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
+import org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter;
+import org.springframework.web.socket.adapter.standard.StandardWebSocketSession;
+import org.springframework.web.socket.adapter.standard.WebSocketToStandardExtensionAdapter;
import org.springframework.web.socket.client.WebSocketClient;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
import org.springframework.web.socket.handler.TextWebSocketHandler;
@@ -94,7 +115,16 @@ void triggerReload() throws Exception {
(msgs) -> msgs.size() == 2);
assertThat(messages.get(0)).contains("http://livereload.com/protocols/official-7");
assertThat(messages.get(1)).contains("command\":\"reload\"");
+ }
+ @Test // gh-26813
+ void triggerReloadWithUppercaseHeaders() throws Exception {
+ LiveReloadWebSocketHandler handler = connect(UppercaseWebSocketClient::new);
+ this.server.triggerReload();
+ List messages = await().atMost(Duration.ofSeconds(10)).until(handler::getMessages,
+ (msgs) -> msgs.size() == 2);
+ assertThat(messages.get(0)).contains("http://livereload.com/protocols/official-7");
+ assertThat(messages.get(1)).contains("command\":\"reload\"");
}
@Test
@@ -126,7 +156,13 @@ void serverClose() throws Exception {
}
private LiveReloadWebSocketHandler connect() throws Exception {
- WebSocketClient client = new StandardWebSocketClient(new WsWebSocketContainer());
+ return connect(StandardWebSocketClient::new);
+ }
+
+ private LiveReloadWebSocketHandler connect(Function clientFactory)
+ throws Exception {
+ WsWebSocketContainer webSocketContainer = new WsWebSocketContainer();
+ WebSocketClient client = clientFactory.apply(webSocketContainer);
LiveReloadWebSocketHandler handler = new LiveReloadWebSocketHandler();
client.doHandshake(handler, "ws://localhost:" + this.port + "/livereload");
handler.awaitHello();
@@ -246,4 +282,69 @@ CloseStatus getCloseStatus() {
}
+ static class UppercaseWebSocketClient extends StandardWebSocketClient {
+
+ private final WebSocketContainer webSocketContainer;
+
+ UppercaseWebSocketClient(WebSocketContainer webSocketContainer) {
+ super(webSocketContainer);
+ this.webSocketContainer = webSocketContainer;
+ }
+
+ @Override
+ protected ListenableFuture doHandshakeInternal(WebSocketHandler webSocketHandler,
+ HttpHeaders headers, URI uri, List protocols, List extensions,
+ Map attributes) {
+ InetSocketAddress localAddress = new InetSocketAddress(getLocalHost(), uri.getPort());
+ InetSocketAddress remoteAddress = new InetSocketAddress(uri.getHost(), uri.getPort());
+ StandardWebSocketSession session = new StandardWebSocketSession(headers, attributes, localAddress,
+ remoteAddress);
+ ClientEndpointConfig endpointConfig = ClientEndpointConfig.Builder.create()
+ .configurator(new UppercaseWebSocketClientConfigurator(headers)).preferredSubprotocols(protocols)
+ .extensions(extensions.stream().map(WebSocketToStandardExtensionAdapter::new)
+ .collect(Collectors.toList()))
+ .build();
+ endpointConfig.getUserProperties().putAll(getUserProperties());
+ Endpoint endpoint = new StandardWebSocketHandlerAdapter(webSocketHandler, session);
+ Callable connectTask = () -> {
+ this.webSocketContainer.connectToServer(endpoint, endpointConfig, uri);
+ return session;
+ };
+ return getTaskExecutor().submitListenable(connectTask);
+ }
+
+ private InetAddress getLocalHost() {
+ try {
+ return InetAddress.getLocalHost();
+ }
+ catch (UnknownHostException ex) {
+ return InetAddress.getLoopbackAddress();
+ }
+ }
+
+ }
+
+ private static class UppercaseWebSocketClientConfigurator extends Configurator {
+
+ private final HttpHeaders headers;
+
+ UppercaseWebSocketClientConfigurator(HttpHeaders headers) {
+ this.headers = headers;
+ }
+
+ @Override
+ public void beforeRequest(Map> requestHeaders) {
+ Map> uppercaseRequestHeaders = new LinkedHashMap<>();
+ requestHeaders.forEach((key, value) -> uppercaseRequestHeaders.put(key.toUpperCase(), value));
+ requestHeaders.clear();
+ requestHeaders.putAll(uppercaseRequestHeaders);
+ requestHeaders.putAll(this.headers);
+ }
+
+ @Override
+ public void afterResponse(HandshakeResponse response) {
+ }
+
+ }
+
}
diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestarterTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestarterTests.java
index c34d0f0579bc..1f45b017f3ad 100644
--- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestarterTests.java
+++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestarterTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2021 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,10 +18,13 @@
import java.net.URL;
import java.net.URLClassLoader;
+import java.time.Duration;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.awaitility.Awaitility;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -80,10 +83,11 @@ void testRestart(CapturedOutput output) throws Exception {
Restarter.clearInstance();
Thread thread = new Thread(SampleApplication::main);
thread.start();
- Thread.sleep(2600);
- assertThat(StringUtils.countOccurrencesOf(output.toString(), "Tick 0")).isGreaterThan(1);
- assertThat(StringUtils.countOccurrencesOf(output.toString(), "Tick 1")).isGreaterThan(1);
- assertThat(CloseCountingApplicationListener.closed).isGreaterThan(0);
+ Awaitility.await().atMost(Duration.ofSeconds(30)).untilAsserted(() -> {
+ assertThat(StringUtils.countOccurrencesOf(output.toString(), "Tick 0")).isGreaterThan(1);
+ assertThat(StringUtils.countOccurrencesOf(output.toString(), "Tick 1")).isGreaterThan(1);
+ assertThat(CloseCountingApplicationListener.closed).isGreaterThan(0);
+ });
}
@Test
@@ -174,7 +178,7 @@ static class SampleApplication {
private int count = 0;
- private static volatile boolean quit = false;
+ private static final AtomicBoolean restart = new AtomicBoolean();
@Scheduled(fixedDelay = 200)
void tickBean() {
@@ -183,8 +187,7 @@ void tickBean() {
@Scheduled(initialDelay = 500, fixedDelay = 500)
void restart() {
- System.out.println("Restart " + Thread.currentThread());
- if (!SampleApplication.quit) {
+ if (SampleApplication.restart.compareAndSet(false, true)) {
Restarter.getInstance().restart();
}
}
@@ -195,18 +198,6 @@ static void main(String... args) {
SampleApplication.class);
context.addApplicationListener(new CloseCountingApplicationListener());
Restarter.getInstance().prepare(context);
- System.out.println("Sleep " + Thread.currentThread());
- sleep();
- quit = true;
- }
-
- private static void sleep() {
- try {
- Thread.sleep(1200);
- }
- catch (InterruptedException ex) {
- // Ignore
- }
}
}
diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/tunnel/client/TunnelClientTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/tunnel/client/TunnelClientTests.java
index 9b7455b37af4..da6eb662b1d7 100644
--- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/tunnel/client/TunnelClientTests.java
+++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/tunnel/client/TunnelClientTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2021 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,14 +24,14 @@
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
+import java.time.Duration;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.awaitility.Awaitility;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
/**
* Tests for {@link TunnelClient}.
@@ -85,9 +85,10 @@ void stopTriggersTunnelClose() throws Exception {
TunnelClient client = new TunnelClient(0, this.tunnelConnection);
int port = client.start();
SocketChannel channel = SocketChannel.open(new InetSocketAddress(port));
- Thread.sleep(200);
+ Awaitility.await().atMost(Duration.ofSeconds(30)).until(this.tunnelConnection::getOpenedTimes,
+ (times) -> times == 1);
+ assertThat(this.tunnelConnection.isOpen()).isTrue();
client.stop();
- assertThat(this.tunnelConnection.getOpenedTimes()).isEqualTo(1);
assertThat(this.tunnelConnection.isOpen()).isFalse();
assertThat(channel.read(ByteBuffer.allocate(1))).isEqualTo(-1);
}
@@ -95,16 +96,34 @@ void stopTriggersTunnelClose() throws Exception {
@Test
void addListener() throws Exception {
TunnelClient client = new TunnelClient(0, this.tunnelConnection);
- TunnelClientListener listener = mock(TunnelClientListener.class);
+ MockTunnelClientListener listener = new MockTunnelClientListener();
client.addListener(listener);
int port = client.start();
SocketChannel channel = SocketChannel.open(new InetSocketAddress(port));
- Thread.sleep(200);
- channel.close();
+ Awaitility.await().atMost(Duration.ofSeconds(30)).until(listener.onOpen::get, (open) -> open == 1);
+ assertThat(listener.onClose).hasValue(0);
client.getServerThread().stopAcceptingConnections();
+ channel.close();
+ Awaitility.await().atMost(Duration.ofSeconds(30)).until(listener.onClose::get, (close) -> close == 1);
client.getServerThread().join(2000);
- verify(listener).onOpen(any(SocketChannel.class));
- verify(listener).onClose(any(SocketChannel.class));
+ }
+
+ static class MockTunnelClientListener implements TunnelClientListener {
+
+ private final AtomicInteger onOpen = new AtomicInteger();
+
+ private final AtomicInteger onClose = new AtomicInteger();
+
+ @Override
+ public void onOpen(SocketChannel socket) {
+ this.onOpen.incrementAndGet();
+ }
+
+ @Override
+ public void onClose(SocketChannel socket) {
+ this.onClose.incrementAndGet();
+ }
+
}
static class MockTunnelConnection implements TunnelConnection {
diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/tunnel/server/HttpTunnelServerTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/tunnel/server/HttpTunnelServerTests.java
index 9d9bd7fad29e..36258d1c8519 100644
--- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/tunnel/server/HttpTunnelServerTests.java
+++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/tunnel/server/HttpTunnelServerTests.java
@@ -22,11 +22,13 @@
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
import java.nio.channels.Channels;
+import java.time.Duration;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
+import org.awaitility.Awaitility;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -236,13 +238,14 @@ void httpConnectionsAreClosedAfterLongPollTimeout() throws Exception {
this.server.setLongPollTimeout(100);
MockHttpConnection h1 = new MockHttpConnection();
this.server.handle(h1);
+ Awaitility.await().atMost(Duration.ofSeconds(30)).until(h1.getServletResponse()::getStatus,
+ (status) -> status == 204);
MockHttpConnection h2 = new MockHttpConnection();
this.server.handle(h2);
- Thread.sleep(400);
+ Awaitility.await().atMost(Duration.ofSeconds(30)).until(h2.getServletResponse()::getStatus,
+ (status) -> status == 204);
this.serverChannel.disconnect();
this.server.getServerThread().join();
- assertThat(h1.getServletResponse().getStatus()).isEqualTo(204);
- assertThat(h2.getServletResponse().getStatus()).isEqualTo(204);
}
@Test
diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/tunnel/server/SocketTargetServerConnectionTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/tunnel/server/SocketTargetServerConnectionTests.java
index 3b2600532dce..07b1e75b29ce 100644
--- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/tunnel/server/SocketTargetServerConnectionTests.java
+++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/tunnel/server/SocketTargetServerConnectionTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2021 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,7 +37,7 @@
*/
class SocketTargetServerConnectionTests {
- private static final int DEFAULT_TIMEOUT = 1000;
+ private static final int DEFAULT_TIMEOUT = 5000;
private MockServer server;
diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/from-1x.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/from-1x.adoc
new file mode 100644
index 000000000000..10b440cac895
--- /dev/null
+++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/from-1x.adoc
@@ -0,0 +1,5 @@
+[[upgrading.from-1x]]
+== Upgrading from 1.x
+
+If you are upgrading from the `1.x` release of Spring Boot, check the {github-wiki}/Spring-Boot-2.0-Migration-Guide["`migration guide`" on the project wiki] that provides detailed upgrade instructions.
+Check also the {github-wiki}["`release notes`"] for a list of "`new and noteworthy`" features for each release.
\ No newline at end of file
diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/attributes.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/attributes.adoc
index 91025fd3094e..16c72c58e653 100644
--- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/attributes.adoc
+++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/attributes.adoc
@@ -1,3 +1,4 @@
+:!version-label:
:doctype: book
:idprefix:
:idseparator: -
diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/deployment/cloud.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/deployment/cloud.adoc
index ea97ae6a4744..3a78c5bbf70c 100644
--- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/deployment/cloud.adoc
+++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/deployment/cloud.adoc
@@ -357,6 +357,12 @@ See the blog post on https://boxfuse.com/blog/spring-boot-ec2.html[deploying Spr
+[[deployment.cloud.azure]]
+=== Azure
+This https://spring.io/guides/gs/spring-boot-for-azure/[Getting Started guide] walks you through deploying your Spring Boot application to either https://azure.microsoft.com/en-ca/services/spring-cloud/[Azure Spring Cloud] or https://docs.microsoft.com/en-ca/azure/app-service/overview[Azure App Service].
+
+
+
[[deployment.cloud.google]]
=== Google Cloud
Google Cloud has several options that can be used to launch Spring Boot applications.
diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation.adoc
index bce98f62d0bd..759efc3b0390 100644
--- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation.adoc
+++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation.adoc
@@ -3,7 +3,7 @@ include::attributes.adoc[]
[[documentation]]
-= Spring Boot Documentation
+= Documentation Overview
include::attributes.adoc[]
@@ -11,20 +11,17 @@ include::attributes.adoc[]
This section provides a brief overview of Spring Boot reference documentation.
It serves as a map for the rest of the document.
+The latest copy of this document is available at {spring-boot-current-docs}.
-include::documentation/about.adoc[]
-
-include::documentation/getting-help.adoc[]
+include::documentation/first-steps.adoc[]
include::documentation/upgrading.adoc[]
-include::documentation/first-steps.adoc[]
-
include::documentation/using.adoc[]
include::documentation/features.adoc[]
include::documentation/actuator.adoc[]
-include::documentation/advanced.adoc[]
+include::documentation/advanced.adoc[]
\ No newline at end of file
diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/about.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/about.adoc
deleted file mode 100644
index 4538620ff1e5..000000000000
--- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/about.adoc
+++ /dev/null
@@ -1,9 +0,0 @@
-[[documentation.about]]
-== About the Documentation
-The Spring Boot reference guide is available as:
-
-* {spring-boot-docs}/html/[Multi-page HTML]
-* {spring-boot-docs}/htmlsingle/[Single page HTML]
-* {spring-boot-docs}/pdf/spring-boot-reference.pdf[PDF]
-
-The latest copy is available at {spring-boot-current-docs}.
diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/getting-help.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/getting-help.adoc
deleted file mode 100644
index 5ba7182b6499..000000000000
--- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/getting-help.adoc
+++ /dev/null
@@ -1,16 +0,0 @@
-[[documentation.getting-help]]
-== Getting Help
-If you have trouble with Spring Boot, we would like to help.
-
-* Try the <>.
- They provide solutions to the most common questions.
-* Learn the Spring basics.
- Spring Boot builds on many other Spring projects.
- Check the https://spring.io[spring.io] web-site for a wealth of reference documentation.
- If you are starting out with Spring, try one of the https://spring.io/guides[guides].
-* Ask a question.
- We monitor https://stackoverflow.com[stackoverflow.com] for questions tagged with https://stackoverflow.com/tags/spring-boot[`spring-boot`].
-* Report bugs with Spring Boot at https://github.com/spring-projects/spring-boot/issues.
-
-NOTE: All of Spring Boot is open source, including the documentation.
-If you find problems with the docs or if you want to improve them, please {spring-boot-code}[get involved].
diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/upgrading.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/upgrading.adoc
index 471205b6ce9d..314239c9ecdb 100644
--- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/upgrading.adoc
+++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/upgrading.adoc
@@ -1,9 +1,11 @@
[[documentation.upgrading]]
== Upgrading From an Earlier Version
-Instructions for how to upgrade from earlier versions of Spring Boot are provided on the project {github-wiki}[wiki].
-Follow the links in the {github-wiki}#release-notes[release notes] section to find the version that you want to upgrade to.
-
-Upgrading instructions are always the first item in the release notes.
-If you are more than one release behind, please make sure that you also review the release notes of the versions that you jumped.
You should always ensure that you are running a {github-wiki}/Supported-Versions[supported version] of Spring Boot.
+
+Depending on the version that you are upgrading to, you can find some additional tips here:
+
+* *From 1.x:* <>
+* *To a new feature release:* <>
+* *Spring Boot CLI:* <>
+
diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/using.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/using.adoc
index c4c9fe185472..122466f852d3 100644
--- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/using.adoc
+++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/using.adoc
@@ -1,5 +1,5 @@
[[documentation.using]]
-== Working with Spring Boot
+== Developing with Spring Boot
Ready to actually start using Spring Boot? <>:
* *Build systems:* <> | <> | <> | <>
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 125fad876740..86d156097f1d 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
@@ -141,7 +141,10 @@ They must be defined as an environment property (typically an OS environment var
If `spring.config.location` contains directories (as opposed to files), they should end in `/`.
At runtime they will be appended with the names generated from `spring.config.name` before being loaded.
-Files specified in `spring.config.location` are used as-is.
+Files specified in `spring.config.location` are imported directly.
+
+NOTE: Both directory and file location values are also expanded to check for <>.
+For example, if you have a `spring.config.location` of `classpath:myconfig.properties`, you will also find appropriate `classpath:myconfig-.properties` files are loaded.
In most situations, each configprop:spring.config.location[] item you add will reference a single file or directory.
Locations are processed in the order that they are defined and later ones can override the values of earlier ones.
@@ -151,7 +154,7 @@ If you have a complex location setup, and you use profile-specific configuration
A location group is a collection of locations that are all considered at the same level.
For example, you might want to group all classpath locations, then all external locations.
Items within a location group should be separated with `;`.
-See the example in the "`<>`" section for more details.
+See the example in the "`<>`" section for more details.
Locations configured by using `spring.config.location` replace the default locations.
For example, if `spring.config.location` is configured with the value `optional:classpath:/custom-config/,optional:file:./custom-config/`, the complete set of locations considered is:
@@ -227,7 +230,7 @@ For example, if profiles `prod,live` are specified by the configprop:spring.prof
[NOTE]
====
-The last-wins strategy applies at the <> level.
+The last-wins strategy applies at the <> level.
A configprop:spring.config.location[] of `classpath:/cfg/,classpath:/ext/` will not have the same override rules as `classpath:/cfg/;classpath:/ext/`.
For example, continuing our `prod,live` example above, we might have the following files:
@@ -281,6 +284,7 @@ For example, you might have the following in your classpath `application.propert
This will trigger the import of a `dev.properties` file in current directory (if such a file exists).
Values from the imported `dev.properties` will take precedence over the file that triggered the import.
In the above example, the `dev.properties` could redefine `spring.application.name` to a different value.
+
An import will only be imported once no matter how many times it is declared.
The order an import is defined inside a single document within the properties/yaml file doesn't matter.
For instance, the two examples below produce the same result:
@@ -308,6 +312,9 @@ In both of the above examples, the values from the `my.properties` file will tak
Several locations can be specified under a single `spring.config.import` key.
Locations will be processed in the order that they are defined, with later imports taking precedence.
+NOTE: When appropriate, <> are also considered for import.
+The example above would import both `my.properties` as well as any `my-.properties` variants.
+
[TIP]
====
Spring Boot includes pluggable API that allows various different location addresses to be supported.
diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/spring-application.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/spring-application.adoc
index df1b879b8e2d..08f0c77489f5 100644
--- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/spring-application.adoc
+++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/spring-application.adoc
@@ -417,4 +417,5 @@ Once configured, you can record data by running the application with the Flight
Spring Boot ships with the `BufferingApplicationStartup` variant; this implementation is meant for buffering the startup steps and draining them into an external metrics system.
Applications can ask for the bean of type `BufferingApplicationStartup` in any component.
-Additionally, Spring Boot Actuator will {spring-boot-actuator-restapi-docs}/#startup[expose a `startup` endpoint to expose this information as a JSON document].
+
+Spring Boot can also be configured to expose a {spring-boot-actuator-restapi-docs}/#startup[`startup` endpoint] that provides this information as a JSON document.
diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-help.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-help.adoc
new file mode 100644
index 000000000000..93c8cbdc82f6
--- /dev/null
+++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-help.adoc
@@ -0,0 +1,18 @@
+[[getting-help]]
+= Getting Help
+include::attributes.adoc[]
+
+If you have trouble with Spring Boot, we would like to help.
+
+* Try the <>.
+They provide solutions to the most common questions.
+* Learn the Spring basics.
+Spring Boot builds on many other Spring projects.
+Check the https://spring.io[spring.io] web-site for a wealth of reference documentation.
+If you are starting out with Spring, try one of the https://spring.io/guides[guides].
+* Ask a question.
+We monitor https://stackoverflow.com[stackoverflow.com] for questions tagged with https://stackoverflow.com/tags/spring-boot[`spring-boot`].
+* Report bugs with Spring Boot at https://github.com/spring-projects/spring-boot/issues.
+
+NOTE: All of Spring Boot is open source, including the documentation.
+If you find problems with the docs or if you want to improve them, please {spring-boot-code}[get involved].
diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started/installing.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started/installing.adoc
index 43b1a6fb814f..e1ba182dcced 100644
--- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started/installing.adoc
+++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started/installing.adoc
@@ -233,29 +233,3 @@ You should see the following output:
Hello World!
----
-
-
-[[getting-started.installing.upgrading]]
-=== Upgrading from an Earlier Version of Spring Boot
-If you are upgrading from the `1.x` release of Spring Boot, check the {github-wiki}/Spring-Boot-2.0-Migration-Guide["`migration guide`" on the project wiki] that provides detailed upgrade instructions.
-Check also the {github-wiki}["`release notes`"] for a list of "`new and noteworthy`" features for each release.
-
-When upgrading to a new feature release, some properties may have been renamed or removed.
-Spring Boot provides a way to analyze your application's environment and print diagnostics at startup, but also temporarily migrate properties at runtime for you.
-To enable that feature, add the following dependency to your project:
-
-[source,xml,indent=0,subs="verbatim"]
-----
-
- org.springframework.boot
- spring-boot-properties-migrator
- runtime
-
-----
-
-WARNING: Properties that are added late to the environment, such as when using `@PropertySource`, will not be taken into account.
-
-NOTE: Once you're done with the migration, please make sure to remove this module from your project's dependencies.
-
-To upgrade an existing CLI installation, use the appropriate package manager command (for example, `brew upgrade`).
-If you manually installed the CLI, follow the <>, remembering to update your `PATH` environment variable to remove any older references.
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 19667ddbb263..cd1be837a028 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
@@ -135,9 +135,9 @@ Given the examples above, if we have the following configuration:
The actual application will show the banner (as overridden by configuration) and uses three sources for the `ApplicationContext`.
The application sources are:
-.`MyApplication` (from the code)
-.`MyDatabaseConfig` (from the external config)
-.`MyJmsConfig`(from the external config)
+. `MyApplication` (from the code)
+. `MyDatabaseConfig` (from the external config)
+. `MyJmsConfig`(from the external config)
diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/index.adoc
index fa23f79cd88a..19feeae45c5d 100644
--- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/index.adoc
+++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/index.adoc
@@ -1,18 +1,20 @@
[[index]]
= Spring Boot Reference Documentation
include::authors.adoc[]
+v{spring-boot-version}
include::attributes.adoc[]
+This document is also available as {spring-boot-docs}/html/[Multi-page HTML], {spring-boot-docs}/htmlsingle/[Single page HTML] and {spring-boot-docs}/pdf/spring-boot-reference.pdf[PDF].
-*{spring-boot-version}*
-
The reference documentation consists of the following sections:
[horizontal]
<> :: Legal information.
-<> :: About the Documentation, Getting Help, First Steps, and more.
+<> :: Resources for getting help.
+<> :: About the Documentation, First Steps, and more.
<> :: Introducing Spring Boot, System Requirements, Servlet Containers, Installing Spring Boot, Developing Your First Spring Boot Application
+<> :: Upgrading from 1.x, Upgrading to a new feature release, Upgrading the Spring Boot CLI
<> :: Build Systems, Structuring Your Code, Configuration, Spring Beans and Dependency Injection, DevTools, and more.
<> :: Profiles, Logging, Security, Caching, Spring Integration, Testing, and more.
<> :: Monitoring, Metrics, Auditing, and more.
diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/index.singleadoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/index.singleadoc
index 86f38e908808..25c6c144c6fc 100644
--- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/index.singleadoc
+++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/index.singleadoc
@@ -1,18 +1,22 @@
[[spring-boot-reference-documentation]]
= Spring Boot Reference Documentation
include::authors.adoc[]
+v{spring-boot-version}
include::attributes.adoc[]
+This document is also available as {spring-boot-docs}/html/[Multi-page HTML], {spring-boot-docs}/htmlsingle/[Single page HTML] and {spring-boot-docs}/pdf/spring-boot-reference.pdf[PDF].
-*{spring-boot-version}*
-
include::legal.adoc[leveloffset=+1]
+include::getting-help.adoc[leveloffset=+1]
+
include::documentation.adoc[leveloffset=+1]
include::getting-started.adoc[leveloffset=+1]
+include::upgrading.adoc[leveloffset=+1]
+
include::using.adoc[leveloffset=+1]
include::features.adoc[leveloffset=+1]
diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading.adoc
new file mode 100644
index 000000000000..a7d72ccb797e
--- /dev/null
+++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading.adoc
@@ -0,0 +1,15 @@
+[[upgrading]]
+= Upgrading Spring Boot
+include::attributes.adoc[]
+
+Instructions for how to upgrade from earlier versions of Spring Boot are provided on the project {github-wiki}[wiki].
+Follow the links in the {github-wiki}#release-notes[release notes] section to find the version that you want to upgrade to.
+
+Upgrading instructions are always the first item in the release notes.
+If you are more than one release behind, please make sure that you also review the release notes of the versions that you jumped.
+
+include::upgrading/from-1x.adoc[]
+
+include::upgrading/to-feature.adoc[]
+
+include::upgrading/cli.adoc[]
\ No newline at end of file
diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading/cli.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading/cli.adoc
new file mode 100644
index 000000000000..692c238105e8
--- /dev/null
+++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading/cli.adoc
@@ -0,0 +1,5 @@
+[[upgrading.cli]]
+== Upgrading the Spring Boot CLI
+
+To upgrade an existing CLI installation, use the appropriate package manager command (for example, `brew upgrade`).
+If you manually installed the CLI, follow the <>, remembering to update your `PATH` environment variable to remove any older references.
\ No newline at end of file
diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading/from-1x.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading/from-1x.adoc
new file mode 100644
index 000000000000..10b440cac895
--- /dev/null
+++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading/from-1x.adoc
@@ -0,0 +1,5 @@
+[[upgrading.from-1x]]
+== Upgrading from 1.x
+
+If you are upgrading from the `1.x` release of Spring Boot, check the {github-wiki}/Spring-Boot-2.0-Migration-Guide["`migration guide`" on the project wiki] that provides detailed upgrade instructions.
+Check also the {github-wiki}["`release notes`"] for a list of "`new and noteworthy`" features for each release.
\ No newline at end of file
diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading/to-feature.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading/to-feature.adoc
new file mode 100644
index 000000000000..1432559e6377
--- /dev/null
+++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/upgrading/to-feature.adoc
@@ -0,0 +1,19 @@
+[[upgrading.to-feature]]
+== Upgrading to a new feature release
+
+When upgrading to a new feature release, some properties may have been renamed or removed.
+Spring Boot provides a way to analyze your application's environment and print diagnostics at startup, but also temporarily migrate properties at runtime for you.
+To enable that feature, add the following dependency to your project:
+
+[source,xml,indent=0,subs="verbatim"]
+----
+
+ org.springframework.boot
+ spring-boot-properties-migrator
+ runtime
+
+----
+
+WARNING: Properties that are added late to the environment, such as when using `@PropertySource`, will not be taken into account.
+
+NOTE: Once you're done with the migration, please make sure to remove this module from your project's dependencies.
\ No newline at end of file
diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using.adoc
index 7f45c5faacc2..832abd9e8679 100644
--- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using.adoc
+++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using.adoc
@@ -1,5 +1,5 @@
[[using]]
-= Using Spring Boot
+= Developing with Spring Boot
include::attributes.adoc[]
diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/using-the-springbootapplication-annotation.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/using-the-springbootapplication-annotation.adoc
index 598f6178c768..f3edc3df127c 100644
--- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/using-the-springbootapplication-annotation.adoc
+++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/using-the-springbootapplication-annotation.adoc
@@ -5,7 +5,8 @@ A single `@SpringBootApplication` annotation can be used to enable those three f
* `@EnableAutoConfiguration`: enable <>
* `@ComponentScan`: enable `@Component` scan on the package where the application is located (see <>)
-* `@Configuration`: allow to register extra beans in the context or import additional configuration classes
+* `@SpringBootConfiguration`: enable registration of extra beans in the context or the import of additional configuration classes.
+An alternative to Spring's standard `@Configuration` that aids <> in your integration tests.
[source,java,indent=0,subs="verbatim"]
----
@@ -24,5 +25,5 @@ For instance, you may not want to use component scan or configuration properties
include::{docs-java}/using/usingthespringbootapplicationannotation/individualannotations/MyApplication.java[]
----
-In this example, `Application` is just like any other Spring Boot application except that `@Component`-annotated classes and `@ConfigurationProperties`-annotated classes are not detected automatically and the user-defined beans are imported explicitly (see `@Import`).
+In this example, `MyApplication` is just like any other Spring Boot application except that `@Component`-annotated classes and `@ConfigurationProperties`-annotated classes are not detected automatically and the user-defined beans are imported explicitly (see `@Import`).
====
diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/using/usingthespringbootapplicationannotation/individualannotations/MyApplication.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/using/usingthespringbootapplicationannotation/individualannotations/MyApplication.java
index 5a8ec3c85b85..9f8b87e820f4 100644
--- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/using/usingthespringbootapplicationannotation/individualannotations/MyApplication.java
+++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/using/usingthespringbootapplicationannotation/individualannotations/MyApplication.java
@@ -17,11 +17,11 @@
package org.springframework.boot.docs.using.usingthespringbootapplicationannotation.individualannotations;
import org.springframework.boot.SpringApplication;
+import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
-import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
-@Configuration(proxyBeanMethods = false)
+@SpringBootConfiguration(proxyBeanMethods = false)
@EnableAutoConfiguration
@Import({ SomeConfiguration.class, AnotherConfiguration.class })
public class MyApplication {
diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/using/usingthespringbootapplicationannotation/springapplication/MyApplication.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/using/usingthespringbootapplicationannotation/springapplication/MyApplication.java
index 5bea9d330631..d8cdde46587e 100644
--- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/using/usingthespringbootapplicationannotation/springapplication/MyApplication.java
+++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/using/usingthespringbootapplicationannotation/springapplication/MyApplication.java
@@ -19,7 +19,8 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
-@SpringBootApplication // same as @Configuration @EnableAutoConfiguration @ComponentScan
+@SpringBootApplication // same as @SpringBootConfiguration @EnableAutoConfiguration
+ // @ComponentScan
public class MyApplication {
public static void main(String[] args) {
diff --git a/spring-boot-project/spring-boot-starters/spring-boot-starter-oauth2-client/build.gradle b/spring-boot-project/spring-boot-starters/spring-boot-starter-oauth2-client/build.gradle
index 8aeed98c7957..3df25780ed6d 100644
--- a/spring-boot-project/spring-boot-starters/spring-boot-starter-oauth2-client/build.gradle
+++ b/spring-boot-project/spring-boot-starters/spring-boot-starter-oauth2-client/build.gradle
@@ -7,18 +7,10 @@ description = "Starter for using Spring Security's OAuth2/OpenID Connect client
dependencies {
api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter"))
api("com.sun.mail:jakarta.mail")
- api("org.springframework.security:spring-security-config") {
- exclude group: "org.springframework.security", module: "spring-security-crypto"
- exclude group: "org.jetbrains.kotlin", module: "kotlin-stdlib-jdk8"
- }
- api("org.springframework.security:spring-security-core") {
- exclude group: "org.springframework.security", module: "spring-security-crypto"
- }
+ api("org.springframework.security:spring-security-config")
+ api("org.springframework.security:spring-security-core")
api("org.springframework.security:spring-security-oauth2-client") {
exclude group: "com.sun.mail", module: "javax.mail"
- exclude group: "org.springframework.security", module: "spring-security-crypto"
- }
- api("org.springframework.security:spring-security-oauth2-jose") {
- exclude group: "org.springframework.security", module: "spring-security-crypto"
}
+ api("org.springframework.security:spring-security-oauth2-jose")
}
diff --git a/spring-boot-project/spring-boot-starters/spring-boot-starter-oauth2-resource-server/build.gradle b/spring-boot-project/spring-boot-starters/spring-boot-starter-oauth2-resource-server/build.gradle
index 34b09afa5741..a91da01d8d68 100644
--- a/spring-boot-project/spring-boot-starters/spring-boot-starter-oauth2-resource-server/build.gradle
+++ b/spring-boot-project/spring-boot-starters/spring-boot-starter-oauth2-resource-server/build.gradle
@@ -6,17 +6,8 @@ description = "Starter for using Spring Security's OAuth2 resource server featur
dependencies {
api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter"))
- api("org.springframework.security:spring-security-config") {
- exclude group: "org.springframework.security", module: "spring-security-crypto"
- exclude group: "org.jetbrains.kotlin", module: "kotlin-stdlib-jdk8"
- }
- api("org.springframework.security:spring-security-core") {
- exclude group: "org.springframework.security", module: "spring-security-crypto"
- }
- api("org.springframework.security:spring-security-oauth2-resource-server") {
- exclude group: "org.springframework.security", module: "spring-security-crypto"
- }
- api("org.springframework.security:spring-security-oauth2-jose") {
- exclude group: "org.springframework.security", module: "spring-security-crypto"
- }
+ api("org.springframework.security:spring-security-config")
+ api("org.springframework.security:spring-security-core")
+ api("org.springframework.security:spring-security-oauth2-resource-server")
+ api("org.springframework.security:spring-security-oauth2-jose")
}
diff --git a/spring-boot-project/spring-boot-starters/spring-boot-starter-security/build.gradle b/spring-boot-project/spring-boot-starters/spring-boot-starter-security/build.gradle
index eedd49405b80..c6e63f44efa3 100644
--- a/spring-boot-project/spring-boot-starters/spring-boot-starter-security/build.gradle
+++ b/spring-boot-project/spring-boot-starters/spring-boot-starter-security/build.gradle
@@ -7,11 +7,6 @@ description = "Starter for using Spring Security"
dependencies {
api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter"))
api("org.springframework:spring-aop")
- api("org.springframework.security:spring-security-config") {
- exclude group: "org.springframework.security", module: "spring-security-crypto"
- exclude group: "org.jetbrains.kotlin", module: "kotlin-stdlib-jdk8"
- }
- api("org.springframework.security:spring-security-web") {
- exclude group: "org.springframework.security", module: "spring-security-crypto"
- }
+ api("org.springframework.security:spring-security-config")
+ api("org.springframework.security:spring-security-web")
}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/index.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/index.adoc
index f48b52878830..0558da915d18 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/index.adoc
+++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/index.adoc
@@ -1,6 +1,8 @@
[[spring-boot-gradle-plugin-documentation]]
= Spring Boot Gradle Plugin Reference Guide
-Andy Wilkinson, Scott Frederick
+Andy Wilkinson; Scott Frederick
+v{gradle-project-version}
+:!version-label:
:doctype: book
:toc: left
:toclevels: 4
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java
index 2ab46db58e4f..b698ce9b1275 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java
@@ -19,7 +19,6 @@
import java.io.File;
import java.util.Collections;
import java.util.List;
-import java.util.Optional;
import java.util.Set;
import org.gradle.api.Action;
@@ -41,6 +40,7 @@
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.SourceSet;
+import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.bundling.Jar;
import org.gradle.api.tasks.compile.JavaCompile;
@@ -195,7 +195,11 @@ private void configureAdditionalMetadataLocations(Project project) {
}
private void configureAdditionalMetadataLocations(JavaCompile compile) {
- compile.doFirst(new AdditionalMetadataLocationsConfigurer());
+ SourceSetContainer sourceSets = compile.getProject().getConvention().getPlugin(JavaPluginConvention.class)
+ .getSourceSets();
+ sourceSets.stream().filter((candidate) -> candidate.getCompileJavaTaskName().equals(compile.getName()))
+ .map((match) -> match.getResources().getSrcDirs()).findFirst()
+ .ifPresent((locations) -> compile.doFirst(new AdditionalMetadataLocationsConfigurer(locations)));
}
private void configureDevelopmentOnlyConfiguration(Project project) {
@@ -225,7 +229,13 @@ private void configureDevelopmentOnlyConfiguration(Project project) {
* inner-class rather than a lambda due to
* https://github.com/gradle/gradle/issues/5510.
*/
- private static class AdditionalMetadataLocationsConfigurer implements Action {
+ private static final class AdditionalMetadataLocationsConfigurer implements Action {
+
+ private final Set locations;
+
+ private AdditionalMetadataLocationsConfigurer(Set locations) {
+ this.locations = locations;
+ }
@Override
public void execute(Task task) {
@@ -234,8 +244,7 @@ public void execute(Task task) {
}
JavaCompile compile = (JavaCompile) task;
if (hasConfigurationProcessorOnClasspath(compile)) {
- findMatchingSourceSet(compile)
- .ifPresent((sourceSet) -> configureAdditionalMetadataLocations(compile, sourceSet));
+ configureAdditionalMetadataLocations(compile);
}
}
@@ -246,15 +255,10 @@ private boolean hasConfigurationProcessorOnClasspath(JavaCompile compile) {
.anyMatch((name) -> name.startsWith("spring-boot-configuration-processor"));
}
- private Optional findMatchingSourceSet(JavaCompile compile) {
- return compile.getProject().getConvention().getPlugin(JavaPluginConvention.class).getSourceSets().stream()
- .filter((sourceSet) -> sourceSet.getCompileJavaTaskName().equals(compile.getName())).findFirst();
- }
-
- private void configureAdditionalMetadataLocations(JavaCompile compile, SourceSet sourceSet) {
- String locations = StringUtils.collectionToCommaDelimitedString(sourceSet.getResources().getSrcDirs());
+ private void configureAdditionalMetadataLocations(JavaCompile compile) {
compile.getOptions().getCompilerArgs()
- .add("-Aorg.springframework.boot.configurationprocessor.additionalMetadataLocations=" + locations);
+ .add("-Aorg.springframework.boot.configurationprocessor.additionalMetadataLocations="
+ + StringUtils.collectionToCommaDelimitedString(this.locations));
}
}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/KotlinPluginAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/KotlinPluginAction.java
index 896cc7e9290f..cbd18e26e921 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/KotlinPluginAction.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/KotlinPluginAction.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -33,14 +33,19 @@ class KotlinPluginAction implements PluginApplicationAction {
@Override
public void execute(Project project) {
- String kotlinVersion = project.getPlugins().getPlugin(KotlinPluginWrapper.class).getKotlinPluginVersion();
ExtraPropertiesExtension extraProperties = project.getExtensions().getExtraProperties();
if (!extraProperties.has("kotlin.version")) {
+ String kotlinVersion = getKotlinVersion(project);
extraProperties.set("kotlin.version", kotlinVersion);
}
enableJavaParametersOption(project);
}
+ @SuppressWarnings("deprecation")
+ private String getKotlinVersion(Project project) {
+ return project.getPlugins().getPlugin(KotlinPluginWrapper.class).getKotlinPluginVersion();
+ }
+
private void enableJavaParametersOption(Project project) {
project.getTasks().withType(KotlinCompile.class,
(compile) -> compile.getKotlinOptions().setJavaParameters(true));
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootPlugin.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootPlugin.java
index 049517a31f2e..015d5ac46e58 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootPlugin.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootPlugin.java
@@ -99,8 +99,8 @@ public void apply(Project project) {
private void verifyGradleVersion() {
GradleVersion currentVersion = GradleVersion.current();
if (currentVersion.compareTo(GradleVersion.version("6.8")) < 0) {
- throw new GradleException(
- "Spring Boot plugin requires Gradle 6.8.x or 7.x. " + "The current version is " + currentVersion);
+ throw new GradleException("Spring Boot plugin requires Gradle 6.8.x, 6.9.x, or 7.x. "
+ + "The current version is " + currentVersion);
}
}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LayerResolver.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LayerResolver.java
index 8efa6e6a3e6e..a68a1007c09d 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LayerResolver.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LayerResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2021 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 org.springframework.boot.gradle.tasks.bundling.ResolvedDependencies.DependencyDescriptor;
import org.springframework.boot.loader.tools.Layer;
import org.springframework.boot.loader.tools.Library;
+import org.springframework.boot.loader.tools.LibraryCoordinates;
/**
* Resolver backed by a {@link LayeredSpec} that provides the destination {@link Layer}
@@ -77,9 +78,12 @@ Iterable getLayers() {
private Library asLibrary(FileCopyDetails details) {
File file = details.getFile();
DependencyDescriptor dependency = this.resolvedDependencies.find(file);
- return (dependency != null)
- ? new Library(null, file, null, dependency.getCoordinates(), false, dependency.isProjectDependency())
- : new Library(file, null);
+ if (dependency == null) {
+ return new Library(null, file, null, null, false, false, true);
+ }
+ LibraryCoordinates coordinates = dependency.getCoordinates();
+ boolean projectDependency = dependency.isProjectDependency();
+ return new Library(null, file, null, coordinates, false, projectDependency, true);
}
}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleCompatibilityExtension.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleCompatibilityExtension.java
index 0d640c767f87..63ca12050d2a 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleCompatibilityExtension.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleCompatibilityExtension.java
@@ -48,10 +48,10 @@ final class GradleCompatibilityExtension implements TestTemplateInvocationContex
static {
JavaVersion javaVersion = JavaVersion.current();
if (javaVersion.isCompatibleWith(JavaVersion.VERSION_16)) {
- GRADLE_VERSIONS = Arrays.asList("7.0.2", "7.1-rc-2");
+ GRADLE_VERSIONS = Arrays.asList("7.0.2", "7.1");
}
else {
- GRADLE_VERSIONS = Arrays.asList("6.8.3", "current", "7.0.2", "7.1-rc-2");
+ GRADLE_VERSIONS = Arrays.asList("6.8.3", "current", "7.0.2", "7.1");
}
}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests.java
index 6c06324d8e00..9633fc18303c 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests.java
@@ -31,11 +31,11 @@
import static org.assertj.core.api.Assertions.assertThat;
/**
- * Integration tests for {@link WarPluginAction}.
+ * Integration tests for {@link JavaPluginAction}.
*
* @author Andy Wilkinson
*/
-@GradleCompatibility
+@GradleCompatibility(configurationCache = true)
class JavaPluginActionIntegrationTests {
GradleBuild gradleBuild;
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/SpringBootPluginIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/SpringBootPluginIntegrationTests.java
index 0921e2a08b85..d51b3f85f080 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/SpringBootPluginIntegrationTests.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/SpringBootPluginIntegrationTests.java
@@ -41,8 +41,8 @@ class SpringBootPluginIntegrationTests {
@Test
void failFastWithVersionOfGradle6LowerThanRequired() {
BuildResult result = this.gradleBuild.gradleVersion("6.7.1").buildAndFail();
- assertThat(result.getOutput())
- .contains("Spring Boot plugin requires Gradle 6.8.x or 7.x. The current version is Gradle 6.7.1");
+ assertThat(result.getOutput()).contains(
+ "Spring Boot plugin requires Gradle 6.8.x, 6.9.x, or 7.x. The current version is Gradle 6.7.1");
}
@DisabledForJreRange(min = JRE.JAVA_16)
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/testkit/GradleBuild.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/testkit/GradleBuild.java
index 629b0fe3f326..d2f3a7be46f4 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/testkit/GradleBuild.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/testkit/GradleBuild.java
@@ -50,6 +50,7 @@
import org.jetbrains.kotlin.gradle.model.KotlinProject;
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerPluginSupportPlugin;
import org.jetbrains.kotlin.gradle.plugin.KotlinPlugin;
+import org.jetbrains.kotlin.project.model.LanguageSettings;
import org.tomlj.Toml;
import org.springframework.asm.ClassVisitor;
@@ -113,6 +114,7 @@ private List pluginClasspath() {
new File(pathOfJarContaining(KotlinPlugin.class)), new File(pathOfJarContaining(KotlinProject.class)),
new File(pathOfJarContaining(KotlinCompilerClient.class)),
new File(pathOfJarContaining(KotlinCompilerPluginSupportPlugin.class)),
+ new File(pathOfJarContaining(LanguageSettings.class)),
new File(pathOfJarContaining(ArchiveEntry.class)), new File(pathOfJarContaining(BuildRequest.class)),
new File(pathOfJarContaining(HttpClientConnectionManager.class)),
new File(pathOfJarContaining(HttpRequest.class)), new File(pathOfJarContaining(Module.class)),
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests.gradle
index 7ffcce40abf6..e96ca9aa2819 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests.gradle
+++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests.gradle
@@ -37,9 +37,13 @@ task('configurationAttributes') {
}
task('configurationResolvabilityAndConsumability') {
- doFirst {
- def configuration = configurations.findByName(configurationName)
- println "canBeResolved: ${configuration.canBeResolved}"
- println "canBeConsumed: ${configuration.canBeConsumed}"
+ if (project.hasProperty("configurationName")) {
+ Configuration configuration = configurations.findByName(configurationName)
+ def canBeResolved = configuration.canBeResolved
+ def canBeConsumed = configuration.canBeConsumed
+ doFirst {
+ println "canBeResolved: ${canBeResolved}"
+ println "canBeConsumed: ${canBeConsumed}"
+ }
}
}
\ No newline at end of file
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AbstractJarWriter.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AbstractJarWriter.java
index b3e2da1b5b20..6e53cb09fc5a 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AbstractJarWriter.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AbstractJarWriter.java
@@ -30,7 +30,7 @@
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
-import java.util.function.Predicate;
+import java.util.function.Function;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
@@ -89,24 +89,33 @@ public void writeManifest(Manifest manifest) throws IOException {
* Write all entries from the specified jar file.
* @param jarFile the source jar file
* @throws IOException if the entries cannot be written
+ * @deprecated since 2.4.8 for removal in 2.6.0
*/
+ @Deprecated
public void writeEntries(JarFile jarFile) throws IOException {
- writeEntries(jarFile, EntryTransformer.NONE, UnpackHandler.NEVER, (name) -> false);
+ writeEntries(jarFile, EntryTransformer.NONE, UnpackHandler.NEVER, (entry) -> null);
}
final void writeEntries(JarFile jarFile, EntryTransformer entryTransformer, UnpackHandler unpackHandler,
- Predicate libraryPredicate) throws IOException {
+ Function libraryLookup) throws IOException {
Enumeration entries = jarFile.entries();
while (entries.hasMoreElements()) {
- JarArchiveEntry entry = new JarArchiveEntry(entries.nextElement());
- setUpEntry(jarFile, entry);
- try (ZipHeaderPeekInputStream inputStream = new ZipHeaderPeekInputStream(jarFile.getInputStream(entry))) {
- EntryWriter entryWriter = new InputStreamEntryWriter(inputStream);
- JarArchiveEntry transformedEntry = entryTransformer.transform(entry);
- if (transformedEntry != null) {
- boolean updateLayerIndex = !libraryPredicate.test(entry.getName());
- writeEntry(transformedEntry, entryWriter, unpackHandler, updateLayerIndex);
- }
+ JarEntry entry = entries.nextElement();
+ Library library = libraryLookup.apply(entry);
+ if (library == null || library.isIncluded()) {
+ writeEntry(jarFile, entryTransformer, unpackHandler, new JarArchiveEntry(entry), library);
+ }
+ }
+ }
+
+ private void writeEntry(JarFile jarFile, EntryTransformer entryTransformer, UnpackHandler unpackHandler,
+ JarArchiveEntry entry, Library library) throws IOException {
+ setUpEntry(jarFile, entry);
+ try (ZipHeaderPeekInputStream inputStream = new ZipHeaderPeekInputStream(jarFile.getInputStream(entry))) {
+ EntryWriter entryWriter = new InputStreamEntryWriter(inputStream);
+ JarArchiveEntry transformedEntry = entryTransformer.transform(entry);
+ if (transformedEntry != null) {
+ writeEntry(transformedEntry, library, entryWriter, unpackHandler);
}
}
}
@@ -160,15 +169,7 @@ public void writeNestedLibrary(String location, Library library) throws IOExcept
entry.setTime(getNestedLibraryTime(library));
new CrcAndSize(library::openStream).setupStoredEntry(entry);
try (InputStream inputStream = library.openStream()) {
- writeEntry(entry, new InputStreamEntryWriter(inputStream), new LibraryUnpackHandler(library), false);
- updateLayerIndex(entry.getName(), library);
- }
- }
-
- private void updateLayerIndex(String name, Library library) {
- if (this.layers != null) {
- Layer layer = this.layers.getLayer(library);
- this.layersIndex.add(layer, name);
+ writeEntry(entry, library, new InputStreamEntryWriter(inputStream), new LibraryUnpackHandler(library));
}
}
@@ -249,20 +250,20 @@ private boolean isClassEntry(JarEntry entry) {
}
private void writeEntry(JarArchiveEntry entry, EntryWriter entryWriter) throws IOException {
- writeEntry(entry, entryWriter, UnpackHandler.NEVER, true);
+ writeEntry(entry, null, entryWriter, UnpackHandler.NEVER);
}
/**
* Perform the actual write of a {@link JarEntry}. All other write methods delegate to
* this one.
* @param entry the entry to write
+ * @param library the library for the entry or {@code null}
* @param entryWriter the entry writer or {@code null} if there is no content
* @param unpackHandler handles possible unpacking for the entry
- * @param updateLayerIndex if the layer index should be updated
* @throws IOException in case of I/O errors
*/
- private void writeEntry(JarArchiveEntry entry, EntryWriter entryWriter, UnpackHandler unpackHandler,
- boolean updateLayerIndex) throws IOException {
+ private void writeEntry(JarArchiveEntry entry, Library library, EntryWriter entryWriter,
+ UnpackHandler unpackHandler) throws IOException {
String name = entry.getName();
writeParentDirectoryEntries(name);
if (this.writtenEntries.add(name)) {
@@ -273,16 +274,14 @@ private void writeEntry(JarArchiveEntry entry, EntryWriter entryWriter, UnpackHa
entry.setSize(entryWriter.size());
}
entryWriter = addUnpackCommentIfNecessary(entry, entryWriter, unpackHandler);
- if (updateLayerIndex) {
- updateLayerIndex(entry);
- }
+ updateLayerIndex(entry, library);
writeToArchive(entry, entryWriter);
}
}
- private void updateLayerIndex(JarArchiveEntry entry) {
+ private void updateLayerIndex(JarArchiveEntry entry, Library library) {
if (this.layers != null && !entry.getName().endsWith("/")) {
- Layer layer = this.layers.getLayer(entry.getName());
+ Layer layer = (library != null) ? this.layers.getLayer(library) : this.layers.getLayer(entry.getName());
this.layersIndex.add(layer, entry.getName());
}
}
@@ -294,7 +293,7 @@ private void writeParentDirectoryEntries(String name) throws IOException {
while (parent.lastIndexOf('/') != -1) {
parent = parent.substring(0, parent.lastIndexOf('/'));
if (!parent.isEmpty()) {
- writeEntry(new JarArchiveEntry(parent + "/"), null, UnpackHandler.NEVER, false);
+ writeEntry(new JarArchiveEntry(parent + "/"), null, null, UnpackHandler.NEVER);
}
}
}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarModeLibrary.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarModeLibrary.java
index acacd916c904..49508ab51879 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarModeLibrary.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarModeLibrary.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -42,7 +42,7 @@ public class JarModeLibrary extends Library {
}
public JarModeLibrary(LibraryCoordinates coordinates) {
- super(getJarName(coordinates), null, LibraryScope.RUNTIME, coordinates, false);
+ super(getJarName(coordinates), null, LibraryScope.RUNTIME, coordinates, false, false, true);
}
private static LibraryCoordinates createCoordinates(String artifactId) {
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Library.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Library.java
index 9c82bae24d9d..20315ce7f4d0 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Library.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Library.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -43,13 +43,15 @@ public class Library {
private final boolean local;
+ private final boolean included;
+
/**
* Create a new {@link Library}.
* @param file the source file
* @param scope the scope of the library
*/
public Library(File file, LibraryScope scope) {
- this(file, scope, false);
+ this(null, file, scope, null, false, false, true);
}
/**
@@ -57,7 +59,10 @@ public Library(File file, LibraryScope scope) {
* @param file the source file
* @param scope the scope of the library
* @param unpackRequired if the library needs to be unpacked before it can be used
+ * @deprecated since 2.4.8 for removal in 2.6.0 in favor of
+ * {@link #Library(String, File, LibraryScope, LibraryCoordinates, boolean, boolean, boolean)}
*/
+ @Deprecated
public Library(File file, LibraryScope scope, boolean unpackRequired) {
this(null, file, scope, unpackRequired);
}
@@ -69,7 +74,10 @@ public Library(File file, LibraryScope scope, boolean unpackRequired) {
* @param file the source file
* @param scope the scope of the library
* @param unpackRequired if the library needs to be unpacked before it can be used
+ * @deprecated since 2.4.8 for removal in 2.6.0 in favor of
+ * {@link #Library(String, File, LibraryScope, LibraryCoordinates, boolean, boolean, boolean)}
*/
+ @Deprecated
public Library(String name, File file, LibraryScope scope, boolean unpackRequired) {
this(name, file, scope, null, unpackRequired);
}
@@ -82,7 +90,10 @@ public Library(String name, File file, LibraryScope scope, boolean unpackRequire
* @param scope the scope of the library
* @param coordinates the library coordinates or {@code null}
* @param unpackRequired if the library needs to be unpacked before it can be used
+ * @deprecated since 2.4.8 for removal in 2.6.0 in favor of
+ * {@link #Library(String, File, LibraryScope, LibraryCoordinates, boolean, boolean, boolean)}
*/
+ @Deprecated
public Library(String name, File file, LibraryScope scope, LibraryCoordinates coordinates, boolean unpackRequired) {
this(name, file, scope, coordinates, unpackRequired, false);
}
@@ -98,15 +109,37 @@ public Library(String name, File file, LibraryScope scope, LibraryCoordinates co
* @param local if the library is local (part of the same build) to the application
* that is being packaged
* @since 2.4.0
+ * @deprecated since 2.4.8 for removal in 2.6.0 in favor of
+ * {@link #Library(String, File, LibraryScope, LibraryCoordinates, boolean, boolean, boolean)}
*/
+ @Deprecated
public Library(String name, File file, LibraryScope scope, LibraryCoordinates coordinates, boolean unpackRequired,
boolean local) {
+ this(name, file, scope, coordinates, unpackRequired, local, true);
+ }
+
+ /**
+ * Create a new {@link Library}.
+ * @param name the name of the library as it should be written or {@code null} to use
+ * the file name
+ * @param file the source file
+ * @param scope the scope of the library
+ * @param coordinates the library coordinates or {@code null}
+ * @param unpackRequired if the library needs to be unpacked before it can be used
+ * @param local if the library is local (part of the same build) to the application
+ * that is being packaged
+ * @param included if the library is included in the fat jar
+ * @since 2.4.8
+ */
+ public Library(String name, File file, LibraryScope scope, LibraryCoordinates coordinates, boolean unpackRequired,
+ boolean local, boolean included) {
this.name = (name != null) ? name : file.getName();
this.file = file;
this.scope = scope;
this.coordinates = coordinates;
this.unpackRequired = unpackRequired;
this.local = local;
+ this.included = included;
}
/**
@@ -172,4 +205,12 @@ public boolean isLocal() {
return this.local;
}
+ /**
+ * Return if the library is included in the fat jar.
+ * @return if the library is included
+ */
+ public boolean isIncluded() {
+ return this.included;
+ }
+
}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Packager.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Packager.java
index 81dbc59a60d8..7f5edd7476fc 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Packager.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Packager.java
@@ -25,7 +25,9 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
@@ -191,14 +193,18 @@ protected final boolean isAlreadyPackaged(File file) {
protected final void write(JarFile sourceJar, Libraries libraries, AbstractJarWriter writer) throws IOException {
Assert.notNull(libraries, "Libraries must not be null");
- WritableLibraries writeableLibraries = new WritableLibraries(libraries);
+ write(sourceJar, writer, new PackagedLibraries(libraries));
+ }
+
+ private void write(JarFile sourceJar, AbstractJarWriter writer, PackagedLibraries libraries) throws IOException {
if (isLayered()) {
writer.useLayers(this.layers, this.layersIndex);
}
writer.writeManifest(buildManifest(sourceJar));
writeLoaderClasses(writer);
- writer.writeEntries(sourceJar, getEntityTransformer(), writeableLibraries, writeableLibraries::containsEntry);
- writeableLibraries.write(writer);
+ writer.writeEntries(sourceJar, getEntityTransformer(), libraries.getUnpackHandler(),
+ libraries.getLibraryLookup());
+ libraries.write(writer);
if (isLayered()) {
writeLayerIndex(writer);
}
@@ -451,11 +457,15 @@ private boolean isTransformable(JarArchiveEntry entry) {
* An {@link UnpackHandler} that determines that an entry needs to be unpacked if a
* library that requires unpacking has a matching entry name.
*/
- private final class WritableLibraries implements UnpackHandler {
+ private final class PackagedLibraries {
private final Map libraries = new LinkedHashMap<>();
- WritableLibraries(Libraries libraries) throws IOException {
+ private final UnpackHandler unpackHandler;
+
+ private final Function libraryLookup;
+
+ PackagedLibraries(Libraries libraries) throws IOException {
libraries.doWithLibraries((library) -> {
if (isZip(library::openStream)) {
addLibrary(library);
@@ -464,6 +474,8 @@ private final class WritableLibraries implements UnpackHandler {
if (isLayered() && Packager.this.includeRelevantJarModeJars) {
addLibrary(JarModeLibrary.LAYER_TOOLS);
}
+ this.unpackHandler = new PackagedLibrariesUnpackHandler();
+ this.libraryLookup = this::lookup;
}
private void addLibrary(Library library) {
@@ -475,41 +487,57 @@ private void addLibrary(Library library) {
}
}
- @Override
- public boolean requiresUnpack(String name) {
- Library library = this.libraries.get(name);
- return library != null && library.isUnpackRequired();
+ private Library lookup(JarEntry entry) {
+ return this.libraries.get(entry.getName());
}
- @Override
- public String sha1Hash(String name) throws IOException {
- Library library = this.libraries.get(name);
- Assert.notNull(library, () -> "No library found for entry name '" + name + "'");
- return Digest.sha1(library::openStream);
+ UnpackHandler getUnpackHandler() {
+ return this.unpackHandler;
}
- boolean containsEntry(String name) {
- return this.libraries.containsKey(name);
+ Function getLibraryLookup() {
+ return this.libraryLookup;
}
- private void write(AbstractJarWriter writer) throws IOException {
+ void write(AbstractJarWriter writer) throws IOException {
+ List writtenPaths = new ArrayList<>();
for (Entry entry : this.libraries.entrySet()) {
String path = entry.getKey();
Library library = entry.getValue();
- String location = path.substring(0, path.lastIndexOf('/') + 1);
- writer.writeNestedLibrary(location, library);
+ if (library.isIncluded()) {
+ String location = path.substring(0, path.lastIndexOf('/') + 1);
+ writer.writeNestedLibrary(location, library);
+ writtenPaths.add(path);
+ }
}
- if (Packager.this.layout instanceof RepackagingLayout) {
- writeClasspathIndex(getLayout(), writer);
+ if (getLayout() instanceof RepackagingLayout) {
+ writeClasspathIndex(writtenPaths, (RepackagingLayout) getLayout(), writer);
}
}
- private void writeClasspathIndex(Layout layout, AbstractJarWriter writer) throws IOException {
- List names = this.libraries.keySet().stream().map((path) -> "- \"" + path + "\"")
- .collect(Collectors.toList());
+ private void writeClasspathIndex(List paths, RepackagingLayout layout, AbstractJarWriter writer)
+ throws IOException {
+ List names = paths.stream().map((path) -> "- \"" + path + "\"").collect(Collectors.toList());
writer.writeIndexFile(layout.getClasspathIndexFileLocation(), names);
}
+ private class PackagedLibrariesUnpackHandler implements UnpackHandler {
+
+ @Override
+ public boolean requiresUnpack(String name) {
+ Library library = PackagedLibraries.this.libraries.get(name);
+ return library != null && library.isUnpackRequired();
+ }
+
+ @Override
+ public String sha1Hash(String name) throws IOException {
+ Library library = PackagedLibraries.this.libraries.get(name);
+ Assert.notNull(library, () -> "No library found for entry name '" + name + "'");
+ return Digest.sha1(library::openStream);
+ }
+
+ }
+
}
}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/AbstractPackagerTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/AbstractPackagerTests.java
index 8bcfc938269d..83389a6e18a0 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/AbstractPackagerTests.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/AbstractPackagerTests.java
@@ -197,9 +197,9 @@ void libraries() throws Exception {
libJarFile.setLastModified(JAN_1_1980);
P packager = createPackager();
execute(packager, (callback) -> {
- callback.library(new Library(libJarFile, LibraryScope.COMPILE));
- callback.library(new Library(libJarFileToUnpack, LibraryScope.COMPILE, true));
- callback.library(new Library(libNonJarFile, LibraryScope.COMPILE));
+ callback.library(newLibrary(libJarFile, LibraryScope.COMPILE, false));
+ callback.library(newLibrary(libJarFileToUnpack, LibraryScope.COMPILE, true));
+ callback.library(newLibrary(libNonJarFile, LibraryScope.COMPILE, false));
});
assertThat(hasPackagedEntry("BOOT-INF/lib/" + libJarFile.getName())).isTrue();
assertThat(hasPackagedEntry("BOOT-INF/lib/" + libJarFileToUnpack.getName())).isTrue();
@@ -226,9 +226,9 @@ void classPathIndex() throws Exception {
File file = this.testJarFile.getFile();
P packager = createPackager(file);
execute(packager, (callback) -> {
- callback.library(new Library(libJarFile1, LibraryScope.COMPILE));
- callback.library(new Library(libJarFile2, LibraryScope.COMPILE));
- callback.library(new Library(libJarFile3, LibraryScope.COMPILE));
+ callback.library(newLibrary(libJarFile1, LibraryScope.COMPILE, false));
+ callback.library(newLibrary(libJarFile2, LibraryScope.COMPILE, false));
+ callback.library(newLibrary(libJarFile3, LibraryScope.COMPILE, false));
});
assertThat(hasPackagedEntry("BOOT-INF/classpath.idx")).isTrue();
String index = getPackagedEntryContent("BOOT-INF/classpath.idx");
@@ -258,9 +258,9 @@ void layersIndex() throws Exception {
packager.setLayers(layers);
packager.setIncludeRelevantJarModeJars(false);
execute(packager, (callback) -> {
- callback.library(new Library(libJarFile1, LibraryScope.COMPILE));
- callback.library(new Library(libJarFile2, LibraryScope.COMPILE));
- callback.library(new Library(libJarFile3, LibraryScope.COMPILE));
+ callback.library(newLibrary(libJarFile1, LibraryScope.COMPILE, false));
+ callback.library(newLibrary(libJarFile2, LibraryScope.COMPILE, false));
+ callback.library(newLibrary(libJarFile3, LibraryScope.COMPILE, false));
});
assertThat(hasPackagedEntry("BOOT-INF/classpath.idx")).isTrue();
String classpathIndex = getPackagedEntryContent("BOOT-INF/classpath.idx");
@@ -316,8 +316,8 @@ void duplicateLibraries() throws Exception {
this.testJarFile.addClass("a/b/C.class", ClassWithMainMethod.class);
P packager = createPackager();
assertThatIllegalStateException().isThrownBy(() -> execute(packager, (callback) -> {
- callback.library(new Library(libJarFile, LibraryScope.COMPILE, false));
- callback.library(new Library(libJarFile, LibraryScope.COMPILE, false));
+ callback.library(newLibrary(libJarFile, LibraryScope.COMPILE, false));
+ callback.library(newLibrary(libJarFile, LibraryScope.COMPILE, false));
})).withMessageContaining("Duplicate library");
}
@@ -334,7 +334,7 @@ void customLayout() throws Exception {
given(layout.getLibraryLocation(anyString(), eq(scope))).willReturn("test/");
given(layout.getLibraryLocation(anyString(), eq(LibraryScope.COMPILE))).willReturn("test-lib/");
packager.setLayout(layout);
- execute(packager, (callback) -> callback.library(new Library(libJarFile, scope)));
+ execute(packager, (callback) -> callback.library(newLibrary(libJarFile, scope, false)));
assertThat(hasPackagedEntry("test/" + libJarFile.getName())).isTrue();
assertThat(getPackagedManifest().getMainAttributes().getValue("Spring-Boot-Lib")).isEqualTo("test-lib/");
assertThat(getPackagedManifest().getMainAttributes().getValue("Main-Class")).isEqualTo("testLauncher");
@@ -351,7 +351,7 @@ void customLayoutNoBootLib() throws Exception {
LibraryScope scope = mock(LibraryScope.class);
given(layout.getLauncherClassName()).willReturn("testLauncher");
packager.setLayout(layout);
- execute(packager, (callback) -> callback.library(new Library(libJarFile, scope)));
+ execute(packager, (callback) -> callback.library(newLibrary(libJarFile, scope, false)));
assertThat(getPackagedManifest().getMainAttributes().getValue("Spring-Boot-Lib")).isNull();
assertThat(getPackagedManifest().getMainAttributes().getValue("Main-Class")).isEqualTo("testLauncher");
}
@@ -405,7 +405,7 @@ void dontRecompressZips() throws Exception {
this.testJarFile.addFile("test/nested.jar", nestedFile);
this.testJarFile.addClass("A.class", ClassWithMainMethod.class);
P packager = createPackager();
- execute(packager, (callback) -> callback.library(new Library(nestedFile, LibraryScope.COMPILE)));
+ execute(packager, (callback) -> callback.library(newLibrary(nestedFile, LibraryScope.COMPILE, false)));
assertThat(getPackagedEntry("BOOT-INF/lib/" + nestedFile.getName()).getMethod()).isEqualTo(ZipEntry.STORED);
assertThat(getPackagedEntry("BOOT-INF/classes/test/nested.jar").getMethod()).isEqualTo(ZipEntry.STORED);
}
@@ -419,7 +419,7 @@ void unpackLibrariesTakePrecedenceOverExistingSourceEntries() throws Exception {
this.testJarFile.addFile(name, nested.getFile());
this.testJarFile.addClass("A.class", ClassWithMainMethod.class);
P packager = createPackager();
- execute(packager, (callback) -> callback.library(new Library(nestedFile, LibraryScope.COMPILE, true)));
+ execute(packager, (callback) -> callback.library(newLibrary(nestedFile, LibraryScope.COMPILE, true)));
assertThat(getPackagedEntry(name).getComment()).startsWith("UNPACK:");
}
@@ -437,7 +437,7 @@ void existingSourceEntriesTakePrecedenceOverStandardLibraries() throws Exception
File toZip = new File(this.tempDir, "to-zip");
toZip.createNewFile();
ZipUtil.packEntry(toZip, nestedFile);
- callback.library(new Library(nestedFile, LibraryScope.COMPILE));
+ callback.library(newLibrary(nestedFile, LibraryScope.COMPILE, false));
});
assertThat(getPackagedEntry("BOOT-INF/lib/" + nestedFile.getName()).getSize()).isEqualTo(sourceLength);
}
@@ -498,14 +498,14 @@ void allEntriesUseUnixPlatformAndUtf8NameEncoding() throws IOException {
@Test
void loaderIsWrittenFirstThenApplicationClassesThenLibraries() throws IOException {
this.testJarFile.addClass("com/example/Application.class", ClassWithMainMethod.class);
- File libraryOne = createLibrary();
- File libraryTwo = createLibrary();
- File libraryThree = createLibrary();
+ File libraryOne = createLibraryJar();
+ File libraryTwo = createLibraryJar();
+ File libraryThree = createLibraryJar();
P packager = createPackager();
execute(packager, (callback) -> {
- callback.library(new Library(libraryOne, LibraryScope.COMPILE, false));
- callback.library(new Library(libraryTwo, LibraryScope.COMPILE, true));
- callback.library(new Library(libraryThree, LibraryScope.COMPILE, false));
+ callback.library(newLibrary(libraryOne, LibraryScope.COMPILE, false));
+ callback.library(newLibrary(libraryTwo, LibraryScope.COMPILE, true));
+ callback.library(newLibrary(libraryThree, LibraryScope.COMPILE, false));
});
assertThat(getPackagedEntryNames()).containsSubsequence("org/springframework/boot/loader/",
"BOOT-INF/classes/com/example/Application.class", "BOOT-INF/lib/" + libraryOne.getName(),
@@ -514,12 +514,12 @@ void loaderIsWrittenFirstThenApplicationClassesThenLibraries() throws IOExceptio
@Test
void existingEntryThatMatchesUnpackLibraryIsMarkedForUnpack() throws IOException {
- File library = createLibrary();
+ File library = createLibraryJar();
this.testJarFile.addClass("WEB-INF/classes/com/example/Application.class", ClassWithMainMethod.class);
this.testJarFile.addFile("WEB-INF/lib/" + library.getName(), library);
P packager = createPackager(this.testJarFile.getFile("war"));
packager.setLayout(new Layouts.War());
- execute(packager, (callback) -> callback.library(new Library(library, LibraryScope.COMPILE, true)));
+ execute(packager, (callback) -> callback.library(newLibrary(library, LibraryScope.COMPILE, true)));
assertThat(getPackagedEntryNames()).containsSubsequence("org/springframework/boot/loader/",
"WEB-INF/classes/com/example/Application.class", "WEB-INF/lib/" + library.getName());
ZipEntry unpackLibrary = getPackagedEntry("WEB-INF/lib/" + library.getName());
@@ -536,7 +536,7 @@ void layoutCanOmitLibraries() throws IOException {
Layout layout = mock(Layout.class);
LibraryScope scope = mock(LibraryScope.class);
packager.setLayout(layout);
- execute(packager, (callback) -> callback.library(new Library(libJarFile, scope)));
+ execute(packager, (callback) -> callback.library(newLibrary(libJarFile, scope, false)));
assertThat(getPackagedEntryNames()).containsExactly("META-INF/", "META-INF/MANIFEST.MF", "a/", "a/b/",
"a/b/C.class");
}
@@ -583,12 +583,39 @@ void kotlinModuleMetadataMovesBeneathBootInfClassesWhenRepackaged() throws Excep
assertThat(getPackagedEntry("BOOT-INF/classes/META-INF/test.kotlin_module")).isNotNull();
}
- private File createLibrary() throws IOException {
+ @Test
+ void entryFiltering() throws Exception {
+ File webLibrary = createLibraryJar();
+ File libraryOne = createLibraryJar();
+ File libraryTwo = createLibraryJar();
+ this.testJarFile.addClass("WEB-INF/classes/com/example/Application.class", ClassWithMainMethod.class);
+ this.testJarFile.addFile("WEB-INF/lib/" + webLibrary.getName(), webLibrary);
+ P packager = createPackager(this.testJarFile.getFile("war"));
+ packager.setLayout(new Layouts.War());
+ execute(packager, (callback) -> {
+ callback.library(newLibrary(webLibrary, LibraryScope.COMPILE, false, false));
+ callback.library(newLibrary(libraryOne, LibraryScope.COMPILE, false, false));
+ callback.library(newLibrary(libraryTwo, LibraryScope.COMPILE, false, true));
+ });
+ Collection packagedEntryNames = getPackagedEntryNames();
+ packagedEntryNames.removeIf((name) -> !name.endsWith(".jar"));
+ assertThat(packagedEntryNames).containsExactly("WEB-INF/lib/" + libraryTwo.getName());
+ }
+
+ private File createLibraryJar() throws IOException {
TestJarFile library = new TestJarFile(this.tempDir);
library.addClass("com/example/library/Library.class", ClassWithoutMainMethod.class);
return library.getFile();
}
+ private Library newLibrary(File file, LibraryScope scope, boolean unpackRequired) {
+ return new Library(null, file, scope, null, unpackRequired, false, true);
+ }
+
+ private Library newLibrary(File file, LibraryScope scope, boolean unpackRequired, boolean included) {
+ return new Library(null, file, scope, null, unpackRequired, false, included);
+ }
+
protected final P createPackager() throws IOException {
return createPackager(this.testJarFile.getFile());
}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/index.adoc b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/index.adoc
index af470f8eecfe..3e291430a2ef 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/index.adoc
+++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/index.adoc
@@ -1,6 +1,8 @@
[[spring-boot-maven-plugin-documentation]]
= Spring Boot Maven Plugin Documentation
-Stephane Nicoll, Andy Wilkinson, Scott Frederick
+Stephane Nicoll; Andy Wilkinson; Scott Frederick
+v{gradle-project-version}
+:!version-label:
:doctype: book
:toc: left
:toclevels: 4
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/WarIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/WarIntegrationTests.java
index f83710223681..6615cd1567c8 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/WarIntegrationTests.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/WarIntegrationTests.java
@@ -190,4 +190,13 @@ void whenWarIsRepackagedWithTheCustomLayers(MavenBuild mavenBuild) {
});
}
+ @TestTemplate
+ void whenEntryIsExcludedItShouldNotBePresentInTheRepackagedWar(MavenBuild mavenBuild) {
+ mavenBuild.project("war-exclude-entry").execute((project) -> {
+ File war = new File(project, "target/war-exclude-entry-0.0.1.BUILD-SNAPSHOT.war");
+ assertThat(jar(war)).hasEntryWithNameStartingWith("WEB-INF/lib/spring-context")
+ .doesNotHaveEntryWithNameStartingWith("WEB-INF/lib/spring-core");
+ });
+ }
+
}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/war-exclude-entry/pom.xml b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/war-exclude-entry/pom.xml
new file mode 100644
index 000000000000..6657f2c89829
--- /dev/null
+++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/war-exclude-entry/pom.xml
@@ -0,0 +1,63 @@
+
+
+ 4.0.0
+ org.springframework.boot.maven.it
+ war-exclude-entry
+ 0.0.1.BUILD-SNAPSHOT
+ war
+
+ UTF-8
+ @java.version@
+ @java.version@
+
+
+
+
+ @project.groupId@
+ @project.artifactId@
+ @project.version@
+
+
+
+ repackage
+
+
+
+
+ org.springframework
+ spring-core
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+ @maven-war-plugin.version@
+
+
+
+ Foo
+
+
+
+
+
+
+
+
+ org.springframework
+ spring-context
+ @spring-framework.version@
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ @jakarta-servlet.version@
+ provided
+
+
+
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/war-exclude-entry/src/main/java/org/test/SampleApplication.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/war-exclude-entry/src/main/java/org/test/SampleApplication.java
new file mode 100644
index 000000000000..16c76e92c504
--- /dev/null
+++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/war-exclude-entry/src/main/java/org/test/SampleApplication.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2012-2021 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.test;
+
+public class SampleApplication {
+
+ public static void main(String[] args) {
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/war-exclude-entry/src/main/webapp/index.html b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/war-exclude-entry/src/main/webapp/index.html
new file mode 100644
index 000000000000..18ecdcb795c3
--- /dev/null
+++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/war-exclude-entry/src/main/webapp/index.html
@@ -0,0 +1 @@
+
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractDependencyFilterMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractDependencyFilterMojo.java
index c05653a2b44a..c2074afb6be6 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractDependencyFilterMojo.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractDependencyFilterMojo.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -76,7 +76,7 @@ protected void setExcludeGroupIds(String excludeGroupIds) {
this.excludeGroupIds = excludeGroupIds;
}
- protected Set filterDependencies(Set dependencies, FilterArtifacts filters)
+ protected final Set filterDependencies(Set dependencies, FilterArtifacts filters)
throws MojoExecutionException {
try {
Set filtered = new LinkedHashSet<>(dependencies);
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 6d2713fdfe4f..3ce19bf4c105 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
@@ -182,11 +182,9 @@ private Document getDocumentIfAvailable(File xmlFile) throws Exception {
* @throws MojoExecutionException on execution error
*/
protected final Libraries getLibraries(Collection unpacks) throws MojoExecutionException {
- String packaging = this.project.getPackaging();
- Set projectArtifacts = this.project.getArtifacts();
- Set artifacts = ("war".equals(packaging)) ? projectArtifacts
- : filterDependencies(projectArtifacts, getFilters(getAdditionalFilters()));
- return new ArtifactsLibraries(artifacts, this.session.getProjects(), unpacks, getLog());
+ Set artifacts = this.project.getArtifacts();
+ Set includedArtifacts = filterDependencies(artifacts, getFilters(getAdditionalFilters()));
+ return new ArtifactsLibraries(artifacts, includedArtifacts, this.session.getProjects(), unpacks, getLog());
}
private ArtifactsFilter[] getAdditionalFilters() {
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ArtifactsLibraries.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ArtifactsLibraries.java
index e73263eb1b2d..3e9b5764040a 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ArtifactsLibraries.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ArtifactsLibraries.java
@@ -16,6 +16,7 @@
package org.springframework.boot.maven;
+import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
@@ -59,6 +60,8 @@ public class ArtifactsLibraries implements Libraries {
private final Set artifacts;
+ private final Set includedArtifacts;
+
private final Collection localProjects;
private final Collection unpacks;
@@ -89,7 +92,23 @@ public ArtifactsLibraries(Set artifacts, Collection unpack
*/
public ArtifactsLibraries(Set artifacts, Collection localProjects,
Collection unpacks, Log log) {
+ this(artifacts, artifacts, localProjects, unpacks, log);
+ }
+
+ /**
+ * Creates a new {@code ArtifactsLibraries} from the given {@code artifacts}.
+ * @param artifacts all artifacts that can be represented as libraries
+ * @param includedArtifacts the actual artifacts to include in the fat jar
+ * @param localProjects projects for which {@link Library#isLocal() local} libraries
+ * should be created
+ * @param unpacks artifacts that should be unpacked on launch
+ * @param log the log
+ * @since 2.4.8
+ */
+ public ArtifactsLibraries(Set artifacts, Set includedArtifacts,
+ Collection localProjects, Collection unpacks, Log log) {
this.artifacts = artifacts;
+ this.includedArtifacts = includedArtifacts;
this.localProjects = localProjects;
this.unpacks = unpacks;
this.log = log;
@@ -99,18 +118,22 @@ public ArtifactsLibraries(Set artifacts, Collection loca
public void doWithLibraries(LibraryCallback callback) throws IOException {
Set duplicates = getDuplicates(this.artifacts);
for (Artifact artifact : this.artifacts) {
+ String name = getFileName(artifact);
+ File file = artifact.getFile();
LibraryScope scope = SCOPES.get(artifact.getScope());
- if (scope != null && artifact.getFile() != null) {
- String name = getFileName(artifact);
- if (duplicates.contains(name)) {
- this.log.debug("Duplicate found: " + name);
- name = artifact.getGroupId() + "-" + name;
- this.log.debug("Renamed to: " + name);
- }
- LibraryCoordinates coordinates = new ArtifactLibraryCoordinates(artifact);
- callback.library(new Library(name, artifact.getFile(), scope, coordinates, isUnpackRequired(artifact),
- isLocal(artifact)));
+ if (scope == null || file == null) {
+ continue;
+ }
+ if (duplicates.contains(name)) {
+ this.log.debug("Duplicate found: " + name);
+ name = artifact.getGroupId() + "-" + name;
+ this.log.debug("Renamed to: " + name);
}
+ LibraryCoordinates coordinates = new ArtifactLibraryCoordinates(artifact);
+ boolean unpackRequired = isUnpackRequired(artifact);
+ boolean local = isLocal(artifact);
+ boolean included = this.includedArtifacts.contains(artifact);
+ callback.library(new Library(name, file, scope, coordinates, unpackRequired, local, included));
}
}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java
index c7c8b5dbc773..637a7831374a 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java
@@ -189,8 +189,8 @@ protected LayoutType getLayout() {
}
/**
- * Return the layout factory that will be used to determine the {@link LayoutType} if
- * no explicit layout is set.
+ * Return the layout factory that will be used to determine the
+ * {@link AbstractPackagerMojo.LayoutType} if no explicit layout is set.
* @return the value of the {@code layoutFactory} parameter, or {@code null} if the
* parameter is not provided
*/
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java
index 391f7f84550d..2e77b5eb8189 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java
@@ -183,8 +183,8 @@ protected LayoutType getLayout() {
}
/**
- * Return the layout factory that will be used to determine the {@link LayoutType} if
- * no explicit layout is set.
+ * Return the layout factory that will be used to determine the
+ * {@link AbstractPackagerMojo.LayoutType} if no explicit layout is set.
* @return the value of the {@code layoutFactory} parameter, or {@code null} if the
* parameter is not provided
*/
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ArtifactsLibrariesTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ArtifactsLibrariesTests.java
index 022affaaecbd..bc8cd7bb1acd 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ArtifactsLibrariesTests.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ArtifactsLibrariesTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -144,6 +144,7 @@ void libraryCoordinatesVersionUsesBaseVersionOfArtifact() throws IOException {
this.artifacts = Collections.singleton(snapshotArtifact);
new ArtifactsLibraries(this.artifacts, Collections.emptyList(), null, mock(Log.class))
.doWithLibraries((library) -> {
+ assertThat(library.isIncluded()).isTrue();
assertThat(library.isLocal()).isFalse();
assertThat(library.getCoordinates().getVersion()).isEqualTo("1.0-SNAPSHOT");
});
@@ -181,4 +182,19 @@ void attachedArtifactForLocalProjectProducesLocalLibrary() throws IOException {
.doWithLibraries((library) -> assertThat(library.isLocal()).isTrue());
}
+ @Test
+ void nonIncludedArtifact() throws IOException {
+ Artifact artifact = mock(Artifact.class);
+ given(artifact.getScope()).willReturn("compile");
+ given(artifact.getArtifactId()).willReturn("artifact");
+ given(artifact.getBaseVersion()).willReturn("1.0-SNAPSHOT");
+ given(artifact.getFile()).willReturn(new File("a"));
+ given(artifact.getArtifactHandler()).willReturn(this.artifactHandler);
+ MavenProject mavenProject = mock(MavenProject.class);
+ given(mavenProject.getArtifact()).willReturn(artifact);
+ this.artifacts = Collections.singleton(artifact);
+ new ArtifactsLibraries(this.artifacts, Collections.emptySet(), Collections.singleton(mavenProject), null,
+ mock(Log.class)).doWithLibraries((library) -> assertThat(library.isIncluded()).isFalse());
+ }
+
}
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplicationShutdownHook.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplicationShutdownHook.java
index c4bd7222194e..cc4faff521e0 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplicationShutdownHook.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplicationShutdownHook.java
@@ -125,6 +125,9 @@ void reset() {
* @param context the context to clean
*/
private void closeAndWait(ConfigurableApplicationContext context) {
+ if (!context.isActive()) {
+ return;
+ }
context.close();
try {
int waited = 0;
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributors.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributors.java
index d453d545437c..0c66cf078a91 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributors.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributors.java
@@ -117,10 +117,9 @@ ConfigDataEnvironmentContributors withProcessedImports(ConfigDataImporter import
result, contributor, activationContext);
ConfigDataLoaderContext loaderContext = new ContributorDataLoaderContext(this);
List imports = contributor.getImports();
- boolean resolveProfileSpecific = !contributor.isFromProfileSpecificImport();
this.logger.trace(LogMessage.format("Processing imports %s", imports));
Map imported = importer.resolveAndLoad(activationContext,
- locationResolverContext, loaderContext, imports, resolveProfileSpecific);
+ locationResolverContext, loaderContext, imports);
this.logger.trace(LogMessage.of(() -> getImportedMessage(imported.keySet())));
ConfigDataEnvironmentContributor contributorAndChildren = contributor.withChildren(importPhase,
asContributors(imported));
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataImporter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataImporter.java
index 4532c000b0f7..0970b4f6a605 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataImporter.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataImporter.java
@@ -75,16 +75,14 @@ class ConfigDataImporter {
* @param locationResolverContext the location resolver context
* @param loaderContext the loader context
* @param locations the locations to resolve
- * @param resolveProfileSpecific if profile specific resolution should be attempted
* @return a map of the loaded locations and data
*/
Map resolveAndLoad(ConfigDataActivationContext activationContext,
ConfigDataLocationResolverContext locationResolverContext, ConfigDataLoaderContext loaderContext,
- List locations, boolean resolveProfileSpecific) {
+ List locations) {
try {
Profiles profiles = (activationContext != null) ? activationContext.getProfiles() : null;
- List resolved = resolve(locationResolverContext, profiles, locations,
- resolveProfileSpecific);
+ List resolved = resolve(locationResolverContext, profiles, locations);
return load(loaderContext, resolved);
}
catch (IOException ex) {
@@ -93,18 +91,18 @@ Map resolveAndLoad(ConfigDataActivationC
}
private List resolve(ConfigDataLocationResolverContext locationResolverContext,
- Profiles profiles, List locations, boolean resolveProfileSpecific) {
+ Profiles profiles, List locations) {
List resolved = new ArrayList<>(locations.size());
for (ConfigDataLocation location : locations) {
- resolved.addAll(resolve(locationResolverContext, profiles, location, resolveProfileSpecific));
+ resolved.addAll(resolve(locationResolverContext, profiles, location));
}
return Collections.unmodifiableList(resolved);
}
private List resolve(ConfigDataLocationResolverContext locationResolverContext,
- Profiles profiles, ConfigDataLocation location, boolean resolveProfileSpecific) {
+ Profiles profiles, ConfigDataLocation location) {
try {
- return this.resolvers.resolve(locationResolverContext, location, profiles, resolveProfileSpecific);
+ return this.resolvers.resolve(locationResolverContext, location, profiles);
}
catch (ConfigDataNotFoundException ex) {
handle(ex, location, null);
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaders.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaders.java
index 8ec01f80a0cb..c9636ed4ccb8 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaders.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaders.java
@@ -51,21 +51,23 @@ class ConfigDataLoaders {
* Create a new {@link ConfigDataLoaders} instance.
* @param logFactory the deferred log factory
* @param bootstrapContext the bootstrap context
- * @param classLoader the class loader used when loading from {@code spring.factories}
+ * @param classLoader the class loader used when loading
*/
ConfigDataLoaders(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
ClassLoader classLoader) {
- this(logFactory, bootstrapContext, SpringFactoriesLoader.loadFactoryNames(ConfigDataLoader.class, classLoader));
+ this(logFactory, bootstrapContext, classLoader,
+ SpringFactoriesLoader.loadFactoryNames(ConfigDataLoader.class, classLoader));
}
/**
* Create a new {@link ConfigDataLoaders} instance.
* @param logFactory the deferred log factory
* @param bootstrapContext the bootstrap context
+ * @param classLoader the class loader used when loading
* @param names the {@link ConfigDataLoader} class names instantiate
*/
ConfigDataLoaders(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
- List names) {
+ ClassLoader classLoader, List names) {
this.logger = logFactory.getLog(getClass());
Instantiator> instantiator = new Instantiator<>(ConfigDataLoader.class,
(availableParameters) -> {
@@ -75,7 +77,7 @@ class ConfigDataLoaders {
availableParameters.add(BootstrapContext.class, bootstrapContext);
availableParameters.add(BootstrapRegistry.class, bootstrapContext);
});
- this.loaders = instantiator.instantiate(names);
+ this.loaders = instantiator.instantiate(classLoader, names);
this.resourceTypes = getResourceTypes(this.loaders);
}
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolvers.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolvers.java
index b2add13a6472..bfdc0ac5fe3b 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolvers.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolvers.java
@@ -77,7 +77,7 @@ class ConfigDataLocationResolvers {
availableParameters.add(BootstrapContext.class, bootstrapContext);
availableParameters.add(BootstrapRegistry.class, bootstrapContext);
});
- this.resolvers = reorder(instantiator.instantiate(names));
+ this.resolvers = reorder(instantiator.instantiate(resourceLoader.getClassLoader(), names));
}
private List> reorder(List> resolvers) {
@@ -98,23 +98,22 @@ private List> reorder(List resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location,
- Profiles profiles, boolean resolveProfileSpecific) {
+ Profiles profiles) {
if (location == null) {
return Collections.emptyList();
}
for (ConfigDataLocationResolver> resolver : getResolvers()) {
if (resolver.isResolvable(context, location)) {
- return resolve(resolver, context, location, profiles, resolveProfileSpecific);
+ return resolve(resolver, context, location, profiles);
}
}
throw new UnsupportedConfigDataLocationException(location);
}
private List resolve(ConfigDataLocationResolver> resolver,
- ConfigDataLocationResolverContext context, ConfigDataLocation location, Profiles profiles,
- boolean resolveProfileSpecific) {
+ ConfigDataLocationResolverContext context, ConfigDataLocation location, Profiles profiles) {
List resolved = resolve(location, false, () -> resolver.resolve(context, location));
- if (profiles == null || !resolveProfileSpecific) {
+ if (profiles == null) {
return resolved;
}
List profileSpecific = resolve(location, true,
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataLocationResolver.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataLocationResolver.java
index a14293ce58f1..4ec0ea2d89d6 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataLocationResolver.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataLocationResolver.java
@@ -145,9 +145,6 @@ private Set getReferences(ConfigDataLocationResolve
@Override
public List resolveProfileSpecific(ConfigDataLocationResolverContext context,
ConfigDataLocation location, Profiles profiles) {
- if (context.getParent() != null) {
- return null;
- }
return resolve(getProfileSpecificReferences(context, location.split(), profiles));
}
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConverter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConverter.java
index ef9d8be2e488..5082ae0f65fc 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConverter.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConverter.java
@@ -34,7 +34,9 @@
import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.ConversionException;
+import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.core.convert.support.GenericConversionService;
@@ -98,17 +100,20 @@ T convert(Object source, ResolvableType targetType, Annotation... targetAnno
}
private Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
- for (int i = 0; i < this.delegates.size() - 1; i++) {
+ ConversionException failure = null;
+ for (ConversionService delegate : this.delegates) {
try {
- ConversionService delegate = this.delegates.get(i);
if (delegate.canConvert(sourceType, targetType)) {
return delegate.convert(source, sourceType, targetType);
}
}
catch (ConversionException ex) {
+ if (failure == null && ex instanceof ConversionFailedException) {
+ failure = ex;
+ }
}
}
- return this.delegates.get(this.delegates.size() - 1).convert(source, sourceType, targetType);
+ throw (failure != null) ? failure : new ConverterNotFoundException(sourceType, targetType);
}
static BindConverter get(List conversionServices,
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BeanNotOfRequiredTypeFailureAnalyzer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BeanNotOfRequiredTypeFailureAnalyzer.java
index d48f1e5ecfaf..43ec018d5bb6 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BeanNotOfRequiredTypeFailureAnalyzer.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BeanNotOfRequiredTypeFailureAnalyzer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -29,6 +29,7 @@
* {@link BeanNotOfRequiredTypeException}.
*
* @author Andy Wilkinson
+ * @author Scott Frederick
* @since 1.4.0
*/
public class BeanNotOfRequiredTypeFailureAnalyzer extends AbstractFailureAnalyzer {
@@ -48,8 +49,12 @@ protected FailureAnalysis analyze(Throwable rootFailure, BeanNotOfRequiredTypeEx
private String getDescription(BeanNotOfRequiredTypeException ex) {
StringWriter description = new StringWriter();
PrintWriter printer = new PrintWriter(description);
- printer.printf("The bean '%s' could not be injected as a '%s' because it is a "
- + "JDK dynamic proxy that implements:%n", ex.getBeanName(), ex.getRequiredType().getName());
+ printer.printf("The bean '%s' could not be injected because it is a JDK dynamic proxy%n%n", ex.getBeanName());
+ printer.printf("The bean is of type '%s' and implements:%n", ex.getActualType().getName());
+ for (Class> actualTypeInterface : ex.getActualType().getInterfaces()) {
+ printer.println("\t" + actualTypeInterface.getName());
+ }
+ printer.printf("%nExpected a bean of type '%s' which implements:%n", ex.getRequiredType().getName());
for (Class> requiredTypeInterface : ex.getRequiredType().getInterfaces()) {
printer.println("\t" + requiredTypeInterface.getName());
}
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BindFailureAnalyzer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BindFailureAnalyzer.java
index 1a735ed667e3..75f1b1c643a7 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BindFailureAnalyzer.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BindFailureAnalyzer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2021 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,6 +38,7 @@
*
* @author Andy Wilkinson
* @author Madhura Bhave
+ * @author Phillip Webb
*/
class BindFailureAnalyzer extends AbstractFailureAnalyzer {
@@ -68,16 +69,33 @@ private void buildDescription(StringBuilder description, ConfigurationProperty p
}
private String getMessage(BindException cause) {
+ Throwable rootCause = getRootCause(cause.getCause());
ConversionFailedException conversionFailure = findCause(cause, ConversionFailedException.class);
if (conversionFailure != null) {
- return "failed to convert " + conversionFailure.getSourceType() + " to "
+ String message = "failed to convert " + conversionFailure.getSourceType() + " to "
+ conversionFailure.getTargetType();
+ if (rootCause != null) {
+ message += " (caused by " + getExceptionTypeAndMessage(rootCause) + ")";
+ }
+ return message;
+ }
+ if (rootCause != null && StringUtils.hasText(rootCause.getMessage())) {
+ return getExceptionTypeAndMessage(rootCause);
}
- Throwable failure = cause;
- while (failure.getCause() != null) {
- failure = failure.getCause();
+ return getExceptionTypeAndMessage(cause);
+ }
+
+ private Throwable getRootCause(Throwable cause) {
+ Throwable rootCause = cause;
+ while (rootCause != null && rootCause.getCause() != null) {
+ rootCause = rootCause.getCause();
}
- return (StringUtils.hasText(failure.getMessage()) ? failure.getMessage() : cause.getMessage());
+ return rootCause;
+ }
+
+ private String getExceptionTypeAndMessage(Throwable ex) {
+ String message = ex.getMessage();
+ return ex.getClass().getName() + (StringUtils.hasText(message) ? ": " + message : "");
}
private FailureAnalysis getFailureAnalysis(Object description, BindException cause) {
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListener.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListener.java
index 68294e16325c..366584760c5a 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListener.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListener.java
@@ -17,6 +17,7 @@
package org.springframework.boot.env;
import java.util.List;
+import java.util.function.Function;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication;
@@ -28,6 +29,7 @@
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.io.ResourceLoader;
/**
* {@link SmartApplicationListener} used to trigger {@link EnvironmentPostProcessor
@@ -47,15 +49,14 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica
private int order = DEFAULT_ORDER;
- private final EnvironmentPostProcessorsFactory postProcessorsFactory;
+ private final Function postProcessorsFactory;
/**
* Create a new {@link EnvironmentPostProcessorApplicationListener} with
* {@link EnvironmentPostProcessor} classes loaded via {@code spring.factories}.
*/
public EnvironmentPostProcessorApplicationListener() {
- this(EnvironmentPostProcessorsFactory
- .fromSpringFactories(EnvironmentPostProcessorApplicationListener.class.getClassLoader()));
+ this((classLoader) -> EnvironmentPostProcessorsFactory.fromSpringFactories(classLoader), new DeferredLogs());
}
/**
@@ -64,11 +65,11 @@ public EnvironmentPostProcessorApplicationListener() {
* @param postProcessorsFactory the post processors factory
*/
public EnvironmentPostProcessorApplicationListener(EnvironmentPostProcessorsFactory postProcessorsFactory) {
- this(postProcessorsFactory, new DeferredLogs());
+ this((classloader) -> postProcessorsFactory, new DeferredLogs());
}
- EnvironmentPostProcessorApplicationListener(EnvironmentPostProcessorsFactory postProcessorsFactory,
- DeferredLogs deferredLogs) {
+ EnvironmentPostProcessorApplicationListener(
+ Function postProcessorsFactory, DeferredLogs deferredLogs) {
this.postProcessorsFactory = postProcessorsFactory;
this.deferredLogs = deferredLogs;
}
@@ -96,7 +97,8 @@ public void onApplicationEvent(ApplicationEvent event) {
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
SpringApplication application = event.getSpringApplication();
- for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(event.getBootstrapContext())) {
+ for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(application.getResourceLoader(),
+ event.getBootstrapContext())) {
postProcessor.postProcessEnvironment(environment, application);
}
}
@@ -113,8 +115,11 @@ private void finish() {
this.deferredLogs.switchOverAll();
}
- List getEnvironmentPostProcessors(ConfigurableBootstrapContext bootstrapContext) {
- return this.postProcessorsFactory.getEnvironmentPostProcessors(this.deferredLogs, bootstrapContext);
+ List getEnvironmentPostProcessors(ResourceLoader resourceLoader,
+ ConfigurableBootstrapContext bootstrapContext) {
+ ClassLoader classLoader = (resourceLoader != null) ? resourceLoader.getClassLoader() : null;
+ EnvironmentPostProcessorsFactory postProcessorsFactory = this.postProcessorsFactory.apply(classLoader);
+ return postProcessorsFactory.getEnvironmentPostProcessors(this.deferredLogs, bootstrapContext);
}
@Override
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorsFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorsFactory.java
index dbd3730c9fc2..0f49d98a189c 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorsFactory.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorsFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -48,7 +48,7 @@ List getEnvironmentPostProcessors(DeferredLogFactory l
* @return an {@link EnvironmentPostProcessorsFactory} instance
*/
static EnvironmentPostProcessorsFactory fromSpringFactories(ClassLoader classLoader) {
- return new ReflectionEnvironmentPostProcessorsFactory(
+ return new ReflectionEnvironmentPostProcessorsFactory(classLoader,
SpringFactoriesLoader.loadFactoryNames(EnvironmentPostProcessor.class, classLoader));
}
@@ -69,7 +69,19 @@ static EnvironmentPostProcessorsFactory of(Class>... classes) {
* @return an {@link EnvironmentPostProcessorsFactory} instance
*/
static EnvironmentPostProcessorsFactory of(String... classNames) {
- return new ReflectionEnvironmentPostProcessorsFactory(classNames);
+ return of(null, classNames);
+ }
+
+ /**
+ * Return a {@link EnvironmentPostProcessorsFactory} that reflectively creates post
+ * processors from the given class names.
+ * @param classLoader the source class loader
+ * @param classNames the post processor class names
+ * @return an {@link EnvironmentPostProcessorsFactory} instance
+ * @since 2.4.8
+ */
+ static EnvironmentPostProcessorsFactory of(ClassLoader classLoader, String... classNames) {
+ return new ReflectionEnvironmentPostProcessorsFactory(classLoader, classNames);
}
}
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/RandomValuePropertySource.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/RandomValuePropertySource.java
index c4085a8f6fea..7c2f23f63153 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/RandomValuePropertySource.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/RandomValuePropertySource.java
@@ -44,6 +44,7 @@
* specified range.
*
When {@literal "long"}, a random {@link Long} value, restricted by an optionally
* specified range.
+ *
When {@literal "uuid"}, a random {@link UUID} value.
*
Otherwise, a {@code byte[]}.
*
* The {@literal "random.int"} and {@literal "random.long"} properties supports a range
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactory.java
index 7b984ff3aef1..bf920bdbe5ab 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactory.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -16,6 +16,7 @@
package org.springframework.boot.env;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -35,17 +36,24 @@
*/
class ReflectionEnvironmentPostProcessorsFactory implements EnvironmentPostProcessorsFactory {
+ private final List> classes;
+
+ private ClassLoader classLoader;
+
private final List classNames;
ReflectionEnvironmentPostProcessorsFactory(Class>... classes) {
- this(Arrays.stream(classes).map(Class::getName).toArray(String[]::new));
+ this.classes = new ArrayList<>(Arrays.asList(classes));
+ this.classNames = null;
}
- ReflectionEnvironmentPostProcessorsFactory(String... classNames) {
- this(Arrays.asList(classNames));
+ ReflectionEnvironmentPostProcessorsFactory(ClassLoader classLoader, String... classNames) {
+ this(classLoader, Arrays.asList(classNames));
}
- ReflectionEnvironmentPostProcessorsFactory(List classNames) {
+ ReflectionEnvironmentPostProcessorsFactory(ClassLoader classLoader, List classNames) {
+ this.classes = null;
+ this.classLoader = classLoader;
this.classNames = classNames;
}
@@ -60,7 +68,8 @@ public List getEnvironmentPostProcessors(DeferredLogFa
parameters.add(BootstrapContext.class, bootstrapContext);
parameters.add(BootstrapRegistry.class, bootstrapContext);
});
- return instantiator.instantiate(this.classNames);
+ return (this.classes != null) ? instantiator.instantiateTypes(this.classes)
+ : instantiator.instantiate(this.classLoader, this.classNames);
}
}
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DataSourceScriptDatabaseInitializer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DataSourceScriptDatabaseInitializer.java
index 6fe2993ac978..ccb131bc6872 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DataSourceScriptDatabaseInitializer.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DataSourceScriptDatabaseInitializer.java
@@ -21,6 +21,9 @@
import javax.sql.DataSource;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer;
@@ -38,6 +41,8 @@
*/
public class DataSourceScriptDatabaseInitializer extends AbstractScriptDatabaseInitializer {
+ private static final Log logger = LogFactory.getLog(DataSourceScriptDatabaseInitializer.class);
+
private final DataSource dataSource;
/**
@@ -61,7 +66,13 @@ protected final DataSource getDataSource() {
@Override
protected boolean isEmbeddedDatabase() {
- return EmbeddedDatabaseConnection.isEmbedded(this.dataSource);
+ try {
+ return EmbeddedDatabaseConnection.isEmbedded(this.dataSource);
+ }
+ catch (Exception ex) {
+ logger.debug("Could not determine if datasource is embedded", ex);
+ return false;
+ }
}
@Override
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/SpringBootConfigurationFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/SpringBootConfigurationFactory.java
index 9cc3cd2cf523..0e48867ffb1c 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/SpringBootConfigurationFactory.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/SpringBootConfigurationFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -30,13 +30,12 @@
*
*
*
Prevent logger warnings from being printed when the application first starts.
- *
Disable its shutdown hook
*
*
* This factory is ordered last and is triggered by a {@code log4j2.springboot} classpath
* resource (which is bundled in this jar). If the {@link Log4J2LoggingSystem} is active,
- * a custom {@link DefaultConfiguration} is returned with the expectation that the system
- * will later re-initialize Log4J2 with the correct configuration file.
+ * a {@link DefaultConfiguration} is returned with the expectation that the system will
+ * later re-initialize Log4J2 with the correct configuration file.
*
* @author Phillip Webb
* @since 1.5.0
@@ -57,15 +56,7 @@ public Configuration getConfiguration(LoggerContext loggerContext, Configuration
if (source == null || source == ConfigurationSource.NULL_SOURCE) {
return null;
}
- return new SpringBootConfiguration();
- }
-
- private static final class SpringBootConfiguration extends DefaultConfiguration {
-
- private SpringBootConfiguration() {
- this.isShutdownHookEnabled = false;
- }
-
+ return new DefaultConfiguration();
}
}
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/SpringBootPropertySource.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/SpringBootPropertySource.java
new file mode 100644
index 000000000000..29c5cc2ec054
--- /dev/null
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/SpringBootPropertySource.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2012-2021 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.logging.log4j2;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
+import org.apache.logging.log4j.util.BiConsumer;
+import org.apache.logging.log4j.util.PropertySource;
+
+/**
+ * Spring Boot {@link PropertySource} that disables Log4j2's shutdown hook.
+ *
+ * @author Andy Wilkinson
+ * @since 2.5.2
+ */
+public class SpringBootPropertySource implements PropertySource {
+
+ private static final String PREFIX = "log4j.";
+
+ private final Map properties = Collections
+ .singletonMap(ShutdownCallbackRegistry.SHUTDOWN_HOOK_ENABLED, "false");
+
+ @Override
+ public void forEach(BiConsumer action) {
+ this.properties.forEach((key, value) -> action.accept(key, value));
+ }
+
+ @Override
+ public CharSequence getNormalForm(Iterable extends CharSequence> tokens) {
+ return PREFIX + Util.joinAsCamelCase(tokens);
+ }
+
+ @Override
+ public int getPriority() {
+ return -200;
+ }
+
+}
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/ConnectionFactoryBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/ConnectionFactoryBuilder.java
index 9c13261b72e6..58f17e48dac3 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/ConnectionFactoryBuilder.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/ConnectionFactoryBuilder.java
@@ -92,7 +92,7 @@ public static ConnectionFactoryBuilder withOptions(Builder options) {
* @param connectionFactory the connection factory whose options are to be used to
* initialize the builder
* @return a new builder initialized with the options from the connection factory
- * @deprecated since 2.5.0 for removal in 2.7.0 in favor of
+ * @deprecated since 2.5.1 for removal in 2.7.0 in favor of
* {@link #derivedFrom(ConnectionFactory)}
*/
@Deprecated
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/AbstractScriptDatabaseInitializer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/AbstractScriptDatabaseInitializer.java
index e170acc3c4a4..4ab066190530 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/AbstractScriptDatabaseInitializer.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/AbstractScriptDatabaseInitializer.java
@@ -71,13 +71,9 @@ public void afterPropertiesSet() throws Exception {
* {@code false}
*/
public boolean initializeDatabase() {
- if (isEnabled()) {
- ScriptLocationResolver locationResolver = new ScriptLocationResolver(this.resourceLoader);
- boolean initialized = applySchemaScripts(locationResolver);
- initialized = applyDataScripts(locationResolver) || initialized;
- return initialized;
- }
- return false;
+ ScriptLocationResolver locationResolver = new ScriptLocationResolver(this.resourceLoader);
+ boolean initialized = applySchemaScripts(locationResolver);
+ return applyDataScripts(locationResolver) || initialized;
}
private boolean isEnabled() {
@@ -107,10 +103,11 @@ private boolean applyDataScripts(ScriptLocationResolver locationResolver) {
private boolean applyScripts(List locations, String type, ScriptLocationResolver locationResolver) {
List scripts = getScripts(locations, type, locationResolver);
- if (!scripts.isEmpty()) {
+ if (!scripts.isEmpty() && isEnabled()) {
runScripts(scripts);
+ return true;
}
- return !scripts.isEmpty();
+ return false;
}
private List getScripts(List locations, String type, ScriptLocationResolver locationResolver) {
@@ -145,9 +142,6 @@ private List doGetResources(String location, ScriptLocationResolver lo
}
private void runScripts(List resources) {
- if (resources.isEmpty()) {
- return;
- }
runScripts(resources, this.settings.isContinueOnError(), this.settings.getSeparator(),
this.settings.getEncoding());
}
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/dependency/DatabaseInitializationDependencyConfigurer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/dependency/DatabaseInitializationDependencyConfigurer.java
index 4a70dd0e71b7..11cd99ec2388 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/dependency/DatabaseInitializationDependencyConfigurer.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/dependency/DatabaseInitializationDependencyConfigurer.java
@@ -34,6 +34,7 @@
import org.springframework.boot.util.Instantiator;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
+import org.springframework.core.Ordered;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.AnnotationMetadata;
@@ -84,7 +85,7 @@ private DependsOnDatabaseInitializationPostProcessor createDependsOnDatabaseInit
* {@link BeanFactoryPostProcessor} used to configure database initialization
* dependency relationships.
*/
- static class DependsOnDatabaseInitializationPostProcessor implements BeanFactoryPostProcessor {
+ static class DependsOnDatabaseInitializationPostProcessor implements BeanFactoryPostProcessor, Ordered {
private final Environment environment;
@@ -92,6 +93,11 @@ static class DependsOnDatabaseInitializationPostProcessor implements BeanFactory
this.environment = environment;
}
+ @Override
+ public int getOrder() {
+ return 0;
+ }
+
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
Set initializerBeanNames = detectInitializerBeanNames(beanFactory);
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/util/Instantiator.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/util/Instantiator.java
index e22865192dbe..aaca052007d0 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/util/Instantiator.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/util/Instantiator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -17,7 +17,6 @@
package org.springframework.boot.util;
import java.lang.reflect.Constructor;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -27,6 +26,9 @@
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.util.Assert;
@@ -85,22 +87,48 @@ public void add(Class> type, Function, Object> factory) {
* @return a list of instantiated instances
*/
public List instantiate(Collection names) {
- List instances = new ArrayList<>(names.size());
- for (String name : names) {
- instances.add(instantiate(name));
- }
+ return instantiate((ClassLoader) null, names);
+ }
+
+ /**
+ * Instantiate the given set of class name, injecting constructor arguments as
+ * necessary.
+ * @param classLoader the source classloader
+ * @param names the class names to instantiate
+ * @return a list of instantiated instances
+ * @since 2.4.8
+ */
+ public List instantiate(ClassLoader classLoader, Collection names) {
+ Assert.notNull(names, "Names must not be null");
+ return instantiate(names.stream().map((name) -> TypeSupplier.forName(classLoader, name)));
+ }
+
+ /**
+ * Instantiate the given set of classes, injecting constructor arguments as necessary.
+ * @param types the types to instantiate
+ * @return a list of instantiated instances
+ * @since 2.4.8
+ */
+ public List instantiateTypes(Collection> types) {
+ Assert.notNull(types, "Types must not be null");
+ return instantiate(types.stream().map((type) -> TypeSupplier.forType(type)));
+ }
+
+ private List instantiate(Stream typeSuppliers) {
+ List instances = typeSuppliers.map(this::instantiate).collect(Collectors.toList());
AnnotationAwareOrderComparator.sort(instances);
return Collections.unmodifiableList(instances);
}
- private T instantiate(String name) {
+ private T instantiate(TypeSupplier typeSupplier) {
try {
- Class> type = ClassUtils.forName(name, null);
+ Class> type = typeSupplier.get();
Assert.isAssignable(this.type, type);
return instantiate(type);
}
catch (Throwable ex) {
- throw new IllegalArgumentException("Unable to instantiate " + this.type.getName() + " [" + name + "]", ex);
+ throw new IllegalArgumentException(
+ "Unable to instantiate " + this.type.getName() + " [" + typeSupplier.getName() + "]", ex);
}
}
@@ -160,4 +188,47 @@ public interface AvailableParameters {
}
+ /**
+ * {@link Supplier} that provides a class type.
+ */
+ private interface TypeSupplier {
+
+ String getName();
+
+ Class> get() throws ClassNotFoundException;
+
+ static TypeSupplier forName(ClassLoader classLoader, String name) {
+ return new TypeSupplier() {
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public Class> get() throws ClassNotFoundException {
+ return ClassUtils.forName(name, classLoader);
+ }
+
+ };
+ }
+
+ static TypeSupplier forType(Class> type) {
+ return new TypeSupplier() {
+
+ @Override
+ public String getName() {
+ return type.getName();
+ }
+
+ @Override
+ public Class> get() throws ClassNotFoundException {
+ return type;
+ }
+
+ };
+ }
+
+ }
+
}
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/SslServerCustomizer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/SslServerCustomizer.java
index d9f57b33ffd2..d0648c66b586 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/SslServerCustomizer.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/SslServerCustomizer.java
@@ -106,24 +106,25 @@ private ServerConnector createServerConnector(Server server, SslContextFactory.S
private ServerConnector createHttp11ServerConnector(Server server, HttpConfiguration config,
SslContextFactory.Server sslContextFactory) {
HttpConnectionFactory connectionFactory = new HttpConnectionFactory(config);
- SslConnectionFactory sslConnectionFactory;
+ return new SslValidatingServerConnector(server, sslContextFactory, this.ssl.getKeyAlias(),
+ createSslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()), connectionFactory);
+ }
+
+ private SslConnectionFactory createSslConnectionFactory(SslContextFactory.Server sslContextFactory,
+ String protocol) {
try {
- sslConnectionFactory = new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString());
+ return new SslConnectionFactory(sslContextFactory, protocol);
}
catch (NoSuchMethodError ex) {
// Jetty 10
try {
- sslConnectionFactory = SslConnectionFactory.class
- .getConstructor(SslContextFactory.Server.class, String.class)
- .newInstance(sslContextFactory, HttpVersion.HTTP_1_1.asString());
+ return SslConnectionFactory.class.getConstructor(SslContextFactory.Server.class, String.class)
+ .newInstance(sslContextFactory, protocol);
}
catch (Exception ex2) {
throw new RuntimeException(ex2);
}
}
-
- return new SslValidatingServerConnector(server, sslContextFactory, this.ssl.getKeyAlias(), sslConnectionFactory,
- connectionFactory);
}
private boolean isJettyAlpnPresent() {
@@ -143,7 +144,7 @@ private ServerConnector createHttp2ServerConnector(Server server, HttpConfigurat
if (isConscryptPresent()) {
sslContextFactory.setProvider("Conscrypt");
}
- SslConnectionFactory ssl = new SslConnectionFactory(sslContextFactory, alpn.getProtocol());
+ SslConnectionFactory ssl = createSslConnectionFactory(sslContextFactory, alpn.getProtocol());
return new SslValidatingServerConnector(server, sslContextFactory, this.ssl.getKeyAlias(), ssl, alpn, h2, http);
}
diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/services/org.apache.logging.log4j.util.PropertySource b/spring-boot-project/spring-boot/src/main/resources/META-INF/services/org.apache.logging.log4j.util.PropertySource
new file mode 100644
index 000000000000..b990694a7891
--- /dev/null
+++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/services/org.apache.logging.log4j.util.PropertySource
@@ -0,0 +1 @@
+org.springframework.boot.logging.log4j2.SpringBootPropertySource
\ No newline at end of file
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationShutdownHookTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationShutdownHookTests.java
index 12c7f9d5c52e..d188a1b731e1 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationShutdownHookTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationShutdownHookTests.java
@@ -26,10 +26,12 @@
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
+import org.springframework.context.support.GenericApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@@ -91,6 +93,15 @@ void runWhenContextIsBeingClosedInAnotherThreadWaitsUntilContextIsInactive() thr
assertThat(finished).containsExactly(context, handlerAction);
}
+ @Test
+ void runDueToExitDuringRefreshWhenContextHasBeenClosedDoesNotDeadlock() throws InterruptedException {
+ GenericApplicationContext context = new GenericApplicationContext();
+ TestSpringApplicationShutdownHook shutdownHook = new TestSpringApplicationShutdownHook();
+ shutdownHook.registerApplicationContext(context);
+ context.registerBean(CloseContextAndExit.class, context, shutdownHook);
+ context.refresh();
+ }
+
@Test
void runWhenContextIsClosedDirectlyRunsHandlerActions() {
TestSpringApplicationShutdownHook shutdownHook = new TestSpringApplicationShutdownHook();
@@ -221,4 +232,28 @@ public void run() {
}
+ static class CloseContextAndExit implements InitializingBean {
+
+ private final ConfigurableApplicationContext context;
+
+ private final Runnable shutdownHook;
+
+ CloseContextAndExit(ConfigurableApplicationContext context, SpringApplicationShutdownHook shutdownHook) {
+ this.context = context;
+ this.shutdownHook = shutdownHook;
+ }
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ this.context.close();
+ // Simulate System.exit by running the hook on a separate thread and waiting
+ // for it to complete
+ Thread thread = new Thread(this.shutdownHook);
+ thread.start();
+ thread.join(15000);
+ assertThat(thread.isAlive()).isFalse();
+ }
+
+ }
+
}
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java
index 3c08c41f59d0..f0280d704724 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java
@@ -44,7 +44,6 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
@@ -122,7 +121,7 @@ void withProcessedImportsResolvesAndLoads() {
Map imported = new LinkedHashMap<>();
imported.put(new ConfigDataResolutionResult(LOCATION_1, new TestConfigDataResource("a"), false),
new ConfigData(Arrays.asList(propertySource)));
- given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations), anyBoolean()))
+ given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations)))
.willReturn(imported);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
@@ -145,14 +144,14 @@ void withProcessedImportsResolvesAndLoadsChainedImports() {
Map initialImported = new LinkedHashMap<>();
initialImported.put(new ConfigDataResolutionResult(LOCATION_1, new TestConfigDataResource("a"), false),
new ConfigData(Arrays.asList(initialPropertySource)));
- given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(initialLocations),
- anyBoolean())).willReturn(initialImported);
+ given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(initialLocations)))
+ .willReturn(initialImported);
List secondLocations = Arrays.asList(LOCATION_2);
MockPropertySource secondPropertySource = new MockPropertySource();
Map secondImported = new LinkedHashMap<>();
secondImported.put(new ConfigDataResolutionResult(LOCATION_2, new TestConfigDataResource("b"), false),
new ConfigData(Arrays.asList(secondPropertySource)));
- given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(secondLocations), anyBoolean()))
+ given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(secondLocations)))
.willReturn(secondImported);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
@@ -179,13 +178,13 @@ void withProcessedImportsProvidesLocationResolverContextWithAccessToBinder() {
Map imported = new LinkedHashMap<>();
imported.put(new ConfigDataResolutionResult(LOCATION_1, new TestConfigDataResource("a'"), false),
new ConfigData(Arrays.asList(propertySource)));
- given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations), anyBoolean()))
+ given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations)))
.willReturn(imported);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapContext, Arrays.asList(existingContributor, contributor));
contributors.withProcessedImports(this.importer, this.activationContext);
- verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), any(), anyBoolean());
+ verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), any());
ConfigDataLocationResolverContext context = this.locationResolverContext.getValue();
assertThat(context.getBinder().bind("test", String.class).get()).isEqualTo("springboot");
}
@@ -199,21 +198,20 @@ void withProcessedImportsProvidesLocationResolverContextWithAccessToParent() {
Map initialImported = new LinkedHashMap<>();
initialImported.put(new ConfigDataResolutionResult(LOCATION_1, new TestConfigDataResource("a"), false),
new ConfigData(Arrays.asList(initialPropertySource)));
- given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(initialLocations),
- anyBoolean())).willReturn(initialImported);
+ given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(initialLocations)))
+ .willReturn(initialImported);
List secondLocations = Arrays.asList(LOCATION_2);
MockPropertySource secondPropertySource = new MockPropertySource();
Map secondImported = new LinkedHashMap<>();
secondImported.put(new ConfigDataResolutionResult(LOCATION_2, new TestConfigDataResource("b"), false),
new ConfigData(Arrays.asList(secondPropertySource)));
- given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(secondLocations), anyBoolean()))
+ given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(secondLocations)))
.willReturn(secondImported);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapContext, Arrays.asList(contributor));
contributors.withProcessedImports(this.importer, this.activationContext);
- verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), eq(secondLocations),
- anyBoolean());
+ verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), eq(secondLocations));
ConfigDataLocationResolverContext context = this.locationResolverContext.getValue();
assertThat(context.getParent()).hasToString("a");
}
@@ -230,13 +228,13 @@ void withProcessedImportsProvidesLocationResolverContextWithAccessToBootstrapReg
Map imported = new LinkedHashMap<>();
imported.put(new ConfigDataResolutionResult(LOCATION_1, new TestConfigDataResource("a'"), false),
new ConfigData(Arrays.asList(propertySource)));
- given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations), anyBoolean()))
+ given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations)))
.willReturn(imported);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapContext, Arrays.asList(existingContributor, contributor));
contributors.withProcessedImports(this.importer, this.activationContext);
- verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), any(), anyBoolean());
+ verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), any());
ConfigDataLocationResolverContext context = this.locationResolverContext.getValue();
assertThat(context.getBootstrapContext()).isSameAs(this.bootstrapContext);
}
@@ -253,13 +251,13 @@ void withProcessedImportsProvidesLoaderContextWithAccessToBootstrapRegistry() {
Map imported = new LinkedHashMap<>();
imported.put(new ConfigDataResolutionResult(LOCATION_1, new TestConfigDataResource("a'"), false),
new ConfigData(Arrays.asList(propertySource)));
- given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations), anyBoolean()))
+ given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations)))
.willReturn(imported);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapContext, Arrays.asList(existingContributor, contributor));
contributors.withProcessedImports(this.importer, this.activationContext);
- verify(this.importer).resolveAndLoad(any(), any(), this.loaderContext.capture(), any(), anyBoolean());
+ verify(this.importer).resolveAndLoad(any(), any(), this.loaderContext.capture(), any());
ConfigDataLoaderContext context = this.loaderContext.getValue();
assertThat(context.getBootstrapContext()).isSameAs(this.bootstrapContext);
}
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java
index 3a5a969f12f4..6d2c1c7d7dcd 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java
@@ -611,13 +611,13 @@ void runWhenImportFromEarlierDocumentUsesPlaceholder() {
assertThat(context.getEnvironment().getProperty("my.value")).isEqualTo("iwasimported");
}
- @Test
+ @Test // gh-26858
void runWhenImportWithProfileVariantOrdersPropertySourcesCorrectly() {
this.application.setAdditionalProfiles("dev");
ConfigurableApplicationContext context = this.application
.run("--spring.config.location=classpath:application-import-with-profile-variant.properties");
assertThat(context.getEnvironment().getProperty("my.value"))
- .isEqualTo("application-import-with-profile-variant-dev");
+ .isEqualTo("application-import-with-profile-variant-imported-dev");
}
@Test
@@ -750,8 +750,8 @@ void runWhenHasProfileSpecificFileWithActiveOnProfileProperty() {
assertThat(environment.getProperty("test2")).isEqualTo("test2");
}
- @Test // gh-26752
- void runWhenHasProfileSpecificImportWithImportDoesNotImportSecondProfileSpecificFile() {
+ @Test // gh-26960
+ void runWhenHasProfileSpecificImportWithImportImportsSecondProfileSpecificFile() {
ConfigurableApplicationContext context = this.application
.run("--spring.config.name=application-profile-specific-import-with-import");
ConfigurableEnvironment environment = context.getEnvironment();
@@ -759,17 +759,17 @@ void runWhenHasProfileSpecificImportWithImportDoesNotImportSecondProfileSpecific
assertThat(environment.containsProperty("application-profile-specific-import-with-import-p1")).isTrue();
assertThat(environment.containsProperty("application-profile-specific-import-with-import-p2")).isFalse();
assertThat(environment.containsProperty("application-profile-specific-import-with-import-import")).isTrue();
- assertThat(environment.containsProperty("application-profile-specific-import-with-import-import-p1")).isFalse();
- assertThat(environment.containsProperty("application-profile-specific-import-with-import-import-p2")).isFalse();
+ assertThat(environment.containsProperty("application-profile-specific-import-with-import-import-p1")).isTrue();
+ assertThat(environment.containsProperty("application-profile-specific-import-with-import-import-p2")).isTrue();
}
- @Test // gh-26753
- void runWhenHasProfileSpecificImportWithCustomImportDoesNotResolveProfileSpecific() {
+ @Test // gh-26960
+ void runWhenHasProfileSpecificImportWithCustomImportResolvesProfileSpecific() {
ConfigurableApplicationContext context = this.application
.run("--spring.config.name=application-profile-specific-import-with-custom-import");
ConfigurableEnvironment environment = context.getEnvironment();
assertThat(environment.containsProperty("test:boot")).isTrue();
- assertThat(environment.containsProperty("test:boot:ps")).isFalse();
+ assertThat(environment.containsProperty("test:boot:ps")).isTrue();
}
@Test // gh-26593
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataImporterTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataImporterTests.java
index ffd9bfbdf448..73082f3f6aea 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataImporterTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataImporterTests.java
@@ -80,16 +80,16 @@ void loadImportsResolvesAndLoadsLocations() throws Exception {
TestResource resource2 = new TestResource("r2");
ConfigData configData1 = new ConfigData(Collections.singleton(new MockPropertySource()));
ConfigData configData2 = new ConfigData(Collections.singleton(new MockPropertySource()));
- given(this.resolvers.resolve(this.locationResolverContext, location1, this.profiles, true))
+ given(this.resolvers.resolve(this.locationResolverContext, location1, this.profiles))
.willReturn(Collections.singletonList(new ConfigDataResolutionResult(location1, resource1, false)));
- given(this.resolvers.resolve(this.locationResolverContext, location2, this.profiles, true))
+ given(this.resolvers.resolve(this.locationResolverContext, location2, this.profiles))
.willReturn(Collections.singletonList(new ConfigDataResolutionResult(location2, resource2, false)));
given(this.loaders.load(this.loaderContext, resource1)).willReturn(configData1);
given(this.loaders.load(this.loaderContext, resource2)).willReturn(configData2);
ConfigDataImporter importer = new ConfigDataImporter(this.logFactory, ConfigDataNotFoundAction.FAIL,
this.resolvers, this.loaders);
Collection loaded = importer.resolveAndLoad(this.activationContext, this.locationResolverContext,
- this.loaderContext, Arrays.asList(location1, location2), true).values();
+ this.loaderContext, Arrays.asList(location1, location2)).values();
assertThat(loaded).containsExactly(configData2, configData1);
}
@@ -106,11 +106,11 @@ void loadImportsWhenAlreadyImportedLocationSkipsLoad() throws Exception {
ConfigData configData1 = new ConfigData(Collections.singleton(new MockPropertySource()));
ConfigData configData2 = new ConfigData(Collections.singleton(new MockPropertySource()));
ConfigData configData3 = new ConfigData(Collections.singleton(new MockPropertySource()));
- given(this.resolvers.resolve(this.locationResolverContext, location1, this.profiles, true))
+ given(this.resolvers.resolve(this.locationResolverContext, location1, this.profiles))
.willReturn(Collections.singletonList(new ConfigDataResolutionResult(location1, resource1, false)));
- given(this.resolvers.resolve(this.locationResolverContext, location2, this.profiles, true))
+ given(this.resolvers.resolve(this.locationResolverContext, location2, this.profiles))
.willReturn(Collections.singletonList(new ConfigDataResolutionResult(location2, resource2, false)));
- given(this.resolvers.resolve(this.locationResolverContext, location3, this.profiles, true))
+ given(this.resolvers.resolve(this.locationResolverContext, location3, this.profiles))
.willReturn(Collections.singletonList(new ConfigDataResolutionResult(location3, resource3, false)));
given(this.loaders.load(this.loaderContext, resource1)).willReturn(configData1);
given(this.loaders.load(this.loaderContext, resource2)).willReturn(configData2);
@@ -118,9 +118,9 @@ void loadImportsWhenAlreadyImportedLocationSkipsLoad() throws Exception {
ConfigDataImporter importer = new ConfigDataImporter(this.logFactory, ConfigDataNotFoundAction.FAIL,
this.resolvers, this.loaders);
Collection loaded1and2 = importer.resolveAndLoad(this.activationContext,
- this.locationResolverContext, this.loaderContext, locations1and2, true).values();
+ this.locationResolverContext, this.loaderContext, locations1and2).values();
Collection loaded2and3 = importer.resolveAndLoad(this.activationContext,
- this.locationResolverContext, this.loaderContext, locations2and3, true).values();
+ this.locationResolverContext, this.loaderContext, locations2and3).values();
assertThat(loaded1and2).containsExactly(configData2, configData1);
assertThat(loaded2and3).containsExactly(configData3);
}
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoadersTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoadersTests.java
index b6ddb6e6ae17..3bd6ad6a9889 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoadersTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoadersTests.java
@@ -53,13 +53,13 @@ class ConfigDataLoadersTests {
@Test
void createWhenLoaderHasLogParameterInjectsLog() {
- new ConfigDataLoaders(this.logFactory, this.bootstrapContext,
+ new ConfigDataLoaders(this.logFactory, this.bootstrapContext, null,
Arrays.asList(LoggingConfigDataLoader.class.getName()));
}
@Test
void createWhenLoaderHasDeferredLogFactoryParameterInjectsDeferredLogFactory() {
- ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext,
+ ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext, null,
Arrays.asList(DeferredLogFactoryConfigDataLoader.class.getName()));
assertThat(loaders).extracting("loaders").asList()
.satisfies(this::containsValidDeferredLogFactoryConfigDataLoader);
@@ -73,7 +73,7 @@ private void containsValidDeferredLogFactoryConfigDataLoader(List> list) {
@Test
void createWhenLoaderHasBootstrapParametersInjectsBootstrapContext() {
- new ConfigDataLoaders(this.logFactory, this.bootstrapContext,
+ new ConfigDataLoaders(this.logFactory, this.bootstrapContext, null,
Arrays.asList(BootstrappingConfigDataLoader.class.getName()));
assertThat(this.bootstrapContext.get(String.class)).isEqualTo("boot");
}
@@ -81,7 +81,7 @@ void createWhenLoaderHasBootstrapParametersInjectsBootstrapContext() {
@Test
void loadWhenSingleLoaderSupportsLocationReturnsLoadedConfigData() throws Exception {
TestConfigDataResource location = new TestConfigDataResource("test");
- ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext,
+ ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext, null,
Arrays.asList(TestConfigDataLoader.class.getName()));
ConfigData loaded = loaders.load(this.context, location);
assertThat(getLoader(loaded)).isInstanceOf(TestConfigDataLoader.class);
@@ -90,7 +90,7 @@ void loadWhenSingleLoaderSupportsLocationReturnsLoadedConfigData() throws Except
@Test
void loadWhenMultipleLoadersSupportLocationThrowsException() {
TestConfigDataResource location = new TestConfigDataResource("test");
- ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext,
+ ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext, null,
Arrays.asList(LoggingConfigDataLoader.class.getName(), TestConfigDataLoader.class.getName()));
assertThatIllegalStateException().isThrownBy(() -> loaders.load(this.context, location))
.withMessageContaining("Multiple loaders found for resource 'test'");
@@ -99,7 +99,7 @@ void loadWhenMultipleLoadersSupportLocationThrowsException() {
@Test
void loadWhenNoLoaderSupportsLocationThrowsException() {
TestConfigDataResource location = new TestConfigDataResource("test");
- ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext,
+ ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext, null,
Arrays.asList(NonLoadableConfigDataLoader.class.getName()));
assertThatIllegalStateException().isThrownBy(() -> loaders.load(this.context, location))
.withMessage("No loader found for resource 'test'");
@@ -108,7 +108,7 @@ void loadWhenNoLoaderSupportsLocationThrowsException() {
@Test
void loadWhenGenericTypeDoesNotMatchSkipsLoader() throws Exception {
TestConfigDataResource location = new TestConfigDataResource("test");
- ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext,
+ ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext, null,
Arrays.asList(OtherConfigDataLoader.class.getName(), SpecificConfigDataLoader.class.getName()));
ConfigData loaded = loaders.load(this.context, location);
assertThat(getLoader(loaded)).isInstanceOf(SpecificConfigDataLoader.class);
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationResolversTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationResolversTests.java
index 999fca26d69f..169101a8d6cc 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationResolversTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationResolversTests.java
@@ -131,7 +131,7 @@ void resolveResolvesUsingFirstSupportedResolver() {
this.binder, this.resourceLoader,
Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName()));
ConfigDataLocation location = ConfigDataLocation.of("LowestTestResolver:test");
- List resolved = resolvers.resolve(this.context, location, null, true);
+ List resolved = resolvers.resolve(this.context, location, null);
assertThat(resolved).hasSize(1);
TestConfigDataResource resource = (TestConfigDataResource) resolved.get(0).getResource();
assertThat(resource.getResolver()).isInstanceOf(LowestTestResolver.class);
@@ -145,7 +145,7 @@ void resolveWhenProfileMergesResolvedLocations() {
this.binder, this.resourceLoader,
Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName()));
ConfigDataLocation location = ConfigDataLocation.of("LowestTestResolver:test");
- List resolved = resolvers.resolve(this.context, location, this.profiles, true);
+ List resolved = resolvers.resolve(this.context, location, this.profiles);
assertThat(resolved).hasSize(2);
TestConfigDataResource resource = (TestConfigDataResource) resolved.get(0).getResource();
assertThat(resource.getResolver()).isInstanceOf(LowestTestResolver.class);
@@ -164,7 +164,7 @@ void resolveWhenNoResolverThrowsException() {
Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName()));
ConfigDataLocation location = ConfigDataLocation.of("Missing:test");
assertThatExceptionOfType(UnsupportedConfigDataLocationException.class)
- .isThrownBy(() -> resolvers.resolve(this.context, location, null, true))
+ .isThrownBy(() -> resolvers.resolve(this.context, location, null))
.satisfies((ex) -> assertThat(ex.getLocation()).isEqualTo(location));
}
@@ -173,7 +173,7 @@ void resolveWhenOptional() {
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext,
this.binder, this.resourceLoader, Arrays.asList(OptionalResourceTestResolver.class.getName()));
ConfigDataLocation location = ConfigDataLocation.of("OptionalResourceTestResolver:test");
- List resolved = resolvers.resolve(this.context, location, null, true);
+ List resolved = resolvers.resolve(this.context, location, null);
assertThat(resolved.get(0).getResource().isOptional()).isTrue();
}
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataPropertiesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataPropertiesTests.java
index 9622287a001b..2c9cd2c6c69d 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataPropertiesTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataPropertiesTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -160,18 +160,39 @@ void isActiveWhenActivateIsNull() {
@Test
void isActiveAgainstBoundData() {
MapConfigurationPropertySource source = new MapConfigurationPropertySource();
- source.put("spring.config.import", "one,two,three");
source.put("spring.config.activate.on-cloud-platform", "kubernetes");
- source.put("spring.config.activate.on-profiles", "a | b");
+ source.put("spring.config.activate.on-profile", "a | b");
Binder binder = new Binder(source);
ConfigDataProperties properties = ConfigDataProperties.get(binder);
ConfigDataActivationContext context = new ConfigDataActivationContext(CloudPlatform.KUBERNETES,
createTestProfiles());
- assertThat(properties.getImports()).containsExactly(ConfigDataLocation.of("one"), ConfigDataLocation.of("two"),
- ConfigDataLocation.of("three"));
assertThat(properties.isActive(context)).isTrue();
}
+ @Test
+ void isActiveAgainstBoundDataWhenProfilesDontMatch() {
+ MapConfigurationPropertySource source = new MapConfigurationPropertySource();
+ source.put("spring.config.activate.on-cloud-platform", "kubernetes");
+ source.put("spring.config.activate.on-profile", "x | z");
+ Binder binder = new Binder(source);
+ ConfigDataProperties properties = ConfigDataProperties.get(binder);
+ ConfigDataActivationContext context = new ConfigDataActivationContext(CloudPlatform.KUBERNETES,
+ createTestProfiles());
+ assertThat(properties.isActive(context)).isFalse();
+ }
+
+ @Test
+ void isActiveAgainstBoundDataWhenCloudPlatformDoesntMatch() {
+ MapConfigurationPropertySource source = new MapConfigurationPropertySource();
+ source.put("spring.config.activate.on-cloud-platform", "cloud-foundry");
+ source.put("spring.config.activate.on-profile", "a | b");
+ Binder binder = new Binder(source);
+ ConfigDataProperties properties = ConfigDataProperties.get(binder);
+ ConfigDataActivationContext context = new ConfigDataActivationContext(CloudPlatform.KUBERNETES,
+ createTestProfiles());
+ assertThat(properties.isActive(context)).isFalse();
+ }
+
@Test
void isActiveWhenBindingToLegacyProperty() {
MapConfigurationPropertySource source = new MapConfigurationPropertySource();
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BindConverterTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BindConverterTests.java
index b28616d394e2..944b45a8cb32 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BindConverterTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BindConverterTests.java
@@ -30,6 +30,7 @@
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.core.ResolvableType;
+import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.convert.TypeDescriptor;
@@ -178,6 +179,15 @@ void fallsBackToApplicationConversionService() {
assertThat(result.getSeconds()).isEqualTo(10);
}
+ @Test // gh-27028
+ void convertWhenConversionFailsThrowsConversionFailedExceptionRatherThanConverterNotFoundException() {
+ BindConverter bindConverter = BindConverter.get(Collections.singletonList(new GenericConversionService()),
+ null);
+ assertThatExceptionOfType(ConversionFailedException.class)
+ .isThrownBy(() -> bindConverter.convert("com.example.Missing", ResolvableType.forClass(Class.class)))
+ .withRootCauseInstanceOf(ClassNotFoundException.class);
+ }
+
private BindConverter getPropertyEditorOnlyBindConverter(
Consumer propertyEditorInitializer) {
return BindConverter.get(Collections.singletonList(new ThrowingConversionService()), propertyEditorInitializer);
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/BeanNotOfRequiredTypeFailureAnalyzerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/BeanNotOfRequiredTypeFailureAnalyzerTests.java
index f247a1b9e0e5..39fa0d576b8c 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/BeanNotOfRequiredTypeFailureAnalyzerTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/BeanNotOfRequiredTypeFailureAnalyzerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -35,6 +35,7 @@
* Tests for {@link BeanNotOfRequiredTypeFailureAnalyzer}.
*
* @author Andy Wilkinson
+ * @author Scott Frederick
*/
class BeanNotOfRequiredTypeFailureAnalyzerTests {
@@ -44,8 +45,13 @@ class BeanNotOfRequiredTypeFailureAnalyzerTests {
void jdkProxyCausesInjectionFailure() {
FailureAnalysis analysis = performAnalysis(JdkProxyConfiguration.class);
assertThat(analysis.getDescription()).startsWith("The bean 'asyncBean'");
- assertThat(analysis.getDescription()).contains("'" + AsyncBean.class.getName() + "'");
- assertThat(analysis.getDescription()).endsWith(String.format("%s%n", SomeInterface.class.getName()));
+ assertThat(analysis.getDescription())
+ .containsPattern("The bean is of type '" + AsyncBean.class.getPackage().getName() + ".\\$Proxy.*'");
+ assertThat(analysis.getDescription())
+ .contains(String.format("and implements:%n\t") + SomeInterface.class.getName());
+ assertThat(analysis.getDescription()).contains("Expected a bean of type '" + AsyncBean.class.getName() + "'");
+ assertThat(analysis.getDescription())
+ .contains(String.format("which implements:%n\t") + SomeInterface.class.getName());
}
private FailureAnalysis performAnalysis(Class> configuration) {
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/BindFailureAnalyzerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/BindFailureAnalyzerTests.java
index 2d7ad180d3ec..c0b5cdb418fb 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/BindFailureAnalyzerTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/BindFailureAnalyzerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -41,6 +41,7 @@
*
* @author Andy Wilkinson
* @author Madhura Bhave
+ * @author Phillip Webb
*/
class BindFailureAnalyzerTests {
@@ -76,7 +77,16 @@ void bindExceptionForUnknownValueInEnumListsValidValuesInAction() {
void bindExceptionWithNestedFailureShouldDisplayNestedMessage() {
FailureAnalysis analysis = performAnalysis(NestedFailureConfiguration.class, "test.foo.value=hello");
assertThat(analysis.getDescription()).contains(failure("test.foo.value", "hello",
- "\"test.foo.value\" from property source \"test\"", "This is a failure"));
+ "\"test.foo.value\" from property source \"test\"", "java.lang.RuntimeException: This is a failure"));
+ }
+
+ @Test // gh-27028
+ void bindExceptionDueToClassNotFoundConvertionFailure() {
+ FailureAnalysis analysis = performAnalysis(GenericFailureConfiguration.class,
+ "test.foo.type=com.example.Missing");
+ assertThat(analysis.getDescription()).contains(failure("test.foo.type", "com.example.Missing",
+ "\"test.foo.type\" from property source \"test\"",
+ "failed to convert java.lang.String to java.lang.Class> (caused by java.lang.ClassNotFoundException: com.example.Missing"));
}
private static String failure(String property, String value, String origin, String reason) {
@@ -178,6 +188,8 @@ static class GenericFailureProperties {
private int value;
+ private Class> type;
+
int getValue() {
return this.value;
}
@@ -186,6 +198,14 @@ void setValue(int value) {
this.value = value;
}
+ Class> getType() {
+ return this.type;
+ }
+
+ void setType(Class> type) {
+ this.type = type;
+ }
+
}
@ConfigurationProperties("test.foo")
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListenerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListenerTests.java
index 82147c558789..2d2db32616fd 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListenerTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListenerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -50,19 +50,21 @@ class EnvironmentPostProcessorApplicationListenerTests {
private DefaultBootstrapContext bootstrapContext = spy(new DefaultBootstrapContext());
private EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener(
- EnvironmentPostProcessorsFactory.of(TestEnvironmentPostProcessor.class), this.deferredLogs);
+ (classLoader) -> EnvironmentPostProcessorsFactory.of(TestEnvironmentPostProcessor.class),
+ this.deferredLogs);
@Test
void createUsesSpringFactories() {
EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener();
- assertThat(listener.getEnvironmentPostProcessors(this.bootstrapContext)).hasSizeGreaterThan(1);
+ assertThat(listener.getEnvironmentPostProcessors(null, this.bootstrapContext)).hasSizeGreaterThan(1);
}
@Test
void createWhenHasFactoryUsesFactory() {
EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener(
EnvironmentPostProcessorsFactory.of(TestEnvironmentPostProcessor.class));
- List postProcessors = listener.getEnvironmentPostProcessors(this.bootstrapContext);
+ List postProcessors = listener.getEnvironmentPostProcessors(null,
+ this.bootstrapContext);
assertThat(postProcessors).hasSize(1);
assertThat(postProcessors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class);
}
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/EnvironmentPostProcessorsFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/EnvironmentPostProcessorsFactoryTests.java
index fd5e6cd5b85b..7682491b8fa0 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/EnvironmentPostProcessorsFactoryTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/EnvironmentPostProcessorsFactoryTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2021 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 org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.logging.DeferredLogFactory;
+import org.springframework.core.OverridingClassLoader;
import org.springframework.core.env.ConfigurableEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
@@ -67,6 +68,25 @@ void ofClassNamesReturnsFactory() {
assertThat(processors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class);
}
+ @Test
+ void ofClassNamesWithClassLoaderReturnsFactory() {
+ OverridingClassLoader classLoader = new OverridingClassLoader(getClass().getClassLoader()) {
+
+ @Override
+ protected boolean isEligibleForOverriding(String className) {
+ return super.isEligibleForOverriding(className)
+ && className.equals(TestEnvironmentPostProcessor.class.getName());
+ }
+
+ };
+ EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory.of(classLoader,
+ TestEnvironmentPostProcessor.class.getName());
+ List processors = factory.getEnvironmentPostProcessors(this.logFactory,
+ this.bootstrapContext);
+ assertThat(processors).hasSize(1);
+ assertThat(processors.get(0).getClass().getClassLoader()).isSameAs(classLoader);
+ }
+
static class TestEnvironmentPostProcessor implements EnvironmentPostProcessor {
TestEnvironmentPostProcessor(DeferredLogFactory logFactory) {
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactoryTests.java
index 568ddf59a1bf..982224626fd1 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactoryTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactoryTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2021 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 @@
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.logging.DeferredLogFactory;
+import org.springframework.core.OverridingClassLoader;
import org.springframework.core.env.ConfigurableEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
@@ -53,49 +54,65 @@ void createWithClassesCreatesFactory() {
@Test
void createWithClassNamesArrayCreatesFactory() {
- ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(
+ ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(null,
TestEnvironmentPostProcessor.class.getName());
assertThatFactory(factory).createsSinglePostProcessor(TestEnvironmentPostProcessor.class);
}
@Test
void createWithClassNamesListCreatesFactory() {
- ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(
+ ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(null,
Arrays.asList(TestEnvironmentPostProcessor.class.getName()));
assertThatFactory(factory).createsSinglePostProcessor(TestEnvironmentPostProcessor.class);
}
+ @Test
+ void createWithClassNamesAndClassLoaderListCreatesFactory() {
+ OverridingClassLoader classLoader = new OverridingClassLoader(getClass().getClassLoader()) {
+
+ @Override
+ protected boolean isEligibleForOverriding(String className) {
+ return super.isEligibleForOverriding(className)
+ && className.equals(TestEnvironmentPostProcessor.class.getName());
+ }
+
+ };
+ ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(classLoader,
+ Arrays.asList(TestEnvironmentPostProcessor.class.getName()));
+ assertThatFactory(factory).createsSinglePostProcessorWithClassLoader(classLoader);
+ }
+
@Test
void getEnvironmentPostProcessorsWhenHasDefaultConstructorCreatesPostProcessors() {
- ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(
+ ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(null,
TestEnvironmentPostProcessor.class.getName());
assertThatFactory(factory).createsSinglePostProcessor(TestEnvironmentPostProcessor.class);
}
@Test
void getEnvironmentPostProcessorsWhenHasLogFactoryConstructorCreatesPostProcessors() {
- ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(
+ ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(null,
TestLogFactoryEnvironmentPostProcessor.class.getName());
assertThatFactory(factory).createsSinglePostProcessor(TestLogFactoryEnvironmentPostProcessor.class);
}
@Test
void getEnvironmentPostProcessorsWhenHasLogConstructorCreatesPostProcessors() {
- ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(
+ ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(null,
TestLogEnvironmentPostProcessor.class.getName());
assertThatFactory(factory).createsSinglePostProcessor(TestLogEnvironmentPostProcessor.class);
}
@Test
void getEnvironmentPostProcessorsWhenHasBootstrapRegistryConstructorCreatesPostProcessors() {
- ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(
+ ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(null,
TestBootstrapRegistryEnvironmentPostProcessor.class.getName());
assertThatFactory(factory).createsSinglePostProcessor(TestBootstrapRegistryEnvironmentPostProcessor.class);
}
@Test
void getEnvironmentPostProcessorsWhenHasNoSuitableConstructorThrowsException() {
- ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(
+ ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(null,
BadEnvironmentPostProcessor.class.getName());
assertThatIllegalArgumentException()
.isThrownBy(() -> factory.getEnvironmentPostProcessors(this.logFactory, this.bootstrapContext))
@@ -115,11 +132,21 @@ class EnvironmentPostProcessorsFactoryAssert {
}
void createsSinglePostProcessor(Class> expectedType) {
+ EnvironmentPostProcessor processor = getSingleProcessor();
+ assertThat(processor).isInstanceOf(expectedType);
+ }
+
+ void createsSinglePostProcessorWithClassLoader(OverridingClassLoader classLoader) {
+ EnvironmentPostProcessor processor = getSingleProcessor();
+ assertThat(processor.getClass().getClassLoader()).isSameAs(classLoader);
+ }
+
+ private EnvironmentPostProcessor getSingleProcessor() {
List processors = this.factory.getEnvironmentPostProcessors(
ReflectionEnvironmentPostProcessorsFactoryTests.this.logFactory,
ReflectionEnvironmentPostProcessorsFactoryTests.this.bootstrapContext);
assertThat(processors).hasSize(1);
- assertThat(processors.get(0)).isInstanceOf(expectedType);
+ return processors.get(0);
}
}
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/init/DataSourceScriptDatabaseInitializerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/init/DataSourceScriptDatabaseInitializerTests.java
index 428f031c658b..26a86fe6e0ce 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/init/DataSourceScriptDatabaseInitializerTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/init/DataSourceScriptDatabaseInitializerTests.java
@@ -22,20 +22,23 @@
import com.zaxxer.hikari.HikariDataSource;
import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
import org.springframework.boot.jdbc.DataSourceBuilder;
-import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer;
import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializerTests;
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
import org.springframework.boot.testsupport.BuildOutput;
import org.springframework.jdbc.core.JdbcTemplate;
+import static org.assertj.core.api.Assertions.assertThat;
+
/**
* Tests for {@link DataSourceScriptDatabaseInitializer}.
*
* @author Andy Wilkinson
*/
-class DataSourceScriptDatabaseInitializerTests extends AbstractScriptDatabaseInitializerTests {
+class DataSourceScriptDatabaseInitializerTests
+ extends AbstractScriptDatabaseInitializerTests {
private final HikariDataSource embeddedDataSource = DataSourceBuilder.create().type(HikariDataSource.class)
.url("jdbc:h2:mem:" + UUID.randomUUID()).build();
@@ -51,14 +54,21 @@ void closeDataSource() {
this.standloneDataSource.close();
}
+ @Test
+ void whenDatabaseIsInaccessibleThenItIsAssumedNotToBeEmbedded() {
+ DataSourceScriptDatabaseInitializer initializer = new DataSourceScriptDatabaseInitializer(
+ new HikariDataSource(), new DatabaseInitializationSettings());
+ assertThat(initializer.isEmbeddedDatabase()).isFalse();
+ }
+
@Override
- protected AbstractScriptDatabaseInitializer createEmbeddedDatabaseInitializer(
+ protected DataSourceScriptDatabaseInitializer createEmbeddedDatabaseInitializer(
DatabaseInitializationSettings settings) {
return new DataSourceScriptDatabaseInitializer(this.embeddedDataSource, settings);
}
@Override
- protected AbstractScriptDatabaseInitializer createStandaloneDatabaseInitializer(
+ protected DataSourceScriptDatabaseInitializer createStandaloneDatabaseInitializer(
DatabaseInitializationSettings settings) {
return new DataSourceScriptDatabaseInitializer(this.standloneDataSource, settings);
}
@@ -77,4 +87,9 @@ private int numberOfRows(DataSource dataSource, String sql) {
return new JdbcTemplate(dataSource).queryForObject(sql, Integer.class);
}
+ @Override
+ protected void assertDatabaseAccessed(boolean accessed, DataSourceScriptDatabaseInitializer initializer) {
+ assertThat(((HikariDataSource) initializer.getDataSource()).isRunning()).isEqualTo(accessed);
+ }
+
}
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java
index 3a58f22c5163..2a96b0781fa1 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java
@@ -35,6 +35,8 @@
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.config.Reconfigurable;
+import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
+import org.apache.logging.log4j.util.PropertiesUtil;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
@@ -370,6 +372,13 @@ void getLoggingConfigurationWithResetLevelWhenAlreadyConfiguredReturnsParentConf
.isEqualTo(new LoggerConfiguration("com.example.test", LogLevel.WARN, LogLevel.WARN));
}
+ @Test
+ void shutdownHookIsDisabled() {
+ assertThat(
+ PropertiesUtil.getProperties().getBooleanProperty(ShutdownCallbackRegistry.SHUTDOWN_HOOK_ENABLED, true))
+ .isFalse();
+ }
+
private String getRelativeClasspathLocation(String fileName) {
String defaultPath = ClassUtils.getPackageName(getClass());
defaultPath = defaultPath.replace('.', '/');
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/r2dbc/init/R2dbcScriptDatabaseInitializerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/r2dbc/init/R2dbcScriptDatabaseInitializerTests.java
index 4e4c3e034251..2baa9b00e314 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/r2dbc/init/R2dbcScriptDatabaseInitializerTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/r2dbc/init/R2dbcScriptDatabaseInitializerTests.java
@@ -21,7 +21,6 @@
import io.r2dbc.spi.ConnectionFactory;
import org.springframework.boot.r2dbc.ConnectionFactoryBuilder;
-import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer;
import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializerTests;
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
import org.springframework.boot.testsupport.BuildOutput;
@@ -32,7 +31,8 @@
*
* @author Andy Wilkinson
*/
-class R2dbcScriptDatabaseInitializerTests extends AbstractScriptDatabaseInitializerTests {
+class R2dbcScriptDatabaseInitializerTests
+ extends AbstractScriptDatabaseInitializerTests {
private final ConnectionFactory embeddedConnectionFactory = ConnectionFactoryBuilder
.withUrl("r2dbc:h2:mem:///" + UUID.randomUUID() + "?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE")
@@ -43,13 +43,13 @@ class R2dbcScriptDatabaseInitializerTests extends AbstractScriptDatabaseInitiali
+ UUID.randomUUID() + "?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE").build();
@Override
- protected AbstractScriptDatabaseInitializer createEmbeddedDatabaseInitializer(
+ protected R2dbcScriptDatabaseInitializer createEmbeddedDatabaseInitializer(
DatabaseInitializationSettings settings) {
return new R2dbcScriptDatabaseInitializer(this.embeddedConnectionFactory, settings);
}
@Override
- protected AbstractScriptDatabaseInitializer createStandaloneDatabaseInitializer(
+ protected R2dbcScriptDatabaseInitializer createStandaloneDatabaseInitializer(
DatabaseInitializationSettings settings) {
return new R2dbcScriptDatabaseInitializer(this.standaloneConnectionFactory, settings);
}
@@ -69,4 +69,9 @@ private int numberOfRows(ConnectionFactory connectionFactory, String sql) {
.map((number) -> ((Number) number).intValue()).block();
}
+ @Override
+ protected void assertDatabaseAccessed(boolean accessed, R2dbcScriptDatabaseInitializer initializer) {
+ // No-op as R2DBC does not need to access the database to determine its type
+ }
+
}
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/sql/init/AbstractScriptDatabaseInitializerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/sql/init/AbstractScriptDatabaseInitializerTests.java
index 664ba215b898..a3854f440ac7 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/sql/init/AbstractScriptDatabaseInitializerTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/sql/init/AbstractScriptDatabaseInitializerTests.java
@@ -29,16 +29,17 @@
/**
* Base class for testing {@link AbstractScriptDatabaseInitializer} implementations.
*
+ * @param type of the initializer being tested
* @author Andy Wilkinson
*/
-public abstract class AbstractScriptDatabaseInitializerTests {
+public abstract class AbstractScriptDatabaseInitializerTests {
@Test
void whenDatabaseIsInitializedThenSchemaAndDataScriptsAreApplied() {
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setSchemaLocations(Arrays.asList("schema.sql"));
settings.setDataLocations(Arrays.asList("data.sql"));
- AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(settings);
+ T initializer = createEmbeddedDatabaseInitializer(settings);
assertThat(initializer.initializeDatabase()).isTrue();
assertThat(numberOfEmbeddedRows("SELECT COUNT(*) FROM EXAMPLE")).isEqualTo(1);
}
@@ -47,8 +48,9 @@ void whenDatabaseIsInitializedThenSchemaAndDataScriptsAreApplied() {
void whenContinueOnErrorIsFalseThenInitializationFailsOnError() {
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setDataLocations(Arrays.asList("data.sql"));
- AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(settings);
+ T initializer = createEmbeddedDatabaseInitializer(settings);
assertThatExceptionOfType(DataAccessException.class).isThrownBy(() -> initializer.initializeDatabase());
+ assertThatDatabaseWasAccessed(initializer);
}
@Test
@@ -56,42 +58,47 @@ void whenContinueOnErrorIsTrueThenInitializationDoesNotFailOnError() {
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setContinueOnError(true);
settings.setDataLocations(Arrays.asList("data.sql"));
- AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(settings);
+ T initializer = createEmbeddedDatabaseInitializer(settings);
assertThat(initializer.initializeDatabase()).isTrue();
+ assertThatDatabaseWasAccessed(initializer);
}
@Test
void whenNoScriptsExistAtASchemaLocationThenInitializationFails() {
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setSchemaLocations(Arrays.asList("does-not-exist.sql"));
- AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(settings);
+ T initializer = createEmbeddedDatabaseInitializer(settings);
assertThatIllegalStateException().isThrownBy(initializer::initializeDatabase)
.withMessage("No schema scripts found at location 'does-not-exist.sql'");
+ assertThatDatabaseWasNotAccessed(initializer);
}
@Test
void whenNoScriptsExistAtADataLocationThenInitializationFails() {
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setDataLocations(Arrays.asList("does-not-exist.sql"));
- AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(settings);
+ T initializer = createEmbeddedDatabaseInitializer(settings);
assertThatIllegalStateException().isThrownBy(initializer::initializeDatabase)
.withMessage("No data scripts found at location 'does-not-exist.sql'");
+ assertThatDatabaseWasNotAccessed(initializer);
}
@Test
- void whenNoScriptsExistAtAnOptionalSchemaLocationThenInitializationSucceeds() {
+ void whenNoScriptsExistAtAnOptionalSchemaLocationThenDatabaseIsNotAccessed() {
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setSchemaLocations(Arrays.asList("optional:does-not-exist.sql"));
- AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(settings);
+ T initializer = createEmbeddedDatabaseInitializer(settings);
assertThat(initializer.initializeDatabase()).isFalse();
+ assertThatDatabaseWasNotAccessed(initializer);
}
@Test
- void whenNoScriptsExistAtAnOptionalDataLocationThenInitializationSucceeds() {
+ void whenNoScriptsExistAtAnOptionalDataLocationThenDatabaseIsNotAccessed() {
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setDataLocations(Arrays.asList("optional:does-not-exist.sql"));
- AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(settings);
+ T initializer = createEmbeddedDatabaseInitializer(settings);
assertThat(initializer.initializeDatabase()).isFalse();
+ assertThatDatabaseWasNotAccessed(initializer);
}
@Test
@@ -100,8 +107,9 @@ void whenModeIsNeverThenEmbeddedDatabaseIsNotInitialized() {
settings.setSchemaLocations(Arrays.asList("schema.sql"));
settings.setDataLocations(Arrays.asList("data.sql"));
settings.setMode(DatabaseInitializationMode.NEVER);
- AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(settings);
+ T initializer = createEmbeddedDatabaseInitializer(settings);
assertThat(initializer.initializeDatabase()).isFalse();
+ assertThatDatabaseWasNotAccessed(initializer);
}
@Test
@@ -110,8 +118,9 @@ void whenModeIsNeverThenStandaloneDatabaseIsNotInitialized() {
settings.setSchemaLocations(Arrays.asList("schema.sql"));
settings.setDataLocations(Arrays.asList("data.sql"));
settings.setMode(DatabaseInitializationMode.NEVER);
- AbstractScriptDatabaseInitializer initializer = createStandaloneDatabaseInitializer(settings);
+ T initializer = createStandaloneDatabaseInitializer(settings);
assertThat(initializer.initializeDatabase()).isFalse();
+ assertThatDatabaseWasNotAccessed(initializer);
}
@Test
@@ -120,7 +129,7 @@ void whenModeIsEmbeddedThenEmbeddedDatabaseIsInitialized() {
settings.setSchemaLocations(Arrays.asList("schema.sql"));
settings.setDataLocations(Arrays.asList("data.sql"));
settings.setMode(DatabaseInitializationMode.EMBEDDED);
- AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(settings);
+ T initializer = createEmbeddedDatabaseInitializer(settings);
assertThat(initializer.initializeDatabase()).isTrue();
assertThat(numberOfEmbeddedRows("SELECT COUNT(*) FROM EXAMPLE")).isEqualTo(1);
}
@@ -131,8 +140,9 @@ void whenModeIsEmbeddedThenStandaloneDatabaseIsNotInitialized() {
settings.setSchemaLocations(Arrays.asList("schema.sql"));
settings.setDataLocations(Arrays.asList("data.sql"));
settings.setMode(DatabaseInitializationMode.EMBEDDED);
- AbstractScriptDatabaseInitializer initializer = createStandaloneDatabaseInitializer(settings);
+ T initializer = createStandaloneDatabaseInitializer(settings);
assertThat(initializer.initializeDatabase()).isFalse();
+ assertThatDatabaseWasAccessed(initializer);
}
@Test
@@ -141,7 +151,7 @@ void whenModeIsAlwaysThenEmbeddedDatabaseIsInitialized() {
settings.setSchemaLocations(Arrays.asList("schema.sql"));
settings.setDataLocations(Arrays.asList("data.sql"));
settings.setMode(DatabaseInitializationMode.ALWAYS);
- AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(settings);
+ T initializer = createEmbeddedDatabaseInitializer(settings);
assertThat(initializer.initializeDatabase()).isTrue();
assertThat(numberOfEmbeddedRows("SELECT COUNT(*) FROM EXAMPLE")).isEqualTo(1);
}
@@ -152,19 +162,27 @@ void whenModeIsAlwaysThenStandaloneDatabaseIsInitialized() {
settings.setSchemaLocations(Arrays.asList("schema.sql"));
settings.setDataLocations(Arrays.asList("data.sql"));
settings.setMode(DatabaseInitializationMode.ALWAYS);
- AbstractScriptDatabaseInitializer initializer = createStandaloneDatabaseInitializer(settings);
+ T initializer = createStandaloneDatabaseInitializer(settings);
assertThat(initializer.initializeDatabase()).isTrue();
assertThat(numberOfStandaloneRows("SELECT COUNT(*) FROM EXAMPLE")).isEqualTo(1);
}
- protected abstract AbstractScriptDatabaseInitializer createStandaloneDatabaseInitializer(
- DatabaseInitializationSettings settings);
+ protected abstract T createStandaloneDatabaseInitializer(DatabaseInitializationSettings settings);
- protected abstract AbstractScriptDatabaseInitializer createEmbeddedDatabaseInitializer(
- DatabaseInitializationSettings settings);
+ protected abstract T createEmbeddedDatabaseInitializer(DatabaseInitializationSettings settings);
protected abstract int numberOfEmbeddedRows(String sql);
protected abstract int numberOfStandaloneRows(String sql);
+ private void assertThatDatabaseWasAccessed(T initializer) {
+ assertDatabaseAccessed(true, initializer);
+ }
+
+ private void assertThatDatabaseWasNotAccessed(T initializer) {
+ assertDatabaseAccessed(false, initializer);
+ }
+
+ protected abstract void assertDatabaseAccessed(boolean accessed, T initializer);
+
}
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/sql/init/dependency/DatabaseInitializationDependencyConfigurerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/sql/init/dependency/DatabaseInitializationDependencyConfigurerTests.java
index c8fd26562731..4a8fc5859f6f 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/sql/init/dependency/DatabaseInitializationDependencyConfigurerTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/sql/init/dependency/DatabaseInitializationDependencyConfigurerTests.java
@@ -24,7 +24,10 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.function.Consumer;
@@ -35,9 +38,11 @@
import org.junit.jupiter.api.io.TempDir;
import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
@@ -70,6 +75,30 @@ void resetMocks() {
MockedDependsOnDatabaseInitializationDetector.instance);
}
+ @Test
+ void beanFactoryPostProcessorHasOrderAllowingSubsequentPostProcessorsToFineTuneDependencies() {
+ performDetection(Arrays.asList(MockDatabaseInitializerDetector.class,
+ MockedDependsOnDatabaseInitializationDetector.class), (context) -> {
+ BeanDefinition alpha = BeanDefinitionBuilder.genericBeanDefinition(String.class)
+ .getBeanDefinition();
+ BeanDefinition bravo = BeanDefinitionBuilder.genericBeanDefinition(String.class)
+ .getBeanDefinition();
+ context.register(DependsOnCaptor.class);
+ context.register(DependencyConfigurerConfiguration.class);
+ context.registerBeanDefinition("alpha", alpha);
+ context.registerBeanDefinition("bravo", bravo);
+ given(MockDatabaseInitializerDetector.instance.detect(context.getBeanFactory()))
+ .willReturn(Collections.singleton("alpha"));
+ given(MockedDependsOnDatabaseInitializationDetector.instance.detect(context.getBeanFactory()))
+ .willReturn(Collections.singleton("bravo"));
+ context.refresh();
+ assertThat(DependsOnCaptor.dependsOn).hasEntrySatisfying("bravo",
+ (dependencies) -> assertThat(dependencies).containsExactly("alpha"));
+ assertThat(DependsOnCaptor.dependsOn).hasEntrySatisfying("alpha",
+ (dependencies) -> assertThat(dependencies).isEmpty());
+ });
+ }
+
@Test
void whenDetectorsAreCreatedThenTheEnvironmentCanBeInjected() {
performDetection(Arrays.asList(ConstructorInjectionDatabaseInitializerDetector.class,
@@ -77,6 +106,7 @@ void whenDetectorsAreCreatedThenTheEnvironmentCanBeInjected() {
BeanDefinition alpha = BeanDefinitionBuilder.genericBeanDefinition(String.class)
.getBeanDefinition();
context.registerBeanDefinition("alpha", alpha);
+ context.register(DependencyConfigurerConfiguration.class);
context.refresh();
assertThat(ConstructorInjectionDatabaseInitializerDetector.environment).isEqualTo(this.environment);
assertThat(ConstructorInjectionDependsOnDatabaseInitializationDetector.environment)
@@ -96,6 +126,7 @@ void whenDependenciesAreConfiguredThenBeansThatDependUponDatabaseInitializationD
.willReturn(Collections.singleton("alpha"));
given(MockedDependsOnDatabaseInitializationDetector.instance.detect(context.getBeanFactory()))
.willReturn(Collections.singleton("bravo"));
+ context.register(DependencyConfigurerConfiguration.class);
context.refresh();
assertThat(alpha.getAttribute(DatabaseInitializerDetector.class.getName()))
.isEqualTo(MockDatabaseInitializerDetector.class.getName());
@@ -123,6 +154,7 @@ void whenDependenciesAreConfiguredDetectedDatabaseInitializersAreInitializedInCo
context.registerBeanDefinition("alpha", alpha);
context.registerBeanDefinition("bravo", bravo);
context.registerBeanDefinition("charlie", charlie);
+ context.register(DependencyConfigurerConfiguration.class);
context.refresh();
assertThat(charlie.getDependsOn()).containsExactly("alpha", "bravo");
assertThat(bravo.getDependsOn()).containsExactly("alpha");
@@ -137,7 +169,6 @@ private void performDetection(Collection> detectors,
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.setEnvironment(this.environment);
context.setClassLoader(detectorSpringFactories);
- context.register(DependencyConfigurerConfiguration.class);
contextCallback.accept(context);
}
}
@@ -270,4 +301,28 @@ public Enumeration getResources(String name) throws IOException {
}
+ @Configuration(proxyBeanMethods = false)
+ static class DependsOnCaptor {
+
+ static final Map> dependsOn = new HashMap<>();
+
+ @Bean
+ static BeanFactoryPostProcessor dependsOnCapturingPostProcessor() {
+ return (beanFactory) -> {
+ dependsOn.clear();
+ for (String name : beanFactory.getBeanDefinitionNames()) {
+ storeDependsOn(name, beanFactory);
+ }
+ };
+ }
+
+ private static void storeDependsOn(String name, ConfigurableListableBeanFactory beanFactory) {
+ String[] dependsOn = beanFactory.getBeanDefinition(name).getDependsOn();
+ if (dependsOn != null) {
+ DependsOnCaptor.dependsOn.put(name, Arrays.asList(dependsOn));
+ }
+ }
+
+ }
+
}
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/util/InstantiatorTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/util/InstantiatorTests.java
index 36aecb33e66c..a622f6de8247 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/util/InstantiatorTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/util/InstantiatorTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2021 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.
@@ -23,6 +23,7 @@
import org.junit.jupiter.api.Test;
import org.springframework.core.Ordered;
+import org.springframework.core.OverridingClassLoader;
import org.springframework.core.annotation.Order;
import static org.assertj.core.api.Assertions.assertThat;
@@ -76,6 +77,29 @@ void instantiateWithFactory() {
assertThat(instance.getParamC()).isEqualTo(this.paramC);
}
+ @Test
+ void instantiateTypesCreatesInstance() {
+ WithDefaultConstructor instance = createInstantiator(WithDefaultConstructor.class)
+ .instantiateTypes(Collections.singleton(WithDefaultConstructor.class)).get(0);
+ assertThat(instance).isInstanceOf(WithDefaultConstructor.class);
+ }
+
+ @Test
+ void instantiateWithClassLoaderCreatesInstance() {
+ OverridingClassLoader classLoader = new OverridingClassLoader(getClass().getClassLoader()) {
+
+ @Override
+ protected boolean isEligibleForOverriding(String className) {
+ return super.isEligibleForOverriding(className)
+ && className.equals(WithDefaultConstructorSubclass.class.getName());
+ }
+
+ };
+ WithDefaultConstructor instance = createInstantiator(WithDefaultConstructor.class)
+ .instantiate(classLoader, Collections.singleton(WithDefaultConstructorSubclass.class.getName())).get(0);
+ assertThat(instance.getClass().getClassLoader()).isSameAs(classLoader);
+ }
+
@Test
void createWhenWrongTypeThrowsException() {
assertThatIllegalArgumentException()
@@ -96,7 +120,7 @@ private Instantiator createInstantiator(Class type) {
});
}
- static class WithDefaultConstructor {
+ static class WithDefaultConstructorSubclass extends WithDefaultConstructor {
}
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/util/WithDefaultConstructor.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/util/WithDefaultConstructor.java
new file mode 100644
index 000000000000..e84e80a67d4e
--- /dev/null
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/util/WithDefaultConstructor.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012-2021 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.util;
+
+/**
+ * Simple public class with a default constructor.
+ *
+ * @author Phillip Webb
+ */
+public class WithDefaultConstructor {
+
+}
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/build.gradle b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/build.gradle
new file mode 100644
index 000000000000..2591ecd3fce9
--- /dev/null
+++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/build.gradle
@@ -0,0 +1,37 @@
+plugins {
+ id "java"
+ id "org.springframework.boot.conventions"
+}
+
+description = "Spring Boot Jetty 10 smoke test"
+
+dependencies {
+ implementation(enforcedPlatform("org.eclipse.jetty:jetty-bom:10.0.5"))
+ implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-jetty")) {
+ exclude group: "org.eclipse.jetty.websocket", module: "websocket-server"
+ exclude group: "org.eclipse.jetty.websocket", module: "javax-websocket-server-impl"
+ }
+ implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-web")) {
+ exclude module: "spring-boot-starter-tomcat"
+ }
+
+ runtimeOnly("org.eclipse.jetty:jetty-alpn-java-server")
+ runtimeOnly("org.eclipse.jetty.http2:http2-server")
+
+ testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test"))
+ testImplementation("org.eclipse.jetty:jetty-client")
+ testImplementation("org.eclipse.jetty.http2:http2-client")
+ testImplementation("org.eclipse.jetty.http2:http2-http-client-transport")
+}
+
+def buildingWithJava11OrLater() {
+ return project.hasProperty("toolchainVersion") || JavaVersion.current().java11Compatible
+}
+
+compileTestJava {
+ enabled = buildingWithJava11OrLater()
+}
+
+test {
+ enabled = buildingWithJava11OrLater()
+}
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/main/java/smoketest/jetty10/ExampleServletContextListener.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/main/java/smoketest/jetty10/ExampleServletContextListener.java
new file mode 100644
index 000000000000..2b408390f887
--- /dev/null
+++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/main/java/smoketest/jetty10/ExampleServletContextListener.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012-2021 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 smoketest.jetty10;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.springframework.stereotype.Component;
+
+/**
+ * Simple {@link ServletContextListener} to test gh-2058.
+ */
+@Component
+public class ExampleServletContextListener implements ServletContextListener {
+
+ @Override
+ public void contextInitialized(ServletContextEvent sce) {
+ System.out.println("*** contextInitialized");
+ }
+
+ @Override
+ public void contextDestroyed(ServletContextEvent sce) {
+ System.out.println("*** contextDestroyed");
+ }
+
+}
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/main/java/smoketest/jetty10/SampleJetty10Application.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/main/java/smoketest/jetty10/SampleJetty10Application.java
new file mode 100644
index 000000000000..4cee5646189d
--- /dev/null
+++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/main/java/smoketest/jetty10/SampleJetty10Application.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2012-2021 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 smoketest.jetty10;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SampleJetty10Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SampleJetty10Application.class, args);
+ }
+
+}
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/main/java/smoketest/jetty10/service/HelloWorldService.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/main/java/smoketest/jetty10/service/HelloWorldService.java
new file mode 100644
index 000000000000..732de935ba6e
--- /dev/null
+++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/main/java/smoketest/jetty10/service/HelloWorldService.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2012-2021 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 smoketest.jetty10.service;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Component
+public class HelloWorldService {
+
+ @Value("${name:World}")
+ private String name;
+
+ public String getHelloMessage() {
+ return "Hello " + this.name;
+ }
+
+}
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/main/java/smoketest/jetty10/web/SampleController.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/main/java/smoketest/jetty10/web/SampleController.java
new file mode 100644
index 000000000000..d089c836d4f2
--- /dev/null
+++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/main/java/smoketest/jetty10/web/SampleController.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2012-2021 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 smoketest.jetty10.web;
+
+import smoketest.jetty10.service.HelloWorldService;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+@Controller
+public class SampleController {
+
+ @Autowired
+ private HelloWorldService helloWorldService;
+
+ @GetMapping("/")
+ @ResponseBody
+ public String helloWorld() {
+ return this.helloWorldService.getHelloMessage();
+ }
+
+}
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/main/resources/application.properties b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/main/resources/application.properties
new file mode 100644
index 000000000000..eab83fbdfd2a
--- /dev/null
+++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/main/resources/application.properties
@@ -0,0 +1,3 @@
+server.compression.enabled: true
+server.compression.min-response-size: 1
+server.jetty.threads.acceptors=2
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/main/resources/sample.jks b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/main/resources/sample.jks
new file mode 100644
index 000000000000..6aa9a28053a5
Binary files /dev/null and b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/main/resources/sample.jks differ
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/test/java/smoketest/jetty10/Jetty10Http2OverTlsTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/test/java/smoketest/jetty10/Jetty10Http2OverTlsTests.java
new file mode 100644
index 000000000000..40114a9a5fe0
--- /dev/null
+++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/test/java/smoketest/jetty10/Jetty10Http2OverTlsTests.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2012-2021 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 smoketest.jetty10;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.http2.client.HTTP2Client;
+import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2;
+import org.eclipse.jetty.io.ClientConnector;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledForJreRange;
+import org.junit.jupiter.api.condition.JRE;
+
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.http.HttpStatus;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for HTTP/2 over TLS (h2) with Jetty 10.
+ *
+ * @author Andy Wilkinson
+ */
+@EnabledForJreRange(min = JRE.JAVA_11)
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT,
+ properties = { "server.http2.enabled=true", "server.ssl.enabled=true",
+ "server.ssl.keystore=classpath:sample.jks", "server.ssl.key-store-password=secret",
+ "server.ssl.key-password=password", "logging.level.org.eclipse.jetty=debug" })
+public class Jetty10Http2OverTlsTests {
+
+ @LocalServerPort
+ private int port;
+
+ @Test
+ void httpOverTlsGetWhenHttp2AndSslAreEnabledSucceeds() throws Exception {
+ SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
+ sslContextFactory.setTrustAll(true);
+ ClientConnector clientConnector = new ClientConnector();
+ clientConnector.setSslContextFactory(sslContextFactory);
+ HttpClient client = new HttpClient(new HttpClientTransportOverHTTP2(new HTTP2Client(clientConnector)));
+ client.start();
+ try {
+ ContentResponse response = client.GET("https://localhost:" + this.port + "/");
+ assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
+ assertThat(response.getContentAsString()).isEqualTo("Hello World");
+ }
+ finally {
+ client.stop();
+ }
+ }
+
+}
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/test/java/smoketest/jetty10/SampleJetty10ApplicationTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/test/java/smoketest/jetty10/SampleJetty10ApplicationTests.java
new file mode 100644
index 000000000000..dafc11215470
--- /dev/null
+++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/test/java/smoketest/jetty10/SampleJetty10ApplicationTests.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2012-2021 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 smoketest.jetty10;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.zip.GZIPInputStream;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledForJreRange;
+import org.junit.jupiter.api.condition.JRE;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.StreamUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Basic integration tests for demo application.
+ *
+ * @author Dave Syer
+ * @author Andy Wilkinson
+ */
+@EnabledForJreRange(min = JRE.JAVA_11)
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
+class SampleJetty10ApplicationTests {
+
+ @Autowired
+ private TestRestTemplate restTemplate;
+
+ @Test
+ void testHome() {
+ ResponseEntity entity = this.restTemplate.getForEntity("/", String.class);
+ assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
+ assertThat(entity.getBody()).isEqualTo("Hello World");
+ }
+
+ @Test
+ void testCompression() throws Exception {
+ HttpHeaders requestHeaders = new HttpHeaders();
+ requestHeaders.set("Accept-Encoding", "gzip");
+ HttpEntity> requestEntity = new HttpEntity<>(requestHeaders);
+ ResponseEntity entity = this.restTemplate.exchange("/", HttpMethod.GET, requestEntity, byte[].class);
+ assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
+ try (GZIPInputStream inflater = new GZIPInputStream(new ByteArrayInputStream(entity.getBody()))) {
+ assertThat(StreamUtils.copyToString(inflater, StandardCharsets.UTF_8)).isEqualTo("Hello World");
+ }
+ }
+
+}
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/build.gradle b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/build.gradle
new file mode 100644
index 000000000000..ea3d1b7b680a
--- /dev/null
+++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/build.gradle
@@ -0,0 +1,22 @@
+plugins {
+ id "java"
+ id "org.springframework.boot.conventions"
+}
+
+description = "Spring Boot WebSocket Jetty 10 smoke test"
+
+dependencies {
+ implementation(enforcedPlatform("org.eclipse.jetty:jetty-bom:10.0.5"))
+ implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-jetty")) {
+ exclude group: "org.eclipse.jetty.websocket", module: "websocket-server"
+ exclude group: "org.eclipse.jetty.websocket", module: "javax-websocket-server-impl"
+ }
+ implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-websocket")) {
+ exclude module: "spring-boot-starter-tomcat"
+ }
+
+ runtimeOnly ("org.eclipse.jetty.websocket:websocket-javax-server")
+ runtimeOnly ("org.eclipse.jetty.websocket:websocket-jetty-server")
+
+ testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test"))
+}
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/SampleJetty10WebSocketsApplication.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/SampleJetty10WebSocketsApplication.java
new file mode 100644
index 000000000000..e2711206b20c
--- /dev/null
+++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/SampleJetty10WebSocketsApplication.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2012-2021 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 smoketest.websocket.jetty10;
+
+import smoketest.websocket.jetty10.client.GreetingService;
+import smoketest.websocket.jetty10.client.SimpleGreetingService;
+import smoketest.websocket.jetty10.echo.DefaultEchoService;
+import smoketest.websocket.jetty10.echo.EchoService;
+import smoketest.websocket.jetty10.echo.EchoWebSocketHandler;
+import smoketest.websocket.jetty10.reverse.ReverseWebSocketEndpoint;
+import smoketest.websocket.jetty10.snake.SnakeWebSocketHandler;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.config.annotation.EnableWebSocket;
+import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
+import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
+import org.springframework.web.socket.handler.PerConnectionWebSocketHandler;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+@Configuration(proxyBeanMethods = false)
+@EnableAutoConfiguration
+@EnableWebSocket
+public class SampleJetty10WebSocketsApplication extends SpringBootServletInitializer implements WebSocketConfigurer {
+
+ @Override
+ public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
+ registry.addHandler(echoWebSocketHandler(), "/echo").withSockJS();
+ registry.addHandler(snakeWebSocketHandler(), "/snake").withSockJS();
+ }
+
+ @Override
+ protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
+ return application.sources(SampleJetty10WebSocketsApplication.class);
+ }
+
+ @Bean
+ public EchoService echoService() {
+ return new DefaultEchoService("Did you say \"%s\"?");
+ }
+
+ @Bean
+ public GreetingService greetingService() {
+ return new SimpleGreetingService();
+ }
+
+ @Bean
+ public WebSocketHandler echoWebSocketHandler() {
+ return new EchoWebSocketHandler(echoService());
+ }
+
+ @Bean
+ public WebSocketHandler snakeWebSocketHandler() {
+ return new PerConnectionWebSocketHandler(SnakeWebSocketHandler.class);
+ }
+
+ @Bean
+ public ReverseWebSocketEndpoint reverseWebSocketEndpoint() {
+ return new ReverseWebSocketEndpoint();
+ }
+
+ @Bean
+ public ServerEndpointExporter serverEndpointExporter() {
+ return new ServerEndpointExporter();
+ }
+
+ public static void main(String[] args) {
+ SpringApplication.run(SampleJetty10WebSocketsApplication.class, args);
+ }
+
+}
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/client/GreetingService.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/client/GreetingService.java
new file mode 100644
index 000000000000..f0a8ff30da9d
--- /dev/null
+++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/client/GreetingService.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012-2021 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 smoketest.websocket.jetty10.client;
+
+public interface GreetingService {
+
+ String getGreeting();
+
+}
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/client/SimpleClientWebSocketHandler.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/client/SimpleClientWebSocketHandler.java
new file mode 100644
index 000000000000..5e628fb65188
--- /dev/null
+++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/client/SimpleClientWebSocketHandler.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2012-2021 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 smoketest.websocket.jetty10.client;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.web.socket.TextMessage;
+import org.springframework.web.socket.WebSocketSession;
+import org.springframework.web.socket.handler.TextWebSocketHandler;
+
+public class SimpleClientWebSocketHandler extends TextWebSocketHandler {
+
+ protected Log logger = LogFactory.getLog(SimpleClientWebSocketHandler.class);
+
+ private final GreetingService greetingService;
+
+ private final CountDownLatch latch;
+
+ private final AtomicReference messagePayload;
+
+ public SimpleClientWebSocketHandler(GreetingService greetingService, CountDownLatch latch,
+ AtomicReference message) {
+ this.greetingService = greetingService;
+ this.latch = latch;
+ this.messagePayload = message;
+ }
+
+ @Override
+ public void afterConnectionEstablished(WebSocketSession session) throws Exception {
+ TextMessage message = new TextMessage(this.greetingService.getGreeting());
+ session.sendMessage(message);
+ }
+
+ @Override
+ public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
+ this.logger.info("Received: " + message + " (" + this.latch.getCount() + ")");
+ session.close();
+ this.messagePayload.set(message.getPayload());
+ this.latch.countDown();
+ }
+
+}
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/client/SimpleGreetingService.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/client/SimpleGreetingService.java
new file mode 100644
index 000000000000..5b5669db0003
--- /dev/null
+++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/client/SimpleGreetingService.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012-2021 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 smoketest.websocket.jetty10.client;
+
+public class SimpleGreetingService implements GreetingService {
+
+ @Override
+ public String getGreeting() {
+ return "Hello world!";
+ }
+
+}
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/echo/DefaultEchoService.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/echo/DefaultEchoService.java
new file mode 100644
index 000000000000..d5df9473e3f7
--- /dev/null
+++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/echo/DefaultEchoService.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2012-2021 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 smoketest.websocket.jetty10.echo;
+
+public class DefaultEchoService implements EchoService {
+
+ private final String echoFormat;
+
+ public DefaultEchoService(String echoFormat) {
+ this.echoFormat = (echoFormat != null) ? echoFormat : "%s";
+ }
+
+ @Override
+ public String getMessage(String message) {
+ return String.format(this.echoFormat, message);
+ }
+
+}
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/echo/EchoService.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/echo/EchoService.java
new file mode 100644
index 000000000000..b0d83dcd9fe5
--- /dev/null
+++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/echo/EchoService.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012-2021 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 smoketest.websocket.jetty10.echo;
+
+public interface EchoService {
+
+ String getMessage(String message);
+
+}
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/echo/EchoWebSocketHandler.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/echo/EchoWebSocketHandler.java
new file mode 100644
index 000000000000..c7cd9186c2a6
--- /dev/null
+++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/echo/EchoWebSocketHandler.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2012-2021 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 smoketest.websocket.jetty10.echo;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.web.socket.CloseStatus;
+import org.springframework.web.socket.TextMessage;
+import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.WebSocketSession;
+import org.springframework.web.socket.handler.TextWebSocketHandler;
+
+/**
+ * Echo messages by implementing a Spring {@link WebSocketHandler} abstraction.
+ */
+public class EchoWebSocketHandler extends TextWebSocketHandler {
+
+ private static Log logger = LogFactory.getLog(EchoWebSocketHandler.class);
+
+ private final EchoService echoService;
+
+ public EchoWebSocketHandler(EchoService echoService) {
+ this.echoService = echoService;
+ }
+
+ @Override
+ public void afterConnectionEstablished(WebSocketSession session) {
+ logger.debug("Opened new session in instance " + this);
+ }
+
+ @Override
+ public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
+ String echoMessage = this.echoService.getMessage(message.getPayload());
+ logger.debug(echoMessage);
+ session.sendMessage(new TextMessage(echoMessage));
+ }
+
+ @Override
+ public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
+ session.close(CloseStatus.SERVER_ERROR);
+ }
+
+}
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/reverse/ReverseWebSocketEndpoint.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/reverse/ReverseWebSocketEndpoint.java
new file mode 100644
index 000000000000..c971981a4c1f
--- /dev/null
+++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/reverse/ReverseWebSocketEndpoint.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012-2021 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 smoketest.websocket.jetty10.reverse;
+
+import java.io.IOException;
+
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+@ServerEndpoint("/reverse")
+public class ReverseWebSocketEndpoint {
+
+ @OnMessage
+ public void handleMessage(Session session, String message) throws IOException {
+ session.getBasicRemote().sendText("Reversed: " + new StringBuilder(message).reverse());
+ }
+
+}
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/snake/Direction.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/snake/Direction.java
new file mode 100644
index 000000000000..76c1378ab5f0
--- /dev/null
+++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/snake/Direction.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012-2021 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 smoketest.websocket.jetty10.snake;
+
+public enum Direction {
+
+ NONE, NORTH, SOUTH, EAST, WEST
+
+}
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/snake/Location.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/snake/Location.java
new file mode 100644
index 000000000000..8121a754adc2
--- /dev/null
+++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/snake/Location.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2012-2021 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 smoketest.websocket.jetty10.snake;
+
+public class Location {
+
+ /**
+ * The X location.
+ */
+ public int x;
+
+ /**
+ * The Y location.
+ */
+ public int y;
+
+ public Location(int x, int y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ public Location getAdjacentLocation(Direction direction) {
+ switch (direction) {
+ case NORTH:
+ return new Location(this.x, this.y - SnakeUtils.GRID_SIZE);
+ case SOUTH:
+ return new Location(this.x, this.y + SnakeUtils.GRID_SIZE);
+ case EAST:
+ return new Location(this.x + SnakeUtils.GRID_SIZE, this.y);
+ case WEST:
+ return new Location(this.x - SnakeUtils.GRID_SIZE, this.y);
+ case NONE:
+ // fall through
+ default:
+ return this;
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Location location = (Location) o;
+ if (this.x != location.x) {
+ return false;
+ }
+ if (this.y != location.y) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = this.x;
+ result = 31 * result + this.y;
+ return result;
+ }
+
+}
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/snake/Snake.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/snake/Snake.java
new file mode 100644
index 000000000000..8f4cc363ea26
--- /dev/null
+++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/snake/Snake.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2012-2021 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 smoketest.websocket.jetty10.snake;
+
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Deque;
+
+import org.springframework.web.socket.TextMessage;
+import org.springframework.web.socket.WebSocketSession;
+
+public class Snake {
+
+ private static final int DEFAULT_LENGTH = 5;
+
+ private final Deque tail = new ArrayDeque<>();
+
+ private final Object monitor = new Object();
+
+ private final int id;
+
+ private final WebSocketSession session;
+
+ private final String hexColor;
+
+ private Direction direction;
+
+ private int length = DEFAULT_LENGTH;
+
+ private Location head;
+
+ public Snake(int id, WebSocketSession session) {
+ this.id = id;
+ this.session = session;
+ this.hexColor = SnakeUtils.getRandomHexColor();
+ resetState();
+ }
+
+ private void resetState() {
+ this.direction = Direction.NONE;
+ this.head = SnakeUtils.getRandomLocation();
+ this.tail.clear();
+ this.length = DEFAULT_LENGTH;
+ }
+
+ private void kill() throws Exception {
+ synchronized (this.monitor) {
+ resetState();
+ sendMessage("{'type': 'dead'}");
+ }
+ }
+
+ private void reward() throws Exception {
+ synchronized (this.monitor) {
+ this.length++;
+ sendMessage("{'type': 'kill'}");
+ }
+ }
+
+ protected void sendMessage(String msg) throws Exception {
+ this.session.sendMessage(new TextMessage(msg));
+ }
+
+ public void update(Collection snakes) throws Exception {
+ synchronized (this.monitor) {
+ Location nextLocation = this.head.getAdjacentLocation(this.direction);
+ if (nextLocation.x >= SnakeUtils.PLAYFIELD_WIDTH) {
+ nextLocation.x = 0;
+ }
+ if (nextLocation.y >= SnakeUtils.PLAYFIELD_HEIGHT) {
+ nextLocation.y = 0;
+ }
+ if (nextLocation.x < 0) {
+ nextLocation.x = SnakeUtils.PLAYFIELD_WIDTH;
+ }
+ if (nextLocation.y < 0) {
+ nextLocation.y = SnakeUtils.PLAYFIELD_HEIGHT;
+ }
+ if (this.direction != Direction.NONE) {
+ this.tail.addFirst(this.head);
+ if (this.tail.size() > this.length) {
+ this.tail.removeLast();
+ }
+ this.head = nextLocation;
+ }
+
+ handleCollisions(snakes);
+ }
+ }
+
+ private void handleCollisions(Collection snakes) throws Exception {
+ for (Snake snake : snakes) {
+ boolean headCollision = this.id != snake.id && snake.getHead().equals(this.head);
+ boolean tailCollision = snake.getTail().contains(this.head);
+ if (headCollision || tailCollision) {
+ kill();
+ if (this.id != snake.id) {
+ snake.reward();
+ }
+ }
+ }
+ }
+
+ public Location getHead() {
+ synchronized (this.monitor) {
+ return this.head;
+ }
+ }
+
+ public Collection getTail() {
+ synchronized (this.monitor) {
+ return this.tail;
+ }
+ }
+
+ public void setDirection(Direction direction) {
+ synchronized (this.monitor) {
+ this.direction = direction;
+ }
+ }
+
+ public String getLocationsJson() {
+ synchronized (this.monitor) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(String.format("{x: %d, y: %d}", Integer.valueOf(this.head.x), Integer.valueOf(this.head.y)));
+ for (Location location : this.tail) {
+ sb.append(',');
+ sb.append(String.format("{x: %d, y: %d}", Integer.valueOf(location.x), Integer.valueOf(location.y)));
+ }
+ return String.format("{'id':%d,'body':[%s]}", Integer.valueOf(this.id), sb.toString());
+ }
+ }
+
+ public int getId() {
+ return this.id;
+ }
+
+ public String getHexColor() {
+ return this.hexColor;
+ }
+
+}
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/snake/SnakeTimer.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/snake/SnakeTimer.java
new file mode 100644
index 000000000000..c140a4703b8e
--- /dev/null
+++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/snake/SnakeTimer.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2012-2021 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 smoketest.websocket.jetty10.snake;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Sets up the timer for the multi-player snake game WebSocket example.
+ */
+public final class SnakeTimer {
+
+ private static final long TICK_DELAY = 100;
+
+ private static final Object MONITOR = new Object();
+
+ private static final Log logger = LogFactory.getLog(SnakeTimer.class);
+
+ private static final ConcurrentHashMap snakes = new ConcurrentHashMap<>();
+
+ private static Timer gameTimer = null;
+
+ private SnakeTimer() {
+ }
+
+ public static void addSnake(Snake snake) {
+ synchronized (MONITOR) {
+ if (snakes.isEmpty()) {
+ startTimer();
+ }
+ snakes.put(Integer.valueOf(snake.getId()), snake);
+ }
+ }
+
+ public static Collection getSnakes() {
+ return Collections.unmodifiableCollection(snakes.values());
+ }
+
+ public static void removeSnake(Snake snake) {
+ synchronized (MONITOR) {
+ snakes.remove(Integer.valueOf(snake.getId()));
+ if (snakes.isEmpty()) {
+ stopTimer();
+ }
+ }
+ }
+
+ public static void tick() throws Exception {
+ StringBuilder sb = new StringBuilder();
+ for (Iterator iterator = SnakeTimer.getSnakes().iterator(); iterator.hasNext();) {
+ Snake snake = iterator.next();
+ snake.update(SnakeTimer.getSnakes());
+ sb.append(snake.getLocationsJson());
+ if (iterator.hasNext()) {
+ sb.append(',');
+ }
+ }
+ broadcast(String.format("{'type': 'update', 'data' : [%s]}", sb.toString()));
+ }
+
+ public static void broadcast(String message) throws Exception {
+ Collection snakes = new CopyOnWriteArrayList<>(SnakeTimer.getSnakes());
+ for (Snake snake : snakes) {
+ try {
+ snake.sendMessage(message);
+ }
+ catch (Throwable ex) {
+ // if Snake#sendMessage fails the client is removed
+ removeSnake(snake);
+ }
+ }
+ }
+
+ public static void startTimer() {
+ gameTimer = new Timer(SnakeTimer.class.getSimpleName() + " Timer");
+ gameTimer.scheduleAtFixedRate(new TimerTask() {
+ @Override
+ public void run() {
+ try {
+ tick();
+ }
+ catch (Throwable ex) {
+ logger.error("Caught to prevent timer from shutting down", ex);
+ }
+ }
+ }, TICK_DELAY, TICK_DELAY);
+ }
+
+ public static void stopTimer() {
+ if (gameTimer != null) {
+ gameTimer.cancel();
+ }
+ }
+
+}
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/snake/SnakeUtils.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/snake/SnakeUtils.java
new file mode 100644
index 000000000000..081e1a6d75f9
--- /dev/null
+++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/snake/SnakeUtils.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2012-2021 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 smoketest.websocket.jetty10.snake;
+
+import java.awt.Color;
+import java.util.Random;
+
+public final class SnakeUtils {
+
+ /**
+ * The width of the playfield.
+ */
+ public static final int PLAYFIELD_WIDTH = 640;
+
+ /**
+ * The height of the playfield.
+ */
+ public static final int PLAYFIELD_HEIGHT = 480;
+
+ /**
+ * The grid size.
+ */
+ public static final int GRID_SIZE = 10;
+
+ private static final Random random = new Random();
+
+ private SnakeUtils() {
+ }
+
+ public static String getRandomHexColor() {
+ float hue = random.nextFloat();
+ // sat between 0.1 and 0.3
+ float saturation = (random.nextInt(2000) + 1000) / 10000f;
+ float luminance = 0.9f;
+ Color color = Color.getHSBColor(hue, saturation, luminance);
+ return '#' + Integer.toHexString((color.getRGB() & 0xffffff) | 0x1000000).substring(1);
+ }
+
+ public static Location getRandomLocation() {
+ int x = roundByGridSize(random.nextInt(PLAYFIELD_WIDTH));
+ int y = roundByGridSize(random.nextInt(PLAYFIELD_HEIGHT));
+ return new Location(x, y);
+ }
+
+ private static int roundByGridSize(int value) {
+ value = value + (GRID_SIZE / 2);
+ value = value / GRID_SIZE;
+ value = value * GRID_SIZE;
+ return value;
+ }
+
+}
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/snake/SnakeWebSocketHandler.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/snake/SnakeWebSocketHandler.java
new file mode 100644
index 000000000000..b7d447655081
--- /dev/null
+++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/java/smoketest/websocket/jetty10/snake/SnakeWebSocketHandler.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2012-2021 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 smoketest.websocket.jetty10.snake;
+
+import java.awt.Color;
+import java.util.Iterator;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.springframework.web.socket.CloseStatus;
+import org.springframework.web.socket.TextMessage;
+import org.springframework.web.socket.WebSocketSession;
+import org.springframework.web.socket.handler.TextWebSocketHandler;
+
+public class SnakeWebSocketHandler extends TextWebSocketHandler {
+
+ private static final AtomicInteger snakeIds = new AtomicInteger();
+
+ private static final Random random = new Random();
+
+ private final int id;
+
+ private Snake snake;
+
+ public static String getRandomHexColor() {
+ float hue = random.nextFloat();
+ // sat between 0.1 and 0.3
+ float saturation = (random.nextInt(2000) + 1000) / 10000f;
+ float luminance = 0.9f;
+ Color color = Color.getHSBColor(hue, saturation, luminance);
+ return '#' + Integer.toHexString((color.getRGB() & 0xffffff) | 0x1000000).substring(1);
+ }
+
+ public static Location getRandomLocation() {
+ int x = roundByGridSize(random.nextInt(SnakeUtils.PLAYFIELD_WIDTH));
+ int y = roundByGridSize(random.nextInt(SnakeUtils.PLAYFIELD_HEIGHT));
+ return new Location(x, y);
+ }
+
+ private static int roundByGridSize(int value) {
+ value = value + (SnakeUtils.GRID_SIZE / 2);
+ value = value / SnakeUtils.GRID_SIZE;
+ value = value * SnakeUtils.GRID_SIZE;
+ return value;
+ }
+
+ public SnakeWebSocketHandler() {
+ this.id = snakeIds.getAndIncrement();
+ }
+
+ @Override
+ public void afterConnectionEstablished(WebSocketSession session) throws Exception {
+ this.snake = new Snake(this.id, session);
+ SnakeTimer.addSnake(this.snake);
+ StringBuilder sb = new StringBuilder();
+ for (Iterator iterator = SnakeTimer.getSnakes().iterator(); iterator.hasNext();) {
+ Snake snake = iterator.next();
+ sb.append(String.format("{id: %d, color: '%s'}", Integer.valueOf(snake.getId()), snake.getHexColor()));
+ if (iterator.hasNext()) {
+ sb.append(',');
+ }
+ }
+ SnakeTimer.broadcast(String.format("{'type': 'join','data':[%s]}", sb.toString()));
+ }
+
+ @Override
+ protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
+ String payload = message.getPayload();
+ if ("west".equals(payload)) {
+ this.snake.setDirection(Direction.WEST);
+ }
+ else if ("north".equals(payload)) {
+ this.snake.setDirection(Direction.NORTH);
+ }
+ else if ("east".equals(payload)) {
+ this.snake.setDirection(Direction.EAST);
+ }
+ else if ("south".equals(payload)) {
+ this.snake.setDirection(Direction.SOUTH);
+ }
+ }
+
+ @Override
+ public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
+ SnakeTimer.removeSnake(this.snake);
+ SnakeTimer.broadcast(String.format("{'type': 'leave', 'id': %d}", Integer.valueOf(this.id)));
+ }
+
+}
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/resources/static/echo.html b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/resources/static/echo.html
new file mode 100644
index 000000000000..54d33f55bd8a
--- /dev/null
+++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-websocket-jetty10/src/main/resources/static/echo.html
@@ -0,0 +1,134 @@
+
+
+
+
+
+ Apache Tomcat WebSocket Examples: Echo
+
+
+
+
+
+
+