From b594b69c270e44108c8c2e3dc6c7efecb37c5ab7 Mon Sep 17 00:00:00 2001 From: Oliver Drotbohm Date: Fri, 16 May 2025 15:58:25 +0200 Subject: [PATCH] GH-1193 - Remove deprecated support for Spring AutoRestDocs for Javadoc extraction. --- .../modulith/docs/Asciidoctor.java | 17 +- ...SpringAutoRestDocsDocumentationSource.java | 305 ------------------ 2 files changed, 1 insertion(+), 321 deletions(-) delete mode 100644 spring-modulith-docs/src/main/java/org/springframework/modulith/docs/SpringAutoRestDocsDocumentationSource.java diff --git a/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Asciidoctor.java b/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Asciidoctor.java index 941b83d1..33fa69c1 100644 --- a/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Asciidoctor.java +++ b/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Asciidoctor.java @@ -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 DOC_SOURCE = getSpringModulithDocsSource() - .or(() -> getSpringAutoRestDocsSource()); + private static final Optional 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 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 getSpringModulithDocsSource() { return SpringModulithDocumentationSource.getInstance() diff --git a/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/SpringAutoRestDocsDocumentationSource.java b/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/SpringAutoRestDocsDocumentationSource.java deleted file mode 100644 index 546f56d5..00000000 --- a/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/SpringAutoRestDocsDocumentationSource.java +++ /dev/null @@ -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 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 getDocumentation(JavaClass type) { - return Optional.of(reader.resolveClassComment(type.reflect())) - .filter(Predicate.not(String::isEmpty)); - } - - static class ClassJavadoc { - - private String comment; - private Map 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 parameters = new HashMap<>(); - private Map 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 classCache = new ConcurrentHashMap<>(); - private final ObjectMapper mapper; - private final List absoluteBaseDirs; - - private JavadocReader(ObjectMapper mapper, List 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 toAbsoluteDirs(String javadocJsonDirs) { - List 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. - *

- * 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. - *

- * 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); - } - } -}