From 824c6384bf7dd9314e1f31e8f11fb63fd611e724 Mon Sep 17 00:00:00 2001 From: Matthieu Ghilain Date: Thu, 25 Apr 2019 18:47:53 +0200 Subject: [PATCH 01/15] #13: Update Doclet in order to make it compatible with Java9 and make it compatible with Gradle build --- .build/checkstyle.xml | 18 +- .gitattributes | 4 - .mvn/wrapper/maven-wrapper.jar | Bin 48336 -> 0 bytes .mvn/wrapper/maven-wrapper.properties | 1 - build.gradle | 28 +- gradle/wrapper/gradle-wrapper.properties | 2 +- mvnw | 227 ---------- mvnw.cmd | 145 ------- pom.xml | 222 ---------- .../JavadocPluginConfiguration.java | 6 +- .../javadoc/doclet/ClassDirectoryOption.java | 69 +++ .../javadoc/doclet/DocletHelper.java | 62 +++ .../javadoc/doclet/DocletOptionParser.java | 47 -- .../javadoc/doclet/DocletOptionsBuilder.java | 44 -- ...ions.java => MethodProcessingContext.java} | 24 +- .../MethodProcessingContextFactory.java | 76 ++++ .../javadoc/doclet/SpringMappingsHelper.java | 31 ++ .../javadoc/doclet/SpringRequestMappings.java | 71 ++++ .../doclet/SpringfoxDocletException.java | 25 ++ .../doclet/SwaggerPropertiesDoclet.java | 402 ++++++------------ .../javadoc/plugin/JavadocBuilderPlugin.java | 57 +-- .../doclet/SwaggerPropertiesDocletTest.java | 132 +----- .../javadoc/example/OtherController.java | 75 ++++ 23 files changed, 616 insertions(+), 1152 deletions(-) delete mode 100644 .gitattributes delete mode 100755 .mvn/wrapper/maven-wrapper.jar delete mode 100755 .mvn/wrapper/maven-wrapper.properties delete mode 100755 mvnw delete mode 100755 mvnw.cmd delete mode 100644 pom.xml create mode 100644 src/main/java/springfox/javadoc/doclet/ClassDirectoryOption.java create mode 100644 src/main/java/springfox/javadoc/doclet/DocletHelper.java delete mode 100644 src/main/java/springfox/javadoc/doclet/DocletOptionParser.java delete mode 100644 src/main/java/springfox/javadoc/doclet/DocletOptionsBuilder.java rename src/main/java/springfox/javadoc/doclet/{DocletOptions.java => MethodProcessingContext.java} (60%) create mode 100644 src/main/java/springfox/javadoc/doclet/MethodProcessingContextFactory.java create mode 100644 src/main/java/springfox/javadoc/doclet/SpringMappingsHelper.java create mode 100644 src/main/java/springfox/javadoc/doclet/SpringRequestMappings.java create mode 100644 src/main/java/springfox/javadoc/doclet/SpringfoxDocletException.java create mode 100644 src/test/java/springfox/javadoc/example/OtherController.java diff --git a/.build/checkstyle.xml b/.build/checkstyle.xml index 87ca1a6..7eab024 100644 --- a/.build/checkstyle.xml +++ b/.build/checkstyle.xml @@ -1,7 +1,7 @@ + "http://www.puppycrawl.com/dtds/configuration_1_1.dtd"> @@ -25,19 +25,13 @@ value="Calls to Throwable.printStackTrace() are not allowed. Log the exception instead."/> - - - - + @@ -86,7 +80,7 @@ - - + + diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 69faa75..0000000 --- a/.gitattributes +++ /dev/null @@ -1,4 +0,0 @@ -src/* linguist-documentation=false -.mvn/* linguist-vendored -mvnw linguist-vendored -mvnw.cmd linguist-vendored \ No newline at end of file diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar deleted file mode 100755 index f775b1c04cf89b25c7814d3a8a7c810301092e57..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48336 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 \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` -fi - -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" - # TODO classpath? -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then - if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" - else - javaExecutable="`readlink -f \"$javaExecutable\"`" - fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="`which java`" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 - fi - - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` - fi - # end of workaround - done - echo "${basedir}" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" - fi -} - -BASE_DIR=`find_maven_basedir "$(pwd)"` -if [ -z "$BASE_DIR" ]; then - exit 1; -fi - -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR -fi -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` -fi - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -exec "$JAVACMD" \ - $MAVEN_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd deleted file mode 100755 index 6a6eec3..0000000 --- a/mvnw.cmd +++ /dev/null @@ -1,145 +0,0 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Maven2 Start Up Batch script -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" - -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause - -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% - -exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml deleted file mode 100644 index bd492a0..0000000 --- a/pom.xml +++ /dev/null @@ -1,222 +0,0 @@ - - - 4.0.0 - - io.springfox - springfox-javadoc - 0.9.4-SNAPSHOT - https://github.com/springfox/springfox-javadoc - springfox-javadoc - generate Swagger/OpenAPI documentation from Javadoc using Springfox - - Springfox - http://springfox.io - - - - https://github.com/springfox/springfox-javadoc.git - https://github.com/springfox/springfox-javadoc.git - https://github.com/springfox/springfox-javadoc - - - - - dilipkrish - Dilip Krishnan - https://github.com/dilipkrish - - architect - - America/Chicago - - https://avatars2.githubusercontent.com/u/73257 - - - - rgoers - Ralph Goers - rgoers@apache.org - https://github.com/rgoers - - developer - - - - MartinNeumannBeTSE - Martin Neumann - martin.neumann@be-tse.de - https://github.com/MartinNeumannBeTSE - Be Think, Solve, Execute GmbH - http://www.be-tse.de - - architect - developer - - Europe/Berlin - - - neumaennl - Martin Neumann - https://github.com/neumaennl - - architect - developer - - Europe/Berlin - - - - - - Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0.txt - repo - A business-friendly OSS license - - - - - 1.6 - 1.6 - 4.3.9.RELEASE - 2.8.0 - UTF-8 - - - - - bintray-springfox-maven-repo - springfox-maven-repo - https://api.bintray.com/maven/springfox/maven-repo/springfox-javadoc/;publish=1 - - - bintray-snapshot-maven - http://oss.jfrog.org/oss-snapshot-local/io/springfox/springfox-javadoc/ - - - - - - - - org.springframework - spring-webmvc - ${org.springframework.version} - provided - - - - - io.springfox - springfox-swagger2 - ${springfox.version} - - - - - com.sun - tools - ${java.source.version} - system - ${java.home}/../lib/tools.jar - - - - - junit - junit - 4.12 - test - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.7.0 - - ${java.source.version} - ${java.target.version} - - - - - org.apache.maven.plugins - maven-dependency-plugin - 3.0.2 - - - build-classpath - generate-sources - - build-classpath - - - ${project.build.directory}/test-classes/.classpath - - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.0.0 - - - validate - validate - - .build/checkstyle.xml - UTF-8 - true - true - false - - - check - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - - - - src/test/resources - true - - - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.0.0 - - - - checkstyle - - - - - - - diff --git a/src/main/java/springfox/javadoc/configuration/JavadocPluginConfiguration.java b/src/main/java/springfox/javadoc/configuration/JavadocPluginConfiguration.java index 8511cd8..9069875 100644 --- a/src/main/java/springfox/javadoc/configuration/JavadocPluginConfiguration.java +++ b/src/main/java/springfox/javadoc/configuration/JavadocPluginConfiguration.java @@ -26,15 +26,17 @@ import springfox.javadoc.doclet.SwaggerPropertiesDoclet; import springfox.javadoc.plugin.JavadocBuilderPlugin; +import static springfox.javadoc.doclet.ClassDirectoryOption.SPRINGFOX_JAVADOC_PROPERTIES; + /** * Spring configuration that adds the properties file generated by the {@link SwaggerPropertiesDoclet} as property * source and also adds the {@link JavadocBuilderPlugin} to the Spring context. - * + * * @author MartinNeumannBeTSE */ @Configuration @PropertySource(value = "classpath:/" - + SwaggerPropertiesDoclet.SPRINGFOX_JAVADOC_PROPERTIES, ignoreResourceNotFound = true) + + SPRINGFOX_JAVADOC_PROPERTIES, ignoreResourceNotFound = true) @ComponentScan("springfox.javadoc.plugin") public class JavadocPluginConfiguration { diff --git a/src/main/java/springfox/javadoc/doclet/ClassDirectoryOption.java b/src/main/java/springfox/javadoc/doclet/ClassDirectoryOption.java new file mode 100644 index 0000000..663b64c --- /dev/null +++ b/src/main/java/springfox/javadoc/doclet/ClassDirectoryOption.java @@ -0,0 +1,69 @@ +/* + * + * Copyright 2018-2019 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 + * + * http://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 springfox.javadoc.doclet; + +import jdk.javadoc.doclet.Doclet; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.List; + +public class ClassDirectoryOption implements Doclet.Option { + + public static final String SPRINGFOX_JAVADOC_PROPERTIES = "META-INF/springfox.javadoc.properties"; + private static final String DEFAULT_OPTION_VALUE = "build/classes/java/main/"; + private String classDirectory = DEFAULT_OPTION_VALUE; + + @Override + public int getArgumentCount() { + return 1; + } + + @Override + public String getDescription() { + return "The path to the output class directory of the project. The file with Springfox properties is " + + "generated @ /" + SPRINGFOX_JAVADOC_PROPERTIES + ". It default to " + DEFAULT_OPTION_VALUE; + } + + @Override + public Kind getKind() { + return Kind.OTHER; + } + + @Override + public List getNames() { + return Collections.singletonList("-classdir"); + } + + @Override + public String getParameters() { + return "file"; + } + + @Override + public boolean process(String option, List arguments) { + classDirectory = arguments.get(0); + return true; + } + + Path getClassDirectory() { + return Paths.get(classDirectory).resolve(SPRINGFOX_JAVADOC_PROPERTIES); + } +} diff --git a/src/main/java/springfox/javadoc/doclet/DocletHelper.java b/src/main/java/springfox/javadoc/doclet/DocletHelper.java new file mode 100644 index 0000000..509b179 --- /dev/null +++ b/src/main/java/springfox/javadoc/doclet/DocletHelper.java @@ -0,0 +1,62 @@ +/* + * + * Copyright 2018-2019 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 + * + * http://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 springfox.javadoc.doclet; + +import jdk.javadoc.doclet.DocletEnvironment; + +import javax.lang.model.element.*; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +class DocletHelper { + + static CharSequence asQualifiedName(AnnotationMirror annotationMirror) { + return ((TypeElement) annotationMirror.getAnnotationType().asElement()).getQualifiedName(); + } + + static Optional getAnnotationOnElement(DocletEnvironment docletEnvironment, + Element element, String annotationClassName) { + return docletEnvironment.getElementUtils().getAllAnnotationMirrors(element) + .stream() + .filter(annotationMirror -> annotationClassName.contentEquals(asQualifiedName(annotationMirror))) + .findFirst(); + } + + /** + * Return the value of an annotation parameter retrieved from the annotation passed as parameter. + * Example: @RequestMapping(method = POST) with paramName = POST would request POST + * + * @param annotationMirror the annotation mirror + * @param paramName param name + * @return the annotationValue or empty if none matches the name + */ + static Optional getAnnotationParam(AnnotationMirror annotationMirror, String paramName) { + Set> entries = + annotationMirror.getElementValues().entrySet(); + for (Map.Entry entry : entries) { + Name annotationAttributeName = entry.getKey().getSimpleName(); + if (paramName.contentEquals(annotationAttributeName)) { + return Optional.of(entry.getValue()); + } + } + return Optional.empty(); + } + +} diff --git a/src/main/java/springfox/javadoc/doclet/DocletOptionParser.java b/src/main/java/springfox/javadoc/doclet/DocletOptionParser.java deleted file mode 100644 index 20579eb..0000000 --- a/src/main/java/springfox/javadoc/doclet/DocletOptionParser.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * - * Copyright 2018-2019 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 - * - * http://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 springfox.javadoc.doclet; - -class DocletOptionParser { - static final String CLASS_DIR_OPTION = "-classdir"; - static final String EXCEPTION_REF_OPTION = "-exceptionRef"; - private final String[][] options; - - DocletOptionParser(String[][] options) { - this.options = options; - } - - DocletOptions parse() { - String propertyFilePath = ""; - Boolean documentExceptions = false; - for (String[] each : options) { - if (CLASS_DIR_OPTION.equalsIgnoreCase(each[0])) { - propertyFilePath = each[1]; - } - if (EXCEPTION_REF_OPTION.equalsIgnoreCase(each[0])) { - documentExceptions = Boolean.valueOf(each[1]); - } - } - - return new DocletOptionsBuilder() - .withPropertyFilePath(propertyFilePath) - .withDocumentExceptions(documentExceptions) - .build(); - } -} diff --git a/src/main/java/springfox/javadoc/doclet/DocletOptionsBuilder.java b/src/main/java/springfox/javadoc/doclet/DocletOptionsBuilder.java deleted file mode 100644 index 10920e5..0000000 --- a/src/main/java/springfox/javadoc/doclet/DocletOptionsBuilder.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * - * Copyright 2018-2019 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 - * - * http://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 springfox.javadoc.doclet; - -import com.google.common.base.Strings; - -public class DocletOptionsBuilder { - private String propertyFilePath; - private boolean documentExceptions; - - DocletOptionsBuilder withPropertyFilePath(String propertyFilePath) { - this.propertyFilePath = propertyFilePath; - return this; - } - - DocletOptionsBuilder withDocumentExceptions(boolean documentExceptions) { - this.documentExceptions = documentExceptions; - return this; - } - - DocletOptions build() { - if (Strings.isNullOrEmpty(propertyFilePath)) { - throw new IllegalStateException("Usage: javadoc -classdir classes directory [-exceptionRef true|false (generate references to exception" - + " classes)] -doclet ..."); - } - return new DocletOptions(propertyFilePath, documentExceptions); - } -} diff --git a/src/main/java/springfox/javadoc/doclet/DocletOptions.java b/src/main/java/springfox/javadoc/doclet/MethodProcessingContext.java similarity index 60% rename from src/main/java/springfox/javadoc/doclet/DocletOptions.java rename to src/main/java/springfox/javadoc/doclet/MethodProcessingContext.java index 8bf7e94..972bcd2 100644 --- a/src/main/java/springfox/javadoc/doclet/DocletOptions.java +++ b/src/main/java/springfox/javadoc/doclet/MethodProcessingContext.java @@ -18,23 +18,23 @@ */ package springfox.javadoc.doclet; -public class DocletOptions { - private final String propertyFilePath; - private final boolean documentExceptions; +class MethodProcessingContext { + private String rootPath; + private String defaultRequestMethod; - DocletOptions( - String propertyFilePath, - boolean documentExceptions) { + String getRootPath() { + return rootPath; + } - this.propertyFilePath = propertyFilePath; - this.documentExceptions = documentExceptions; + void setRootPath(String rootPath) { + this.rootPath = rootPath; } - public String getPropertyFilePath() { - return propertyFilePath; + String getDefaultRequestMethod() { + return defaultRequestMethod; } - public boolean isDocumentExceptions() { - return documentExceptions; + void setDefaultRequestMethod(String defaultRequestMethod) { + this.defaultRequestMethod = defaultRequestMethod; } } diff --git a/src/main/java/springfox/javadoc/doclet/MethodProcessingContextFactory.java b/src/main/java/springfox/javadoc/doclet/MethodProcessingContextFactory.java new file mode 100644 index 0000000..d789d43 --- /dev/null +++ b/src/main/java/springfox/javadoc/doclet/MethodProcessingContextFactory.java @@ -0,0 +1,76 @@ +/* + * + * Copyright 2018-2019 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 + * + * http://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 springfox.javadoc.doclet; + +import jdk.javadoc.doclet.DocletEnvironment; +import org.springframework.web.bind.annotation.RequestMapping; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import java.util.Optional; + +class MethodProcessingContextFactory { + + private final DocletEnvironment environment; + + MethodProcessingContextFactory(DocletEnvironment environment) { + this.environment = environment; + } + + /** + * Extract the method request context from the @RequestMapping annotation on the element + * + * @param element the candidate element + * @return the method processing context if element annotated with RequestMapping or Optional.empty otherwise + */ + Optional from(Element element) { + Optional annotationOnElement = DocletHelper.getAnnotationOnElement(environment, + element, RequestMapping.class.getName()); + return annotationOnElement.map(annotationMirror -> { + MethodProcessingContext methodProcessingContext = new MethodProcessingContext(); + Optional path = SpringMappingsHelper.getPath(annotationMirror); + path.ifPresent(value -> sanitizePathAndSetRootPath(methodProcessingContext, value)); + setDefaultMethod(annotationMirror, methodProcessingContext); + return methodProcessingContext; + }); + } + + private void setDefaultMethod(AnnotationMirror annotationMirror, MethodProcessingContext methodProcessingContext) { + DocletHelper.getAnnotationParam(annotationMirror, "method") + .map(annotationValue -> annotationValue.getValue().toString()) + .ifPresent(methodProcessingContext::setDefaultRequestMethod); + } + + private void sanitizePathAndSetRootPath(MethodProcessingContext methodProcessingContext, String path) { + path = sanitizePath(path); + methodProcessingContext.setRootPath(path); + } + + private String sanitizePath(String path) { + path = path.replaceAll("\"$|^\"", ""); + if (!path.startsWith("/")) { + path = "/"+path; + } + if (path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + return path; + } + +} diff --git a/src/main/java/springfox/javadoc/doclet/SpringMappingsHelper.java b/src/main/java/springfox/javadoc/doclet/SpringMappingsHelper.java new file mode 100644 index 0000000..87ad586 --- /dev/null +++ b/src/main/java/springfox/javadoc/doclet/SpringMappingsHelper.java @@ -0,0 +1,31 @@ +/* + * + * Copyright 2018-2019 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 + * + * http://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 springfox.javadoc.doclet; + +import javax.lang.model.element.AnnotationMirror; +import java.util.Optional; + +class SpringMappingsHelper { + + static Optional getPath(AnnotationMirror annotationMirror) { + return DocletHelper.getAnnotationParam(annotationMirror, "path") + .or(() -> DocletHelper.getAnnotationParam(annotationMirror, "value")) + .map(annotationValue -> annotationValue.getValue().toString()); + } +} diff --git a/src/main/java/springfox/javadoc/doclet/SpringRequestMappings.java b/src/main/java/springfox/javadoc/doclet/SpringRequestMappings.java new file mode 100644 index 0000000..ec68914 --- /dev/null +++ b/src/main/java/springfox/javadoc/doclet/SpringRequestMappings.java @@ -0,0 +1,71 @@ +/* + * + * Copyright 2018-2019 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 + * + * http://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 springfox.javadoc.doclet; + +import org.springframework.web.bind.annotation.*; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import java.util.Arrays; +import java.util.Optional; + +@SuppressWarnings("unused") +public enum SpringRequestMappings { + + DELETE_MAPPING(DeleteMapping.class), + GET_MAPPING(GetMapping.class), + PATCH_MAPPING(PatchMapping.class), + POST_MAPPING(PostMapping.class), + PUT_MAPPING(PutMapping.class), + REQUEST_MAPPING(RequestMapping.class); + + Class annotationClass; + + SpringRequestMappings(Class annotationClass) { + this.annotationClass = annotationClass; + } + + static boolean isMapping(AnnotationMirror annotationMirror) { + CharSequence charSequence = DocletHelper.asQualifiedName(annotationMirror); + return Arrays.stream(values()).anyMatch(value -> value.annotationClass.getName().contentEquals(charSequence)); + } + + static String getRequestMethod(AnnotationMirror annotationMirror, String defaultRequestMethod) { + CharSequence annotationClassName = DocletHelper.asQualifiedName(annotationMirror); + + if (REQUEST_MAPPING.annotationClass.getName().contentEquals(annotationClassName)) { + Optional method = DocletHelper.getAnnotationParam(annotationMirror, "method"); + if (method.isPresent()) { + RequestMethod[] methods = (RequestMethod[]) method.get().getValue(); + //TODO Make the simple assumption that there is only one method... + return methods[0].name(); + } + } + + for (SpringRequestMappings value : values()) { + if (value.annotationClass.getName().contentEquals(annotationClassName)) { + RequestMethod[] requestMethod = value.annotationClass.getAnnotationsByType(RequestMapping.class)[0].method(); + return requestMethod[0].name(); + } + } + + return defaultRequestMethod; + } + +} diff --git a/src/main/java/springfox/javadoc/doclet/SpringfoxDocletException.java b/src/main/java/springfox/javadoc/doclet/SpringfoxDocletException.java new file mode 100644 index 0000000..aaf90ee --- /dev/null +++ b/src/main/java/springfox/javadoc/doclet/SpringfoxDocletException.java @@ -0,0 +1,25 @@ +/* + * + * Copyright 2018-2019 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 + * + * http://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 springfox.javadoc.doclet; + +class SpringfoxDocletException extends RuntimeException { + SpringfoxDocletException(Exception exception) { + super(exception); + } +} diff --git a/src/main/java/springfox/javadoc/doclet/SwaggerPropertiesDoclet.java b/src/main/java/springfox/javadoc/doclet/SwaggerPropertiesDoclet.java index 1746d66..fc46baf 100644 --- a/src/main/java/springfox/javadoc/doclet/SwaggerPropertiesDoclet.java +++ b/src/main/java/springfox/javadoc/doclet/SwaggerPropertiesDoclet.java @@ -18,26 +18,24 @@ */ package springfox.javadoc.doclet; -import com.sun.javadoc.AnnotationDesc; -import com.sun.javadoc.ClassDoc; -import com.sun.javadoc.DocErrorReporter; -import com.sun.javadoc.MethodDoc; -import com.sun.javadoc.ParamTag; -import com.sun.javadoc.RootDoc; -import com.sun.javadoc.Tag; -import com.sun.javadoc.ThrowsTag; +import com.sun.source.doctree.*; +import jdk.javadoc.doclet.Doclet; +import jdk.javadoc.doclet.DocletEnvironment; +import jdk.javadoc.doclet.Reporter; import springfox.javadoc.plugin.JavadocBuilderPlugin; -import java.io.File; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; -import java.util.Properties; - -import static springfox.javadoc.doclet.DocletOptionParser.*; - -// the NOSONAR comment is added to ignore sonar warning about usage of Sun classes -// because doclets can only be written using Sun classes +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; /** * Generate properties file based on Javadoc. @@ -48,311 +46,159 @@ * @author rgoers * @author MartinNeumannBeTSE */ -public class SwaggerPropertiesDoclet { +public class SwaggerPropertiesDoclet implements Doclet { - public static final String SPRINGFOX_JAVADOC_PROPERTIES = "META-INF/springfox.javadoc.properties"; - private static final String REQUEST_MAPPING = "org.springframework.web.bind.annotation.RequestMapping"; - private static final String REQUEST_GET_MAPPING = "org.springframework.web.bind.annotation.RequestMethod.GET"; - private static final String REQUEST_POST_MAPPING = "org.springframework.web.bind.annotation.RequestMethod.POST"; - private static final String REQUEST_PUT_MAPPING = "org.springframework.web.bind.annotation.RequestMethod.PUT"; - private static final String REQUEST_PATCH_MAPPING = "org.springframework.web.bind.annotation.RequestMethod.PATCH"; - private static final String REQUEST_DELETE_MAPPING = "org.springframework.web.bind.annotation.RequestMethod.DELETE"; - private static final String DELETE_MAPPING = "org.springframework.web.bind.annotation.DeleteMapping"; - private static final String GET_MAPPING = "org.springframework.web.bind.annotation.GetMapping"; - private static final String PATCH_MAPPING = "org.springframework.web.bind.annotation.PatchMapping"; - private static final String POST_MAPPING = "org.springframework.web.bind.annotation.PostMapping"; - private static final String PUT_MAPPING = "org.springframework.web.bind.annotation.PutMapping"; - private static final String RETURN = "@return"; - private static final String PATH = "path"; - private static final String VALUE = "value"; private static final String NEWLINE = "\n"; private static final String EMPTY = ""; - private static final String METHOD = "method"; - - private static final String[] MAPPINGS = new String[] { - DELETE_MAPPING, - GET_MAPPING, - PATCH_MAPPING, - POST_MAPPING, - PUT_MAPPING, - REQUEST_MAPPING }; - private static final String[][] REQUEST_MAPPINGS = new String[][] { - { REQUEST_DELETE_MAPPING, "DELETE" }, - { REQUEST_GET_MAPPING, "GET" }, - { REQUEST_PATCH_MAPPING, "PATCH" }, - { REQUEST_POST_MAPPING, "POST" }, - { REQUEST_PUT_MAPPING, "PUT" } - }; + private ClassDirectoryOption classDirectoryOption = new ClassDirectoryOption(); + private Reporter reporter; + private OutputStream springfoxPropertiesOutputStream; + private DocletEnvironment environment; + private MethodProcessingContextFactory methodProcessingContextFactory; - private static DocletOptions docletOptions; - - private SwaggerPropertiesDoclet() { - throw new UnsupportedOperationException(); + @Override + public void init(Locale locale, Reporter reporter) { + reporter.print(Diagnostic.Kind.NOTE, "Doclet using locale: " + locale); + this.reporter = reporter; } - - /** - * See Using - * custom command-line options - * @param option option evaluate an expected length for a given option - * @return number of options - */ - @SuppressWarnings("WeakerAccess") - public static int optionLength(String option) { - int length = 0; - if (option.equalsIgnoreCase(CLASS_DIR_OPTION)) { - length = 2; - } - if (option.equalsIgnoreCase(EXCEPTION_REF_OPTION)) { - length = 2; - } - return length; + @Override + public String getName() { + return "springfox-javadoc-doclet"; } - /** - * See Using - * custom command-line options - * @param options command line options split as key value pairs on index 0 and 1 - * @param reporter reporter for errors - * @return true if options are valid - */ - @SuppressWarnings("WeakerAccess") - public static boolean validOptions( - String[][] options, - DocErrorReporter reporter) { + @Override + public Set getSupportedOptions() { + return Collections.singleton(classDirectoryOption); + } - DocletOptionParser parser = new DocletOptionParser(options); + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.RELEASE_9; + } + @Override + public boolean run(DocletEnvironment environment) { + this.environment = environment; + this.methodProcessingContextFactory = new MethodProcessingContextFactory(environment); try { - docletOptions = parser.parse(); + preparePropertiesOutputStream(); + environment.getIncludedElements() + .stream() + .filter(element -> element.getKind().isClass()) + .map(element -> (TypeElement) element) + .forEach(this::processClass); return true; - } catch (IllegalStateException e) { - reporter.printError(e.getMessage()); + } finally { + closeSpringfoxPropertiesStream(); } - return false; } - /** - * See A - * Simple Example Doclet - * @param root {@link RootDoc} - * @return true if it started successfully - */ - @SuppressWarnings({ "unused", "WeakerAccess", "UnusedReturnValue" }) - public static boolean start(RootDoc root) { - - String propertyFilePath = docletOptions.getPropertyFilePath(); - if (propertyFilePath == null || propertyFilePath.length() == 0) { - root.printError("No output location was specified"); - return false; - } else { - StringBuilder sb = new StringBuilder(propertyFilePath); - if (!propertyFilePath.endsWith("/")) { - sb.append("/"); - } - sb.append(SPRINGFOX_JAVADOC_PROPERTIES); - String out = sb.toString(); - root.printNotice("Writing output to " + out); - File file = new File(out); - //noinspection ResultOfMethodCallIgnored - file.getParentFile().mkdirs(); - OutputStream javadoc = null; - try { - javadoc = new FileOutputStream(file); - Properties properties = new Properties(); - - for (ClassDoc classDoc : root.classes()) { - sb.setLength(0); - String defaultRequestMethod = processClass(classDoc, sb); - String pathRoot = sb.toString(); - for (MethodDoc methodDoc : classDoc.methods()) { - processMethod( - properties, - methodDoc, - defaultRequestMethod, - pathRoot, - docletOptions.isDocumentExceptions()); - } - } - properties.store(javadoc, "Springfox javadoc properties"); - } catch (IOException e) { - root.printError(e.getMessage()); - } finally { - if (javadoc != null) { - try { - javadoc.close(); - } catch (IOException e) { - // close for real - } - } + private void closeSpringfoxPropertiesStream() { + try { + if (springfoxPropertiesOutputStream != null) { + springfoxPropertiesOutputStream.close(); } + } catch (IOException e) { + reporter.print(Diagnostic.Kind.WARNING, "Could not close output stream: " + e.getMessage()); } - return true; } - private static String processClass( - ClassDoc classDoc, - StringBuilder pathRoot) { - - String defaultRequestMethod = null; - for (AnnotationDesc annotationDesc : classDoc.annotations()) { - if (REQUEST_MAPPING.equals(annotationDesc.annotationType().qualifiedTypeName())) { - for (AnnotationDesc.ElementValuePair pair : annotationDesc.elementValues()) { - - if (VALUE.equals(pair.element().name()) || PATH.equals(pair.element().name())) { - setRoot(pathRoot, pair); - } - if (METHOD.equals(pair.element().name())) { - defaultRequestMethod = pair.value().toString(); - } - } - break; - } + private void preparePropertiesOutputStream() { + Path outputFile = classDirectoryOption.getClassDirectory(); + reporter.print(Diagnostic.Kind.NOTE, "Writing output to " + outputFile.toAbsolutePath().toString()); + try { + Files.createDirectories(outputFile.getParent()); + springfoxPropertiesOutputStream = new FileOutputStream(classDirectoryOption.getClassDirectory().toFile()); + } catch (IOException e) { + throw new SpringfoxDocletException(e); } - return defaultRequestMethod; } - private static void setRoot( - StringBuilder pathRoot, - AnnotationDesc.ElementValuePair pair) { - - String value = pair.value().toString().replaceAll("\"$|^\"", ""); - if (!value.startsWith("/")) { - pathRoot.append("/"); - } - if (value.endsWith("/")) { - pathRoot.append(value, 0, value.length() - 1); + private static void appendPath(StringBuilder rootPath, String path) { + String value = path.replaceAll("\"$|^\"", ""); + if (value.startsWith("/")) { + rootPath.append(value).append("."); } else { - pathRoot.append(value); + rootPath.append("/").append(value).append("."); } } - private static void processMethod( + private static void saveProperty( Properties properties, - MethodDoc methodDoc, - String defaultRequestMethod, - String pathRoot, - boolean exceptionRef) { - - for (AnnotationDesc annotationDesc : methodDoc.annotations()) { - String annotationType = annotationDesc.annotationType().toString(); - if (isMapping(annotationType)) { - StringBuilder path = new StringBuilder(pathRoot); - for (AnnotationDesc.ElementValuePair pair : annotationDesc.elementValues()) { - if (VALUE.equals(pair.element().name()) || PATH.equals(pair.element().name())) { - appendPath(path, pair); - break; - } - } - if (!path.substring(path.length() - 1).equals(".")) { - path.append("."); - } - String requestMethod = getRequestMethod(annotationDesc, annotationType, defaultRequestMethod); - if (requestMethod != null) { - path.append(requestMethod); - saveProperty(properties, path.toString() + ".notes", methodDoc.commentText()); + String key, + String value) { - for (ParamTag paramTag : methodDoc.paramTags()) { - saveProperty(properties, path.toString() + ".param." + paramTag.parameterName(), - paramTag.parameterComment()); - } - for (Tag tag : methodDoc.tags()) { - if (tag.name().equals(RETURN)) { - saveProperty(properties, path.toString() + ".return", tag.text()); - break; - } - } - if (exceptionRef) { - processThrows(properties, methodDoc.throwsTags(), path); - } - } - } + value = value.replaceAll(NEWLINE, EMPTY); + if (value.length() > 0) { + properties.setProperty(key, value); } } - private static void appendPath( - StringBuilder path, - AnnotationDesc.ElementValuePair pair) { - - String value = pair.value().toString().replaceAll("\"$|^\"", ""); - if (value.startsWith("/")) { - path.append(value).append("."); - } else { - path.append("/").append(value).append("."); - } + private void processClass(TypeElement typeElement) { + methodProcessingContextFactory.from(typeElement) + .ifPresent(methodProcessingContext -> { + Properties properties = new Properties(); + environment.getElementUtils().getAllMembers(typeElement).stream() + .filter(element -> element.getKind() == ElementKind.METHOD) + .forEach(methodElement -> this.processMethod(properties, methodProcessingContext, methodElement)); + storeProperties(properties); + }); } - private static boolean isMapping(String name) { - for (String mapping : MAPPINGS) { - if (mapping.equals(name)) { - return true; - } + private void storeProperties(Properties properties) { + try { + properties.store(springfoxPropertiesOutputStream, "Springfox javadoc properties"); + } catch (IOException e) { + throw new SpringfoxDocletException(e); } - return false; } - private static String getRequestMethod( - AnnotationDesc annotationDesc, - String name, - String defaultRequestMethod) { - - if (REQUEST_MAPPING.equals(name)) { - for (AnnotationDesc.ElementValuePair pair : annotationDesc.elementValues()) { - if (METHOD.equals(pair.element().name())) { - return resolveRequestMethod(pair, defaultRequestMethod); + private void processMethod(Properties properties, MethodProcessingContext methodProcessingContext, + Element methodElement) { + for (AnnotationMirror annotationMirror : environment.getElementUtils().getAllAnnotationMirrors(methodElement)) { + if (SpringRequestMappings.isMapping(annotationMirror)) { + String path = getMappingFullPath(methodProcessingContext, annotationMirror); + String requestMethod = SpringRequestMappings.getRequestMethod(annotationMirror, + methodProcessingContext.getDefaultRequestMethod()); + if (requestMethod != null) { + path = path + requestMethod; + DocCommentTree docCommentTree = environment.getDocTrees().getDocCommentTree(methodElement); + saveProperty(properties, path + ".notes", docCommentTree.getFullBody().toString()); + int throwIndex = 0; + for (DocTree docTree : docCommentTree.getBlockTags()) { + if (docTree instanceof ParamTree) { + ParamTree paramTree = (ParamTree) docTree; + saveProperty(properties, path + ".param." + paramTree.getName(), + paramTree.getDescription().toString()); + } + if (docTree instanceof ReturnTree) { + ReturnTree returnTree = (ReturnTree) docTree; + saveProperty(properties, path + ".return", returnTree.getDescription().toString()); + } + if (docTree instanceof ThrowsTree) { + ThrowsTree throwTree = (ThrowsTree) docTree; + String key = path + ".throws." + throwIndex; + String value = + throwTree.getExceptionName().getSignature() + "-" + throwTree.getDescription().toString(); + saveProperty(properties, key, value); + throwIndex++; + } + } } } - } else if (PUT_MAPPING.equals(name)) { - return "PUT"; - } else if (POST_MAPPING.equals(name)) { - return "POST"; - } else if (PATCH_MAPPING.equals(name)) { - return "PATCH"; - } else if (GET_MAPPING.equals(name)) { - return "GET"; - } else if (DELETE_MAPPING.equals(name)) { - return "DELETE"; } - return defaultRequestMethod; } - private static String resolveRequestMethod( - AnnotationDesc.ElementValuePair pair, - String defaultRequestMethod) { - - String value = pair.value().toString(); - for (String[] each : REQUEST_MAPPINGS) { - if (each[0].equals(value)) { - return each[1]; - } - } - return defaultRequestMethod; - } - - private static void processThrows( - Properties properties, - ThrowsTag[] throwsTags, - StringBuilder path) { - - for (int i = 0; i < throwsTags.length; i++) { - String key = path.toString() + ".throws." + i; - String value = throwsTags[i].exceptionType().typeName() + "-" + throwsTags[i].exceptionComment(); - saveProperty(properties, key, value); - } - } - - private static void saveProperty( - Properties properties, - String key, - String value) { - - value = value.replaceAll(NEWLINE, EMPTY); - if (value.length() > 0) { - properties.setProperty(key, value); + private String getMappingFullPath(MethodProcessingContext methodProcessingContext, + AnnotationMirror annotationMirror) { + StringBuilder path = new StringBuilder(methodProcessingContext.getRootPath()); + Optional mappingPath = SpringMappingsHelper.getPath(annotationMirror); + mappingPath.ifPresent(mappingAnnotationPath -> appendPath(path, mappingAnnotationPath)); + if (!path.substring(path.length() - 1).equals(".")) { + path.append("."); } + return path.toString(); } } diff --git a/src/main/java/springfox/javadoc/plugin/JavadocBuilderPlugin.java b/src/main/java/springfox/javadoc/plugin/JavadocBuilderPlugin.java index 779e7c9..d797d96 100644 --- a/src/main/java/springfox/javadoc/plugin/JavadocBuilderPlugin.java +++ b/src/main/java/springfox/javadoc/plugin/JavadocBuilderPlugin.java @@ -18,10 +18,6 @@ */ package springfox.javadoc.plugin; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Optional; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; @@ -41,6 +37,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.HashSet; +import java.util.Optional; import java.util.Set; /** @@ -51,15 +48,18 @@ * @author MartinNeumannBeTSE */ @Component -@Order(Ordered.LOWEST_PRECEDENCE) +@Order public class JavadocBuilderPlugin implements OperationBuilderPlugin, ParameterBuilderPlugin { private static final String PERIOD = "."; private static final String API_PARAM = "io.swagger.annotations.ApiParam"; private static final String REQUEST_PARAM = "org.springframework.web.bind.annotation.RequestParam"; private static final String PATH_VARIABLE = "org.springframework.web.bind.annotation.PathVariable"; - @Autowired - private Environment environment; + private final Environment environment; + + public JavadocBuilderPlugin(Environment environment) { + this.environment = environment; + } private static Annotation annotationFromField(ParameterContext context, String annotationType) { @@ -94,11 +94,11 @@ public void apply(OperationContext context) { String throwsDescription = context.requestMappingPattern() + PERIOD + context.httpMethod().toString() + ".throws."; int i = 0; - Set responseMessages = new HashSet(); + Set responseMessages = new HashSet<>(); while (StringUtils.hasText(throwsDescription + i) && StringUtils.hasText(environment.getProperty(throwsDescription + i))) { String[] throwsValues = StringUtils.split(environment.getProperty(throwsDescription + i), "-"); - if (throwsValues.length == 2) { + if (throwsValues!= null && throwsValues.length == 2) { // TODO[MN]: proper mapping once // https://github.com/springfox/springfox/issues/521 is solved String thrownExceptionName = throwsValues[0]; @@ -117,17 +117,15 @@ public void apply(OperationContext context) { @Override public void apply(ParameterContext context) { String description = null; - Optional parmName = context.resolvedMethodParameter().defaultName(); + Optional parameterName = context.resolvedMethodParameter().defaultName(); Annotation apiParam = annotationFromField(context, API_PARAM); if (apiParam != null) { - Optional isRequired = isParamRequired(apiParam, context); - if (isRequired.isPresent()) { - context.parameterBuilder().required(isRequired.get()); - } + java.util.Optional isRequired = isParamRequired(apiParam, context); + isRequired.ifPresent(aBoolean -> context.parameterBuilder().required(aBoolean)); } - if (parmName.isPresent() && (apiParam == null || !hasValue(apiParam, context))) { + if (parameterName.isPresent() && (apiParam == null || !hasValue(apiParam))) { String key = context.getOperationContext().requestMappingPattern() + PERIOD - + context.getOperationContext().httpMethod().name() + ".param." + parmName.get(); + + context.getOperationContext().httpMethod().name() + ".param." + parameterName.get(); description = environment.getProperty(key); } if (description != null) { @@ -135,15 +133,9 @@ public void apply(ParameterContext context) { } } - @VisibleForTesting - String extractApiParamDescription(Annotation annotation) { - return annotation != null ? annotation.annotationType().getName() : null; - } - - @VisibleForTesting - Optional isParamRequired(Annotation apiParam, ParameterContext context) { + private java.util.Optional isParamRequired(Annotation apiParam, ParameterContext context) { if (apiParam != null) { - Optional required = isRequired(apiParam, context); + java.util.Optional required = isRequired(apiParam); if (required.isPresent()) { return required; } @@ -152,30 +144,27 @@ Optional isParamRequired(Annotation apiParam, ParameterContext context) if (annotation == null) { annotation = annotationFromField(context, PATH_VARIABLE); } - return annotation != null ? isRequired(annotation, context) : Optional.absent(); + return annotation != null ? isRequired(annotation) : java.util.Optional.empty(); } - @VisibleForTesting - Optional isRequired(Annotation annotation, ParameterContext context) { + private java.util.Optional isRequired(Annotation annotation) { for (Method method : annotation.annotationType().getDeclaredMethods()) { if (method.getName().equals("required")) { try { - return Optional.of((Boolean) method.invoke(annotation, (Object) null)); + return java.util.Optional.of((Boolean) method.invoke(annotation, (Object) null)); } catch (Exception ex) { - return Optional.absent(); + return java.util.Optional.empty(); } } } - return Optional.absent(); + return java.util.Optional.empty(); } - @VisibleForTesting - boolean hasValue(Annotation annotation, ParameterContext context) { + private boolean hasValue(Annotation annotation) { for (Method method : annotation.annotationType().getDeclaredMethods()) { if (method.getName().equals("value")) { try { - Optional value = Optional.of((String) method.invoke(annotation, (Object) null)); - return value.isPresent(); + method.invoke(annotation, (Object) null); } catch (Exception ex) { return false; } diff --git a/src/test/java/springfox/javadoc/doclet/SwaggerPropertiesDocletTest.java b/src/test/java/springfox/javadoc/doclet/SwaggerPropertiesDocletTest.java index 36dc34d..2cac22c 100644 --- a/src/test/java/springfox/javadoc/doclet/SwaggerPropertiesDocletTest.java +++ b/src/test/java/springfox/javadoc/doclet/SwaggerPropertiesDocletTest.java @@ -18,23 +18,21 @@ */ package springfox.javadoc.doclet; -import com.sun.javadoc.DocErrorReporter; -import com.sun.javadoc.SourcePosition; -import com.sun.tools.javadoc.Main; -import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; +import javax.tools.DocumentationTool; +import javax.tools.ToolProvider; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.PrintWriter; -import java.io.StringWriter; +import java.util.Arrays; import java.util.Properties; -import static org.junit.Assert.*; -import static springfox.javadoc.doclet.SwaggerPropertiesDoclet.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static springfox.javadoc.doclet.ClassDirectoryOption.SPRINGFOX_JAVADOC_PROPERTIES; public class SwaggerPropertiesDocletTest { @@ -43,71 +41,39 @@ public class SwaggerPropertiesDocletTest { String.format("%s/%s", BUILD_PROPERTY_FILE_LOCATION, SPRINGFOX_JAVADOC_PROPERTIES); @BeforeClass - public static void setupFixture() { - deletePropertyFile(); - } - - @AfterClass - public static void cleanupFixture() { - deletePropertyFile(); - } - - @Test - public void testValidOptionLength() { - assertEquals(2, optionLength("-classdir")); - } - - @Test - public void testInvalidOptionLength() { - assertEquals(0, optionLength("dummy")); - } - - @Test - public void testValidOptions() { - String[][] options = new String[][] { new String[] { "foo", "bar" }, new String[] { "-classdir", "dummy" } }; - DummyDocErrorReporter reporter = new DummyDocErrorReporter(); - assertTrue(validOptions(options, reporter)); - assertTrue(reporter.getErrors().isEmpty()); - } - - @Test - public void testInvalidOptions() { - String[][] options = new String[][] { new String[] { "foo", "bar" }, new String[] { "baz", "dummy" } }; - DummyDocErrorReporter reporter = new DummyDocErrorReporter(); - assertFalse(validOptions(options, reporter)); - assertTrue(reporter.getErrors().contains("-classdir")); + public static void deletePropertyFile() { + File propertyFile = new File(GENERATED_PROPERTY_FILE); + if (propertyFile.exists()) { + //noinspection ResultOfMethodCallIgnored + propertyFile.delete(); + } } @Test public void testPropertiesGeneration() throws IOException { - - StringWriter err = new StringWriter(); - StringWriter warn = new StringWriter(); - StringWriter notice = new StringWriter(); - + DocumentationTool systemDocumentationTool = ToolProvider.getSystemDocumentationTool(); String[] args = new String[] { "-sourcepath", "./src/test/java", "-subpackages", - "springfox.javadoc", - "springfox.javadoc", + "springfox.javadoc.doclet", + "springfox.javadoc.example", "-classdir", BUILD_PROPERTY_FILE_LOCATION }; + DocumentationTool.DocumentationTask task = systemDocumentationTool.getTask(null, null, null, + SwaggerPropertiesDoclet.class, Arrays.asList(args), null); - Main.execute( - "SwaggerPropertiesDoclet", - new PrintWriter(err), - new PrintWriter(warn), - new PrintWriter(notice), - SwaggerPropertiesDoclet.class.getName(), - args); + task.call(); Properties props = generatedProperties(); assertEquals("test method", props.getProperty("/test/test.GET.notes")); assertEquals("dummy value", props.getProperty("/test/test.GET.return")); assertEquals("dummy param", props.getProperty("/test/test.GET.param.param")); assertEquals("without value or path", props.getProperty("/test.POST.notes")); + assertEquals("other without value or path", props.getProperty("/other.POST.notes")); + assertEquals("other without value or path other line in delete mapping", props.getProperty("/other.DELETE.notes")); + assertEquals("InvalidNameException-when parameter smaller than 1", props.getProperty("/other/test.GET.throws.1")); assertEquals("retval", props.getProperty("/test.POST.return")); assertEquals("param", props.getProperty("/test.POST.param.bar")); } @@ -123,60 +89,4 @@ private Properties generatedProperties() throws IOException { return props; } - private static void deletePropertyFile() { - File propertyFile = new File(GENERATED_PROPERTY_FILE); - if (propertyFile.exists()) { - propertyFile.delete(); - } - } - - public class DummyDocErrorReporter implements DocErrorReporter { - - - private final StringBuilder errors = new StringBuilder(); - private final StringBuilder notices = new StringBuilder(); - private final StringBuilder warnings = new StringBuilder(); - - @Override - public void printError(String error) { - errors.append(error).append("\n"); - } - - @Override - public void printError(SourcePosition position, String error) { - errors.append(error).append("\n"); - } - - @Override - public void printNotice(String notice) { - notices.append(notice).append("\n"); - } - - @Override - public void printNotice(SourcePosition position, String notice) { - notices.append(notice).append("\n"); - } - - @Override - public void printWarning(String warning) { - warnings.append(warning).append("\n"); - } - - @Override - public void printWarning(SourcePosition position, String warning) { - warnings.append(warning).append("\n"); - } - - public String getErrors() { - return errors.toString(); - } - - public String getNotices() { - return notices.toString(); - } - - public String getWarnings() { - return warnings.toString(); - } - } } diff --git a/src/test/java/springfox/javadoc/example/OtherController.java b/src/test/java/springfox/javadoc/example/OtherController.java new file mode 100644 index 0000000..a1188bc --- /dev/null +++ b/src/test/java/springfox/javadoc/example/OtherController.java @@ -0,0 +1,75 @@ +/* + * + * Copyright 2018-2019 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 + * + * http://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 springfox.javadoc.example; + +import org.springframework.web.bind.annotation.*; + +import javax.naming.InvalidNameException; +import java.io.IOException; + +/** + * test controller class + * + * @author Matthieu Ghilain + */ +@RequestMapping(path = "/other", method = RequestMethod.PUT) +public class OtherController { + + /** + * other test method + * + * @param param + * dummy param + * @return dummy value + * @throws IOException some exception + * @throws InvalidNameException when parameter smaller than 1 + */ + @GetMapping("test") + public String test(String param) throws IOException, InvalidNameException { + if (param.length() > 5) { + throw new IOException("Just to test :)"); + } + if (param.length() < 1) { + throw new InvalidNameException(); + } + return "dummy " + param; + } + + /** + * other without value or path + * + * @param bar + * param + * @return retval + */ + @PostMapping + public String bla(@RequestBody String bar) { + return "foo" + bar; + } + + /** + * other without value or path + * other line in delete mapping + * + */ + @DeleteMapping + public void delete() { + + } +} From 85c4534d55cbf40eed5daca21687b92e1977282c Mon Sep 17 00:00:00 2001 From: Matthieu Ghilain Date: Fri, 26 Apr 2019 10:15:03 +0200 Subject: [PATCH 02/15] #13: Added support for retrieving Javadoc from controller and added Gradle example --- .circleci/config.yml | 3 +- README.md | 26 +++++-- build.gradle | 65 ++++++----------- gradle.properties | 2 +- settings.gradle | 5 +- springfox-javadoc-gradle-example/build.gradle | 33 +++++++++ .../javadoc/example/TestApplication.java | 49 +++++++++++++ .../springfox/javadoc/example/Unicorn.java | 39 ++++++++++ .../javadoc/example/UnicornController.java | 45 ++++++++++++ springfox-javadoc/build.gradle | 26 +++++++ .../publishing.gradle | 0 .../javadoc/doclet/ClassDirectoryOption.java | 0 .../javadoc/doclet/DocletHelper.java | 11 +++ .../springfox/javadoc/doclet/DummyOption.java | 51 +++++++++++++ .../doclet/MethodProcessingContext.java | 0 .../MethodProcessingContextFactory.java | 0 .../javadoc/doclet/SpringMappingsHelper.java | 0 .../javadoc/doclet/SpringRequestMappings.java | 0 .../doclet/SpringfoxDocletException.java | 0 .../doclet/SwaggerPropertiesDoclet.java | 60 +++++++++------- .../javadoc/plugin/AnnotationHelper.java | 20 ++++++ .../JavadocApiListingBuilderPlugin.java | 51 +++++++++++++ .../plugin/JavadocOperationBuilderPlugin.java | 71 +++++++++++++++++++ .../plugin/JavadocParameterBuilderPlugin.java | 64 ++--------------- .../plugin}/JavadocPluginConfiguration.java | 24 ++++--- .../main/resources/META-INF/spring.factories | 2 + .../doclet/SwaggerPropertiesDocletTest.java | 10 +++ .../javadoc/example/OtherController.java | 0 .../javadoc/example/TestController.java | 0 .../javadoc/example/package-info.java | 50 ++++++------- 30 files changed, 536 insertions(+), 171 deletions(-) create mode 100644 springfox-javadoc-gradle-example/build.gradle create mode 100644 springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/TestApplication.java create mode 100644 springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/Unicorn.java create mode 100644 springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/UnicornController.java create mode 100644 springfox-javadoc/build.gradle rename publishing.gradle => springfox-javadoc/publishing.gradle (100%) rename {src => springfox-javadoc/src}/main/java/springfox/javadoc/doclet/ClassDirectoryOption.java (100%) rename {src => springfox-javadoc/src}/main/java/springfox/javadoc/doclet/DocletHelper.java (83%) create mode 100644 springfox-javadoc/src/main/java/springfox/javadoc/doclet/DummyOption.java rename {src => springfox-javadoc/src}/main/java/springfox/javadoc/doclet/MethodProcessingContext.java (100%) rename {src => springfox-javadoc/src}/main/java/springfox/javadoc/doclet/MethodProcessingContextFactory.java (100%) rename {src => springfox-javadoc/src}/main/java/springfox/javadoc/doclet/SpringMappingsHelper.java (100%) rename {src => springfox-javadoc/src}/main/java/springfox/javadoc/doclet/SpringRequestMappings.java (100%) rename {src => springfox-javadoc/src}/main/java/springfox/javadoc/doclet/SpringfoxDocletException.java (100%) rename {src => springfox-javadoc/src}/main/java/springfox/javadoc/doclet/SwaggerPropertiesDoclet.java (89%) create mode 100644 springfox-javadoc/src/main/java/springfox/javadoc/plugin/AnnotationHelper.java create mode 100644 springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocApiListingBuilderPlugin.java create mode 100644 springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocOperationBuilderPlugin.java rename src/main/java/springfox/javadoc/plugin/JavadocBuilderPlugin.java => springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocParameterBuilderPlugin.java (59%) rename {src/main/java/springfox/javadoc/configuration => springfox-javadoc/src/main/java/springfox/javadoc/plugin}/JavadocPluginConfiguration.java (65%) create mode 100644 springfox-javadoc/src/main/resources/META-INF/spring.factories rename {src => springfox-javadoc/src}/test/java/springfox/javadoc/doclet/SwaggerPropertiesDocletTest.java (88%) rename {src => springfox-javadoc/src}/test/java/springfox/javadoc/example/OtherController.java (100%) rename {src => springfox-javadoc/src}/test/java/springfox/javadoc/example/TestController.java (100%) rename {src => springfox-javadoc/src}/test/java/springfox/javadoc/example/package-info.java (97%) diff --git a/.circleci/config.yml b/.circleci/config.yml index d35f047..7350237 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -54,7 +54,8 @@ jobs: - deploy: command: | if [ "${CIRCLE_BRANCH}" == "master" ]; then - ./gradlew artifactoryPublish -x check + cd springfox-javadoc + ../gradlew artifactoryPublish -x check fi notify: webhooks: diff --git a/README.md b/README.md index 3c649b0..9777bbd 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,25 @@ [![CircleCI](https://circleci.com/gh/springfox/springfox-javadoc.svg?style=svg)](https://circleci.com/gh/springfox/springfox-javadoc) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/32f99b3650794b5eb1f7c155a57d5100)](https://app.codacy.com/app/dilip-krishnan-github/springfox-javadoc?utm_source=github.com&utm_medium=referral&utm_content=springfox/springfox-javadoc&utm_campaign=badger) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fspringfox%2Fspringfox-javadoc.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fspringfox%2Fspringfox-javadoc?ref=badge_shield) -Ability to use Javadoc for documentation for generating OpenAPI specifications +# Overview +Ability to use Javadoc for documentation for generating OpenAPI specifications. -To use this, make sure that `JavadocPluginConfiguration` is found by your spring context and add the execution of the javadoc doclet to your build process. +Using Spring Boot the necessary plugins are automatically bootstrapped, so no configuration is needed. -Maven example: +Otherwise you have to manually instantiate them (see the list in JavadocPluginConfiguration class). + +# Compatibility + +Only Java9+ is supported. + +Spring MVC is supported but not SpringWebFlux. + +# Howe to use? + +## Gradle Spring Boot example +[See the example project](./springfox-javadoc-gradle-example). + +## Maven example ```xml org.apache.maven.plugins @@ -38,6 +52,10 @@ Maven example: ``` +## TODO + +- Develop plugin to retrieve javadoc for the model properties also +- Prefix generated properties with common prefix (like io.springfox.javadoc) ## License -[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fspringfox%2Fspringfox-javadoc.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fspringfox%2Fspringfox-javadoc?ref=badge_large) \ No newline at end of file +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fspringfox%2Fspringfox-javadoc.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fspringfox%2Fspringfox-javadoc?ref=badge_large) diff --git a/build.gradle b/build.gradle index 3529252..9a22547 100644 --- a/build.gradle +++ b/build.gradle @@ -4,50 +4,31 @@ plugins { id "com.jfrog.bintray" version "1.8.4" } -apply plugin: 'idea' -apply plugin: 'java-library' -apply plugin: 'maven' -apply plugin: 'osgi' -apply plugin: 'jacoco' -apply plugin: 'maven-publish' -apply from: "publishing.gradle" -apply plugin: 'checkstyle' - -group = 'io.springfox' -description = "springfox-javadoc" - -sourceCompatibility = 1.10 -targetCompatibility = 1.10 - -tasks.withType(JavaCompile) { - options.encoding = 'UTF-8' - options.deprecation = true - options.compilerArgs += ["-Xlint:unchecked", "-parameters"] -} - -repositories { - jcenter() - mavenCentral() - maven { - name = 'bintray-springfox-snapshot' - url = "http://oss.jfrog.org/oss-snapshot-local/" +allprojects { + apply plugin: 'com.github.ben-manes.versions' + apply plugin: 'com.jfrog.artifactory' + apply plugin: 'com.jfrog.bintray' + apply plugin: 'idea' + apply plugin: 'java-library' + + group = 'io.springfox' + + sourceCompatibility = 1.9 + targetCompatibility = 1.9 + + tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' + options.deprecation = true + options.compilerArgs += ["-Xlint:unchecked", "-parameters"] } -} - -dependencies { - implementation 'io.springfox:springfox-swagger2:3.0.0-SNAPSHOT' - implementation 'org.springframework:spring-webmvc:5.0.7.RELEASE' - testImplementation 'junit:junit:4.12' - testImplementation 'org.mockito:mockito-core:2.21.0' -} -jacocoTestReport { - reports { - xml.enabled true - html.enabled true + repositories { + jcenter() + mavenCentral() + maven { + name = 'bintray-springfox-snapshot' + url = "http://oss.jfrog.org/oss-snapshot-local/" + } } -} -checkstyle { - configFile = rootProject.file('.build/checkstyle.xml') } diff --git a/gradle.properties b/gradle.properties index 6230d72..8d0c7be 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=0.10.0-SNAPSHOT +version=1.0.0-SNAPSHOT diff --git a/settings.gradle b/settings.gradle index 69056ca..6719a56 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,4 @@ -rootProject.name = 'springfox-javadoc' +rootProject.name = 'springfox-javadoc-root' + +include "springfox-javadoc" +include "springfox-javadoc-gradle-example" diff --git a/springfox-javadoc-gradle-example/build.gradle b/springfox-javadoc-gradle-example/build.gradle new file mode 100644 index 0000000..d341417 --- /dev/null +++ b/springfox-javadoc-gradle-example/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'org.springframework.boot' version '2.1.3.RELEASE' +} + +description = "springfox-javadoc-gradle-example" + +configurations { + springfoxDoclet +} + + +dependencies { + springfoxDoclet project(":springfox-javadoc") + implementation platform("org.springframework.boot:spring-boot-dependencies:2.1.3.RELEASE") + implementation("io.springfox:springfox-spring-webmvc:3.0.0-SNAPSHOT") + implementation("io.springfox:springfox-bean-validators:3.0.0-SNAPSHOT") + implementation("io.springfox:springfox-swagger-ui:3.0.0-SNAPSHOT") + implementation("io.springfox:springfox-swagger2:3.0.0-SNAPSHOT") + implementation project(":springfox-javadoc") + implementation("org.springframework.boot:spring-boot-starter-web") +} + +task generateSwaggerApiDocs(type: Javadoc) { + source = sourceSets.main.allJava + destinationDir = reporting.file("rest-api-docs") + options.doclet = "springfox.javadoc.doclet.SwaggerPropertiesDoclet" + options.docletpath = configurations.springfoxDoclet.files.asType(List) + options.classpath = configurations.runtimeClasspath.files.asType(List) + verbose = true +} + +bootRun.dependsOn(generateSwaggerApiDocs) +assemble.dependsOn(generateSwaggerApiDocs) diff --git a/springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/TestApplication.java b/springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/TestApplication.java new file mode 100644 index 0000000..612c932 --- /dev/null +++ b/springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/TestApplication.java @@ -0,0 +1,49 @@ +/* + * + * Copyright 2018-2019 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 + * + * http://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 springfox.javadoc.example; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc; + +@EnableSwagger2WebMvc +@SpringBootApplication +public class TestApplication { + + public static void main(String[] args) { + SpringApplication.run(TestApplication.class, args); + } + + @Bean + public Docket sandboxBackendAPI() { + return new Docket(DocumentationType.SWAGGER_2) + .select() + .apis(RequestHandlerSelectors.any()) + .paths(PathSelectors.any()) + .build() + .pathMapping("/") + .useDefaultResponseMessages(false); + } + +} diff --git a/springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/Unicorn.java b/springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/Unicorn.java new file mode 100644 index 0000000..e4abc1e --- /dev/null +++ b/springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/Unicorn.java @@ -0,0 +1,39 @@ +package springfox.javadoc.example; + +/** + * A beautiful unicorn + */ +public class Unicorn { + /** + * Unicorn name. + */ + private String name; + /** + * Unicorn magic name (use with care!) + */ + private String magicName; + + Unicorn(String name, String magicName) { + this.name = name; + this.magicName = magicName; + } + + public Unicorn() { + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getMagicName() { + return magicName; + } + + public void setMagicName(String magicName) { + this.magicName = magicName; + } +} diff --git a/springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/UnicornController.java b/springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/UnicornController.java new file mode 100644 index 0000000..7662bcd --- /dev/null +++ b/springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/UnicornController.java @@ -0,0 +1,45 @@ +/* + * + * Copyright 2018-2019 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 + * + * http://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 springfox.javadoc.example; + +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +/** + * Manage unicorns across the coolest place in the universe. + */ +@RestController +@RequestMapping(path = "/unicorns") +public class UnicornController { + + private Map unicorns = Map.of("Poppy", new Unicorn("Poppy", "Poppy The Mighty")); + + /** + * Retrieve unicorn by their name (try with Poppy) + * + * @param unicornName the unicorn name + * @return the unicorn! + */ + @GetMapping("/{unicornName}") + public Unicorn findUnicorn(@PathVariable String unicornName) { + return unicorns.get(unicornName); + } + +} diff --git a/springfox-javadoc/build.gradle b/springfox-javadoc/build.gradle new file mode 100644 index 0000000..41fe91e --- /dev/null +++ b/springfox-javadoc/build.gradle @@ -0,0 +1,26 @@ +apply plugin: 'maven' +apply plugin: 'osgi' +apply plugin: 'jacoco' +apply plugin: 'maven-publish' +apply from: "publishing.gradle" +apply plugin: 'checkstyle' + +description = "springfox-javadoc" + +dependencies { + implementation 'io.springfox:springfox-swagger2:3.0.0-SNAPSHOT' + implementation 'org.springframework:spring-webmvc:5.0.7.RELEASE' + testImplementation 'junit:junit:4.12' + testImplementation 'org.mockito:mockito-core:2.21.0' +} + +jacocoTestReport { + reports { + xml.enabled true + html.enabled true + } +} + +checkstyle { + configFile = rootProject.file('.build/checkstyle.xml') +} diff --git a/publishing.gradle b/springfox-javadoc/publishing.gradle similarity index 100% rename from publishing.gradle rename to springfox-javadoc/publishing.gradle diff --git a/src/main/java/springfox/javadoc/doclet/ClassDirectoryOption.java b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/ClassDirectoryOption.java similarity index 100% rename from src/main/java/springfox/javadoc/doclet/ClassDirectoryOption.java rename to springfox-javadoc/src/main/java/springfox/javadoc/doclet/ClassDirectoryOption.java diff --git a/src/main/java/springfox/javadoc/doclet/DocletHelper.java b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/DocletHelper.java similarity index 83% rename from src/main/java/springfox/javadoc/doclet/DocletHelper.java rename to springfox-javadoc/src/main/java/springfox/javadoc/doclet/DocletHelper.java index 509b179..37c559e 100644 --- a/src/main/java/springfox/javadoc/doclet/DocletHelper.java +++ b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/DocletHelper.java @@ -18,9 +18,11 @@ */ package springfox.javadoc.doclet; +import com.sun.source.doctree.DocCommentTree; import jdk.javadoc.doclet.DocletEnvironment; import javax.lang.model.element.*; +import javax.swing.text.html.Option; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -59,4 +61,13 @@ static Optional getAnnotationParam(AnnotationMirror annotationM return Optional.empty(); } + static Optional getTypeElementDoc(DocletEnvironment docletEnvironment, TypeElement typeElement) { + DocCommentTree docCommentTree = docletEnvironment.getDocTrees().getDocCommentTree(typeElement); + if (docCommentTree != null) { + return Optional.of(docCommentTree.getFullBody().toString()); + } else { + return Optional.empty(); + } + } + } diff --git a/springfox-javadoc/src/main/java/springfox/javadoc/doclet/DummyOption.java b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/DummyOption.java new file mode 100644 index 0000000..e1cbe4d --- /dev/null +++ b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/DummyOption.java @@ -0,0 +1,51 @@ +package springfox.javadoc.doclet; + +import jdk.javadoc.doclet.Doclet; + +import java.util.Collections; +import java.util.List; + +/** + * Dummy options are added to support default options provided by tool like Gradle + * which are not supported by default and otherwise create error at runtime. + */ +public class DummyOption implements Doclet.Option { + + private int argumentCount; + private String name; + + DummyOption(int argumentCount, String name) { + this.argumentCount = argumentCount; + this.name = name; + } + + @Override + public int getArgumentCount() { + return argumentCount; + } + + @Override + public String getDescription() { + return null; + } + + @Override + public Kind getKind() { + return null; + } + + @Override + public List getNames() { + return Collections.singletonList(name); + } + + @Override + public String getParameters() { + return null; + } + + @Override + public boolean process(String option, List arguments) { + return false; + } +} diff --git a/src/main/java/springfox/javadoc/doclet/MethodProcessingContext.java b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/MethodProcessingContext.java similarity index 100% rename from src/main/java/springfox/javadoc/doclet/MethodProcessingContext.java rename to springfox-javadoc/src/main/java/springfox/javadoc/doclet/MethodProcessingContext.java diff --git a/src/main/java/springfox/javadoc/doclet/MethodProcessingContextFactory.java b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/MethodProcessingContextFactory.java similarity index 100% rename from src/main/java/springfox/javadoc/doclet/MethodProcessingContextFactory.java rename to springfox-javadoc/src/main/java/springfox/javadoc/doclet/MethodProcessingContextFactory.java diff --git a/src/main/java/springfox/javadoc/doclet/SpringMappingsHelper.java b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/SpringMappingsHelper.java similarity index 100% rename from src/main/java/springfox/javadoc/doclet/SpringMappingsHelper.java rename to springfox-javadoc/src/main/java/springfox/javadoc/doclet/SpringMappingsHelper.java diff --git a/src/main/java/springfox/javadoc/doclet/SpringRequestMappings.java b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/SpringRequestMappings.java similarity index 100% rename from src/main/java/springfox/javadoc/doclet/SpringRequestMappings.java rename to springfox-javadoc/src/main/java/springfox/javadoc/doclet/SpringRequestMappings.java diff --git a/src/main/java/springfox/javadoc/doclet/SpringfoxDocletException.java b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/SpringfoxDocletException.java similarity index 100% rename from src/main/java/springfox/javadoc/doclet/SpringfoxDocletException.java rename to springfox-javadoc/src/main/java/springfox/javadoc/doclet/SpringfoxDocletException.java diff --git a/src/main/java/springfox/javadoc/doclet/SwaggerPropertiesDoclet.java b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/SwaggerPropertiesDoclet.java similarity index 89% rename from src/main/java/springfox/javadoc/doclet/SwaggerPropertiesDoclet.java rename to springfox-javadoc/src/main/java/springfox/javadoc/doclet/SwaggerPropertiesDoclet.java index fc46baf..9f466d3 100644 --- a/src/main/java/springfox/javadoc/doclet/SwaggerPropertiesDoclet.java +++ b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/SwaggerPropertiesDoclet.java @@ -22,7 +22,7 @@ import jdk.javadoc.doclet.Doclet; import jdk.javadoc.doclet.DocletEnvironment; import jdk.javadoc.doclet.Reporter; -import springfox.javadoc.plugin.JavadocBuilderPlugin; +import springfox.javadoc.plugin.JavadocParameterBuilderPlugin; import javax.lang.model.SourceVersion; import javax.lang.model.element.AnnotationMirror; @@ -41,7 +41,7 @@ * Generate properties file based on Javadoc. *

* The generated properties file will then be read by the - * {@link JavadocBuilderPlugin} to enhance the Swagger documentation. + * {@link JavadocParameterBuilderPlugin} to enhance the Swagger documentation. * * @author rgoers * @author MartinNeumannBeTSE @@ -57,6 +57,26 @@ public class SwaggerPropertiesDoclet implements Doclet { private DocletEnvironment environment; private MethodProcessingContextFactory methodProcessingContextFactory; + private static void appendPath(StringBuilder rootPath, String path) { + String value = path.replaceAll("\"$|^\"", ""); + if (value.startsWith("/")) { + rootPath.append(value).append("."); + } else { + rootPath.append("/").append(value).append("."); + } + } + + private static void saveProperty( + Properties properties, + String key, + String value) { + + value = value.replaceAll(NEWLINE, EMPTY); + if (value.length() > 0) { + properties.setProperty(key, value); + } + } + @Override public void init(Locale locale, Reporter reporter) { reporter.print(Diagnostic.Kind.NOTE, "Doclet using locale: " + locale); @@ -70,7 +90,10 @@ public String getName() { @Override public Set getSupportedOptions() { - return Collections.singleton(classDirectoryOption); + return new HashSet<>(Arrays.asList(new DummyOption(1, "-doctitle"), + new DummyOption(1, "-windowtitle"), + new DummyOption(1, "-author"), new DummyOption(1, "-d"), + classDirectoryOption)); } @Override @@ -116,40 +139,23 @@ private void preparePropertiesOutputStream() { } } - private static void appendPath(StringBuilder rootPath, String path) { - String value = path.replaceAll("\"$|^\"", ""); - if (value.startsWith("/")) { - rootPath.append(value).append("."); - } else { - rootPath.append("/").append(value).append("."); - } - } - - private static void saveProperty( - Properties properties, - String key, - String value) { - - value = value.replaceAll(NEWLINE, EMPTY); - if (value.length() > 0) { - properties.setProperty(key, value); - } - } - private void processClass(TypeElement typeElement) { + Properties properties = new Properties(); + DocletHelper.getTypeElementDoc(environment, typeElement).ifPresent(typeElementDoc -> { + properties.put(typeElement.getQualifiedName().toString(), typeElementDoc); + }); methodProcessingContextFactory.from(typeElement) .ifPresent(methodProcessingContext -> { - Properties properties = new Properties(); environment.getElementUtils().getAllMembers(typeElement).stream() .filter(element -> element.getKind() == ElementKind.METHOD) .forEach(methodElement -> this.processMethod(properties, methodProcessingContext, methodElement)); - storeProperties(properties); }); + storeProperties(typeElement, properties); } - private void storeProperties(Properties properties) { + private void storeProperties(TypeElement typeElement, Properties properties) { try { - properties.store(springfoxPropertiesOutputStream, "Springfox javadoc properties"); + properties.store(springfoxPropertiesOutputStream, "Class = "+typeElement.getQualifiedName().toString()); } catch (IOException e) { throw new SpringfoxDocletException(e); } diff --git a/springfox-javadoc/src/main/java/springfox/javadoc/plugin/AnnotationHelper.java b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/AnnotationHelper.java new file mode 100644 index 0000000..c4772e1 --- /dev/null +++ b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/AnnotationHelper.java @@ -0,0 +1,20 @@ +package springfox.javadoc.plugin; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +public class AnnotationHelper { + + static boolean hasValue(Annotation annotation) { + for (Method method : annotation.annotationType().getDeclaredMethods()) { + if (method.getName().equals("value")) { + try { + method.invoke(annotation, (Object) null); + } catch (Exception ex) { + return false; + } + } + } + return false; + } +} diff --git a/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocApiListingBuilderPlugin.java b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocApiListingBuilderPlugin.java new file mode 100644 index 0000000..fb5f377 --- /dev/null +++ b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocApiListingBuilderPlugin.java @@ -0,0 +1,51 @@ +/* + * + * Copyright 2018-2019 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 + * + * http://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 springfox.javadoc.plugin; + +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.service.ApiListingBuilderPlugin; +import springfox.documentation.spi.service.contexts.ApiListingContext; + +@Component +public class JavadocApiListingBuilderPlugin implements ApiListingBuilderPlugin { + + private Environment environment; + + public JavadocApiListingBuilderPlugin(Environment environment) { + this.environment = environment; + } + + @Override + public void apply(ApiListingContext apiListingContext) { + + apiListingContext.getResourceGroup().getControllerClass().ifPresent(controllerClass -> { + String notes = environment.getProperty(controllerClass.getName()); + if (notes != null) { + apiListingContext.apiListingBuilder().description(notes); + } + }); + } + + @Override + public boolean supports(DocumentationType documentationType) { + return true; + } +} diff --git a/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocOperationBuilderPlugin.java b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocOperationBuilderPlugin.java new file mode 100644 index 0000000..88e5bb1 --- /dev/null +++ b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocOperationBuilderPlugin.java @@ -0,0 +1,71 @@ +package springfox.javadoc.plugin; + +import org.springframework.core.annotation.Order; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import springfox.documentation.builders.ResponseMessageBuilder; +import springfox.documentation.schema.ModelRef; +import springfox.documentation.schema.ModelReference; +import springfox.documentation.service.ResponseMessage; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.service.OperationBuilderPlugin; +import springfox.documentation.spi.service.contexts.OperationContext; + +import java.util.HashSet; +import java.util.Set; + +/** + * Plugin to generate the @@ApiOperation values from the properties + * file generated by the {@link springfox.javadoc.doclet.SwaggerPropertiesDoclet} + */ +@Component +public class JavadocOperationBuilderPlugin implements OperationBuilderPlugin { + + private static final String PERIOD = "."; + private final Environment environment; + + public JavadocOperationBuilderPlugin(Environment environment) { + this.environment = environment; + } + + + @Override + public void apply(OperationContext context) { + + String notes = context.requestMappingPattern() + PERIOD + context.httpMethod().toString() + ".notes"; + if (StringUtils.hasText(notes) && StringUtils.hasText(environment.getProperty(notes))) { + context.operationBuilder().notes("" + context.getName() + "
" + environment.getProperty(notes)); + } + String returnDescription = context.requestMappingPattern() + PERIOD + context.httpMethod().toString() + + ".return"; + if (StringUtils.hasText(returnDescription) && StringUtils.hasText(environment.getProperty(returnDescription))) { + context.operationBuilder().summary("returns " + environment.getProperty(returnDescription)); + } + String throwsDescription = context.requestMappingPattern() + PERIOD + context.httpMethod().toString() + + ".throws."; + int i = 0; + Set responseMessages = new HashSet<>(); + while (StringUtils.hasText(throwsDescription + i) + && StringUtils.hasText(environment.getProperty(throwsDescription + i))) { + String[] throwsValues = StringUtils.split(environment.getProperty(throwsDescription + i), "-"); + if (throwsValues != null && throwsValues.length == 2) { + // TODO[MN]: proper mapping once + // https://github.com/springfox/springfox/issues/521 is solved + String thrownExceptionName = throwsValues[0]; + String throwComment = throwsValues[1]; + ModelReference model = new ModelRef(thrownExceptionName); + ResponseMessage message = new ResponseMessageBuilder().code(500).message(throwComment) + .responseModel(model).build(); + responseMessages.add(message); + } + i++; + } + context.operationBuilder().responseMessages(responseMessages); + } + + @Override + public boolean supports(DocumentationType documentationType) { + return true; + } +} diff --git a/src/main/java/springfox/javadoc/plugin/JavadocBuilderPlugin.java b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocParameterBuilderPlugin.java similarity index 59% rename from src/main/java/springfox/javadoc/plugin/JavadocBuilderPlugin.java rename to springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocParameterBuilderPlugin.java index d797d96..8bf506f 100644 --- a/src/main/java/springfox/javadoc/plugin/JavadocBuilderPlugin.java +++ b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocParameterBuilderPlugin.java @@ -18,27 +18,19 @@ */ package springfox.javadoc.plugin; -import org.springframework.core.annotation.Order; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; -import springfox.documentation.builders.ResponseMessageBuilder; -import springfox.documentation.schema.ModelRef; -import springfox.documentation.schema.ModelReference; import springfox.documentation.service.ResolvedMethodParameter; -import springfox.documentation.service.ResponseMessage; import springfox.documentation.spi.DocumentationType; -import springfox.documentation.spi.service.OperationBuilderPlugin; import springfox.documentation.spi.service.ParameterBuilderPlugin; -import springfox.documentation.spi.service.contexts.OperationContext; import springfox.documentation.spi.service.contexts.ParameterContext; import springfox.javadoc.doclet.SwaggerPropertiesDoclet; import java.lang.annotation.Annotation; import java.lang.reflect.Method; -import java.util.HashSet; import java.util.Optional; -import java.util.Set; + +import static springfox.javadoc.plugin.AnnotationHelper.hasValue; /** * Plugin to generate the @ApiParam and @ApiOperation values from the properties @@ -48,8 +40,7 @@ * @author MartinNeumannBeTSE */ @Component -@Order -public class JavadocBuilderPlugin implements OperationBuilderPlugin, ParameterBuilderPlugin { +public class JavadocParameterBuilderPlugin implements ParameterBuilderPlugin { private static final String PERIOD = "."; private static final String API_PARAM = "io.swagger.annotations.ApiParam"; @@ -57,7 +48,7 @@ public class JavadocBuilderPlugin implements OperationBuilderPlugin, ParameterBu private static final String PATH_VARIABLE = "org.springframework.web.bind.annotation.PathVariable"; private final Environment environment; - public JavadocBuilderPlugin(Environment environment) { + public JavadocParameterBuilderPlugin(Environment environment) { this.environment = environment; } @@ -79,41 +70,6 @@ public boolean supports(DocumentationType delimiter) { return true; } - @Override - public void apply(OperationContext context) { - - String notes = context.requestMappingPattern() + PERIOD + context.httpMethod().toString() + ".notes"; - if (StringUtils.hasText(notes) && StringUtils.hasText(environment.getProperty(notes))) { - context.operationBuilder().notes("" + context.getName() + "
" + environment.getProperty(notes)); - } - String returnDescription = context.requestMappingPattern() + PERIOD + context.httpMethod().toString() - + ".return"; - if (StringUtils.hasText(returnDescription) && StringUtils.hasText(environment.getProperty(returnDescription))) { - context.operationBuilder().summary("returns " + environment.getProperty(returnDescription)); - } - String throwsDescription = context.requestMappingPattern() + PERIOD + context.httpMethod().toString() - + ".throws."; - int i = 0; - Set responseMessages = new HashSet<>(); - while (StringUtils.hasText(throwsDescription + i) - && StringUtils.hasText(environment.getProperty(throwsDescription + i))) { - String[] throwsValues = StringUtils.split(environment.getProperty(throwsDescription + i), "-"); - if (throwsValues!= null && throwsValues.length == 2) { - // TODO[MN]: proper mapping once - // https://github.com/springfox/springfox/issues/521 is solved - String thrownExceptionName = throwsValues[0]; - String throwComment = throwsValues[1]; - ModelReference model = new ModelRef(thrownExceptionName); - ResponseMessage message = new ResponseMessageBuilder().code(500).message(throwComment) - .responseModel(model).build(); - responseMessages.add(message); - } - i++; - } - context.operationBuilder().responseMessages(responseMessages); - - } - @Override public void apply(ParameterContext context) { String description = null; @@ -160,16 +116,4 @@ private java.util.Optional isRequired(Annotation annotation) { return java.util.Optional.empty(); } - private boolean hasValue(Annotation annotation) { - for (Method method : annotation.annotationType().getDeclaredMethods()) { - if (method.getName().equals("value")) { - try { - method.invoke(annotation, (Object) null); - } catch (Exception ex) { - return false; - } - } - } - return false; - } } diff --git a/src/main/java/springfox/javadoc/configuration/JavadocPluginConfiguration.java b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocPluginConfiguration.java similarity index 65% rename from src/main/java/springfox/javadoc/configuration/JavadocPluginConfiguration.java rename to springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocPluginConfiguration.java index 9069875..2067045 100644 --- a/src/main/java/springfox/javadoc/configuration/JavadocPluginConfiguration.java +++ b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocPluginConfiguration.java @@ -16,35 +16,39 @@ * * */ -package springfox.javadoc.configuration; +package springfox.javadoc.plugin; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; +import org.springframework.core.env.Environment; import springfox.javadoc.doclet.SwaggerPropertiesDoclet; -import springfox.javadoc.plugin.JavadocBuilderPlugin; import static springfox.javadoc.doclet.ClassDirectoryOption.SPRINGFOX_JAVADOC_PROPERTIES; /** * Spring configuration that adds the properties file generated by the {@link SwaggerPropertiesDoclet} as property - * source and also adds the {@link JavadocBuilderPlugin} to the Spring context. + * source and also adds the {@link JavadocParameterBuilderPlugin} to the Spring context. * * @author MartinNeumannBeTSE */ @Configuration @PropertySource(value = "classpath:/" + SPRINGFOX_JAVADOC_PROPERTIES, ignoreResourceNotFound = true) -@ComponentScan("springfox.javadoc.plugin") public class JavadocPluginConfiguration { - @Autowired - JavadocBuilderPlugin javadocBuilderPlugin; + @Bean + public JavadocApiListingBuilderPlugin javadocApiListingBuilderPlugin(Environment environment) { + return new JavadocApiListingBuilderPlugin(environment); + } + + @Bean + public JavadocOperationBuilderPlugin javadocOperationBuilderPlugin(Environment environment) { + return new JavadocOperationBuilderPlugin(environment); + } @Bean - public JavadocBuilderPlugin javadocBuilder() { - return javadocBuilderPlugin; + public JavadocParameterBuilderPlugin javadocParameterBuilderPlugin(Environment environment) { + return new JavadocParameterBuilderPlugin(environment); } } diff --git a/springfox-javadoc/src/main/resources/META-INF/spring.factories b/springfox-javadoc/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..2f14e47 --- /dev/null +++ b/springfox-javadoc/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +springfox.javadoc.plugin.JavadocPluginConfiguration diff --git a/src/test/java/springfox/javadoc/doclet/SwaggerPropertiesDocletTest.java b/springfox-javadoc/src/test/java/springfox/javadoc/doclet/SwaggerPropertiesDocletTest.java similarity index 88% rename from src/test/java/springfox/javadoc/doclet/SwaggerPropertiesDocletTest.java rename to springfox-javadoc/src/test/java/springfox/javadoc/doclet/SwaggerPropertiesDocletTest.java index 2cac22c..b634170 100644 --- a/src/test/java/springfox/javadoc/doclet/SwaggerPropertiesDocletTest.java +++ b/springfox-javadoc/src/test/java/springfox/javadoc/doclet/SwaggerPropertiesDocletTest.java @@ -20,6 +20,7 @@ import org.junit.BeforeClass; import org.junit.Test; +import springfox.javadoc.example.TestController; import javax.tools.DocumentationTool; import javax.tools.ToolProvider; @@ -58,6 +59,14 @@ public void testPropertiesGeneration() throws IOException { "-subpackages", "springfox.javadoc.doclet", "springfox.javadoc.example", + "-d", + "whatever not used just to show compatibility", + "-author", + "whatever not used just to show compatibility", + "-doctitle", + "whatever not used just to show compatibility", + "-windowtitle", + "whatever not used just to show compatibility", "-classdir", BUILD_PROPERTY_FILE_LOCATION }; @@ -67,6 +76,7 @@ public void testPropertiesGeneration() throws IOException { task.call(); Properties props = generatedProperties(); + assertEquals("test controller class", props.getProperty(TestController.class.getName())); assertEquals("test method", props.getProperty("/test/test.GET.notes")); assertEquals("dummy value", props.getProperty("/test/test.GET.return")); assertEquals("dummy param", props.getProperty("/test/test.GET.param.param")); diff --git a/src/test/java/springfox/javadoc/example/OtherController.java b/springfox-javadoc/src/test/java/springfox/javadoc/example/OtherController.java similarity index 100% rename from src/test/java/springfox/javadoc/example/OtherController.java rename to springfox-javadoc/src/test/java/springfox/javadoc/example/OtherController.java diff --git a/src/test/java/springfox/javadoc/example/TestController.java b/springfox-javadoc/src/test/java/springfox/javadoc/example/TestController.java similarity index 100% rename from src/test/java/springfox/javadoc/example/TestController.java rename to springfox-javadoc/src/test/java/springfox/javadoc/example/TestController.java diff --git a/src/test/java/springfox/javadoc/example/package-info.java b/springfox-javadoc/src/test/java/springfox/javadoc/example/package-info.java similarity index 97% rename from src/test/java/springfox/javadoc/example/package-info.java rename to springfox-javadoc/src/test/java/springfox/javadoc/example/package-info.java index 8bd3fb5..7050ee3 100644 --- a/src/test/java/springfox/javadoc/example/package-info.java +++ b/springfox-javadoc/src/test/java/springfox/javadoc/example/package-info.java @@ -1,25 +1,25 @@ -/* - * - * Copyright 2018-2019 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 - * - * http://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. - * - * - */ - -/** - * contains example REST controller classes that are only used to test (and - * therefore demonstrate) how the - * {@link springfox.javadoc.doclet.SwaggerPropertiesDoclet} works. - */ -package springfox.javadoc.example; +/* + * + * Copyright 2018-2019 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 + * + * http://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. + * + * + */ + +/** + * contains example REST controller classes that are only used to test (and + * therefore demonstrate) how the + * {@link springfox.javadoc.doclet.SwaggerPropertiesDoclet} works. + */ +package springfox.javadoc.example; From 05cb0b88e86d9b3fc501e096d15ec3fdbfabdf1e Mon Sep 17 00:00:00 2001 From: Matthieu Ghilain Date: Fri, 26 Apr 2019 10:27:54 +0200 Subject: [PATCH 03/15] Update circleci build image for jdk9 --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7350237..4b4ff1e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ jobs: build: working_directory: ~/code docker: - - image: circleci/openjdk:8-jdk-node-browsers + - image: circleci/openjdk:9-jdk-node-browsers environment: JVM_OPTIONS: -Xmx1024M -XX:MaxPermSize=512M -XX:ReservedCodeCacheSize=512M GRADLE_OPTS: '-Dorg.gradle.daemon=false -Dorg.gradle.jvmargs="-Xmx3840m -XX:+HeapDumpOnOutOfMemoryError"' From 419ed5562568e63ef040cbe1e3e7dd7d70c8bcfc Mon Sep 17 00:00:00 2001 From: Matthieu Ghilain Date: Fri, 26 Apr 2019 10:33:50 +0200 Subject: [PATCH 04/15] Fixed checkstyle issues --- .../springfox/javadoc/example/Unicorn.java | 18 +++++++++++++++++ .../javadoc/doclet/DocletHelper.java | 1 - .../springfox/javadoc/doclet/DummyOption.java | 18 +++++++++++++++++ .../javadoc/plugin/AnnotationHelper.java | 20 ++++++++++++++++++- .../plugin/JavadocOperationBuilderPlugin.java | 19 +++++++++++++++++- 5 files changed, 73 insertions(+), 3 deletions(-) diff --git a/springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/Unicorn.java b/springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/Unicorn.java index e4abc1e..3c2b988 100644 --- a/springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/Unicorn.java +++ b/springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/Unicorn.java @@ -1,3 +1,21 @@ +/* + * + * Copyright 2018-2019 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 + * + * http://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 springfox.javadoc.example; /** diff --git a/springfox-javadoc/src/main/java/springfox/javadoc/doclet/DocletHelper.java b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/DocletHelper.java index 37c559e..0155ff8 100644 --- a/springfox-javadoc/src/main/java/springfox/javadoc/doclet/DocletHelper.java +++ b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/DocletHelper.java @@ -22,7 +22,6 @@ import jdk.javadoc.doclet.DocletEnvironment; import javax.lang.model.element.*; -import javax.swing.text.html.Option; import java.util.Map; import java.util.Optional; import java.util.Set; diff --git a/springfox-javadoc/src/main/java/springfox/javadoc/doclet/DummyOption.java b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/DummyOption.java index e1cbe4d..d786465 100644 --- a/springfox-javadoc/src/main/java/springfox/javadoc/doclet/DummyOption.java +++ b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/DummyOption.java @@ -1,3 +1,21 @@ +/* + * + * Copyright 2018-2019 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 + * + * http://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 springfox.javadoc.doclet; import jdk.javadoc.doclet.Doclet; diff --git a/springfox-javadoc/src/main/java/springfox/javadoc/plugin/AnnotationHelper.java b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/AnnotationHelper.java index c4772e1..4b4d8cd 100644 --- a/springfox-javadoc/src/main/java/springfox/javadoc/plugin/AnnotationHelper.java +++ b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/AnnotationHelper.java @@ -1,9 +1,27 @@ +/* + * + * Copyright 2018-2019 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 + * + * http://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 springfox.javadoc.plugin; import java.lang.annotation.Annotation; import java.lang.reflect.Method; -public class AnnotationHelper { +class AnnotationHelper { static boolean hasValue(Annotation annotation) { for (Method method : annotation.annotationType().getDeclaredMethods()) { diff --git a/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocOperationBuilderPlugin.java b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocOperationBuilderPlugin.java index 88e5bb1..f71fa5b 100644 --- a/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocOperationBuilderPlugin.java +++ b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocOperationBuilderPlugin.java @@ -1,6 +1,23 @@ +/* + * + * Copyright 2018-2019 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 + * + * http://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 springfox.javadoc.plugin; -import org.springframework.core.annotation.Order; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; From a170aedd93b84e493ac3b9da86e93f32c0b49184 Mon Sep 17 00:00:00 2001 From: Matthieu Ghilain Date: Fri, 26 Apr 2019 10:36:52 +0200 Subject: [PATCH 05/15] Fixed test on CI --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4b4ff1e..2114ed2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -41,7 +41,7 @@ jobs: mkdir -p ~/junit/ mkdir -p ~/reports/ find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ~/junit/ \; - cp -R build/reports/tests ~/reports + cp -R springfox-javadoc/build/reports/tests ~/reports when: always - store_test_results: path: ~/junit From afbdfe82318e96d9cb5111bdab236c0b4b7bf91d Mon Sep 17 00:00:00 2001 From: Matthieu Ghilain Date: Fri, 26 Apr 2019 10:40:30 +0200 Subject: [PATCH 06/15] Configure correctly test report exports --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2114ed2..402bcb0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -41,7 +41,7 @@ jobs: mkdir -p ~/junit/ mkdir -p ~/reports/ find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ~/junit/ \; - cp -R springfox-javadoc/build/reports/tests ~/reports + cp -R springfox-javadoc/build/reports ~/reports when: always - store_test_results: path: ~/junit From f047715e407e1c44c08307c606b28f228239063d Mon Sep 17 00:00:00 2001 From: Matthieu Ghilain Date: Fri, 26 Apr 2019 10:41:32 +0200 Subject: [PATCH 07/15] Removed useless stages --- .circleci/config.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 402bcb0..68d5b8b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -45,11 +45,7 @@ jobs: when: always - store_test_results: path: ~/junit - - store_test_results: - path: ~/reports - - store_artifacts: - path: ~/junit - - store_artifacts: + - store_reports: path: ~/reports - deploy: command: | From 361e7daabd9558678a9165a7b00a90db00e7a9bd Mon Sep 17 00:00:00 2001 From: Matthieu Ghilain Date: Fri, 26 Apr 2019 10:43:41 +0200 Subject: [PATCH 08/15] Fixed step name --- .circleci/config.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 68d5b8b..52c2a06 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -45,8 +45,10 @@ jobs: when: always - store_test_results: path: ~/junit - - store_reports: - path: ~/reports + - store_artifacts: + path: ~/junit + - store_artifacts: + path: ~/reports - deploy: command: | if [ "${CIRCLE_BRANCH}" == "master" ]; then From ef1634683de168a3eaed55c207dbfdb6376aa01b Mon Sep 17 00:00:00 2001 From: Matthieu Ghilain Date: Fri, 26 Apr 2019 11:04:43 +0200 Subject: [PATCH 09/15] Added missing maven plugin --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 9a22547..5bab0c1 100644 --- a/build.gradle +++ b/build.gradle @@ -10,6 +10,7 @@ allprojects { apply plugin: 'com.jfrog.bintray' apply plugin: 'idea' apply plugin: 'java-library' + apply plugin: 'maven' group = 'io.springfox' From acaf694c3a1ca148f4860cedb282689c4d867c33 Mon Sep 17 00:00:00 2001 From: Matthieu Ghilain Date: Fri, 26 Apr 2019 11:45:28 +0200 Subject: [PATCH 10/15] Added descriptor for jitpack --- jitpack.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 jitpack.yml diff --git a/jitpack.yml b/jitpack.yml new file mode 100644 index 0000000..36dad5e --- /dev/null +++ b/jitpack.yml @@ -0,0 +1,2 @@ +jdk: + - openjdk9 From 58ac3475e6088cab0838eaef5867022b0aa158c2 Mon Sep 17 00:00:00 2001 From: Matthieu Ghilain Date: Fri, 26 Apr 2019 13:57:05 +0200 Subject: [PATCH 11/15] #14: New Feature > Document model properties using javadoc --- springfox-javadoc/build.gradle | 1 + .../javadoc/doclet/DocletHelper.java | 2 +- .../doclet/SwaggerPropertiesDoclet.java | 36 ++++++++++---- .../JavadocApiListingBuilderPlugin.java | 3 -- .../plugin/JavadocModelBuilderPlugin.java | 28 +++++++++++ .../JavadocModelPropertyBuilderPlugin.java | 34 ++++++++++++++ .../plugin/JavadocOperationBuilderPlugin.java | 1 - .../plugin/JavadocParameterBuilderPlugin.java | 1 - .../plugin/JavadocPluginConfiguration.java | 10 ++++ .../doclet/SwaggerPropertiesDocletTest.java | 3 ++ .../javadoc/example/SecretAgent.java | 20 ++++++++ .../example/SecretAgentController.java | 47 +++++++++++++++++++ 12 files changed, 172 insertions(+), 14 deletions(-) create mode 100644 springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocModelBuilderPlugin.java create mode 100644 springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocModelPropertyBuilderPlugin.java create mode 100644 springfox-javadoc/src/test/java/springfox/javadoc/example/SecretAgent.java create mode 100644 springfox-javadoc/src/test/java/springfox/javadoc/example/SecretAgentController.java diff --git a/springfox-javadoc/build.gradle b/springfox-javadoc/build.gradle index 41fe91e..40c5333 100644 --- a/springfox-javadoc/build.gradle +++ b/springfox-javadoc/build.gradle @@ -10,6 +10,7 @@ description = "springfox-javadoc" dependencies { implementation 'io.springfox:springfox-swagger2:3.0.0-SNAPSHOT' implementation 'org.springframework:spring-webmvc:5.0.7.RELEASE' + implementation("com.fasterxml.jackson.core:jackson-databind:2.9.8") testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.21.0' } diff --git a/springfox-javadoc/src/main/java/springfox/javadoc/doclet/DocletHelper.java b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/DocletHelper.java index 0155ff8..42aef1b 100644 --- a/springfox-javadoc/src/main/java/springfox/javadoc/doclet/DocletHelper.java +++ b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/DocletHelper.java @@ -60,7 +60,7 @@ static Optional getAnnotationParam(AnnotationMirror annotationM return Optional.empty(); } - static Optional getTypeElementDoc(DocletEnvironment docletEnvironment, TypeElement typeElement) { + static Optional getElementDoc(DocletEnvironment docletEnvironment, Element typeElement) { DocCommentTree docCommentTree = docletEnvironment.getDocTrees().getDocCommentTree(typeElement); if (docCommentTree != null) { return Optional.of(docCommentTree.getFullBody().toString()); diff --git a/springfox-javadoc/src/main/java/springfox/javadoc/doclet/SwaggerPropertiesDoclet.java b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/SwaggerPropertiesDoclet.java index 9f466d3..53e73ea 100644 --- a/springfox-javadoc/src/main/java/springfox/javadoc/doclet/SwaggerPropertiesDoclet.java +++ b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/SwaggerPropertiesDoclet.java @@ -25,10 +25,7 @@ import springfox.javadoc.plugin.JavadocParameterBuilderPlugin; import javax.lang.model.SourceVersion; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.TypeElement; +import javax.lang.model.element.*; import javax.tools.Diagnostic; import java.io.FileOutputStream; import java.io.IOException; @@ -141,16 +138,39 @@ private void preparePropertiesOutputStream() { private void processClass(TypeElement typeElement) { Properties properties = new Properties(); - DocletHelper.getTypeElementDoc(environment, typeElement).ifPresent(typeElementDoc -> { - properties.put(typeElement.getQualifiedName().toString(), typeElementDoc); - }); + storeClassJavadocAsProperty(typeElement, properties); + storeClassFieldJavadocAsProperties(typeElement, properties); + storeClassMethodAsProperties(typeElement, properties); + storeProperties(typeElement, properties); + } + + private void storeClassFieldJavadocAsProperties(TypeElement typeElement, Properties properties) { + for (Element classMember : environment.getElementUtils().getAllMembers(typeElement)) { + if(classMember.getKind().isField()){ + VariableElement variableElement = (VariableElement) classMember; + DocletHelper.getElementDoc(environment, variableElement) + .ifPresent(variableDoc -> properties.put(typeElement.getQualifiedName().toString()+"."+variableElement.getSimpleName().toString(), variableDoc)); + } + } + } + + /** + * For Legacy only methods with request mappings are handled. + * This could be change to store the javadoc for all to be more generic and have + * simpler logic. + */ + private void storeClassMethodAsProperties(TypeElement typeElement, Properties properties) { methodProcessingContextFactory.from(typeElement) .ifPresent(methodProcessingContext -> { environment.getElementUtils().getAllMembers(typeElement).stream() .filter(element -> element.getKind() == ElementKind.METHOD) .forEach(methodElement -> this.processMethod(properties, methodProcessingContext, methodElement)); }); - storeProperties(typeElement, properties); + } + + private void storeClassJavadocAsProperty(TypeElement typeElement, Properties properties) { + DocletHelper.getElementDoc(environment, typeElement) + .ifPresent(typeElementDoc -> properties.put(typeElement.getQualifiedName().toString(), typeElementDoc)); } private void storeProperties(TypeElement typeElement, Properties properties) { diff --git a/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocApiListingBuilderPlugin.java b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocApiListingBuilderPlugin.java index fb5f377..2552ecf 100644 --- a/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocApiListingBuilderPlugin.java +++ b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocApiListingBuilderPlugin.java @@ -19,12 +19,10 @@ package springfox.javadoc.plugin; import org.springframework.core.env.Environment; -import org.springframework.stereotype.Component; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spi.service.ApiListingBuilderPlugin; import springfox.documentation.spi.service.contexts.ApiListingContext; -@Component public class JavadocApiListingBuilderPlugin implements ApiListingBuilderPlugin { private Environment environment; @@ -35,7 +33,6 @@ public JavadocApiListingBuilderPlugin(Environment environment) { @Override public void apply(ApiListingContext apiListingContext) { - apiListingContext.getResourceGroup().getControllerClass().ifPresent(controllerClass -> { String notes = environment.getProperty(controllerClass.getName()); if (notes != null) { diff --git a/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocModelBuilderPlugin.java b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocModelBuilderPlugin.java new file mode 100644 index 0000000..76fa563 --- /dev/null +++ b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocModelBuilderPlugin.java @@ -0,0 +1,28 @@ +package springfox.javadoc.plugin; + +import org.springframework.core.env.Environment; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.schema.ModelBuilderPlugin; +import springfox.documentation.spi.schema.contexts.ModelContext; + +public class JavadocModelBuilderPlugin implements ModelBuilderPlugin { + + private final Environment environment; + + public JavadocModelBuilderPlugin(Environment environment) { + this.environment = environment; + } + + @Override + public void apply(ModelContext context) { + String notes = environment.getProperty(context.getType().getTypeName()); + if (notes != null) { + context.getBuilder().description(notes); + } + } + + @Override + public boolean supports(DocumentationType documentationType) { + return true; + } +} diff --git a/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocModelPropertyBuilderPlugin.java b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocModelPropertyBuilderPlugin.java new file mode 100644 index 0000000..65376d2 --- /dev/null +++ b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocModelPropertyBuilderPlugin.java @@ -0,0 +1,34 @@ +package springfox.javadoc.plugin; + +import org.springframework.core.env.Environment; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.schema.ModelPropertyBuilderPlugin; +import springfox.documentation.spi.schema.contexts.ModelPropertyContext; + +public class JavadocModelPropertyBuilderPlugin implements ModelPropertyBuilderPlugin { + + private final Environment environment; + + public JavadocModelPropertyBuilderPlugin(Environment environment) { + this.environment = environment; + } + + @Override + public void apply(ModelPropertyContext context) { + if (context.getBeanPropertyDefinition().isPresent()) { + com.fasterxml.jackson.databind.introspect.AnnotatedField field = + context.getBeanPropertyDefinition().get().getField(); + String key = field.getDeclaringClass().getName() + "." + field.getName(); + System.out.println(key); + String notes = environment.getProperty(key); + if (notes != null) { + context.getBuilder().description(notes); + } + } + } + + @Override + public boolean supports(DocumentationType documentationType) { + return true; + } +} diff --git a/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocOperationBuilderPlugin.java b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocOperationBuilderPlugin.java index f71fa5b..4fb68d8 100644 --- a/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocOperationBuilderPlugin.java +++ b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocOperationBuilderPlugin.java @@ -36,7 +36,6 @@ * Plugin to generate the @@ApiOperation values from the properties * file generated by the {@link springfox.javadoc.doclet.SwaggerPropertiesDoclet} */ -@Component public class JavadocOperationBuilderPlugin implements OperationBuilderPlugin { private static final String PERIOD = "."; diff --git a/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocParameterBuilderPlugin.java b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocParameterBuilderPlugin.java index 8bf506f..9280df9 100644 --- a/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocParameterBuilderPlugin.java +++ b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocParameterBuilderPlugin.java @@ -39,7 +39,6 @@ * @author rgoers * @author MartinNeumannBeTSE */ -@Component public class JavadocParameterBuilderPlugin implements ParameterBuilderPlugin { private static final String PERIOD = "."; diff --git a/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocPluginConfiguration.java b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocPluginConfiguration.java index 2067045..3204efa 100644 --- a/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocPluginConfiguration.java +++ b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocPluginConfiguration.java @@ -51,4 +51,14 @@ public JavadocOperationBuilderPlugin javadocOperationBuilderPlugin(Environment e public JavadocParameterBuilderPlugin javadocParameterBuilderPlugin(Environment environment) { return new JavadocParameterBuilderPlugin(environment); } + + @Bean + public JavadocModelBuilderPlugin javadocModelBuilderPlugin(Environment environment) { + return new JavadocModelBuilderPlugin(environment); + } + + @Bean + public JavadocModelPropertyBuilderPlugin javadocModelPropertyBuilderPlugin(Environment environment) { + return new JavadocModelPropertyBuilderPlugin(environment); + } } diff --git a/springfox-javadoc/src/test/java/springfox/javadoc/doclet/SwaggerPropertiesDocletTest.java b/springfox-javadoc/src/test/java/springfox/javadoc/doclet/SwaggerPropertiesDocletTest.java index b634170..e7fe54a 100644 --- a/springfox-javadoc/src/test/java/springfox/javadoc/doclet/SwaggerPropertiesDocletTest.java +++ b/springfox-javadoc/src/test/java/springfox/javadoc/doclet/SwaggerPropertiesDocletTest.java @@ -20,6 +20,7 @@ import org.junit.BeforeClass; import org.junit.Test; +import springfox.javadoc.example.SecretAgent; import springfox.javadoc.example.TestController; import javax.tools.DocumentationTool; @@ -77,6 +78,8 @@ public void testPropertiesGeneration() throws IOException { Properties props = generatedProperties(); assertEquals("test controller class", props.getProperty(TestController.class.getName())); + assertEquals("Secret Agent, it can be James Bond!", props.getProperty(SecretAgent.class.getName())); + assertEquals("Secret agent name, probably something badass", props.getProperty(SecretAgent.class.getName()+".secretAgentName")); assertEquals("test method", props.getProperty("/test/test.GET.notes")); assertEquals("dummy value", props.getProperty("/test/test.GET.return")); assertEquals("dummy param", props.getProperty("/test/test.GET.param.param")); diff --git a/springfox-javadoc/src/test/java/springfox/javadoc/example/SecretAgent.java b/springfox-javadoc/src/test/java/springfox/javadoc/example/SecretAgent.java new file mode 100644 index 0000000..48aac92 --- /dev/null +++ b/springfox-javadoc/src/test/java/springfox/javadoc/example/SecretAgent.java @@ -0,0 +1,20 @@ +package springfox.javadoc.example; + +/** + * Secret Agent, it can be James Bond! + */ +public class SecretAgent { + + /** + * Secret agent name, probably something badass + */ + private String secretAgentName; + + public String getSecretAgentName() { + return secretAgentName; + } + + public void setSecretAgentName(String secretAgentName) { + this.secretAgentName = secretAgentName; + } +} diff --git a/springfox-javadoc/src/test/java/springfox/javadoc/example/SecretAgentController.java b/springfox-javadoc/src/test/java/springfox/javadoc/example/SecretAgentController.java new file mode 100644 index 0000000..b1045b7 --- /dev/null +++ b/springfox-javadoc/src/test/java/springfox/javadoc/example/SecretAgentController.java @@ -0,0 +1,47 @@ +/* + * + * Copyright 2018-2019 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 + * + * http://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 springfox.javadoc.example; + +import org.springframework.web.bind.annotation.*; + +import javax.naming.InvalidNameException; +import java.io.IOException; + +/** + * Service to retrieve secret agents (could replace M in the future). + * + * @author Matthieu Ghilain + */ +@RestController +@RequestMapping(path = "/agent", method = RequestMethod.PUT) +public class SecretAgentController { + + + /** + * Retrieve James Bond... + * @return James Bond of course :) + */ + @GetMapping("jamesBond") + public SecretAgent getJamesBond() { + SecretAgent secretAgent = new SecretAgent(); + secretAgent.setSecretAgentName("James Bond"); + return secretAgent; + } + +} From 3aa6f4087d5e5cf303b5e64a19a8581407309ca8 Mon Sep 17 00:00:00 2001 From: Matthieu Ghilain Date: Fri, 26 Apr 2019 13:59:05 +0200 Subject: [PATCH 12/15] Updated readme --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9777bbd..907162f 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,13 @@ Only Java9+ is supported. Spring MVC is supported but not SpringWebFlux. +# How does it work? + +The Javadoc is extracted using a custom Doclet which is passed to the javadoc tool. +The Javadoc is stored in a file which must be on the classpath of the application. + +At runtime, this file is read by a bunch of Springfox plugins to customize the Documentation model. + # Howe to use? ## Gradle Spring Boot example @@ -54,7 +61,6 @@ Spring MVC is supported but not SpringWebFlux. ## TODO -- Develop plugin to retrieve javadoc for the model properties also - Prefix generated properties with common prefix (like io.springfox.javadoc) ## License From ccac409f9c3af75ea91b323d08de61d2eafbb1de Mon Sep 17 00:00:00 2001 From: Matthieu Ghilain Date: Fri, 26 Apr 2019 14:02:07 +0200 Subject: [PATCH 13/15] Fixed checkstyle errors --- .../doclet/SwaggerPropertiesDoclet.java | 12 ++++++++---- .../plugin/JavadocModelBuilderPlugin.java | 18 ++++++++++++++++++ .../JavadocModelPropertyBuilderPlugin.java | 19 ++++++++++++++++++- .../plugin/JavadocOperationBuilderPlugin.java | 1 - .../plugin/JavadocParameterBuilderPlugin.java | 1 - 5 files changed, 44 insertions(+), 7 deletions(-) diff --git a/springfox-javadoc/src/main/java/springfox/javadoc/doclet/SwaggerPropertiesDoclet.java b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/SwaggerPropertiesDoclet.java index 53e73ea..6aa0320 100644 --- a/springfox-javadoc/src/main/java/springfox/javadoc/doclet/SwaggerPropertiesDoclet.java +++ b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/SwaggerPropertiesDoclet.java @@ -146,10 +146,14 @@ private void processClass(TypeElement typeElement) { private void storeClassFieldJavadocAsProperties(TypeElement typeElement, Properties properties) { for (Element classMember : environment.getElementUtils().getAllMembers(typeElement)) { - if(classMember.getKind().isField()){ + if (classMember.getKind().isField()) { VariableElement variableElement = (VariableElement) classMember; DocletHelper.getElementDoc(environment, variableElement) - .ifPresent(variableDoc -> properties.put(typeElement.getQualifiedName().toString()+"."+variableElement.getSimpleName().toString(), variableDoc)); + .ifPresent(variableDoc -> { + String classKey = + typeElement.getQualifiedName().toString() + "." + variableElement.getSimpleName().toString(); + properties.put(classKey, variableDoc); + }); } } } @@ -170,12 +174,12 @@ private void storeClassMethodAsProperties(TypeElement typeElement, Properties pr private void storeClassJavadocAsProperty(TypeElement typeElement, Properties properties) { DocletHelper.getElementDoc(environment, typeElement) - .ifPresent(typeElementDoc -> properties.put(typeElement.getQualifiedName().toString(), typeElementDoc)); + .ifPresent(typeElementDoc -> properties.put(typeElement.getQualifiedName().toString(), typeElementDoc)); } private void storeProperties(TypeElement typeElement, Properties properties) { try { - properties.store(springfoxPropertiesOutputStream, "Class = "+typeElement.getQualifiedName().toString()); + properties.store(springfoxPropertiesOutputStream, "Class = " + typeElement.getQualifiedName().toString()); } catch (IOException e) { throw new SpringfoxDocletException(e); } diff --git a/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocModelBuilderPlugin.java b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocModelBuilderPlugin.java index 76fa563..1bc2425 100644 --- a/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocModelBuilderPlugin.java +++ b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocModelBuilderPlugin.java @@ -1,3 +1,21 @@ +/* + * + * Copyright 2018-2019 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 + * + * http://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 springfox.javadoc.plugin; import org.springframework.core.env.Environment; diff --git a/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocModelPropertyBuilderPlugin.java b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocModelPropertyBuilderPlugin.java index 65376d2..a9e0e70 100644 --- a/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocModelPropertyBuilderPlugin.java +++ b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocModelPropertyBuilderPlugin.java @@ -1,3 +1,21 @@ +/* + * + * Copyright 2018-2019 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 + * + * http://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 springfox.javadoc.plugin; import org.springframework.core.env.Environment; @@ -19,7 +37,6 @@ public void apply(ModelPropertyContext context) { com.fasterxml.jackson.databind.introspect.AnnotatedField field = context.getBeanPropertyDefinition().get().getField(); String key = field.getDeclaringClass().getName() + "." + field.getName(); - System.out.println(key); String notes = environment.getProperty(key); if (notes != null) { context.getBuilder().description(notes); diff --git a/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocOperationBuilderPlugin.java b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocOperationBuilderPlugin.java index 4fb68d8..e45e054 100644 --- a/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocOperationBuilderPlugin.java +++ b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocOperationBuilderPlugin.java @@ -19,7 +19,6 @@ package springfox.javadoc.plugin; import org.springframework.core.env.Environment; -import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import springfox.documentation.builders.ResponseMessageBuilder; import springfox.documentation.schema.ModelRef; diff --git a/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocParameterBuilderPlugin.java b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocParameterBuilderPlugin.java index 9280df9..dd29b6c 100644 --- a/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocParameterBuilderPlugin.java +++ b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocParameterBuilderPlugin.java @@ -19,7 +19,6 @@ package springfox.javadoc.plugin; import org.springframework.core.env.Environment; -import org.springframework.stereotype.Component; import springfox.documentation.service.ResolvedMethodParameter; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spi.service.ParameterBuilderPlugin; From f6e5ef12efe6c8491c131ece4bcab4d87e262fdb Mon Sep 17 00:00:00 2001 From: Matthieu Ghilain Date: Fri, 26 Apr 2019 14:05:13 +0200 Subject: [PATCH 14/15] Fixed checkstyle in test --- .../springfox/javadoc/example/SecretAgent.java | 18 ++++++++++++++++++ .../javadoc/example/SecretAgentController.java | 8 ++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/springfox-javadoc/src/test/java/springfox/javadoc/example/SecretAgent.java b/springfox-javadoc/src/test/java/springfox/javadoc/example/SecretAgent.java index 48aac92..597ac98 100644 --- a/springfox-javadoc/src/test/java/springfox/javadoc/example/SecretAgent.java +++ b/springfox-javadoc/src/test/java/springfox/javadoc/example/SecretAgent.java @@ -1,3 +1,21 @@ +/* + * + * Copyright 2018-2019 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 + * + * http://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 springfox.javadoc.example; /** diff --git a/springfox-javadoc/src/test/java/springfox/javadoc/example/SecretAgentController.java b/springfox-javadoc/src/test/java/springfox/javadoc/example/SecretAgentController.java index b1045b7..e11b312 100644 --- a/springfox-javadoc/src/test/java/springfox/javadoc/example/SecretAgentController.java +++ b/springfox-javadoc/src/test/java/springfox/javadoc/example/SecretAgentController.java @@ -18,10 +18,10 @@ */ package springfox.javadoc.example; -import org.springframework.web.bind.annotation.*; - -import javax.naming.InvalidNameException; -import java.io.IOException; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; /** * Service to retrieve secret agents (could replace M in the future). From b2a7344780b51ff4b86080d788dac1d36f4d3270 Mon Sep 17 00:00:00 2001 From: Matthieu Ghilain Date: Fri, 26 Apr 2019 14:39:35 +0200 Subject: [PATCH 15/15] Added example to showcase that model documentation for deep field is working --- .../springfox/javadoc/example/Unicorn.java | 16 ++++++- .../javadoc/example/UnicornChild.java | 47 +++++++++++++++++++ .../javadoc/example/UnicornController.java | 2 +- 3 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/UnicornChild.java diff --git a/springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/Unicorn.java b/springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/Unicorn.java index 3c2b988..1b6d02b 100644 --- a/springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/Unicorn.java +++ b/springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/Unicorn.java @@ -21,6 +21,7 @@ /** * A beautiful unicorn */ +@SuppressWarnings("unused") public class Unicorn { /** * Unicorn name. @@ -30,10 +31,15 @@ public class Unicorn { * Unicorn magic name (use with care!) */ private String magicName; + /** + * A magical unicorn child, they can only have one, that's why they have disappeared + */ + private UnicornChild unicornChild; - Unicorn(String name, String magicName) { + Unicorn(String name, String magicName, UnicornChild unicornChild) { this.name = name; this.magicName = magicName; + this.unicornChild = unicornChild; } public Unicorn() { @@ -54,4 +60,12 @@ public String getMagicName() { public void setMagicName(String magicName) { this.magicName = magicName; } + + public UnicornChild getUnicornChild() { + return unicornChild; + } + + public void setUnicornChild(UnicornChild unicornChild) { + this.unicornChild = unicornChild; + } } diff --git a/springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/UnicornChild.java b/springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/UnicornChild.java new file mode 100644 index 0000000..1e51360 --- /dev/null +++ b/springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/UnicornChild.java @@ -0,0 +1,47 @@ + +/* + * + * Copyright 2018-2019 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 + * + * http://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 springfox.javadoc.example; + +/** + * Unicorns can also have children ! + */ +@SuppressWarnings("unused") +public class UnicornChild { + + /** + * The baby name. Take care choosing the right one. + */ + private String childName; + + UnicornChild(String childName) { + this.childName = childName; + } + + public UnicornChild() { + } + + public String getChildName() { + return childName; + } + + public void setChildName(String childName) { + this.childName = childName; + } +} diff --git a/springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/UnicornController.java b/springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/UnicornController.java index 7662bcd..c0a1019 100644 --- a/springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/UnicornController.java +++ b/springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/UnicornController.java @@ -29,7 +29,7 @@ @RequestMapping(path = "/unicorns") public class UnicornController { - private Map unicorns = Map.of("Poppy", new Unicorn("Poppy", "Poppy The Mighty")); + private Map unicorns = Map.of("Poppy", new Unicorn("Poppy", "Poppy The Mighty", new UnicornChild("Baby"))); /** * Retrieve unicorn by their name (try with Poppy)