GH-1193 - Remove deprecated support for Spring AutoRestDocs for Javadoc extraction.
This commit is contained in:
@@ -40,7 +40,6 @@ import org.springframework.modulith.core.Source;
|
||||
import org.springframework.modulith.core.SpringBean;
|
||||
import org.springframework.modulith.docs.ConfigurationProperties.ModuleProperty;
|
||||
import org.springframework.modulith.docs.Documenter.CanvasOptions;
|
||||
import org.springframework.modulith.docs.util.BuildSystemUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -57,8 +56,7 @@ class Asciidoctor {
|
||||
private static final Pattern LINE_BREAKS = Pattern.compile("\\<\\s*br\\s*\\>");
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Asciidoctor.class);
|
||||
|
||||
private static final Optional<DocumentationSource> DOC_SOURCE = getSpringModulithDocsSource()
|
||||
.or(() -> getSpringAutoRestDocsSource());
|
||||
private static final Optional<DocumentationSource> DOC_SOURCE = getSpringModulithDocsSource();
|
||||
|
||||
private final ApplicationModules modules;
|
||||
private final String javaDocBase;
|
||||
@@ -424,19 +422,6 @@ class Asciidoctor {
|
||||
return "include::" + componentsFilename + "[]" + System.lineSeparator();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static Optional<DocumentationSource> getSpringAutoRestDocsSource() {
|
||||
|
||||
return BuildSystemUtils.getTargetResource("generated-javadoc-json")
|
||||
.map(__ -> SpringAutoRestDocsDocumentationSource.INSTANCE)
|
||||
.map(it -> {
|
||||
LOG.debug("Using Javadoc generated by Spring Auto RESTDocs found in generated-javadoc-json.");
|
||||
LOG.warn(
|
||||
"Javadoc metadata generated by Spring Auto RESTDocs is deprecated! Switch to spring-modulith-apt instead!");
|
||||
return it;
|
||||
});
|
||||
}
|
||||
|
||||
private static Optional<DocumentationSource> getSpringModulithDocsSource() {
|
||||
|
||||
return SpringModulithDocumentationSource.getInstance()
|
||||
|
||||
@@ -1,305 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2025 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.modulith.docs;
|
||||
|
||||
import static org.slf4j.LoggerFactory.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.tngtech.archunit.core.domain.JavaClass;
|
||||
import com.tngtech.archunit.core.domain.JavaMethod;
|
||||
|
||||
/**
|
||||
* A {@link DocumentationSource} that uses metadata generated by Spring Auto REST Docs' Javadoc Doclet.
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
* @deprecated since 1.3, use {@link SpringModulithDocumentationSource} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
enum SpringAutoRestDocsDocumentationSource implements DocumentationSource {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
static {
|
||||
Assert.isTrue(
|
||||
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper",
|
||||
SpringModulithDocumentationSource.class.getClassLoader()),
|
||||
"Jackson is required on the classpath for Spring Auto RESTDocs generated Javadoc metadata!");
|
||||
}
|
||||
|
||||
private final JavadocReader reader = JavadocReader.createWithSystemProperty();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.modulith.docs.JavadocSource#getDocumentation(com.tngtech.archunit.core.domain.JavaMethod)
|
||||
*/
|
||||
@Override
|
||||
public Optional<String> getDocumentation(JavaMethod method) {
|
||||
return Optional.of(reader.resolveMethodComment(method.getOwner().reflect(), method.getName()))
|
||||
.filter(it -> !it.isEmpty());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.modulith.docs.DocumentationSource#getDocumentation(com.tngtech.archunit.core.domain.JavaClass)
|
||||
*/
|
||||
@Override
|
||||
public Optional<String> getDocumentation(JavaClass type) {
|
||||
return Optional.of(reader.resolveClassComment(type.reflect()))
|
||||
.filter(Predicate.not(String::isEmpty));
|
||||
}
|
||||
|
||||
static class ClassJavadoc {
|
||||
|
||||
private String comment;
|
||||
private Map<String, MethodJavadoc> methods = new HashMap<>();
|
||||
|
||||
public String getClassComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public String getMethodComment(String methodName) {
|
||||
MethodJavadoc methodJavadoc = methods.get(methodName);
|
||||
if (methodJavadoc != null) {
|
||||
return trimToEmpty(methodJavadoc.getComment());
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static String trimToEmpty(@Nullable String source) {
|
||||
return source == null ? "" : source.trim();
|
||||
}
|
||||
|
||||
static class MethodJavadoc {
|
||||
private String comment;
|
||||
private Map<String, String> parameters = new HashMap<>();
|
||||
private Map<String, String> tags = new HashMap<>();
|
||||
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public String getParameterComment(String parameterName) {
|
||||
return parameters.get(parameterName);
|
||||
}
|
||||
|
||||
public String getTag(String tagName) {
|
||||
return tags.get(tagName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class JavadocReader {
|
||||
|
||||
private static final Logger log = getLogger(JavadocReader.class);
|
||||
private static final String PATH_DELIMITER = ",";
|
||||
private static final String JAVADOC_JSON_DIR_PROPERTY = "org.springframework.restdocs.javadocJsonDir";
|
||||
|
||||
private final Map<String, ClassJavadoc> classCache = new ConcurrentHashMap<>();
|
||||
private final ObjectMapper mapper;
|
||||
private final List<File> absoluteBaseDirs;
|
||||
|
||||
private JavadocReader(ObjectMapper mapper, List<File> absoluteBaseDirs) {
|
||||
this.mapper = mapper;
|
||||
this.absoluteBaseDirs = absoluteBaseDirs;
|
||||
}
|
||||
|
||||
public static JavadocReader createWithSystemProperty() {
|
||||
String jsonDir = System.getProperties().getProperty(JAVADOC_JSON_DIR_PROPERTY);
|
||||
if (!StringUtils.hasText(jsonDir)) {
|
||||
jsonDir = getDefaultJsonDirectory();
|
||||
}
|
||||
return new JavadocReader(objectMapper(), toAbsoluteDirs(jsonDir));
|
||||
}
|
||||
|
||||
private static String getDefaultJsonDirectory() {
|
||||
if (new File("pom.xml").exists()) {
|
||||
return "target/generated-javadoc-json";
|
||||
}
|
||||
return "build/generated-javadoc-json";
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for testing.
|
||||
*/
|
||||
static JavadocReader createWith(String javadocJsonDir) {
|
||||
return new JavadocReader(objectMapper(), toAbsoluteDirs(javadocJsonDir));
|
||||
}
|
||||
|
||||
public String resolveMethodComment(Class<?> javaBaseClass, final String javaMethodName) {
|
||||
return resolveCommentFromClassHierarchy(javaBaseClass,
|
||||
classJavadoc -> classJavadoc.getMethodComment(javaMethodName));
|
||||
}
|
||||
|
||||
public String resolveClassComment(Class<?> javaBaseClass) {
|
||||
return classJavadoc(javaBaseClass).getClassComment();
|
||||
}
|
||||
|
||||
private ClassJavadoc classJavadoc(Class<?> clazz) {
|
||||
String relativePath = classToRelativePath(clazz);
|
||||
ClassJavadoc classJavadocFromCache = classCache.get(relativePath);
|
||||
if (classJavadocFromCache != null) {
|
||||
return classJavadocFromCache;
|
||||
} else {
|
||||
ClassJavadoc classJavadoc = readFiles(clazz, relativePath);
|
||||
classCache.put(relativePath, classJavadoc);
|
||||
return classJavadoc;
|
||||
}
|
||||
}
|
||||
|
||||
private String classToRelativePath(Class<?> clazz) {
|
||||
String packageName = clazz.getPackage().getName();
|
||||
String packageDir = packageName.replace(".", File.separator);
|
||||
String className = clazz.getCanonicalName().replaceAll(packageName + "\\.?", "");
|
||||
return new File(packageDir, className + ".json").getPath();
|
||||
}
|
||||
|
||||
private ClassJavadoc readFiles(Class<?> clazz, String relativePath) {
|
||||
if (absoluteBaseDirs.isEmpty()) {
|
||||
// No absolute directory is configured and thus we try to find the file relative.
|
||||
ClassJavadoc classJavadoc = readJson(new File(relativePath));
|
||||
if (classJavadoc != null) {
|
||||
return classJavadoc;
|
||||
}
|
||||
} else {
|
||||
// Try to find the file in all configured directories.
|
||||
for (File dir : absoluteBaseDirs) {
|
||||
ClassJavadoc classJavadoc = readJson(new File(dir, relativePath));
|
||||
if (classJavadoc != null) {
|
||||
return classJavadoc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// might be in some jar on the classpath
|
||||
URL url = getClass().getClassLoader().getResource(relativePath);
|
||||
if (url != null) {
|
||||
return readJson(url);
|
||||
}
|
||||
|
||||
log.debug("No Javadoc found for class {} in any of the found JSON files", clazz.getCanonicalName());
|
||||
return new ClassJavadoc();
|
||||
}
|
||||
|
||||
private ClassJavadoc readJson(File docSource) {
|
||||
try {
|
||||
return mapper
|
||||
.readerFor(ClassJavadoc.class)
|
||||
.readValue(docSource);
|
||||
} catch (FileNotFoundException e) {
|
||||
// Ignored as we might try more than one file and we warn if no Javadoc file
|
||||
// is found at the end.
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to read file {}", docSource.getName(), e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ClassJavadoc readJson(URL docSource) {
|
||||
try {
|
||||
return mapper
|
||||
.readerFor(ClassJavadoc.class)
|
||||
.readValue(docSource);
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to read url {}", docSource, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ObjectMapper objectMapper() {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
mapper.setVisibility(mapper.getSerializationConfig().getDefaultVisibilityChecker()
|
||||
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
|
||||
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
|
||||
.withSetterVisibility(JsonAutoDetect.Visibility.NONE)
|
||||
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE));
|
||||
return mapper;
|
||||
}
|
||||
|
||||
private static List<File> toAbsoluteDirs(String javadocJsonDirs) {
|
||||
List<File> absoluteDirs = new ArrayList<>();
|
||||
if (StringUtils.hasText(javadocJsonDirs)) {
|
||||
String[] dirs = javadocJsonDirs.split(PATH_DELIMITER);
|
||||
for (String dir : dirs) {
|
||||
if (StringUtils.hasText(dir)) {
|
||||
absoluteDirs.add(new File(dir.trim()).getAbsoluteFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
return absoluteDirs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks up the class hierarchy and interfaces until a comment is found or top most class is reached.
|
||||
* <p>
|
||||
* Javadoc on super classes and Javadoc on interfaces of super classes has precedence over the Javadoc on direct
|
||||
* interfaces of the class. This is only important in the rare case of competing Javadoc comments.
|
||||
* <p>
|
||||
* As we do not know the full method signature here, we can not check whether a method in the super class actually
|
||||
* overwrites the given method. However, the Javadoc model ignores method signatures anyway and it should not cause
|
||||
* issues for the usual use case.
|
||||
*/
|
||||
private String resolveCommentFromClassHierarchy(Class<?> javaBaseClass,
|
||||
CommentExtractor commentExtractor) {
|
||||
String comment = commentExtractor.comment(classJavadoc(javaBaseClass));
|
||||
if (StringUtils.hasText(comment)) {
|
||||
// Direct Javadoc on a method always wins.
|
||||
return comment;
|
||||
}
|
||||
// Super class has precedence over interfaces, but this also means that interfaces
|
||||
// of super classes have precedence over interfaces of the class itself.
|
||||
if (javaBaseClass.getSuperclass() != null) {
|
||||
String superClassComment = resolveCommentFromClassHierarchy(javaBaseClass.getSuperclass(),
|
||||
commentExtractor);
|
||||
if (StringUtils.hasText(superClassComment)) {
|
||||
return superClassComment;
|
||||
}
|
||||
}
|
||||
for (Class<?> i : javaBaseClass.getInterfaces()) {
|
||||
String interfaceComment = resolveCommentFromClassHierarchy(i, commentExtractor);
|
||||
if (StringUtils.hasText(interfaceComment)) {
|
||||
return interfaceComment;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private interface CommentExtractor {
|
||||
String comment(ClassJavadoc classJavadoc);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user