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/.circleci/config.yml b/.circleci/config.yml index d35f047..52c2a06 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"' @@ -41,20 +41,19 @@ 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 ~/reports when: always - store_test_results: path: ~/junit - - store_test_results: - path: ~/reports - store_artifacts: - path: ~/junit + path: ~/junit - store_artifacts: - path: ~/reports + path: ~/reports - deploy: command: | if [ "${CIRCLE_BRANCH}" == "master" ]; then - ./gradlew artifactoryPublish -x check + cd springfox-javadoc + ../gradlew artifactoryPublish -x check fi notify: webhooks: 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 f775b1c..0000000 Binary files a/.mvn/wrapper/maven-wrapper.jar and /dev/null differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties deleted file mode 100755 index a447c9f..0000000 --- a/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1 +0,0 @@ -distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.2/apache-maven-3.5.2-bin.zip \ No newline at end of file diff --git a/README.md b/README.md index 3c649b0..907162f 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,32 @@ [![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. + +# 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 +[See the example project](./springfox-javadoc-gradle-example). + +## Maven example ```xml org.apache.maven.plugins @@ -38,6 +59,9 @@ Maven example: ``` +## TODO + +- 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 943d69f..5bab0c1 100644 --- a/build.gradle +++ b/build.gradle @@ -4,46 +4,32 @@ plugins { id "com.jfrog.bintray" version "1.8.4" } -apply plugin: 'idea' -apply plugin: 'java' -apply plugin: 'maven' -apply plugin: 'osgi' -apply plugin: 'jacoco' -apply plugin: 'maven-publish' -apply from: "publishing.gradle" - -group = 'io.springfox' -description = "springfox-javadoc" - -sourceCompatibility = 1.6 -targetCompatibility = 1.6 - -tasks.withType(JavaCompile) { - options.encoding = 'UTF-8' - options.deprecation = true - options.compilerArgs += ["-Xlint:unchecked", "-parameters"] -} - -repositories { - jcenter() - mavenCentral() -} - -dependencies { - compile 'io.springfox:springfox-swagger2:2.9.2' - compile 'org.springframework:spring-webmvc:4.3.18.RELEASE' - testCompile 'junit:junit:4.12' - testCompile 'org.mockito:mockito-core:2.21.0' - compile files("${System.getProperty('java.home')}/../lib/tools.jar") -} - -jacoco { - toolVersion = "0.8.1" -} +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' + apply plugin: 'maven' + + group = 'io.springfox' + + sourceCompatibility = 1.9 + targetCompatibility = 1.9 + + tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' + options.deprecation = true + options.compilerArgs += ["-Xlint:unchecked", "-parameters"] + } -jacocoTestReport { - reports { - xml.enabled true - html.enabled true + repositories { + jcenter() + mavenCentral() + maven { + name = 'bintray-springfox-snapshot' + url = "http://oss.jfrog.org/oss-snapshot-local/" + } } + } 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/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a95009c..51fb1c4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.3.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists 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 diff --git a/mvnw b/mvnw deleted file mode 100755 index e96ccd5..0000000 --- a/mvnw +++ /dev/null @@ -1,227 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Maven2 Start Up Batch script -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir -# -# Optional ENV vars -# ----------------- -# M2_HOME - location of maven2's installed home dir -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files -# ---------------------------------------------------------------------------- - -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi - -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "`uname`" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" - else - export JAVA_HOME="/Library/Java/Home" - fi - fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` - fi -fi - -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - 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/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..1b6d02b --- /dev/null +++ b/springfox-javadoc-gradle-example/src/main/java/springfox/javadoc/example/Unicorn.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.example; + +/** + * A beautiful unicorn + */ +@SuppressWarnings("unused") +public class Unicorn { + /** + * Unicorn name. + */ + private String name; + /** + * 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, UnicornChild unicornChild) { + this.name = name; + this.magicName = magicName; + this.unicornChild = unicornChild; + } + + 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; + } + + 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 new file mode 100644 index 0000000..c0a1019 --- /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", new UnicornChild("Baby"))); + + /** + * 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..40c5333 --- /dev/null +++ b/springfox-javadoc/build.gradle @@ -0,0 +1,27 @@ +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' + implementation("com.fasterxml.jackson.core:jackson-databind:2.9.8") + 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/springfox-javadoc/src/main/java/springfox/javadoc/doclet/ClassDirectoryOption.java b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/ClassDirectoryOption.java new file mode 100644 index 0000000..663b64c --- /dev/null +++ b/springfox-javadoc/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/springfox-javadoc/src/main/java/springfox/javadoc/doclet/DocletHelper.java b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/DocletHelper.java new file mode 100644 index 0000000..42aef1b --- /dev/null +++ b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/DocletHelper.java @@ -0,0 +1,72 @@ +/* + * + * 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.sun.source.doctree.DocCommentTree; +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(); + } + + static Optional getElementDoc(DocletEnvironment docletEnvironment, Element 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..d786465 --- /dev/null +++ b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/DummyOption.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.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/DocletOptions.java b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/MethodProcessingContext.java similarity index 60% rename from src/main/java/springfox/javadoc/doclet/DocletOptions.java rename to springfox-javadoc/src/main/java/springfox/javadoc/doclet/MethodProcessingContext.java index 8bf7e94..972bcd2 100644 --- a/src/main/java/springfox/javadoc/doclet/DocletOptions.java +++ b/springfox-javadoc/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/springfox-javadoc/src/main/java/springfox/javadoc/doclet/MethodProcessingContextFactory.java b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/MethodProcessingContextFactory.java new file mode 100644 index 0000000..d789d43 --- /dev/null +++ b/springfox-javadoc/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/springfox-javadoc/src/main/java/springfox/javadoc/doclet/SpringMappingsHelper.java b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/SpringMappingsHelper.java new file mode 100644 index 0000000..87ad586 --- /dev/null +++ b/springfox-javadoc/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/springfox-javadoc/src/main/java/springfox/javadoc/doclet/SpringRequestMappings.java b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/SpringRequestMappings.java new file mode 100644 index 0000000..ec68914 --- /dev/null +++ b/springfox-javadoc/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/springfox-javadoc/src/main/java/springfox/javadoc/doclet/SpringfoxDocletException.java b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/SpringfoxDocletException.java new file mode 100644 index 0000000..aaf90ee --- /dev/null +++ b/springfox-javadoc/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/springfox-javadoc/src/main/java/springfox/javadoc/doclet/SwaggerPropertiesDoclet.java b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/SwaggerPropertiesDoclet.java new file mode 100644 index 0000000..6aa0320 --- /dev/null +++ b/springfox-javadoc/src/main/java/springfox/javadoc/doclet/SwaggerPropertiesDoclet.java @@ -0,0 +1,234 @@ +/* + * + * 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.sun.source.doctree.*; +import jdk.javadoc.doclet.Doclet; +import jdk.javadoc.doclet.DocletEnvironment; +import jdk.javadoc.doclet.Reporter; +import springfox.javadoc.plugin.JavadocParameterBuilderPlugin; + +import javax.lang.model.SourceVersion; +import javax.lang.model.element.*; +import javax.tools.Diagnostic; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; + +/** + * Generate properties file based on Javadoc. + *

+ * The generated properties file will then be read by the + * {@link JavadocParameterBuilderPlugin} to enhance the Swagger documentation. + * + * @author rgoers + * @author MartinNeumannBeTSE + */ +public class SwaggerPropertiesDoclet implements Doclet { + + private static final String NEWLINE = "\n"; + private static final String EMPTY = ""; + + private ClassDirectoryOption classDirectoryOption = new ClassDirectoryOption(); + private Reporter reporter; + private OutputStream springfoxPropertiesOutputStream; + 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); + this.reporter = reporter; + } + + @Override + public String getName() { + return "springfox-javadoc-doclet"; + } + + @Override + public Set getSupportedOptions() { + return new HashSet<>(Arrays.asList(new DummyOption(1, "-doctitle"), + new DummyOption(1, "-windowtitle"), + new DummyOption(1, "-author"), new DummyOption(1, "-d"), + classDirectoryOption)); + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.RELEASE_9; + } + + @Override + public boolean run(DocletEnvironment environment) { + this.environment = environment; + this.methodProcessingContextFactory = new MethodProcessingContextFactory(environment); + try { + preparePropertiesOutputStream(); + environment.getIncludedElements() + .stream() + .filter(element -> element.getKind().isClass()) + .map(element -> (TypeElement) element) + .forEach(this::processClass); + return true; + } finally { + closeSpringfoxPropertiesStream(); + } + } + + private void closeSpringfoxPropertiesStream() { + try { + if (springfoxPropertiesOutputStream != null) { + springfoxPropertiesOutputStream.close(); + } + } catch (IOException e) { + reporter.print(Diagnostic.Kind.WARNING, "Could not close output stream: " + e.getMessage()); + } + } + + 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); + } + } + + private void processClass(TypeElement typeElement) { + Properties properties = new Properties(); + 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 -> { + String classKey = + typeElement.getQualifiedName().toString() + "." + variableElement.getSimpleName().toString(); + properties.put(classKey, 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)); + }); + } + + 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) { + try { + properties.store(springfoxPropertiesOutputStream, "Class = " + typeElement.getQualifiedName().toString()); + } catch (IOException e) { + throw new SpringfoxDocletException(e); + } + } + + 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++; + } + } + } + } + } + } + + 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/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..4b4d8cd --- /dev/null +++ b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/AnnotationHelper.java @@ -0,0 +1,38 @@ +/* + * + * 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; + +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..2552ecf --- /dev/null +++ b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocApiListingBuilderPlugin.java @@ -0,0 +1,48 @@ +/* + * + * 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 springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.service.ApiListingBuilderPlugin; +import springfox.documentation.spi.service.contexts.ApiListingContext; + +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/JavadocModelBuilderPlugin.java b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocModelBuilderPlugin.java new file mode 100644 index 0000000..1bc2425 --- /dev/null +++ b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocModelBuilderPlugin.java @@ -0,0 +1,46 @@ +/* + * + * 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 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..a9e0e70 --- /dev/null +++ b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocModelPropertyBuilderPlugin.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 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(); + 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 new file mode 100644 index 0000000..e45e054 --- /dev/null +++ b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocOperationBuilderPlugin.java @@ -0,0 +1,86 @@ +/* + * + * 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.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} + */ +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/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocParameterBuilderPlugin.java b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocParameterBuilderPlugin.java new file mode 100644 index 0000000..dd29b6c --- /dev/null +++ b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocParameterBuilderPlugin.java @@ -0,0 +1,117 @@ +/* + * + * 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 springfox.documentation.service.ResolvedMethodParameter; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.service.ParameterBuilderPlugin; +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.Optional; + +import static springfox.javadoc.plugin.AnnotationHelper.hasValue; + +/** + * Plugin to generate the @ApiParam and @ApiOperation values from the properties + * file generated by the {@link SwaggerPropertiesDoclet}. + * + * @author rgoers + * @author MartinNeumannBeTSE + */ +public class JavadocParameterBuilderPlugin implements 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"; + private final Environment environment; + + public JavadocParameterBuilderPlugin(Environment environment) { + this.environment = environment; + } + + private static Annotation annotationFromField(ParameterContext context, String annotationType) { + + ResolvedMethodParameter methodParam = context.resolvedMethodParameter(); + + for (Annotation annotation : methodParam.getAnnotations()) { + if (annotation.annotationType().getName().equals(annotationType)) { + return annotation; + } + } + return null; + + } + + @Override + public boolean supports(DocumentationType delimiter) { + return true; + } + + @Override + public void apply(ParameterContext context) { + String description = null; + Optional parameterName = context.resolvedMethodParameter().defaultName(); + Annotation apiParam = annotationFromField(context, API_PARAM); + if (apiParam != null) { + java.util.Optional isRequired = isParamRequired(apiParam, context); + isRequired.ifPresent(aBoolean -> context.parameterBuilder().required(aBoolean)); + } + if (parameterName.isPresent() && (apiParam == null || !hasValue(apiParam))) { + String key = context.getOperationContext().requestMappingPattern() + PERIOD + + context.getOperationContext().httpMethod().name() + ".param." + parameterName.get(); + description = environment.getProperty(key); + } + if (description != null) { + context.parameterBuilder().description(description); + } + } + + private java.util.Optional isParamRequired(Annotation apiParam, ParameterContext context) { + if (apiParam != null) { + java.util.Optional required = isRequired(apiParam); + if (required.isPresent()) { + return required; + } + } + Annotation annotation = annotationFromField(context, REQUEST_PARAM); + if (annotation == null) { + annotation = annotationFromField(context, PATH_VARIABLE); + } + return annotation != null ? isRequired(annotation) : java.util.Optional.empty(); + } + + private java.util.Optional isRequired(Annotation annotation) { + for (Method method : annotation.annotationType().getDeclaredMethods()) { + if (method.getName().equals("required")) { + try { + return java.util.Optional.of((Boolean) method.invoke(annotation, (Object) null)); + } catch (Exception ex) { + return java.util.Optional.empty(); + } + } + } + return java.util.Optional.empty(); + } + +} diff --git a/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocPluginConfiguration.java b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocPluginConfiguration.java new file mode 100644 index 0000000..3204efa --- /dev/null +++ b/springfox-javadoc/src/main/java/springfox/javadoc/plugin/JavadocPluginConfiguration.java @@ -0,0 +1,64 @@ +/* + * + * 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.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.core.env.Environment; +import springfox.javadoc.doclet.SwaggerPropertiesDoclet; + +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 JavadocParameterBuilderPlugin} to the Spring context. + * + * @author MartinNeumannBeTSE + */ +@Configuration +@PropertySource(value = "classpath:/" + + SPRINGFOX_JAVADOC_PROPERTIES, ignoreResourceNotFound = true) +public class JavadocPluginConfiguration { + + @Bean + public JavadocApiListingBuilderPlugin javadocApiListingBuilderPlugin(Environment environment) { + return new JavadocApiListingBuilderPlugin(environment); + } + + @Bean + public JavadocOperationBuilderPlugin javadocOperationBuilderPlugin(Environment environment) { + return new JavadocOperationBuilderPlugin(environment); + } + + @Bean + 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/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/springfox-javadoc/src/test/java/springfox/javadoc/doclet/SwaggerPropertiesDocletTest.java b/springfox-javadoc/src/test/java/springfox/javadoc/doclet/SwaggerPropertiesDocletTest.java new file mode 100644 index 0000000..e7fe54a --- /dev/null +++ b/springfox-javadoc/src/test/java/springfox/javadoc/doclet/SwaggerPropertiesDocletTest.java @@ -0,0 +1,105 @@ +/* + * + * 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.junit.BeforeClass; +import org.junit.Test; +import springfox.javadoc.example.SecretAgent; +import springfox.javadoc.example.TestController; + +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.util.Arrays; +import java.util.Properties; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static springfox.javadoc.doclet.ClassDirectoryOption.SPRINGFOX_JAVADOC_PROPERTIES; + +public class SwaggerPropertiesDocletTest { + + private static final String BUILD_PROPERTY_FILE_LOCATION = "./build/property-file-location"; + private static final String GENERATED_PROPERTY_FILE = + String.format("%s/%s", BUILD_PROPERTY_FILE_LOCATION, SPRINGFOX_JAVADOC_PROPERTIES); + + @BeforeClass + public static void deletePropertyFile() { + File propertyFile = new File(GENERATED_PROPERTY_FILE); + if (propertyFile.exists()) { + //noinspection ResultOfMethodCallIgnored + propertyFile.delete(); + } + } + + @Test + public void testPropertiesGeneration() throws IOException { + DocumentationTool systemDocumentationTool = ToolProvider.getSystemDocumentationTool(); + String[] args = new String[] { + "-sourcepath", + "./src/test/java", + "-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 + }; + DocumentationTool.DocumentationTask task = systemDocumentationTool.getTask(null, null, null, + SwaggerPropertiesDoclet.class, Arrays.asList(args), null); + + task.call(); + + 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")); + 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")); + } + + private Properties generatedProperties() throws IOException { + // read in the properties file created by the SwaggerPropertiesDoclet + InputStream inputStream = new FileInputStream(GENERATED_PROPERTY_FILE); + assertNotNull(inputStream); + + // check that the properties match the example sources + Properties props = new Properties(); + props.load(inputStream); + return props; + } + +} diff --git a/springfox-javadoc/src/test/java/springfox/javadoc/example/OtherController.java b/springfox-javadoc/src/test/java/springfox/javadoc/example/OtherController.java new file mode 100644 index 0000000..a1188bc --- /dev/null +++ b/springfox-javadoc/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() { + + } +} 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..597ac98 --- /dev/null +++ b/springfox-javadoc/src/test/java/springfox/javadoc/example/SecretAgent.java @@ -0,0 +1,38 @@ +/* + * + * 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; + +/** + * 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..e11b312 --- /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.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). + * + * @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; + } + +} 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; diff --git a/src/main/java/springfox/javadoc/configuration/JavadocPluginConfiguration.java b/src/main/java/springfox/javadoc/configuration/JavadocPluginConfiguration.java deleted file mode 100644 index 8511cd8..0000000 --- a/src/main/java/springfox/javadoc/configuration/JavadocPluginConfiguration.java +++ /dev/null @@ -1,48 +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.configuration; - -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 springfox.javadoc.doclet.SwaggerPropertiesDoclet; -import springfox.javadoc.plugin.JavadocBuilderPlugin; - -/** - * 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) -@ComponentScan("springfox.javadoc.plugin") -public class JavadocPluginConfiguration { - - @Autowired - JavadocBuilderPlugin javadocBuilderPlugin; - - @Bean - public JavadocBuilderPlugin javadocBuilder() { - return javadocBuilderPlugin; - } -} 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/SwaggerPropertiesDoclet.java b/src/main/java/springfox/javadoc/doclet/SwaggerPropertiesDoclet.java deleted file mode 100644 index 1746d66..0000000 --- a/src/main/java/springfox/javadoc/doclet/SwaggerPropertiesDoclet.java +++ /dev/null @@ -1,358 +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.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 springfox.javadoc.plugin.JavadocBuilderPlugin; - -import java.io.File; -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 - -/** - * Generate properties file based on Javadoc. - *

- * The generated properties file will then be read by the - * {@link JavadocBuilderPlugin} to enhance the Swagger documentation. - * - * @author rgoers - * @author MartinNeumannBeTSE - */ -public class SwaggerPropertiesDoclet { - - 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 static DocletOptions docletOptions; - - private SwaggerPropertiesDoclet() { - throw new UnsupportedOperationException(); - } - - - /** - * 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; - } - - /** - * 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) { - - DocletOptionParser parser = new DocletOptionParser(options); - - try { - docletOptions = parser.parse(); - return true; - } catch (IllegalStateException e) { - reporter.printError(e.getMessage()); - } - 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 - } - } - } - } - 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; - } - } - 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); - } else { - pathRoot.append(value); - } - } - - private static void processMethod( - 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()); - - 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); - } - } - } - } - } - - 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 static boolean isMapping(String name) { - for (String mapping : MAPPINGS) { - if (mapping.equals(name)) { - return true; - } - } - 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); - } - } - } 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); - } - } -} diff --git a/src/main/java/springfox/javadoc/plugin/JavadocBuilderPlugin.java b/src/main/java/springfox/javadoc/plugin/JavadocBuilderPlugin.java deleted file mode 100644 index 779e7c9..0000000 --- a/src/main/java/springfox/javadoc/plugin/JavadocBuilderPlugin.java +++ /dev/null @@ -1,186 +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.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; -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.Set; - -/** - * Plugin to generate the @ApiParam and @ApiOperation values from the properties - * file generated by the {@link SwaggerPropertiesDoclet}. - * - * @author rgoers - * @author MartinNeumannBeTSE - */ -@Component -@Order(Ordered.LOWEST_PRECEDENCE) -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 static Annotation annotationFromField(ParameterContext context, String annotationType) { - - ResolvedMethodParameter methodParam = context.resolvedMethodParameter(); - - for (Annotation annotation : methodParam.getAnnotations()) { - if (annotation.annotationType().getName().equals(annotationType)) { - return annotation; - } - } - return null; - - } - - @Override - 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.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; - Optional parmName = 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()); - } - } - if (parmName.isPresent() && (apiParam == null || !hasValue(apiParam, context))) { - String key = context.getOperationContext().requestMappingPattern() + PERIOD - + context.getOperationContext().httpMethod().name() + ".param." + parmName.get(); - description = environment.getProperty(key); - } - if (description != null) { - context.parameterBuilder().description(description); - } - } - - @VisibleForTesting - String extractApiParamDescription(Annotation annotation) { - return annotation != null ? annotation.annotationType().getName() : null; - } - - @VisibleForTesting - Optional isParamRequired(Annotation apiParam, ParameterContext context) { - if (apiParam != null) { - Optional required = isRequired(apiParam, context); - if (required.isPresent()) { - return required; - } - } - Annotation annotation = annotationFromField(context, REQUEST_PARAM); - if (annotation == null) { - annotation = annotationFromField(context, PATH_VARIABLE); - } - return annotation != null ? isRequired(annotation, context) : Optional.absent(); - } - - @VisibleForTesting - Optional isRequired(Annotation annotation, ParameterContext context) { - for (Method method : annotation.annotationType().getDeclaredMethods()) { - if (method.getName().equals("required")) { - try { - return Optional.of((Boolean) method.invoke(annotation, (Object) null)); - } catch (Exception ex) { - return Optional.absent(); - } - } - } - return Optional.absent(); - } - - @VisibleForTesting - boolean hasValue(Annotation annotation, ParameterContext context) { - 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(); - } catch (Exception ex) { - return false; - } - } - } - return false; - } -} diff --git a/src/test/java/springfox/javadoc/doclet/SwaggerPropertiesDocletTest.java b/src/test/java/springfox/javadoc/doclet/SwaggerPropertiesDocletTest.java deleted file mode 100644 index 36dc34d..0000000 --- a/src/test/java/springfox/javadoc/doclet/SwaggerPropertiesDocletTest.java +++ /dev/null @@ -1,182 +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.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 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.Properties; - -import static org.junit.Assert.*; -import static springfox.javadoc.doclet.SwaggerPropertiesDoclet.*; - -public class SwaggerPropertiesDocletTest { - - private static final String BUILD_PROPERTY_FILE_LOCATION = "./build/property-file-location"; - private static final String GENERATED_PROPERTY_FILE = - 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")); - } - - @Test - public void testPropertiesGeneration() throws IOException { - - StringWriter err = new StringWriter(); - StringWriter warn = new StringWriter(); - StringWriter notice = new StringWriter(); - - String[] args = new String[] { - "-sourcepath", - "./src/test/java", - "-subpackages", - "springfox.javadoc", - "springfox.javadoc", - "-classdir", - BUILD_PROPERTY_FILE_LOCATION - }; - - Main.execute( - "SwaggerPropertiesDoclet", - new PrintWriter(err), - new PrintWriter(warn), - new PrintWriter(notice), - SwaggerPropertiesDoclet.class.getName(), - args); - - 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("retval", props.getProperty("/test.POST.return")); - assertEquals("param", props.getProperty("/test.POST.param.bar")); - } - - private Properties generatedProperties() throws IOException { - // read in the properties file created by the SwaggerPropertiesDoclet - InputStream inputStream = new FileInputStream(GENERATED_PROPERTY_FILE); - assertNotNull(inputStream); - - // check that the properties match the example sources - Properties props = new Properties(); - props.load(inputStream); - 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(); - } - } -}