Merge branch '4.0.x' into 4.1.x

This commit is contained in:
Phillip Webb
2026-08-31 21:24:40 -07:00
12 changed files with 128 additions and 63 deletions
@@ -232,16 +232,16 @@ public class AntoraAsciidocAttributes {
private void addUrlLibraryLinkAttributes(Map<String, String> attributes) {
Map<String, String> packageAttributes = new LinkedHashMap<>();
this.libraries.forEach((library) -> {
library.getLinks().forEach((name, links) -> links.forEach((link) -> {
library.getLinks().forEachLink((type, link) -> {
String linkRootName = (link.rootName() != null) ? link.rootName() : library.getLinkRootName();
String linkName = "url-" + linkRootName + "-" + name;
String linkName = "url-" + linkRootName + "-" + type.attributeName();
attributes.put(linkName, link.url(library));
link.packages()
.stream()
.map(this::packageAttributeName)
.forEach((packageAttributeName) -> packageAttributes.put(packageAttributeName,
"{" + linkName + "}"));
}));
});
});
attributes.putAll(packageAttributes);
}
@@ -50,6 +50,8 @@ import org.springframework.boot.build.bom.Library.Group;
import org.springframework.boot.build.bom.Library.ImportedBom;
import org.springframework.boot.build.bom.Library.LibraryVersion;
import org.springframework.boot.build.bom.Library.Link;
import org.springframework.boot.build.bom.Library.LinkType;
import org.springframework.boot.build.bom.Library.Links;
import org.springframework.boot.build.bom.Library.Module;
import org.springframework.boot.build.bom.Library.PermittedDependency;
import org.springframework.boot.build.bom.Library.PomPropertyVersionAlignment;
@@ -131,7 +133,7 @@ public class BomExtension {
addLibrary(new Library(name, libraryHandler.calendarName, libraryVersion, libraryHandler.groups,
libraryHandler.upgradePolicy, libraryHandler.prohibitedVersions, firstParty,
versionAlignment(libraryHandler), libraryHandler.alignWith.bomAlignment, libraryHandler.linkRootName,
libraryHandler.links));
new Links(libraryHandler.links)));
}
private VersionAlignment versionAlignment(LibraryHandler libraryHandler) {
@@ -230,7 +232,7 @@ public class BomExtension {
private String linkRootName;
private final Map<String, List<Link>> links = new HashMap<>();
private final Map<LinkType, List<Link>> links = new HashMap<>();
@Inject
public LibraryHandler(Project project, String version) {
@@ -524,14 +526,14 @@ public class BomExtension {
public static class LinksHandler {
private final Map<String, List<Link>> links = new HashMap<>();
private final Map<LinkType, List<Link>> links = new HashMap<>();
public void site(String linkTemplate) {
site(asFactory(linkTemplate));
}
public void site(Function<LibraryVersion, String> linkFactory) {
add("site", linkFactory);
add(LinkType.SITE, linkFactory);
}
public void github(String linkTemplate) {
@@ -539,7 +541,7 @@ public class BomExtension {
}
public void github(Function<LibraryVersion, String> linkFactory) {
add("github", linkFactory);
add(LinkType.GITHUB, linkFactory);
}
public void docs(String linkTemplate) {
@@ -547,7 +549,7 @@ public class BomExtension {
}
public void docs(Function<LibraryVersion, String> linkFactory) {
add("docs", linkFactory);
add(LinkType.DOCS, linkFactory);
}
public void javadoc(String linkTemplate) {
@@ -559,15 +561,15 @@ public class BomExtension {
}
public void javadoc(Function<LibraryVersion, String> linkFactory) {
add("javadoc", linkFactory);
add(LinkType.JAVADOC, linkFactory);
}
public void javadoc(Function<LibraryVersion, String> linkFactory, String... packages) {
add("javadoc", linkFactory, packages);
add(LinkType.JAVADOC, linkFactory, packages);
}
public void javadoc(String rootName, Function<LibraryVersion, String> linkFactory, String... packages) {
add(rootName, "javadoc", linkFactory, packages);
add(rootName, LinkType.JAVADOC, linkFactory, packages);
}
public void releaseNotes(String linkTemplate) {
@@ -575,25 +577,29 @@ public class BomExtension {
}
public void releaseNotes(Function<LibraryVersion, String> linkFactory) {
add("releaseNotes", linkFactory);
add(LinkType.RELEASE_NOTES, linkFactory);
}
public void add(String name, String linkTemplate) {
add(name, asFactory(linkTemplate));
public void layersXsd(String linkTemplate) {
layersXsd(asFactory(linkTemplate));
}
public void add(String name, Function<LibraryVersion, String> linkFactory) {
public void layersXsd(Function<LibraryVersion, String> linkFactory) {
add(LinkType.LAYERS_XSD, linkFactory);
}
private void add(LinkType name, Function<LibraryVersion, String> linkFactory) {
add(name, linkFactory, null);
}
public void add(String name, Function<LibraryVersion, String> linkFactory, String[] packages) {
add(null, name, linkFactory, packages);
private void add(LinkType type, Function<LibraryVersion, String> linkFactory, String[] packages) {
add(null, type, linkFactory, packages);
}
private void add(String rootName, String name, Function<LibraryVersion, String> linkFactory,
private void add(String rootName, LinkType type, Function<LibraryVersion, String> linkFactory,
String[] packages) {
Link link = new Link(rootName, linkFactory, (packages != null) ? List.of(packages) : null);
this.links.computeIfAbsent(name, (key) -> new ArrayList<>()).add(link);
this.links.computeIfAbsent(type, (key) -> new ArrayList<>()).add(link);
}
private Function<LibraryVersion, String> asFactory(String linkTemplate) {
@@ -42,6 +42,7 @@ import org.w3c.dom.NodeList;
import org.springframework.boot.build.bom.Library.Group;
import org.springframework.boot.build.bom.Library.ImportedBom;
import org.springframework.boot.build.bom.Library.Link;
import org.springframework.boot.build.bom.Library.LinkType;
import org.springframework.boot.build.bom.Library.Module;
import org.springframework.boot.build.bom.ResolvedBom.Bom;
import org.springframework.boot.build.bom.ResolvedBom.Id;
@@ -98,7 +99,7 @@ class BomResolver {
}
private List<Link> javadocLinksOf(Library library) {
List<Link> javadocLinks = library.getLinks("javadoc");
List<Link> javadocLinks = library.getLinks(LinkType.JAVADOC);
return (javadocLinks != null) ? javadocLinks : Collections.emptyList();
}
@@ -61,18 +61,18 @@ public abstract class CheckLinks extends DefaultTask {
.defaultStatusHandler((status) -> true, NOOP_ERROR_HANDLER)
.build();
for (Library library : this.bom.getLibraries()) {
library.getLinks().forEach((name, links) -> links.forEach((link) -> {
library.getLinks().forEachLink((type, link) -> {
URI uri;
try {
uri = new URI(link.url(library));
ResponseEntity<String> response = restClient.head().uri(uri).retrieve().toEntity(String.class);
System.out.printf("[%3d] %s - %s (%s)%n", response.getStatusCode().value(), library.getName(), name,
System.out.printf("[%3d] %s - %s (%s)%n", response.getStatusCode().value(), library.getName(), type,
uri);
}
catch (URISyntaxException ex) {
throw new RuntimeException(ex);
}
}));
});
}
}
@@ -29,6 +29,7 @@ import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Matcher;
@@ -81,7 +82,7 @@ public class Library {
private final String linkRootName;
private final Map<String, List<Link>> links;
private final Links links;
/**
* Create a new {@code Library} with the given {@code name}, {@code version}, and
@@ -103,8 +104,7 @@ public class Library {
*/
public Library(String name, String calendarName, LibraryVersion version, List<Group> groups,
UpgradePolicy upgradePolicy, List<ProhibitedVersion> prohibitedVersions, FirstParty firstParty,
VersionAlignment versionAlignment, BomAlignment bomAlignment, String linkRootName,
Map<String, List<Link>> links) {
VersionAlignment versionAlignment, BomAlignment bomAlignment, String linkRootName, Links links) {
this.name = name;
this.calendarName = (calendarName != null) ? calendarName : name;
this.version = version;
@@ -117,7 +117,7 @@ public class Library {
this.versionAlignment = versionAlignment;
this.bomAlignment = bomAlignment;
this.linkRootName = (linkRootName != null) ? linkRootName : generateLinkRootName(name);
this.links = (links != null) ? Collections.unmodifiableMap(new TreeMap<>(links)) : Collections.emptyMap();
this.links = (links != null) ? links : Links.empty();
}
private static String generateLinkRootName(String name) {
@@ -172,23 +172,23 @@ public class Library {
return this.bomAlignment;
}
public Map<String, List<Link>> getLinks() {
public Links getLinks() {
return this.links;
}
public String getLinkUrl(String name) {
List<Link> links = getLinks(name);
public String getLinkUrl(LinkType type) {
List<Link> links = getLinks(type);
if (links == null || links.isEmpty()) {
return null;
}
if (links.size() > 1) {
throw new IllegalStateException("Expected a single '%s' link for %s".formatted(name, getName()));
throw new IllegalStateException("Expected a single '%s' link for %s".formatted(type, getName()));
}
return links.get(0).url(this);
}
public List<Link> getLinks(String name) {
return this.links.get(name);
public List<Link> getLinks(LinkType type) {
return this.links.byType().get(type);
}
public String getNameAndVersion() {
@@ -716,6 +716,68 @@ public class Library {
}
public enum LinkType {
/**
* A link to the library site.
*/
SITE("site"),
/**
* A link to GitHub.
*/
GITHUB("github"),
/**
* A link to the documentation.
*/
DOCS("docs"),
/**
* A link to javadoc.
*/
JAVADOC("javadoc"),
/**
* A link to release notes.
*/
RELEASE_NOTES("release-notes"),
/**
* A link to the layers schema.
*/
LAYERS_XSD("layers-xsd");
private final String attributeName;
LinkType(String attributeName) {
this.attributeName = attributeName;
}
public String attributeName() {
return this.attributeName;
}
}
public record Links(Map<LinkType, List<Link>> byType) {
private static final Links EMPTY = new Links(Collections.emptyMap());
public Links {
byType = (byType != null) ? Collections.unmodifiableMap(new TreeMap<>(byType)) : Collections.emptyMap();
}
public void forEachLink(BiConsumer<LinkType, Link> action) {
byType().forEach((type, links) -> links.forEach((link) -> action.accept(type, link)));
}
public static Links empty() {
return EMPTY;
}
}
public record Link(String rootName, Function<LibraryVersion, String> factory, List<String> packages) {
private static final Pattern PACKAGE_EXPAND = Pattern.compile("^(.*)\\[(.*)\\]$");
@@ -27,6 +27,7 @@ import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
import org.springframework.boot.build.bom.BomExtension;
import org.springframework.boot.build.bom.Library;
import org.springframework.boot.build.bom.Library.LinkType;
import org.springframework.boot.build.properties.BuildProperties;
/**
@@ -80,13 +81,9 @@ public abstract class UpgradeBom extends UpgradeDependencies {
System.out.println();
for (Upgrade upgrade : upgrades) {
Library library = upgrade.toRelease();
String releaseNotes = library.getLinkUrl("releaseNotes");
if (releaseNotes != null) {
System.out.println("* " + releaseNotes + "[" + library.getNameAndVersion() + "]");
}
else {
System.out.println("* " + library.getNameAndVersion());
}
String releaseNotesLinkUrl = library.getLinkUrl(LinkType.RELEASE_NOTES);
System.out.println("* " + ((releaseNotesLinkUrl != null)
? releaseNotesLinkUrl + "[" + library.getNameAndVersion() + "]" : library.getNameAndVersion()));
}
System.out.println();
}
@@ -50,6 +50,7 @@ import org.gradle.api.tasks.options.Option;
import org.springframework.boot.build.bom.BomExtension;
import org.springframework.boot.build.bom.Library;
import org.springframework.boot.build.bom.Library.LinkType;
import org.springframework.boot.build.bom.UpgradePolicy;
import org.springframework.boot.build.bom.bomr.github.GitHub;
import org.springframework.boot.build.bom.bomr.github.GitHubRepository;
@@ -342,8 +343,8 @@ public abstract class UpgradeDependencies extends DefaultTask {
protected String issueBody(Upgrade upgrade, Issue existingUpgrade) {
String description = upgrade.toRelease().getNameAndVersion();
String releaseNotesLink = upgrade.toRelease().getLinkUrl("releaseNotes");
String body = (releaseNotesLink != null) ? "Upgrade to [%s](%s).".formatted(description, releaseNotesLink)
String releaseNotesLinkUrl = upgrade.toRelease().getLinkUrl(LinkType.RELEASE_NOTES);
String body = (releaseNotesLinkUrl != null) ? "Upgrade to [%s](%s).".formatted(description, releaseNotesLinkUrl)
: "Upgrade to %s.".formatted(description);
if (existingUpgrade != null) {
body += "\n\nSupersedes #" + existingUpgrade.getNumber();
@@ -31,6 +31,8 @@ import org.springframework.boot.build.bom.Library.FirstParty;
import org.springframework.boot.build.bom.Library.Group;
import org.springframework.boot.build.bom.Library.LibraryVersion;
import org.springframework.boot.build.bom.Library.Link;
import org.springframework.boot.build.bom.Library.LinkType;
import org.springframework.boot.build.bom.Library.Links;
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;
@@ -196,10 +198,10 @@ class AntoraAsciidocAttributesTests {
@Test
void urlLinksFromLibrary() {
Map<String, List<Link>> links = new LinkedHashMap<>();
links.put("site", singleLink((version) -> "https://example.com/site/" + version));
links.put("docs", singleLink((version) -> "https://example.com/docs/" + version));
links.put("javadoc",
Map<LinkType, List<Link>> links = new LinkedHashMap<>();
links.put(LinkType.SITE, singleLink((version) -> "https://example.com/site/" + version));
links.put(LinkType.DOCS, singleLink((version) -> "https://example.com/docs/" + version));
links.put(LinkType.JAVADOC,
singleLink((version) -> "https://example.com/api/" + version, "org.springframework.[core|util]"));
Library library = mockLibrary(links);
AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3.1-SNAPSHOT", false,
@@ -229,7 +231,7 @@ class AntoraAsciidocAttributesTests {
assertThat(keys.indexOf("include-java")).isLessThan(keys.indexOf("code-spring-boot-latest"));
}
private Library mockLibrary(Map<String, List<Link>> links) {
private Library mockLibrary(Map<LinkType, List<Link>> links) {
String name = "Spring Framework";
String calendarName = null;
LibraryVersion version = new LibraryVersion(DependencyVersion.parse("1.2.3"));
@@ -240,7 +242,7 @@ class AntoraAsciidocAttributesTests {
BomAlignment alignsWithBom = null;
String linkRootName = null;
Library library = new Library(name, calendarName, version, groups, null, prohibitedVersion, firstParty,
versionAlignment, alignsWithBom, linkRootName, links);
versionAlignment, alignsWithBom, linkRootName, new Links(links));
return library;
}
@@ -18,7 +18,6 @@ package org.springframework.boot.build.bom;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
@@ -26,7 +25,7 @@ import org.springframework.boot.build.bom.Library.BomAlignment;
import org.springframework.boot.build.bom.Library.FirstParty;
import org.springframework.boot.build.bom.Library.Group;
import org.springframework.boot.build.bom.Library.LibraryVersion;
import org.springframework.boot.build.bom.Library.Link;
import org.springframework.boot.build.bom.Library.Links;
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;
@@ -51,7 +50,7 @@ class LibraryTests {
VersionAlignment versionAlignment = null;
BomAlignment alignsWithBom = null;
String linkRootName = null;
Map<String, List<Link>> links = Collections.emptyMap();
Links links = null;
Library library = new Library(name, calendarName, version, groups, null, prohibitedVersion, firstParty,
versionAlignment, alignsWithBom, linkRootName, links);
assertThat(library.getLinkRootName()).isEqualTo("spring-framework");
@@ -68,7 +67,7 @@ class LibraryTests {
VersionAlignment versionAlignment = null;
BomAlignment alignsWithBom = null;
String linkRootName = "spring-data";
Map<String, List<Link>> links = Collections.emptyMap();
Links links = null;
Library library = new Library(name, calendarName, version, groups, null, prohibitedVersion, firstParty,
versionAlignment, alignsWithBom, linkRootName, links);
assertThat(library.getLinkRootName()).isEqualTo("spring-data");
@@ -21,7 +21,6 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.Collections;
import java.util.Properties;
import org.junit.jupiter.api.Test;
@@ -53,7 +52,7 @@ class UpgradeApplicatorTests {
File gradleProperties = new File(this.temp, "gradle.properties");
FileCopyUtils.copy(new File("src/test/resources/gradle.properties"), gradleProperties);
Library activeMq = new Library("ActiveMQ", null, new LibraryVersion(DependencyVersion.parse("5.15.11")), null,
null, null, null, null, null, null, Collections.emptyMap());
null, null, null, null, null, null, null);
new UpgradeApplicator(bom.toPath(), gradleProperties.toPath())
.apply(new Upgrade(activeMq, activeMq.withVersion(new LibraryVersion(DependencyVersion.parse("5.16")))));
String bomContents = Files.readString(bom.toPath());
@@ -67,7 +66,7 @@ class UpgradeApplicatorTests {
File gradleProperties = new File(this.temp, "gradle.properties");
FileCopyUtils.copy(new File("src/test/resources/gradle.properties"), gradleProperties);
Library kotlin = new Library("Kotlin", null, new LibraryVersion(DependencyVersion.parse("1.3.70")), null, null,
null, null, null, null, null, Collections.emptyMap());
null, null, null, null, null, null);
new UpgradeApplicator(bom.toPath(), gradleProperties.toPath())
.apply(new Upgrade(kotlin, kotlin.withVersion(new LibraryVersion(DependencyVersion.parse("1.4")))));
Properties properties = new Properties();
@@ -237,7 +237,7 @@ This takes precedence over anything that is applied by the auto-configuration.
[[howto.data-access.configure-hibernate-naming-strategy]]
== Configure Hibernate Naming Strategy
Hibernate uses {url-hibernate-userguide}#naming[two different naming strategies] to map names from the object model to the corresponding database names.
Hibernate uses {url-hibernate-docs}#naming[two different naming strategies] to map names from the object model to the corresponding database names.
The fully qualified class name of the physical and the implicit strategy implementations can be configured by setting the `spring.jpa.hibernate.naming.physical-strategy` and `spring.jpa.hibernate.naming.implicit-strategy` properties, respectively.
Alternatively, if javadoc:org.hibernate.boot.model.naming.ImplicitNamingStrategy[] or javadoc:org.hibernate.boot.model.naming.PhysicalNamingStrategy[] beans are available in the application context, Hibernate will be automatically configured to use them.
@@ -271,7 +271,7 @@ See {code-spring-boot-hibernate-src}/autoconfigure/HibernateJpaAutoConfiguration
[[howto.data-access.configure-hibernate-second-level-caching]]
== Configure Hibernate Second-Level Caching
Hibernate {url-hibernate-userguide}#caching[second-level cache] can be configured for a range of cache providers.
Hibernate {url-hibernate-docs}#caching[second-level cache] can be configured for a range of cache providers.
Rather than configuring Hibernate to lookup the cache provider again, it is better to provide the one that is available in the context whenever possible.
To do this with JCache, first make sure that `org.hibernate.orm:hibernate-jcache` is available on the classpath.
@@ -281,7 +281,7 @@ include-code::MyHibernateSecondLevelCacheConfiguration[]
This customizer will configure Hibernate to use the same javadoc:org.springframework.cache.CacheManager[] as the one that the application uses.
It is also possible to use separate javadoc:org.springframework.cache.CacheManager[] instances.
For details, see {url-hibernate-userguide}#caching-provider-jcache[the Hibernate user guide].
For details, see {url-hibernate-docs}#caching-provider-jcache[the Hibernate user guide].
@@ -665,12 +665,10 @@ bom {
site("https://hibernate.org/orm")
javadoc(version -> "https://docs.jboss.org/hibernate/orm/%s.%s/javadocs"
.formatted(version.major(), version.minor()), "org.hibernate.boot", "org.hibernate.resource")
docs(version -> "https://hibernate.org/orm/documentation/%s.%s"
docs(version -> "https://docs.hibernate.org/orm/%s.%s/userguide/html_single/"
.formatted(version.major(), version.minor()))
releaseNotes(version -> "https://github.com/hibernate/hibernate-orm/releases/tag/%s"
.formatted(version.toString().replace(".Final", "")))
add("userguide", version -> "https://docs.jboss.org/hibernate/orm/%s.%s/userguide/html_single/Hibernate_User_Guide.html"
.formatted(version.major(), version.minor()))
}
}
library("Hibernate Validator", "9.1.3.Final") {
@@ -2441,7 +2439,7 @@ bom {
javadoc("https://docs.spring.io/spring-boot/{version}/api/java", "org.springframework.boot")
docs("https://docs.spring.io/spring-boot/{version}")
releaseNotes("https://github.com/spring-projects/spring-boot/releases/tag/v{version}")
add("layers-xsd", version -> "https://www.springframework.org/schema/boot/layers/layers-%s.%s.xsd"
layersXsd(version -> "https://www.springframework.org/schema/boot/layers/layers-%s.%s.xsd"
.formatted(version.major(), version.minor()))
}
}