├── .gitignore ├── README.md ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── knappsack │ │ └── swagger4springweb │ │ ├── annotation │ │ └── ApiExclude.java │ │ ├── controller │ │ └── ApiDocumentationController.java │ │ ├── filter │ │ ├── AnnotationFilter.java │ │ ├── ApiExcludeFilter.java │ │ └── Filter.java │ │ ├── model │ │ └── AnnotatedParameter.java │ │ ├── parser │ │ ├── ApiDescriptionParser.java │ │ ├── ApiModelParser.java │ │ ├── ApiOperationParser.java │ │ ├── ApiParameterParser.java │ │ ├── ApiParser.java │ │ ├── ApiParserImpl.java │ │ └── ApiPathParser.java │ │ └── util │ │ ├── AnnotationUtils.java │ │ ├── ModelUtils.java │ │ └── ScalaObjectMapper.java └── scala │ └── com │ └── knappsack │ └── swagger4springweb │ ├── parser │ ├── SpringApiReader.scala │ └── SpringMVCApiReader.scala │ └── util │ ├── ApiListingUtil.scala │ ├── JavaToScalaUtil.scala │ └── ScalaToJavaUtil.scala └── test └── java └── com └── knappsack └── swagger4springweb ├── AbstractTest.java ├── AnnotationUtilsTest.java ├── ApiParserTest.java ├── controller └── ApiDocumentationControllerTest.java ├── parser ├── ApiParameterParserTest.java └── DocumentationPathParserTest.java ├── testController ├── EmptyTestController.java ├── MockController.java ├── NoClassLevelMappingController.java └── exclude │ ├── ExcludeClassTestController.java │ ├── ExcludeSingleOpTestController.java │ └── PartialExcludeTestController.java └── testModels ├── MockPojo.java └── MockPojoChild.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | target/ 3 | *.iml 4 | .DS_STORE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | swagger4spring-web 2 | ================== 3 | 4 | Please note: This project is no longer actively supported. 5 | 6 | Supports Swagger 1.3 as of version 0.3.0! 7 | 8 | This project aims at providing Swagger support to your Spring-Web based application. It will attempt to document your API based on existing Spring-Web annotations if no Swagger annotations exist. If Swagger annotations do exist, it will utilize those in conjunction with the Spring-Web annotations. 9 | 10 | ##How-To 11 | 12 | To include swagger4spring-web in your project, you need to include the jar in your project. If you use Maven, please include the following dependency: 13 | 14 | 15 | com.knappsack 16 | swagger4spring-web 17 | 0.3.5 18 | 19 | 20 | Java 8+ users, please compile your source using the javac "-parameters" argument. This ensures that your parameter names display correctly in your API documentation. 21 | 22 | In order to use swagger4spring-web in your project, you need to declare an ApiDocumentationController bean in your 23 | servlet context. For example: 24 | 25 | 30 | 31 | * basePath - optional - the base URL of your web application, for example http://localhost/swagger4spring-web-example 32 | * baseControllerPackage - optional - this is the package you want swagger4spring-web to scan to look for classes annotated with @Controller. If this is not set, all your packages are scanned. 33 | * baseModelPackage - optional - this is the package you want to scan if all your model objects are in a specific directory. These classes will be added to your documentation schema. If no package is specified only certain return types and parameters are added to the documentation schema. 34 | * additionalControllerPackage - optional - if you have more packages with controllers outside of the baseControllerPackage, specify them here. 35 | * additionalModelPackage - optional - if you have packages outside of the baseModelPackage that you want to scan for models, specify them here. 36 | * apiVersion - required - this is the version of your API 37 | * apiInfo - optional - if you have information you wish to provide, such as license and terms of service, set this. 38 | 39 | If you are using version 0.3.0 or above, you'll also need to add the following to the appropriate Spring context file in your application: 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | Once the ApiDocumentationController is wired, you may call go to your base path + /api/resourceList (ex: http://localhost/swagger4spring-web-example/api/resourceList) in order to retrieve an inventory of your APIs. For an example JSP see this [page](https://github.com/wkennedy/swagger4spring-web-example/blob/master/src/main/webapp/WEB-INF/views/documentation.jsp). 52 | 53 | #####Alternative Implementation 54 | 55 | If you wish to use a different request mapping then you may extend create a new controller that extends ApiDocumentationController. For example if you want the URL to be /documentation/resourceList instead of /api/resourceList you can create a controller like this: 56 | 57 | @Controller 58 | @RequestMapping(value = "/documentation") 59 | public class ExampleDocumentationController extends ApiDocumentationController { 60 | 61 | public ExampleDocumentationController() { 62 | setBaseControllerPackage("com.knappsack.swagger4springweb.controllers.api"); 63 | setBaseModelPackage("com.knappsack.swagger4springweb.models"); 64 | setApiVersion("v1"); 65 | } 66 | 67 | @RequestMapping(value = "/", method = RequestMethod.GET) 68 | public String documentation() { 69 | return "documentation"; 70 | } 71 | } 72 | 73 | In this case you don't have to create the controller bean in your servlet context if you are using component scanning and your new controller is set to be picked up in the scan. 74 | 75 | To see a working example, please take a look at [swagger4spring-web-example](https://github.com/wkennedy/swagger4spring-web-example/ "swagger4spring-web-example"). 76 | 77 | ##Annotation Support 78 | The following Spring-Web annotations are supported: 79 | 80 | * @Controller 81 | * @RestController 82 | * @RequestMapping 83 | * @ResponseBody 84 | * @RequestBody 85 | * @PathVariable 86 | * @RequestParam 87 | * @ApiExclude - This annotation is unique to swagger4spring-web. It allows you to specify a controller or method for which you do not want to generate Swagger documentation. 88 | 89 | The following Swagger annotations are supported: 90 | 91 | * @Api 92 | * @ApiResponse 93 | * @ApiResponses 94 | * @ApiOperation 95 | * @ApiParam 96 | * @ApiModel 97 | * @ApiModelProperty 98 | 99 | ##External Links 100 | [Swagger Home](http://developers.helloreverb.com/swagger/ "Swagger Home") 101 | 102 | [Swagger Wiki](https://github.com/wordnik/swagger-core/wiki "Swagger Wiki") 103 | 104 | ##Change Log 105 | https://github.com/wkennedy/swagger4spring-web/wiki/Change-Log 106 | 107 | ##License 108 | Copyright (c) 2014 Will Kennedy 109 | 110 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 111 | 112 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 113 | 114 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 115 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | org.sonatype.oss 7 | oss-parent 8 | 9 9 | 10 | 11 | swagger4spring-web 12 | com.knappsack 13 | swagger4spring-web 14 | jar 15 | 0.3.6 16 | Swagger implementation for the Spring-Web framework 17 | https://github.com/wkennedy/swagger4spring-web 18 | 19 | 20 | The MIT License (MIT) 21 | http://opensource.org/licenses/MIT 22 | repo 23 | 24 | 25 | 26 | https://github.com/wkennedy/swagger4spring-web 27 | scm:git:git@github.com:wkennedy/swagger4spring-web.git 28 | scm:git:git@github.com:wkennedy/swagger4spring-web.git 29 | 30 | 31 | 32 | kennedyw 33 | Will Kennedy 34 | will.kennedy@sparcedge.com 35 | 36 | 37 | 38 | 39 | 4.0.6.RELEASE 40 | 1.3.7 41 | 1.6.4 42 | 1.0.1 43 | 4.9 44 | 1.7 45 | 2.4.1 46 | 0.9.9-RC2 47 | 3.1.0 48 | 2.6.1 49 | 50 | 51 | 52 | 53 | org.scala-lang 54 | scala-library 55 | 2.10.4 56 | 57 | 58 | 59 | org.scalaj 60 | scalaj-collection_2.10 61 | 1.5 62 | 63 | 64 | scala-library 65 | org.scala-lang 66 | 67 | 68 | 69 | 70 | 71 | org.springframework 72 | spring-webmvc 73 | ${org.springframework-version} 74 | 75 | 76 | org.springframework 77 | spring-context 78 | ${org.springframework-version} 79 | 80 | 81 | com.fasterxml.jackson.module 82 | jackson-module-scala_2.10 83 | ${jackson-module-scala_2.10.version} 84 | 85 | 86 | scala-library 87 | org.scala-lang 88 | 89 | 90 | paranamer 91 | com.thoughtworks.paranamer 92 | 93 | 94 | 95 | 96 | com.wordnik 97 | swagger-core_2.10 98 | ${org.wordnik-swagger-version} 99 | 100 | 101 | jackson-databind 102 | com.fasterxml.jackson.core 103 | 104 | 105 | jackson-core 106 | com.fasterxml.jackson.core 107 | 108 | 109 | jackson-annotations 110 | com.fasterxml.jackson.core 111 | 112 | 113 | scala-library 114 | org.scala-lang 115 | 116 | 117 | paranamer 118 | com.thoughtworks.paranamer 119 | 120 | 121 | scala-reflect 122 | org.scala-lang 123 | 124 | 125 | slf4j-api 126 | org.slf4j 127 | 128 | 129 | 130 | 131 | org.reflections 132 | reflections 133 | ${org.reflections.version} 134 | 135 | 136 | 137 | javax.servlet 138 | javax.servlet-api 139 | ${servlet-api-version} 140 | provided 141 | 142 | 143 | 144 | com.thoughtworks.paranamer 145 | paranamer 146 | ${paranamer-version} 147 | 148 | 149 | 150 | 151 | org.slf4j 152 | slf4j-api 153 | ${org.slf4j-version} 154 | 155 | 156 | 157 | ch.qos.logback 158 | logback-classic 159 | ${org.logback-version} 160 | runtime 161 | 162 | 163 | 164 | ch.qos.logback 165 | logback-core 166 | ${org.logback-version} 167 | runtime 168 | 169 | 170 | 171 | 172 | junit 173 | junit 174 | ${junit-version} 175 | test 176 | 177 | 178 | 179 | org.springframework 180 | spring-test 181 | ${org.springframework-version} 182 | test 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | net.alchim31.maven 192 | scala-maven-plugin 193 | 3.1.6 194 | 195 | 196 | org.apache.maven.plugins 197 | maven-compiler-plugin 198 | 199 | 200 | 201 | 202 | 203 | net.alchim31.maven 204 | scala-maven-plugin 205 | 3.1.6 206 | 207 | false 208 | 209 | 210 | 211 | scala-compile-first 212 | process-resources 213 | 214 | add-source 215 | compile 216 | 217 | 218 | 219 | scala-test-compile 220 | process-test-resources 221 | 222 | testCompile 223 | 224 | 225 | 226 | 227 | 228 | org.apache.maven.plugins 229 | maven-compiler-plugin 230 | 3.1 231 | 232 | ${java.version} 233 | ${java.version} 234 | 235 | 236 | 237 | compile 238 | 239 | compile 240 | 241 | 242 | 243 | 244 | 245 | org.apache.maven.plugins 246 | maven-source-plugin 247 | 2.3 248 | 249 | 250 | attach-sources 251 | 252 | jar 253 | 254 | 255 | 256 | 257 | 258 | org.apache.maven.plugins 259 | maven-javadoc-plugin 260 | 2.9.1 261 | 262 | 263 | attach-javadocs 264 | 265 | jar 266 | 267 | 268 | 269 | 270 | 271 | org.apache.maven.plugins 272 | maven-release-plugin 273 | 2.5 274 | 275 | 276 | 277 | 278 | 279 | -------------------------------------------------------------------------------- /src/main/java/com/knappsack/swagger4springweb/annotation/ApiExclude.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * An annotation which indicates if this Api should be excluded from the 10 | * automatically generated swagger documents 11 | * 12 | * This annotation is applicable to the controller class or a method inside the controller 13 | */ 14 | @Target(value = {ElementType.METHOD, ElementType.TYPE}) 15 | @Retention(value = RetentionPolicy.RUNTIME) 16 | public @interface ApiExclude { 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/knappsack/swagger4springweb/controller/ApiDocumentationController.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.controller; 2 | 3 | import com.knappsack.swagger4springweb.filter.Filter; 4 | import com.knappsack.swagger4springweb.parser.ApiParser; 5 | import com.knappsack.swagger4springweb.parser.ApiParserImpl; 6 | import com.wordnik.swagger.model.ApiInfo; 7 | import com.wordnik.swagger.model.ApiListing; 8 | import com.wordnik.swagger.model.AuthorizationType; 9 | import com.wordnik.swagger.model.ResourceListing; 10 | import org.springframework.stereotype.Controller; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RequestMethod; 13 | import org.springframework.web.bind.annotation.ResponseBody; 14 | import org.springframework.web.context.request.RequestContextHolder; 15 | import org.springframework.web.context.request.ServletRequestAttributes; 16 | import org.springframework.web.servlet.HandlerMapping; 17 | 18 | import javax.servlet.http.HttpServletRequest; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | import java.util.Map; 22 | 23 | @Controller 24 | @RequestMapping(value = "/api") 25 | public class ApiDocumentationController { 26 | 27 | private String baseControllerPackage = ""; 28 | private List additionalControllerPackages = new ArrayList(); 29 | 30 | /** 31 | * @deprecated no need in model packages 32 | */ 33 | private String baseModelPackage = ""; 34 | 35 | /** 36 | * @deprecated no need in model packages 37 | */ 38 | private List additionalModelPackages = new ArrayList(); 39 | private String basePath = ""; 40 | private String apiVersion = "v1"; 41 | private Map documentation; 42 | private List ignorableAnnotations = new ArrayList(); 43 | private boolean ignoreUnusedPathVariables = true; 44 | private boolean basePathFromReferer = false; 45 | private ResourceListing resourceList; 46 | private ApiInfo apiInfo; 47 | private List authorizationTypes = new ArrayList<>(); 48 | private List filters; 49 | 50 | @RequestMapping(value = "/resourceList", method = RequestMethod.GET, produces = "application/json") 51 | public 52 | @ResponseBody 53 | ResourceListing getResources(HttpServletRequest request) { 54 | return getResourceList(request); 55 | } 56 | 57 | @RequestMapping(value = "/resourceList/doc/**", method = RequestMethod.GET, produces = "application/json") 58 | public 59 | @ResponseBody 60 | ApiListing getDocumentation(HttpServletRequest request) { 61 | String handlerMappingPath = (String) request.getAttribute( 62 | HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE); 63 | //trim the operation request mapping from the desired value 64 | handlerMappingPath = handlerMappingPath 65 | .substring(handlerMappingPath.indexOf("/doc/") + 4, handlerMappingPath.length()); 66 | 67 | Map docs = getDocs(request); 68 | if (docs == null) { 69 | //TODO throw exception 70 | return null; 71 | } 72 | 73 | return docs.get(handlerMappingPath); 74 | } 75 | 76 | @SuppressWarnings("unused") 77 | public String getBasePath() { 78 | if (basePath == null || basePath.isEmpty()) { 79 | //If no base path was specified, attempt to get the base path from the request URL 80 | HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder 81 | .getRequestAttributes()).getRequest(); 82 | if (request != null) { 83 | // requested from 84 | String referer = request.getHeader("Referer"); 85 | 86 | if (basePathFromReferer && referer != null) { 87 | basePath = referer.substring(0, referer.lastIndexOf("/")); 88 | } else { 89 | String mapping = request.getServletPath(); 90 | basePath = request.getRequestURL().toString(); 91 | basePath = basePath.substring(0, basePath.indexOf(mapping)); 92 | } 93 | } 94 | } 95 | return basePath; 96 | } 97 | 98 | private Map getDocs(HttpServletRequest request) { 99 | if (documentation == null || (filters != null && !filters.isEmpty())) { 100 | String servletPath = null; 101 | if (request != null) { 102 | servletPath = request.getServletPath(); 103 | } 104 | ApiParser apiParser = new ApiParserImpl(apiInfo, authorizationTypes, getControllerPackages(), getBasePath(), 105 | servletPath, apiVersion, ignorableAnnotations, ignoreUnusedPathVariables, filters); 106 | documentation = apiParser.createApiListings(); 107 | } 108 | return documentation; 109 | } 110 | 111 | private ResourceListing getResourceList(HttpServletRequest request) { 112 | if (resourceList == null || (filters != null && !filters.isEmpty())) { 113 | String servletPath = null; 114 | if (request != null) { 115 | servletPath = request.getServletPath(); 116 | servletPath = servletPath.replace("/resourceList", ""); 117 | } 118 | ApiParser apiParser = new ApiParserImpl(apiInfo, authorizationTypes, getControllerPackages(), getBasePath(), 119 | servletPath, apiVersion, ignorableAnnotations, ignoreUnusedPathVariables, filters); 120 | resourceList = apiParser.getResourceListing(getDocs(request)); 121 | } 122 | return resourceList; 123 | } 124 | 125 | private List getControllerPackages() { 126 | List controllerPackages = new ArrayList(); 127 | if (baseControllerPackage != null && !baseControllerPackage.isEmpty()) { 128 | controllerPackages.add(baseControllerPackage); 129 | } 130 | 131 | if (additionalControllerPackages != null && !additionalControllerPackages.isEmpty()) { 132 | controllerPackages.addAll(additionalControllerPackages); 133 | } 134 | 135 | return controllerPackages; 136 | } 137 | 138 | @SuppressWarnings("unused") 139 | public void setResourceList(ResourceListing resourceList) { 140 | this.resourceList = resourceList; 141 | } 142 | 143 | @SuppressWarnings("unused") 144 | public String getBaseControllerPackage() { 145 | return baseControllerPackage; 146 | } 147 | 148 | @SuppressWarnings("unused") 149 | public void setBaseControllerPackage(String baseControllerPackage) { 150 | this.baseControllerPackage = baseControllerPackage; 151 | } 152 | 153 | @SuppressWarnings("unused") 154 | public List getAdditionalControllerPackages() { 155 | return additionalControllerPackages; 156 | } 157 | 158 | @SuppressWarnings("unused") 159 | public void setAdditionalControllerPackages(List additionalControllerPackages) { 160 | this.additionalControllerPackages = additionalControllerPackages; 161 | } 162 | 163 | @SuppressWarnings("unused") 164 | public String getBaseModelPackage() { 165 | return baseModelPackage; 166 | } 167 | 168 | @SuppressWarnings("unused") 169 | public void setBaseModelPackage(String baseModelPackage) { 170 | this.baseModelPackage = baseModelPackage; 171 | } 172 | 173 | @SuppressWarnings("unused") 174 | public List getAdditionalModelPackages() { 175 | return additionalModelPackages; 176 | } 177 | 178 | @SuppressWarnings("unused") 179 | public void setAdditionalModelPackages(List additionalModelPackages) { 180 | this.additionalModelPackages = additionalModelPackages; 181 | } 182 | 183 | @SuppressWarnings("unused") 184 | public Map getDocumentation() { 185 | return documentation; 186 | } 187 | 188 | @SuppressWarnings("unused") 189 | public void setDocumentation(Map documentation) { 190 | this.documentation = documentation; 191 | } 192 | 193 | @SuppressWarnings("unused") 194 | public List getIgnorableAnnotations() { 195 | return ignorableAnnotations; 196 | } 197 | 198 | @SuppressWarnings("unused") 199 | public void setIgnorableAnnotations(List ignorableAnnotations) { 200 | this.ignorableAnnotations = ignorableAnnotations; 201 | } 202 | 203 | @SuppressWarnings("unused") 204 | public boolean isIgnoreUnusedPathVariables() { 205 | return ignoreUnusedPathVariables; 206 | } 207 | 208 | @SuppressWarnings("unused") 209 | public void setIgnoreUnusedPathVariables(final boolean ignoreUnusedPathVariables) { 210 | this.ignoreUnusedPathVariables = ignoreUnusedPathVariables; 211 | } 212 | 213 | @SuppressWarnings("unused") 214 | public void setBasePathFromReferer(final boolean basePathFromReferer) { 215 | this.basePathFromReferer = basePathFromReferer; 216 | } 217 | 218 | @SuppressWarnings("unused") 219 | public ApiInfo getApiInfo() { 220 | return apiInfo; 221 | } 222 | 223 | @SuppressWarnings("unused") 224 | public void setApiInfo(ApiInfo apiInfo) { 225 | this.apiInfo = apiInfo; 226 | } 227 | 228 | @SuppressWarnings("unused") 229 | public List getAuthorizationTypes() { 230 | return authorizationTypes; 231 | } 232 | 233 | @SuppressWarnings("unused") 234 | public void setAuthorizationTypes(List authorizationTypes) { 235 | this.authorizationTypes = authorizationTypes; 236 | } 237 | 238 | @SuppressWarnings("unused") 239 | public void setFilters(final List filters) { 240 | this.filters = filters; 241 | } 242 | 243 | @SuppressWarnings("unused") 244 | public void setBasePath(final String basePath) { 245 | this.basePath = basePath; 246 | } 247 | 248 | @SuppressWarnings("unused") 249 | public void setApiVersion(final String apiVersion) { 250 | this.apiVersion = apiVersion; 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/main/java/com/knappsack/swagger4springweb/filter/AnnotationFilter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 AlertMe.com Ltd 3 | */ 4 | 5 | package com.knappsack.swagger4springweb.filter; 6 | 7 | import java.lang.annotation.Annotation; 8 | import java.lang.reflect.AnnotatedElement; 9 | import java.lang.reflect.Method; 10 | 11 | public abstract class AnnotationFilter implements Filter { 12 | 13 | protected final Class annotation; 14 | 15 | protected AnnotationFilter(final Class annotation) { 16 | this.annotation = annotation; 17 | } 18 | 19 | @Override 20 | public final boolean isApplicable(final Method method) { 21 | return isApplicable((AnnotatedElement) method) || isApplicable(method.getDeclaringClass()); 22 | } 23 | 24 | @Override 25 | public final boolean ignore(final Method method) { 26 | final T annotation = method.getAnnotation(this.annotation); 27 | return ignore(annotation != null ? annotation : method.getDeclaringClass().getAnnotation(this.annotation)); 28 | } 29 | 30 | public abstract boolean ignore(final T annotation); 31 | 32 | protected boolean isApplicable(final AnnotatedElement element) { 33 | return element.isAnnotationPresent(annotation); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/knappsack/swagger4springweb/filter/ApiExcludeFilter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 AlertMe.com Ltd 3 | */ 4 | 5 | package com.knappsack.swagger4springweb.filter; 6 | 7 | import com.knappsack.swagger4springweb.annotation.ApiExclude; 8 | 9 | public class ApiExcludeFilter extends AnnotationFilter { 10 | 11 | public ApiExcludeFilter() { 12 | super(ApiExclude.class); 13 | } 14 | 15 | @Override 16 | public boolean ignore(final ApiExclude annotation) { 17 | return true; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/knappsack/swagger4springweb/filter/Filter.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.filter; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | public interface Filter { 6 | 7 | boolean isApplicable(Method method); 8 | 9 | boolean ignore(Method method); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/knappsack/swagger4springweb/model/AnnotatedParameter.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.model; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Type; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class AnnotatedParameter { 9 | 10 | private String parameterName; 11 | private Class parameterClass; 12 | private Type parameterType; 13 | private List annotations = new ArrayList(); 14 | 15 | public String getParameterName() { 16 | return parameterName; 17 | } 18 | 19 | public void setParameterName(String parameterName) { 20 | this.parameterName = parameterName; 21 | } 22 | 23 | public Class getParameterClass() { 24 | return parameterClass; 25 | } 26 | 27 | public void setParameterClass(Class parameterClass) { 28 | this.parameterClass = parameterClass; 29 | } 30 | 31 | public List getAnnotations() { 32 | return annotations; 33 | } 34 | 35 | public void addAnnotation(Annotation annotation) { 36 | this.annotations.add(annotation); 37 | } 38 | 39 | public void addAnnotations(List annotations) { 40 | this.annotations.addAll(annotations); 41 | } 42 | 43 | public Type getParameterType() { 44 | return parameterType; 45 | } 46 | 47 | public void setParameterType(final Type parameterType) { 48 | this.parameterType = parameterType; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/knappsack/swagger4springweb/parser/ApiDescriptionParser.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.parser; 2 | 3 | 4 | import com.knappsack.swagger4springweb.util.AnnotationUtils; 5 | import com.wordnik.swagger.model.ApiDescription; 6 | import scala.Option; 7 | 8 | import java.lang.reflect.Method; 9 | 10 | public class ApiDescriptionParser { 11 | 12 | /** 13 | * @param method Method - Controller method to investigate 14 | * @param description String - Description of this API 15 | * @param resourcePath String - the path of this API. For Spring MVC this would be the value of the RequestMapping 16 | * @return ApiDescription 17 | */ 18 | public ApiDescription parseApiDescription(Method method, String description, String resourcePath) { 19 | String requestMappingValue = AnnotationUtils.getMethodRequestMappingValue(method); 20 | String path; 21 | if (resourcePath != null && !resourcePath.isEmpty()) { 22 | path = resourcePath + requestMappingValue; 23 | } else { 24 | path = requestMappingValue; 25 | } 26 | 27 | Option descriptionOption = Option.apply(description); 28 | 29 | return new ApiDescription(path, descriptionOption, null); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/knappsack/swagger4springweb/parser/ApiModelParser.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.parser; 2 | 3 | import com.knappsack.swagger4springweb.util.ModelUtils; 4 | import com.wordnik.swagger.model.Model; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.web.bind.annotation.ResponseBody; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import java.lang.reflect.Method; 11 | import java.lang.reflect.Type; 12 | import java.util.Map; 13 | 14 | public class ApiModelParser { 15 | 16 | private static final Logger LOGGER = LoggerFactory.getLogger(ApiModelParser.class); 17 | 18 | private final Map models; 19 | 20 | public ApiModelParser(final Map models) { 21 | this.models = models; 22 | } 23 | 24 | /** 25 | * @param method Method for which to evaluate the return type 26 | */ 27 | public void parseResponseBodyModels(Method method) { 28 | //In Spring 4, the RestController annotation specifies that the class is a Controller and the return type 29 | //is automatically assumed to be the ResponseBody, therefore no ResponseBody annotation is needed when marked 30 | //as a RestController 31 | boolean isRestController = false; 32 | try { 33 | isRestController = method.getDeclaringClass().getAnnotation(RestController.class) != null; 34 | } catch (NoClassDefFoundError e) { 35 | //Check for NoClassDefFoundError in the case that this is being used in a Spring 3 project where the RestController does not exist. 36 | LOGGER.debug("No RestController found. RestController is found in Spring 4. This is potentially an earlier version of Spring", e); 37 | } 38 | if (method.getAnnotation(ResponseBody.class) != null || isRestController) { 39 | Type type = method.getGenericReturnType(); 40 | 41 | ModelUtils.addModels(type, models); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/knappsack/swagger4springweb/parser/ApiOperationParser.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.parser; 2 | 3 | import com.knappsack.swagger4springweb.util.JavaToScalaUtil; 4 | import com.wordnik.swagger.annotations.*; 5 | import com.wordnik.swagger.converter.ModelConverters; 6 | import com.wordnik.swagger.model.*; 7 | import com.wordnik.swagger.model.Authorization; 8 | import com.wordnik.swagger.model.AuthorizationScope; 9 | import org.apache.commons.lang.ArrayUtils; 10 | import org.springframework.http.HttpMethod; 11 | import org.springframework.util.StringUtils; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RequestMethod; 14 | import scala.Option; 15 | 16 | import java.lang.reflect.Method; 17 | import java.lang.reflect.ParameterizedType; 18 | import java.lang.reflect.Type; 19 | import java.lang.reflect.WildcardType; 20 | import java.util.ArrayList; 21 | import java.util.Arrays; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | import static java.lang.String.format; 26 | 27 | public class ApiOperationParser { 28 | 29 | private final Map models; 30 | private String resourcePath; 31 | private List ignorableAnnotations; 32 | private boolean ignoreUnusedPathVariables; 33 | 34 | public ApiOperationParser(String resourcePath, List ignorableAnnotations, 35 | boolean ignoreUnusedPathVariables, Map models) { 36 | this.ignorableAnnotations = ignorableAnnotations; 37 | this.ignoreUnusedPathVariables = ignoreUnusedPathVariables; 38 | this.resourcePath = resourcePath; 39 | this.models = models; 40 | } 41 | 42 | public Operation parseDocumentationOperation(Method method) { 43 | 44 | DocumentationOperation documentationOperation = new DocumentationOperation(); 45 | documentationOperation.setNickname(method.getName());// method name 46 | 47 | Type returnType = method.getGenericReturnType(); 48 | if (returnType instanceof ParameterizedType) { 49 | final ParameterizedType parameterizedType = (ParameterizedType) returnType; 50 | 51 | if (parameterizedType.getActualTypeArguments().length == 1) { 52 | final Type type = parameterizedType.getActualTypeArguments()[0]; 53 | documentationOperation.setResponseClass(getResponseClass(type)); 54 | documentationOperation.setResponseContainer(((Class) parameterizedType.getRawType())); 55 | } else { 56 | // TODO what to do here? 57 | // not supporting generic with several values 58 | } 59 | } 60 | 61 | if (StringUtils.isEmpty(documentationOperation.getResponseClass())) { 62 | Class clazz = method.getReturnType(); 63 | if (clazz.isArray()) { 64 | documentationOperation.setResponseClass(clazz.getComponentType()); 65 | } else { 66 | documentationOperation.setResponseClass(clazz); 67 | } 68 | } 69 | 70 | String httpMethod = ""; 71 | RequestMapping methodRequestMapping = method.getAnnotation(RequestMapping.class); 72 | for (RequestMethod requestMethod : methodRequestMapping.method()) { 73 | httpMethod += requestMethod.name() + " "; 74 | } 75 | httpMethod = httpMethod.trim(); 76 | if(StringUtils.isEmpty(httpMethod) || " ".equals(httpMethod)) { 77 | httpMethod = HttpMethod.GET.toString(); 78 | } 79 | documentationOperation.setHttpMethod(httpMethod); 80 | documentationOperation.addConsumes(getConsumes(method, methodRequestMapping)); 81 | documentationOperation.addProduces(getProduces(method, methodRequestMapping)); 82 | 83 | // get ApiOperation information 84 | ApiOperation apiOperation = method.getAnnotation(ApiOperation.class); 85 | if (apiOperation != null) { 86 | documentationOperation.setHttpMethod(apiOperation.httpMethod()); 87 | documentationOperation.setResponseClass(apiOperation.response()); 88 | documentationOperation.setResponseContainer(apiOperation.responseContainer()); 89 | documentationOperation.addProduces(apiOperation.produces()); 90 | documentationOperation.addConsumes(apiOperation.consumes()); 91 | documentationOperation.setSummary(apiOperation.value()); 92 | documentationOperation.setNotes(apiOperation.notes()); 93 | documentationOperation.setPosition(apiOperation.position()); 94 | documentationOperation.addProtocols(apiOperation.protocols()); 95 | documentationOperation.addAuthorizations(apiOperation.authorizations()); 96 | } 97 | 98 | ApiResponse apiResponse = method.getAnnotation(ApiResponse.class); 99 | if (apiResponse != null) { 100 | addResponse(documentationOperation, apiResponse); 101 | } 102 | 103 | ApiResponses apiResponses = method.getAnnotation(ApiResponses.class); 104 | if (apiResponses != null) { 105 | ApiResponse[] responses = apiResponses.value(); 106 | for (ApiResponse response : responses) { 107 | addResponse(documentationOperation, response); 108 | } 109 | } 110 | 111 | ApiParameterParser apiParameterParser = new ApiParameterParser(ignorableAnnotations, models); 112 | List documentationParameters = apiParameterParser.parseApiParametersAndArgumentModels(method); 113 | documentationOperation.setParameters(documentationParameters); 114 | addUnusedPathVariables(documentationOperation, methodRequestMapping.value()); 115 | 116 | return documentationOperation.toScalaOperation(); 117 | } 118 | 119 | private void addResponse(DocumentationOperation documentationOperation, ApiResponse apiResponse) { 120 | Option responseOption = Option.apply(apiResponse.response().getName()); 121 | ResponseMessage responseMessage = new ResponseMessage(apiResponse.code(), apiResponse.message(), 122 | responseOption); 123 | documentationOperation.addResponseMessage(responseMessage); 124 | } 125 | 126 | private void addUnusedPathVariables(DocumentationOperation documentationOperation, String[] methodPath) { 127 | if (ignoreUnusedPathVariables) { 128 | return; 129 | } 130 | 131 | for (Parameter documentationParameter : new ApiPathParser().getPathParameters(resourcePath, methodPath)) { 132 | if (!isParameterPresented(documentationOperation, documentationParameter.name())) { 133 | documentationOperation.addParameter(documentationParameter); 134 | } 135 | } 136 | } 137 | 138 | private boolean isParameterPresented(DocumentationOperation documentationOperation, String parameter) { 139 | if (documentationOperation.getParameters().isEmpty()) { 140 | return false; 141 | } 142 | for (Parameter documentationParameter : documentationOperation.getParameters()) { 143 | if (parameter.equals(documentationParameter.name())) { 144 | return true; 145 | } 146 | } 147 | return false; 148 | } 149 | 150 | private List getConsumes(Method method, RequestMapping methodRequestMapping) { 151 | List consumes = Arrays.asList(methodRequestMapping.consumes()); 152 | if(consumes.isEmpty()) { 153 | RequestMapping controllerRequestMapping = method.getDeclaringClass().getAnnotation(RequestMapping.class); 154 | if(controllerRequestMapping != null) { 155 | consumes = Arrays.asList(controllerRequestMapping.consumes()); 156 | } 157 | } 158 | 159 | return consumes; 160 | } 161 | 162 | private List getProduces(Method method, RequestMapping methodRequestMapping) { 163 | List produces = Arrays.asList(methodRequestMapping.produces()); 164 | if(produces.isEmpty()) { 165 | RequestMapping controllerRequestMapping = method.getDeclaringClass().getAnnotation(RequestMapping.class); 166 | if(controllerRequestMapping != null) { 167 | produces = Arrays.asList(controllerRequestMapping.produces()); 168 | } 169 | } 170 | 171 | return produces; 172 | } 173 | 174 | private Class getResponseClass(Type type) { 175 | Class responseClass = null; 176 | if (type instanceof ParameterizedType) { 177 | responseClass = (Class) ((ParameterizedType) type).getRawType(); 178 | } else if(type instanceof WildcardType) { 179 | Type[] lowerBounds = ((WildcardType) type).getLowerBounds(); 180 | Type[] upperBounds = ((WildcardType) type).getUpperBounds(); 181 | if(lowerBounds.length > 0) { 182 | responseClass = (Class) lowerBounds[0]; 183 | } else if(upperBounds.length > 0) { 184 | responseClass = (Class) upperBounds[0]; 185 | } 186 | 187 | } else { 188 | responseClass = (Class) type; 189 | } 190 | 191 | return responseClass; 192 | } 193 | 194 | //This class is used as a temporary solution to create a Swagger Operation object, since the Operation is immutable 195 | class DocumentationOperation { 196 | 197 | private String nickname; 198 | private String responseClass; 199 | private String summary; 200 | private String notes; 201 | private String httpMethod; 202 | private List parameters = new ArrayList(); 203 | private List responseMessages = new ArrayList(); 204 | private int position; 205 | private List produces = new ArrayList(); 206 | private List consumes = new ArrayList(); 207 | private List protocols = new ArrayList(); 208 | private List authorizations = new ArrayList(); 209 | 210 | Operation toScalaOperation() { 211 | return new Operation(httpMethod, 212 | summary, 213 | notes, 214 | responseClass, 215 | nickname, 216 | position, 217 | JavaToScalaUtil.toScalaList(produces), 218 | JavaToScalaUtil.toScalaList(consumes), 219 | JavaToScalaUtil.toScalaList(protocols), 220 | JavaToScalaUtil.toScalaList(authorizations), 221 | JavaToScalaUtil.toScalaList(parameters), 222 | JavaToScalaUtil.toScalaList(responseMessages), 223 | null); 224 | } 225 | 226 | void setNickname(String nickname) { 227 | this.nickname = nickname; 228 | } 229 | 230 | void setResponseClass(Class responseClass) { 231 | if (responseClass == null || responseClass == Void.class) { 232 | return; 233 | } 234 | 235 | Option model = ModelConverters.read(responseClass, ModelConverters.typeMap()); 236 | if (model.nonEmpty()) { 237 | this.responseClass = model.get().name(); 238 | } else { 239 | this.responseClass = responseClass.getSimpleName(); 240 | } 241 | } 242 | 243 | void setSummary(String summary) { 244 | this.summary = summary; 245 | } 246 | 247 | void setNotes(String notes) { 248 | this.notes = notes; 249 | } 250 | 251 | void setHttpMethod(String httpMethod) { 252 | if (StringUtils.isEmpty(httpMethod)) { 253 | return; 254 | } 255 | this.httpMethod = httpMethod; 256 | } 257 | 258 | void setParameters(List parameters) { 259 | this.parameters = parameters; 260 | } 261 | 262 | void setPosition(int position) { 263 | this.position = position; 264 | } 265 | 266 | void addConsumes(final List consumes) { 267 | this.consumes.addAll(consumes); 268 | } 269 | 270 | void addProduces(final List produces) { 271 | this.produces.addAll(produces); 272 | } 273 | 274 | public void addResponseMessage(final ResponseMessage responseMessage) { 275 | this.responseMessages.add(responseMessage); 276 | } 277 | 278 | public List getParameters() { 279 | return parameters; 280 | } 281 | 282 | public void addParameter(final Parameter parameter) { 283 | this.parameters.add(parameter); 284 | } 285 | 286 | public void addAuthorizations(final com.wordnik.swagger.annotations.Authorization[] authorizations) { 287 | if (ArrayUtils.isEmpty(authorizations)) { 288 | return; 289 | } 290 | 291 | for (com.wordnik.swagger.annotations.Authorization authorization : authorizations) { 292 | AuthorizationScope[] authorizationScopes = new AuthorizationScope[authorization.scopes().length]; 293 | for(int i = 0;i < authorization.scopes().length;i++) { 294 | com.wordnik.swagger.annotations.AuthorizationScope authScope = authorization.scopes()[i]; 295 | authorizationScopes[i] = new AuthorizationScope(authScope.scope(), authScope.description()); 296 | } 297 | this.authorizations.add(new Authorization(authorization.value(), authorizationScopes)); 298 | } 299 | } 300 | 301 | void addProtocols(final String protocols) { 302 | if (StringUtils.isEmpty(protocols)) { 303 | return; 304 | } 305 | this.protocols.add(protocols); 306 | } 307 | 308 | public void addProduces(final String produces) { 309 | if (StringUtils.isEmpty(produces)) { 310 | return; 311 | } 312 | this.produces.add(produces); 313 | } 314 | 315 | public void addConsumes(final String consumes) { 316 | if (StringUtils.isEmpty(consumes)) { 317 | return; 318 | } 319 | this.consumes.add(consumes); 320 | } 321 | 322 | public void setResponseContainer(final String container) { 323 | if (StringUtils.isEmpty(container)) { 324 | return; 325 | } 326 | this.responseClass = format("%s[%s]", container, responseClass); 327 | } 328 | 329 | public void setResponseContainer(final Class type) { 330 | Option model = ModelConverters.read(type, ModelConverters.typeMap()); 331 | if (model.nonEmpty()) { 332 | setResponseContainer(model.get().name()); 333 | } else { 334 | setResponseContainer(type.getSimpleName()); 335 | } 336 | } 337 | 338 | public String getResponseClass() { 339 | return responseClass; 340 | } 341 | 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /src/main/java/com/knappsack/swagger4springweb/parser/ApiParameterParser.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.parser; 2 | 3 | import com.knappsack.swagger4springweb.model.AnnotatedParameter; 4 | import com.knappsack.swagger4springweb.util.AnnotationUtils; 5 | import com.knappsack.swagger4springweb.util.ModelUtils; 6 | import com.knappsack.swagger4springweb.util.JavaToScalaUtil; 7 | import com.wordnik.swagger.annotations.ApiParam; 8 | import com.wordnik.swagger.core.ApiValues; 9 | import com.wordnik.swagger.model.AllowableListValues; 10 | import com.wordnik.swagger.model.AllowableValues; 11 | import com.wordnik.swagger.model.Model; 12 | import com.wordnik.swagger.model.Parameter; 13 | import org.springframework.web.bind.annotation.*; 14 | import scala.Option; 15 | 16 | import java.lang.annotation.Annotation; 17 | import java.lang.reflect.Method; 18 | import java.util.ArrayList; 19 | import java.util.Arrays; 20 | import java.util.List; 21 | import java.util.Map; 22 | 23 | public class ApiParameterParser { 24 | 25 | private Map models; 26 | private List ignorableAnnotations; 27 | 28 | public ApiParameterParser(List ignorableAnnotations, final Map models) { 29 | this.ignorableAnnotations = ignorableAnnotations; 30 | this.models = models; 31 | } 32 | 33 | public List parseApiParametersAndArgumentModels(Method method) { 34 | List documentationParameters = new ArrayList(); 35 | 36 | List annotatedParameters = AnnotationUtils 37 | .getAnnotatedParameters(method); 38 | 39 | for (AnnotatedParameter annotatedParameter : annotatedParameters) { 40 | if (hasIgnorableAnnotations(annotatedParameter.getAnnotations())) { 41 | continue; 42 | } 43 | 44 | // Adding processed model 45 | ModelUtils.addModels(annotatedParameter.getParameterType(), models); 46 | 47 | DocumentationParameter documentationParameter = new DocumentationParameter(); 48 | 49 | // default values from the Method 50 | String dataType = ModelUtils.getSwaggerTypeFor(annotatedParameter.getParameterClass()); 51 | documentationParameter.setDataType(ModelUtils.getSwaggerTypeFor(annotatedParameter.getParameterClass())); 52 | documentationParameter.setName(annotatedParameter.getParameterName()); 53 | boolean allowMultiple = ModelUtils.isAllowMultiple(annotatedParameter.getParameterClass()); 54 | documentationParameter.setAllowMultiple(allowMultiple); 55 | // apply default values from spring annotations first 56 | for (Annotation annotation : annotatedParameter.getAnnotations()) { 57 | addSpringParams(annotation, documentationParameter); 58 | } 59 | // apply swagger annotations 60 | for (Annotation annotation : annotatedParameter.getAnnotations()) { 61 | if (annotation instanceof ApiParam) { 62 | addApiParams((ApiParam) annotation, documentationParameter); 63 | } 64 | } 65 | 66 | Option descriptionOption = Option.apply(documentationParameter.getDescription()); 67 | Option defaultValueOption = Option.apply(documentationParameter.getDefaultValue()); 68 | Option paramAccessOption = Option.apply(documentationParameter.getParamAccess()); 69 | 70 | Parameter parameter = 71 | new Parameter(documentationParameter.getName(), descriptionOption, defaultValueOption, 72 | documentationParameter.isRequired(), documentationParameter.isAllowMultiple(), dataType, 73 | documentationParameter.getAllowableValues(), 74 | documentationParameter.getParamType(), paramAccessOption); 75 | 76 | documentationParameters.add(parameter); 77 | } 78 | 79 | return documentationParameters; 80 | } 81 | 82 | private void addSpringParams(Annotation annotation, DocumentationParameter documentationParameter) { 83 | if (annotation instanceof RequestParam) { 84 | addRequestParams((RequestParam) annotation, documentationParameter); 85 | } 86 | if (annotation instanceof RequestHeader) { 87 | addRequestHeader((RequestHeader) annotation, documentationParameter); 88 | } 89 | if (annotation instanceof RequestBody) { 90 | addRequestBody(documentationParameter); 91 | } 92 | if (annotation instanceof PathVariable) { 93 | addPathVariable((PathVariable) annotation, documentationParameter); 94 | } 95 | if (annotation instanceof ModelAttribute) { 96 | addModelAttribute((ModelAttribute) annotation, documentationParameter); 97 | } 98 | } 99 | 100 | private void addModelAttribute(final ModelAttribute modelAttribute, 101 | final DocumentationParameter documentationParameter) { 102 | if (ModelUtils.isSet(modelAttribute.value())) { 103 | documentationParameter.setName(modelAttribute.value()); 104 | } 105 | documentationParameter.setParamType(ApiValues.TYPE_FORM()); 106 | } 107 | 108 | private void addRequestBody(DocumentationParameter documentationParameter) { 109 | documentationParameter.setRequired(true); 110 | documentationParameter.setParamType(ApiValues.TYPE_BODY()); 111 | } 112 | 113 | private void addPathVariable(PathVariable pathVariable, DocumentationParameter documentationParameter) { 114 | if (ModelUtils.isSet(pathVariable.value())) { 115 | documentationParameter.setName(pathVariable.value()); 116 | } 117 | 118 | documentationParameter.setRequired(true); 119 | documentationParameter.setParamType(ApiValues.TYPE_PATH()); 120 | } 121 | 122 | private void addRequestParams(RequestParam requestParam, 123 | DocumentationParameter documentationParameter) { 124 | if (ModelUtils.isSet(requestParam.value())) { 125 | documentationParameter.setName(requestParam.value()); 126 | } 127 | if (ModelUtils.isSet(requestParam.defaultValue())) { 128 | documentationParameter.setDefaultValue(requestParam.defaultValue()); 129 | } 130 | documentationParameter.setRequired(requestParam.required()); 131 | if ("file".equals(documentationParameter.getDataType())) { 132 | documentationParameter.setParamType(ApiValues.TYPE_FORM()); 133 | } else { 134 | documentationParameter.setParamType(ApiValues.TYPE_QUERY()); 135 | } 136 | } 137 | 138 | private void addRequestHeader(RequestHeader requestHeader, 139 | DocumentationParameter documentationParameter) { 140 | if (ModelUtils.isSet(requestHeader.value())) { 141 | documentationParameter.setName(requestHeader.value()); 142 | } 143 | if (ModelUtils.isSet(requestHeader.defaultValue())) { 144 | documentationParameter.setDefaultValue(requestHeader.defaultValue()); 145 | } 146 | documentationParameter.setRequired(requestHeader.required()); 147 | documentationParameter.setParamType(ApiValues.TYPE_HEADER()); 148 | } 149 | 150 | private void addApiParams(ApiParam apiParam, DocumentationParameter documentationParameter) { 151 | if (ModelUtils.isSet(apiParam.allowableValues())) { 152 | // we use only one simple string 153 | List allowableValues = Arrays.asList(apiParam.allowableValues().split("\\s*,\\s*")); 154 | documentationParameter 155 | .setAllowableValues(new AllowableListValues(JavaToScalaUtil.toScalaList(allowableValues), "LIST")); 156 | } 157 | documentationParameter.setAllowMultiple(apiParam.allowMultiple()); 158 | 159 | if (ModelUtils.isSet(apiParam.defaultValue())) { 160 | documentationParameter.setDefaultValue(apiParam.defaultValue()); 161 | } 162 | documentationParameter.setDescription(apiParam.value()); 163 | // overwrite default name 164 | if (ModelUtils.isSet(apiParam.name())) { 165 | documentationParameter.setName(apiParam.name()); 166 | } 167 | // documentationParameter.setNotes(apiParam.); 168 | documentationParameter.setParamAccess(apiParam.access()); 169 | // required is default true in the annotation 170 | // so if its false, der RequestParam has set it 171 | if (!documentationParameter.isRequired()) { 172 | documentationParameter.setRequired(apiParam.required()); 173 | } 174 | } 175 | 176 | private boolean hasIgnorableAnnotations(List annotations) { 177 | for (Annotation annotation : annotations) { 178 | if (ignorableAnnotations.contains(annotation.annotationType().getCanonicalName())) { 179 | return true; 180 | } 181 | } 182 | return false; 183 | } 184 | 185 | class DocumentationParameter { 186 | 187 | private String name; 188 | private String description; 189 | private String defaultValue; 190 | private boolean required; 191 | private boolean allowMultiple; 192 | private String dataType; 193 | private String paramType; 194 | private String paramAccess; 195 | AllowableValues allowableValues; 196 | 197 | String getName() { 198 | return name; 199 | } 200 | 201 | void setName(String name) { 202 | this.name = name; 203 | } 204 | 205 | String getDescription() { 206 | return description; 207 | } 208 | 209 | void setDescription(String description) { 210 | this.description = description; 211 | } 212 | 213 | String getDefaultValue() { 214 | return defaultValue; 215 | } 216 | 217 | void setDefaultValue(String defaultValue) { 218 | this.defaultValue = defaultValue; 219 | } 220 | 221 | boolean isRequired() { 222 | return required; 223 | } 224 | 225 | void setRequired(boolean required) { 226 | this.required = required; 227 | } 228 | 229 | boolean isAllowMultiple() { 230 | return allowMultiple; 231 | } 232 | 233 | void setAllowMultiple(boolean allowMultiple) { 234 | this.allowMultiple = allowMultiple; 235 | } 236 | 237 | String getDataType() { 238 | return dataType; 239 | } 240 | 241 | void setDataType(String dataType) { 242 | this.dataType = dataType; 243 | } 244 | 245 | AllowableValues getAllowableValues() { 246 | return allowableValues; 247 | } 248 | 249 | void setAllowableValues(AllowableValues allowableValues) { 250 | this.allowableValues = allowableValues; 251 | } 252 | 253 | String getParamType() { 254 | return paramType; 255 | } 256 | 257 | void setParamType(String paramType) { 258 | this.paramType = paramType; 259 | } 260 | 261 | String getParamAccess() { 262 | return paramAccess; 263 | } 264 | 265 | void setParamAccess(String paramAccess) { 266 | this.paramAccess = paramAccess; 267 | } 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /src/main/java/com/knappsack/swagger4springweb/parser/ApiParser.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.parser; 2 | 3 | import com.wordnik.swagger.model.ApiListing; 4 | import com.wordnik.swagger.model.ResourceListing; 5 | 6 | import java.util.Map; 7 | 8 | public interface ApiParser { 9 | /** 10 | * @param documentationMap Map - A map of different API declarations for which you want to 11 | * create a resource listing. 12 | * @return ResourceListing - This returns a resource listing which is an inventory of all APIs 13 | */ 14 | ResourceListing getResourceListing(Map documentationMap); 15 | 16 | /** 17 | * @return Map - a map of different API declarations discovered when scanning for classes 18 | * annotated with @Controller. The key value is the resource path of the API. 19 | */ 20 | Map createApiListings(); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.parser; 2 | 3 | import com.knappsack.swagger4springweb.controller.ApiDocumentationController; 4 | import com.knappsack.swagger4springweb.filter.ApiExcludeFilter; 5 | import com.knappsack.swagger4springweb.filter.Filter; 6 | import com.knappsack.swagger4springweb.util.AnnotationUtils; 7 | import com.knappsack.swagger4springweb.util.ApiListingUtil; 8 | import com.knappsack.swagger4springweb.util.JavaToScalaUtil; 9 | import com.knappsack.swagger4springweb.util.ScalaToJavaUtil; 10 | import com.wordnik.swagger.annotations.Api; 11 | import com.wordnik.swagger.config.SwaggerConfig; 12 | import com.wordnik.swagger.model.*; 13 | import org.reflections.Reflections; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | import org.springframework.stereotype.Controller; 17 | import org.springframework.web.bind.annotation.RequestMapping; 18 | import org.springframework.web.bind.annotation.RestController; 19 | import scala.Option; 20 | 21 | import java.lang.reflect.Method; 22 | import java.util.*; 23 | 24 | public class ApiParserImpl implements ApiParser { 25 | 26 | private static final Logger LOGGER = LoggerFactory.getLogger(ApiParserImpl.class); 27 | 28 | private static final String swaggerVersion = com.wordnik.swagger.core.SwaggerSpec.version(); 29 | 30 | private final Map apiListingMap = new HashMap(); 31 | 32 | private final List controllerPackages; 33 | private final List ignorableAnnotations; 34 | private final List filters; 35 | 36 | private String basePath = ""; 37 | private String apiVersion = "v1"; 38 | private boolean ignoreUnusedPathVariables; 39 | private SwaggerConfig swaggerConfig; 40 | 41 | public ApiParserImpl(ApiInfo apiInfo, List authorizationTypes, List baseControllerPackage, String basePath, String servletPath, 42 | String apiVersion, List ignorableAnnotations, boolean ignoreUnusedPathVariables, 43 | List filters) { 44 | 45 | this.controllerPackages = baseControllerPackage; 46 | this.ignorableAnnotations = ignorableAnnotations; 47 | this.ignoreUnusedPathVariables = ignoreUnusedPathVariables; 48 | this.basePath = basePath; 49 | this.apiVersion = apiVersion; 50 | 51 | swaggerConfig = new SwaggerConfig(); 52 | if (apiInfo != null) { 53 | swaggerConfig.setApiInfo(apiInfo); 54 | } 55 | swaggerConfig.setApiPath(servletPath); 56 | swaggerConfig.setApiVersion(apiVersion); 57 | swaggerConfig.setBasePath(basePath); 58 | swaggerConfig.setSwaggerVersion(swaggerVersion); 59 | swaggerConfig.setAuthorizations(JavaToScalaUtil.toScalaList(authorizationTypes)); 60 | 61 | this.filters = new ArrayList(); 62 | this.filters.add(new ApiExcludeFilter()); // @ApiExclude filter 63 | if (filters != null) { 64 | this.filters.addAll(filters); 65 | } 66 | } 67 | 68 | public ResourceListing getResourceListing(Map apiListingMap) { 69 | List apiListingReferences = new ArrayList(); 70 | for (String key : apiListingMap.keySet()) { 71 | ApiListing apiListing = apiListingMap.get(key); 72 | String docPath = "/doc"; //servletPath + "/doc"; //"/api/doc"; 73 | ApiListingReference apiListingReference = new ApiListingReference(docPath + key, apiListing.description(), 74 | apiListing.position()); 75 | 76 | apiListingReferences.add(apiListingReference); 77 | } 78 | 79 | Collections.sort(apiListingReferences, new Comparator() { 80 | @Override 81 | public int compare(ApiListingReference o1, ApiListingReference o2) { 82 | if (o1.position() == o2.position()) 83 | return 0; 84 | else if(o1.position() == 0) 85 | return 1; 86 | else if(o2.position() == 0) 87 | return -1; 88 | else if (o1.position() < o2.position()) 89 | return -1; 90 | else if (o1.position() > o2.position()) 91 | return 1; 92 | return 0; 93 | } 94 | }); 95 | 96 | return new ResourceListing(apiVersion, swaggerVersion, JavaToScalaUtil.toScalaList(apiListingReferences), null, 97 | swaggerConfig.info()); 98 | } 99 | 100 | public Map createApiListings() { 101 | Set> controllerClasses = new HashSet>(); 102 | for (String controllerPackage : controllerPackages) { 103 | Reflections reflections = new Reflections(controllerPackage); 104 | controllerClasses.addAll(reflections.getTypesAnnotatedWith(Controller.class)); 105 | try { 106 | controllerClasses.addAll(reflections.getTypesAnnotatedWith(RestController.class)); 107 | } catch (NoClassDefFoundError e) { 108 | //Check for NoClassDefFoundError in the case that this is being used in a Spring 3 project where the RestController does not exist. 109 | LOGGER.debug("No RestController found. RestController is found in Spring 4. This is potentially an earlier version of Spring", e); 110 | } 111 | } 112 | 113 | return processControllers(controllerClasses); 114 | } 115 | 116 | private Map processControllers(Set> controllerClasses) { 117 | //Loop over end points (controllers) 118 | for (Class controllerClass : controllerClasses) { 119 | if (ApiDocumentationController.class.isAssignableFrom(controllerClass)) { 120 | continue; 121 | } 122 | 123 | Set requestMappingMethods = AnnotationUtils.getAnnotatedMethods(controllerClass, RequestMapping.class); 124 | ApiListing apiListing = processControllerApi(controllerClass); 125 | String description = ""; 126 | Api controllerApi = controllerClass.getAnnotation(Api.class); 127 | if (controllerApi != null) { 128 | description = controllerApi.description(); 129 | } 130 | 131 | if (apiListing.apis().size() == 0) { 132 | apiListing = processMethods(requestMappingMethods, controllerClass, apiListing, description); 133 | } 134 | 135 | //Allow for multiple controllers having the same resource path. 136 | ApiListing existingApiListing = apiListingMap.get(apiListing.resourcePath()); 137 | if (existingApiListing != null) { 138 | apiListing = ApiListingUtil.mergeApiListing(existingApiListing, apiListing); 139 | } 140 | 141 | // controllers without any operations are excluded from the apiListingMap list 142 | if (apiListing.apis() != null && !apiListing.apis().isEmpty()) { 143 | apiListingMap.put(apiListing.resourcePath(), apiListing); 144 | } 145 | } 146 | 147 | return apiListingMap; 148 | } 149 | 150 | private ApiListing processControllerApi(Class controllerClass) { 151 | String resourcePath = ""; 152 | Api controllerApi = controllerClass.getAnnotation(Api.class); 153 | if (controllerApi != null) { 154 | resourcePath = controllerApi.basePath(); 155 | } 156 | 157 | if (controllerApi == null || resourcePath.isEmpty()) { 158 | RequestMapping controllerRequestMapping = controllerClass.getAnnotation(RequestMapping.class); 159 | if (controllerRequestMapping != null && controllerRequestMapping.value() != null && 160 | controllerRequestMapping.value().length > 0) { 161 | resourcePath = controllerRequestMapping.value()[0]; 162 | } else { 163 | resourcePath = controllerClass.getName(); 164 | } 165 | } 166 | if (!resourcePath.startsWith("/")) { 167 | resourcePath = "/" + resourcePath; 168 | } 169 | 170 | String docRoot = resourcePath; 171 | if(docRoot.contains(controllerClass.getName())) { 172 | docRoot = docRoot.replace(controllerClass.getName(), ""); 173 | } 174 | SpringApiReader reader = new SpringApiReader(); 175 | Option apiListingOption = reader.read(docRoot, controllerClass, swaggerConfig); 176 | ApiListing apiListing = null; 177 | if (apiListingOption.nonEmpty()) { 178 | apiListing = apiListingOption.get(); 179 | } 180 | 181 | if (apiListing != null) { 182 | return apiListing; 183 | } 184 | 185 | return ApiListingUtil.baseApiListing(apiVersion, swaggerVersion, basePath, resourcePath); 186 | } 187 | 188 | private ApiListing processMethods(Collection methods, Class controllerClass, ApiListing apiListing, String description) { 189 | 190 | Map endpoints = new HashMap(); 191 | Map models = new HashMap(); 192 | Map> operations = new HashMap>(); 193 | List descriptions = new ArrayList(); 194 | 195 | populateApiDescriptionMapForApiListing(apiListing, endpoints); 196 | 197 | ApiModelParser apiModelParser = new ApiModelParser(models); 198 | 199 | //This is for the case where there is no request mapping at the class level. When this occurs, the resourcePath 200 | //is the class name, which we don't want to be appended to the path of the operation. Therefore, we replace 201 | //the class name. 202 | String resourcePath = apiListing.resourcePath(); 203 | if(resourcePath.contains(controllerClass.getName())) { 204 | resourcePath = resourcePath.replace("/" + controllerClass.getName(), ""); 205 | } 206 | for (Method method : methods) { 207 | if (ignore(method)) { 208 | continue; 209 | } 210 | 211 | String value = AnnotationUtils.getMethodRequestMappingValue(method); 212 | ApiDescriptionParser documentationEndPointParser = new ApiDescriptionParser(); 213 | ApiDescription apiDescription = documentationEndPointParser 214 | .parseApiDescription(method, description, resourcePath); 215 | if (!endpoints.containsKey(value)) { 216 | endpoints.put(value, apiDescription); 217 | } 218 | 219 | List ops = operations.get(value); 220 | if (ops == null) { 221 | ops = new ArrayList(); 222 | operations.put(value, ops); 223 | } 224 | 225 | ApiOperationParser apiOperationParser = new ApiOperationParser(resourcePath, 226 | ignorableAnnotations, ignoreUnusedPathVariables, models); 227 | Operation operation = apiOperationParser.parseDocumentationOperation(method); 228 | ops.add(operation); 229 | 230 | apiModelParser.parseResponseBodyModels(method); 231 | } 232 | 233 | for (String key : endpoints.keySet()) { 234 | ApiDescription apiDescription = endpoints.get(key); 235 | ApiDescription newApiDescription = new ApiDescription(apiDescription.path(), apiDescription.description(), 236 | JavaToScalaUtil.toScalaList(operations.get(key))); 237 | descriptions.add(newApiDescription); 238 | } 239 | 240 | Option> modelOptions = Option 241 | .apply(JavaToScalaUtil.toScalaImmutableMap(models)); 242 | 243 | return new ApiListing(apiListing.apiVersion(), apiListing.swaggerVersion(), apiListing.basePath(), 244 | apiListing.resourcePath(), apiListing.produces(), apiListing.consumes(), apiListing.protocols(), 245 | apiListing.authorizations(), JavaToScalaUtil.toScalaList(descriptions), modelOptions, 246 | apiListing.description(), apiListing.position()); 247 | } 248 | 249 | private void populateApiDescriptionMapForApiListing(ApiListing apiListing, 250 | Map apiDescriptionMap) { 251 | if (apiListing.apis() != null) { 252 | 253 | List apiDescriptions = ScalaToJavaUtil.toJavaList(apiListing.apis()); 254 | for (ApiDescription apiDescription : apiDescriptions) { 255 | apiDescriptionMap.put(apiDescription.path(), apiDescription); 256 | } 257 | } 258 | } 259 | 260 | private boolean ignore(Method method) { 261 | for (Filter filter : filters) { 262 | if (filter.isApplicable(method) && filter.ignore(method)) { 263 | return true; 264 | } 265 | } 266 | return false; 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/main/java/com/knappsack/swagger4springweb/parser/ApiPathParser.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.parser; 2 | 3 | import com.knappsack.swagger4springweb.util.ModelUtils; 4 | import com.wordnik.swagger.core.ApiValues; 5 | import com.wordnik.swagger.model.Parameter; 6 | import scala.Option; 7 | 8 | import java.util.*; 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | 12 | public class ApiPathParser { 13 | 14 | private static final Pattern PATTERN = Pattern.compile("\\{([a-zA-Z]+)\\}"); 15 | private static final Class TYPE = String.class; 16 | 17 | public List getPathParameters(String resourcePath, String[] methodPaths) { 18 | Map parameters = new HashMap(); 19 | 20 | addParameters(parameters, resourcePath); 21 | 22 | if (methodPaths != null) { 23 | for (String methodPath : methodPaths) { 24 | addParameters(parameters, methodPath); 25 | } 26 | } 27 | 28 | return new ArrayList(parameters.values()); 29 | } 30 | 31 | private void addParameters(Map parameters, String path) { 32 | for (String parameter : getPathParameters(path)) { 33 | parameters.put(parameter, createParameter(parameter)); 34 | } 35 | } 36 | 37 | private Parameter createParameter(String parameter) { 38 | Option descriptionOption = Option.apply(getDescription(parameter)); 39 | 40 | return new Parameter(parameter, descriptionOption, null, true, ModelUtils.isAllowMultiple(TYPE), 41 | ModelUtils.getSwaggerTypeFor(TYPE), null, ApiValues.TYPE_PATH(), null); 42 | } 43 | 44 | private String getDescription(String parameter) { 45 | StringBuilder builder = new StringBuilder(); 46 | 47 | //Parameters with name length less then 3 will have description equal to their name 48 | if (parameter == null || parameter.length() < 3) { 49 | return parameter; 50 | } 51 | 52 | String tmp = parameter; 53 | 54 | for (int i = 0; i < tmp.length(); i++) { 55 | if (Character.isUpperCase(tmp.charAt(i))) { 56 | if (builder.length() == 0) { 57 | // First letter is capital 58 | builder.append(tmp.substring(0, 1).toUpperCase()).append(tmp.substring(1, i)); 59 | } else { 60 | builder.append(tmp.substring(0, i).toLowerCase()); 61 | } 62 | builder.append(" "); 63 | tmp = tmp.substring(i); 64 | i = 0; 65 | } 66 | } 67 | 68 | return builder.append(tmp.toLowerCase()).toString(); 69 | } 70 | 71 | private List getPathParameters(String path) { 72 | List parameters = new ArrayList(); 73 | 74 | if (path == null || path.trim().length() == 0) { 75 | return Collections.emptyList(); 76 | } 77 | 78 | Matcher matcher = PATTERN.matcher(path); 79 | 80 | while (matcher.find()) { 81 | parameters.add(matcher.group(1)); 82 | } 83 | 84 | return parameters; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/knappsack/swagger4springweb/util/AnnotationUtils.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.util; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.knappsack.swagger4springweb.model.AnnotatedParameter; 5 | import com.thoughtworks.paranamer.BytecodeReadingParanamer; 6 | import com.thoughtworks.paranamer.Paranamer; 7 | import com.wordnik.swagger.annotations.ApiParam; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | 10 | import java.lang.annotation.Annotation; 11 | import java.lang.reflect.Method; 12 | import java.lang.reflect.Parameter; 13 | import java.lang.reflect.Type; 14 | import java.util.ArrayList; 15 | import java.util.HashSet; 16 | import java.util.List; 17 | import java.util.Set; 18 | 19 | public class AnnotationUtils { 20 | 21 | /** 22 | * @param method 23 | * Method - check to see if this method has a parameter annotated 24 | * with @ApiParam 25 | * @return boolean true if this method has a parameter annotated with @ApiParam 26 | */ 27 | public static boolean hasApiParam(Method method) { 28 | Annotation[][] parameterAnnotations = method.getParameterAnnotations(); 29 | for (Annotation[] parameterAnnotation : parameterAnnotations) { 30 | for (Annotation annotation : parameterAnnotation) { 31 | if (annotation instanceof ApiParam) { 32 | return true; 33 | 34 | } 35 | } 36 | } 37 | return false; 38 | } 39 | 40 | /** 41 | * @param method 42 | * Method - get the request mapping for this method 43 | * @return String - the value of the RequestMapping annotation on this 44 | * method 45 | */ 46 | public static String getMethodRequestMappingValue(Method method) { 47 | RequestMapping requestMapping = method 48 | .getAnnotation(RequestMapping.class); 49 | String requestMappingValue = ""; 50 | if (requestMapping != null) { 51 | String[] requestMappingValues = requestMapping.value(); 52 | if (requestMappingValues != null && requestMappingValues.length > 0) { 53 | requestMappingValue = requestMappingValues[0]; 54 | } 55 | } 56 | 57 | return requestMappingValue; 58 | } 59 | 60 | /** 61 | * @param method 62 | * Method 63 | * @return List - if the method contains parameters 64 | * annotated with ApiParam, RequestMapping, PathVariable, or 65 | * RequestBody, this method will return a list of AnnotatedParameter 66 | * objects based on the values of the parameter annotations 67 | */ 68 | public static List getAnnotatedParameters(Method method) { 69 | List annotatedParameters = new ArrayList<>(); 70 | Paranamer paranamer = new BytecodeReadingParanamer(); 71 | String[] parameterNames; 72 | //Attempt to use Paranamer to look up the parameter names for those not using Java 8+. 73 | //This will fail if trying to evaluate a class using Lambdas, in which case fall back and look up using 74 | //standard java reflections. This will provide the paramter name if using the -parameters javac argument. 75 | try { 76 | parameterNames = paranamer.lookupParameterNames(method); 77 | } catch(Exception e) { 78 | Parameter[] parameters = method.getParameters(); 79 | parameterNames = new String[parameters.length]; 80 | for (int i = 0; i < parameters.length; i++) { 81 | Parameter parameter = parameters[i]; 82 | parameterNames[i] = parameter.getName(); 83 | } 84 | } 85 | Annotation[][] parameterAnnotations = method.getParameterAnnotations(); 86 | Class[] parameterTypes = method.getParameterTypes(); 87 | Type[] genericParameterTypes = method.getGenericParameterTypes(); 88 | 89 | int i = 0; 90 | for (Annotation[] annotations : parameterAnnotations) { 91 | if (annotations.length > 0) { 92 | AnnotatedParameter annotatedParameter = new AnnotatedParameter(); 93 | annotatedParameter.setParameterClass(parameterTypes[i]); 94 | annotatedParameter.setParameterName(parameterNames[i]); 95 | annotatedParameter.setParameterType(genericParameterTypes[i]); 96 | annotatedParameter.addAnnotations(Lists.newArrayList(annotations)); 97 | annotatedParameters.add(annotatedParameter); 98 | } 99 | i++; 100 | } 101 | return annotatedParameters; 102 | } 103 | 104 | /** 105 | * 106 | * @param clazz Class - scan this class for methods with the specified annotation 107 | * @param annotationClass Class - return all methods with this annotation 108 | * @return Set - all methods of this class with the specified annotation 109 | */ 110 | public static Set getAnnotatedMethods(Class clazz, Class annotationClass) { 111 | Method[] methods = clazz.getDeclaredMethods(); 112 | Set annotatedMethods = new HashSet<>(methods.length); 113 | for (Method method : methods) { 114 | if( method.isAnnotationPresent(annotationClass)){ 115 | annotatedMethods.add(method); 116 | } 117 | } 118 | return annotatedMethods; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/com/knappsack/swagger4springweb/util/ModelUtils.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.util; 2 | 3 | import com.wordnik.swagger.converter.ModelConverters; 4 | import com.wordnik.swagger.model.Model; 5 | import org.springframework.web.bind.annotation.ValueConstants; 6 | import org.springframework.web.multipart.MultipartFile; 7 | import scala.Option; 8 | 9 | import java.lang.reflect.ParameterizedType; 10 | import java.lang.reflect.Type; 11 | import java.util.Collection; 12 | import java.util.Date; 13 | import java.util.Map; 14 | 15 | public class ModelUtils { 16 | 17 | public static String getSwaggerTypeFor(Class parameterType) { 18 | Class type = parameterType; 19 | if (parameterType.isArray()) { 20 | type = type.getComponentType(); 21 | } 22 | // swagger types are 23 | // byte 24 | // boolean 25 | // int 26 | // long 27 | // float 28 | // double 29 | // string 30 | // Date 31 | if (String.class.isAssignableFrom(type)) { 32 | return "string"; 33 | } else if (Boolean.class.isAssignableFrom(type)) { 34 | return "boolean"; 35 | } else if (Byte.class.isAssignableFrom(type)) { 36 | return "byte"; 37 | } else if (Long.class.isAssignableFrom(type)) { 38 | return "int64"; 39 | } else if (Integer.class.isAssignableFrom(type)) { 40 | return "int32"; 41 | } else if (Float.class.isAssignableFrom(type)) { 42 | return "float"; 43 | } else if (MultipartFile.class.isAssignableFrom(type)) { 44 | return "file"; 45 | } else if (Number.class.isAssignableFrom(type)) { 46 | return "double"; 47 | } else if (Double.class.isAssignableFrom(type)) { 48 | return "double"; 49 | } else if (Date.class.isAssignableFrom(type)) { 50 | return "date"; 51 | } 52 | // others 53 | return type.getSimpleName(); 54 | } 55 | 56 | public static boolean isSet(String value) { 57 | return value != null && !value.trim().isEmpty() && !ValueConstants.DEFAULT_NONE.equals(value); 58 | } 59 | 60 | public static boolean isAllowMultiple(Class parameterType) { 61 | return parameterType != null && (parameterType.isArray() || Collection.class.isAssignableFrom(parameterType)); 62 | } 63 | 64 | static boolean isIgnorableModel(String name) { 65 | return name.equalsIgnoreCase("map") || name.equalsIgnoreCase("list") || name.equalsIgnoreCase("string") 66 | || name.equalsIgnoreCase("set") || name.equalsIgnoreCase("collection"); 67 | } 68 | 69 | public static void addModels(final Class clazz, final Map models) { 70 | Option modelOption = ModelConverters.read(clazz, JavaToScalaUtil.toScalaImmutableMap(new java.util.HashMap())); 71 | Model model; 72 | if(!modelOption.isEmpty()) { 73 | model = modelOption.get(); 74 | if(!isIgnorableModel(model.name())) { 75 | models.put(model.name(), model); 76 | } 77 | } 78 | } 79 | 80 | public static void addModels(final Type type, final Map models) { 81 | if (type instanceof ParameterizedType) { 82 | // Adding both part of generic type 83 | final ParameterizedType parameterizedType = (ParameterizedType) type; 84 | addModels(parameterizedType.getRawType(), models); 85 | for (Type t : parameterizedType.getActualTypeArguments()) { 86 | addModels(t, models); 87 | } 88 | } else if (type instanceof Class) { 89 | addModels((Class) type, models); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/com/knappsack/swagger4springweb/util/ScalaObjectMapper.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.util; 2 | 3 | import com.fasterxml.jackson.core.JsonParser; 4 | import com.fasterxml.jackson.databind.DeserializationContext; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer; 7 | import com.fasterxml.jackson.module.scala.DefaultScalaModule; 8 | import com.wordnik.swagger.model.AllowableListValues; 9 | import com.wordnik.swagger.model.AllowableValues; 10 | 11 | import java.io.IOException; 12 | import java.util.ArrayList; 13 | 14 | @SuppressWarnings("unused") 15 | public class ScalaObjectMapper extends ObjectMapper { 16 | 17 | public ScalaObjectMapper() { 18 | registerModule(new DefaultScalaModule()); 19 | } 20 | 21 | class AllowableValuesDeserializer extends StdScalarDeserializer { 22 | 23 | protected AllowableValuesDeserializer() { 24 | super(AllowableValues.class); 25 | } 26 | 27 | @Override 28 | public AllowableValues deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { 29 | 30 | String currentName = jp.getCurrentName(); 31 | if (currentName.equalsIgnoreCase("AllowableValues")) { 32 | if (jp.getText().isEmpty() || jp.getText().equals("{}") || jp.getText().equals("{")) { 33 | return new AllowableListValues(JavaToScalaUtil.toScalaList(new ArrayList()), ""); 34 | } 35 | } 36 | return new AllowableListValues(JavaToScalaUtil.toScalaList(new ArrayList()), ""); 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/scala/com/knappsack/swagger4springweb/parser/SpringApiReader.scala: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.parser 2 | 3 | import org.springframework.web.bind.annotation._ 4 | import org.springframework.context.ApplicationContext 5 | 6 | import com.wordnik.swagger.model._ 7 | import com.wordnik.swagger.annotations._ 8 | import com.wordnik.swagger.core.ApiValues._ 9 | 10 | import java.lang.annotation.Annotation 11 | import java.lang.reflect.Method 12 | 13 | class SpringApiReader() extends SpringMVCApiReader { 14 | 15 | override def processParamAnnotations(mutable: MutableParameter, paramAnnotations: Array[Annotation]): Option[Parameter] = { 16 | var shouldIgnore = false 17 | for (pa <- paramAnnotations) { 18 | pa match { 19 | case apiParam: ApiParam => parseApiParamAnnotation(mutable, apiParam) 20 | case wsParam: RequestParam => { 21 | mutable.name = readString(wsParam.value, mutable.name) 22 | mutable.paramType = readString(TYPE_QUERY, mutable.paramType) 23 | } 24 | case wsParam: PathVariable => { 25 | mutable.name = readString(wsParam.value, mutable.name) 26 | mutable.required = true 27 | mutable.paramType = readString(TYPE_PATH, mutable.paramType) 28 | } 29 | //not supported 30 | // case wsParam: MatrixParam => { 31 | // docParam.name = readString(wsParam.value, docParam.name) 32 | // docParam.paramType = readString(TYPE_MATRIX, docParam.paramType) 33 | // } 34 | case wsParam: RequestHeader => { 35 | mutable.name = readString(wsParam.value, mutable.name) 36 | mutable.paramType = readString(TYPE_HEADER, mutable.paramType) 37 | } 38 | case wsParam: ModelAttribute => { 39 | mutable.name = readString(wsParam.value, mutable.name) 40 | mutable.paramType = readString(TYPE_FORM, mutable.paramType) 41 | } 42 | case wsParam: CookieValue => { 43 | mutable.name = readString(wsParam.value, mutable.name) 44 | mutable.paramType = readString(TYPE_COOKIE, mutable.paramType) 45 | } 46 | case wsParam: ApplicationContext => shouldIgnore = true 47 | case _ => 48 | } 49 | } 50 | if(!shouldIgnore) { 51 | if(mutable.paramType == null) { 52 | mutable.paramType = TYPE_BODY 53 | mutable.name = TYPE_BODY 54 | } 55 | Some(mutable.asParameter) 56 | } 57 | else None 58 | } 59 | 60 | def findSubresourceType(method: Method): Class[_] = { 61 | method.getReturnType 62 | } 63 | 64 | // def parseHttpMethod(method: Method, apiOperation: ApiOperation): String = { 65 | // if (apiOperation.httpMethod() != null && apiOperation.httpMethod().trim().length() > 0) 66 | // apiOperation.httpMethod().trim() 67 | // else { 68 | // val requestMapping = method.getAnnotation(classOf[org.springframework.web.bind.annotation.RequestMapping]) 69 | // val requestMethod = requestMapping.method()(0) 70 | // if(requestMethod.equals(RequestMethod.GET)) ApiMethodType.GET 71 | // if(requestMethod.equals(RequestMethod.POST)) ApiMethodType.POST 72 | // if(requestMethod.equals(RequestMethod.PUT)) ApiMethodType.PUT 73 | // if(requestMethod.equals(RequestMethod.DELETE)) ApiMethodType.DELETE 74 | // if(requestMethod.equals(RequestMethod.HEAD)) ApiMethodType.HEAD 75 | // 76 | // null 77 | // } 78 | // } 79 | // Finds the type of the subresource this method produces, in case it's a subresource locator 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/scala/com/knappsack/swagger4springweb/parser/SpringMVCApiReader.scala: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.parser 2 | 3 | 4 | import java.lang.annotation.Annotation 5 | import java.lang.reflect.{Field, Method, Type} 6 | 7 | import com.knappsack.swagger4springweb.annotation.ApiExclude 8 | import com.wordnik.swagger.annotations._ 9 | import com.wordnik.swagger.config._ 10 | import com.wordnik.swagger.core._ 11 | import com.wordnik.swagger.core.util._ 12 | import com.wordnik.swagger.model._ 13 | import com.wordnik.swagger.reader.{ClassReader, ClassReaderUtils} 14 | import org.slf4j.LoggerFactory 15 | import org.springframework.web.bind.annotation._ 16 | 17 | import scala.collection.mutable.ListBuffer 18 | 19 | trait SpringMVCApiReader extends ClassReader with ClassReaderUtils { 20 | 21 | private val LOGGER = LoggerFactory.getLogger(classOf[SpringMVCApiReader]) 22 | val GenericTypeMapper = "([a-zA-Z\\.]*)<([a-zA-Z0-9\\.\\,\\s]*)>".r 23 | 24 | // decorates a Parameter based on annotations, returns None if param should be ignored 25 | def processParamAnnotations(mutable: MutableParameter, paramAnnotations: Array[Annotation]): Option[Parameter] 26 | 27 | // Finds the type of the subresource this method produces, in case it's a subresource locator 28 | // In case it's not a subresource locator the entity type is returned 29 | def findSubresourceType(method: Method): Class[_] 30 | 31 | def processDataType(paramType: Class[_], genericParamType: Type) = { 32 | paramType.getName match { 33 | case "[I" => "Array[int]" 34 | case "[Z" => "Array[boolean]" 35 | case "[D" => "Array[double]" 36 | case "[F" => "Array[float]" 37 | case "[J" => "Array[long]" 38 | case _ => { 39 | if(paramType.isArray) { 40 | "Array[%s]".format(paramType.getComponentType.getName) 41 | } 42 | else { 43 | genericParamType.toString match { 44 | case GenericTypeMapper(container, base) => { 45 | val qt = SwaggerTypes(base.split("\\.").last) match { 46 | case "object" => base 47 | case e: String => e 48 | } 49 | val b = ModelUtil.modelFromString(qt) match { 50 | case Some(e) => e._2.qualifiedType 51 | case None => qt 52 | } 53 | "%s[%s]".format(normalizeContainer(container), b) 54 | } 55 | case _ => paramType.getName 56 | } 57 | } 58 | } 59 | } 60 | } 61 | 62 | def normalizeContainer(str: String) = { 63 | if(str.indexOf(".List") >= 0) "List" 64 | else if(str.indexOf(".Set") >= 0) "Set" 65 | else { 66 | println("UNKNOWN TYPE: " + str) 67 | "UNKNOWN" 68 | } 69 | } 70 | 71 | def parseOperation( 72 | method: Method, 73 | apiOperation: ApiOperation, 74 | apiResponses: List[ResponseMessage], 75 | isDeprecated: String, 76 | parentParams: List[Parameter], 77 | parentMethods: ListBuffer[Method] 78 | ) = { 79 | // val api = method.getAnnotation(classOf[Api]) 80 | val responseClass = { 81 | if(apiOperation != null){ 82 | val baseName = apiOperation.response.getName 83 | val output = apiOperation.responseContainer match { 84 | case "" => baseName 85 | case e: String => "%s[%s]".format(e, baseName) 86 | } 87 | output 88 | } 89 | else { 90 | if(!"javax.ws.rs.core.Response".equals(method.getReturnType.getCanonicalName)) 91 | method.getReturnType.getName 92 | else 93 | "void" 94 | } 95 | } 96 | var paramAnnotations: Array[Array[java.lang.annotation.Annotation]] = null 97 | var paramTypes: Array[java.lang.Class[_]] = null 98 | var genericParamTypes: Array[java.lang.reflect.Type] = null 99 | 100 | if (parentMethods.isEmpty) { 101 | paramAnnotations = method.getParameterAnnotations 102 | paramTypes = method.getParameterTypes 103 | genericParamTypes = method.getGenericParameterTypes 104 | } else { 105 | paramAnnotations = parentMethods.map(pm => pm.getParameterAnnotations).reduceRight(_ ++ _) ++ method.getParameterAnnotations 106 | paramTypes = parentMethods.map(pm => pm.getParameterTypes).reduceRight(_ ++ _) ++ method.getParameterTypes 107 | genericParamTypes = parentMethods.map(pm => pm.getGenericParameterTypes).reduceRight(_ ++ _) ++ method.getGenericParameterTypes 108 | } 109 | 110 | val (nickname, produces, consumes, protocols, authorizations) = { 111 | if(apiOperation != null) { 112 | ( 113 | (if(apiOperation.nickname != null && apiOperation.nickname != "") 114 | apiOperation.nickname 115 | else 116 | method.getName 117 | ), 118 | Option(apiOperation.produces) match { 119 | case Some(e) if(e != "") => e.split(",").map(_.trim).toList 120 | case _ => method.getAnnotation(classOf[RequestMapping]).produces() match { 121 | case e: Array[String] => e.toList 122 | case _ => List() 123 | } 124 | }, 125 | Option(apiOperation.consumes) match { 126 | case Some(e) if(e != "") => e.split(",").map(_.trim).toList 127 | case _ => method.getAnnotation(classOf[RequestMapping]).consumes() match { 128 | case e: Array[String] => e.toList 129 | case _ => List() 130 | } 131 | }, 132 | Option(apiOperation.protocols) match { 133 | case Some(e) if(e != "") => e.split(",").map(_.trim).toList 134 | case _ => List() 135 | }, 136 | Option(apiOperation.authorizations) match { 137 | case Some(e) => (for(a <- e) yield { 138 | val scopes = (for(s <- a.scopes) yield com.wordnik.swagger.model.AuthorizationScope(s.scope, s.description)).toArray 139 | new com.wordnik.swagger.model.Authorization(a.value, scopes) 140 | }).toList 141 | case _ => List() 142 | }) 143 | } 144 | else(method.getName, List(), List(), List(), List()) 145 | } 146 | val params = parentParams ++ (for((annotations, paramType, genericParamType) <- (paramAnnotations, paramTypes, genericParamTypes).zipped.toList) yield { 147 | if(annotations.length > 0) { 148 | val param = new MutableParameter 149 | param.dataType = processDataType(paramType, genericParamType) 150 | processParamAnnotations(param, annotations) 151 | } 152 | else None 153 | }).flatten.toList 154 | 155 | val implicitParams = { 156 | val returnType = method.getReturnType 157 | LOGGER.debug("checking for implicits") 158 | Option(method.getAnnotation(classOf[ApiImplicitParams])) match { 159 | case Some(e) => { 160 | (for(param <- e.value) yield { 161 | LOGGER.debug("processing " + param) 162 | val allowableValues = toAllowableValues(param.allowableValues) 163 | Parameter( 164 | name = param.name, 165 | description = Option(readString(param.value)), 166 | defaultValue = Option(param.defaultValue).filter(_.trim.nonEmpty), 167 | required = param.required, 168 | allowMultiple = param.allowMultiple, 169 | dataType = param.dataType, 170 | allowableValues = allowableValues, 171 | paramType = param.paramType, 172 | paramAccess = Option(param.access).filter(_.trim.nonEmpty)) 173 | }).toList 174 | } 175 | case _ => List() 176 | } 177 | } 178 | 179 | val (summary, notes, position) = { 180 | if(apiOperation != null) (apiOperation.value, apiOperation.notes, apiOperation.position) 181 | else ("","",0) 182 | } 183 | 184 | Operation( 185 | method = parseHttpMethod(method, apiOperation), 186 | summary = summary, 187 | notes = notes, 188 | responseClass = responseClass, 189 | nickname = nickname, 190 | position = position, 191 | produces = produces, 192 | consumes = consumes, 193 | protocols = protocols, 194 | authorizations = authorizations, 195 | parameters = params ++ implicitParams, 196 | responseMessages = apiResponses, 197 | `deprecated` = Option(isDeprecated)) 198 | } 199 | 200 | def readMethod(method: Method, parentParams: List[Parameter], parentMethods: ListBuffer[Method]): Option[Operation] = { 201 | val apiOperation = method.getAnnotation(classOf[ApiOperation]) 202 | val responseAnnotation = method.getAnnotation(classOf[ApiResponses]) 203 | val apiResponses = { 204 | if(responseAnnotation == null) List() 205 | else (for(response <- responseAnnotation.value) yield { 206 | val apiResponseClass = { 207 | if(response.response != classOf[Void]) 208 | Some(response.response.getName) 209 | else None 210 | } 211 | ResponseMessage(response.code, response.message, apiResponseClass)} 212 | ).toList 213 | } 214 | val isDeprecated = Option(method.getAnnotation(classOf[Deprecated])).map(m => "true").getOrElse(null) 215 | 216 | //Don't process methods that are marked with ApiExclude or are synthetic (in the case of Java lambda expressions) 217 | val hidden = if(method.getAnnotation(classOf[ApiExclude]) != null || method.isSynthetic) true 218 | else if(apiOperation != null) apiOperation.hidden 219 | else false 220 | 221 | if(!hidden) Some(parseOperation(method, apiOperation, apiResponses, isDeprecated, parentParams, parentMethods)) 222 | else None 223 | } 224 | 225 | def appendOperation(endpoint: String, path: String, op: Operation, operations: ListBuffer[Tuple3[String, String, ListBuffer[Operation]]]) = { 226 | operations.filter(op => op._1 == endpoint) match { 227 | case e: ListBuffer[Tuple3[String, String, ListBuffer[Operation]]] if(e.size > 0) => e.head._3 += op 228 | case _ => operations += Tuple3(endpoint, path, new ListBuffer[Operation]() ++= List(op)) 229 | } 230 | } 231 | 232 | def read(docRoot: String, cls: Class[_], config: SwaggerConfig): Option[ApiListing] = { 233 | readRecursive(docRoot, "", cls, config, new ListBuffer[Tuple3[String, String, ListBuffer[Operation]]], new ListBuffer[Method]) 234 | } 235 | 236 | var ignoredRoutes: Set[String] = Set() 237 | 238 | def ignoreRoutes = ignoredRoutes 239 | 240 | def readRecursive( 241 | docRoot: String, 242 | parentPath: String, cls: Class[_], 243 | config: SwaggerConfig, 244 | operations: ListBuffer[Tuple3[String, String, ListBuffer[Operation]]], 245 | parentMethods: ListBuffer[Method]): Option[ApiListing] = { 246 | val api = cls.getAnnotation(classOf[Api]) 247 | if(api == null || cls.getAnnotation(classOf[ApiExclude]) != null) return None 248 | 249 | val pathAnnotation = cls.getAnnotation(classOf[RequestMapping]) 250 | 251 | val r = Option(api) match { 252 | case Some(api) => api.value 253 | case None => Option(pathAnnotation) match { 254 | case Some(p) => p.value()(0) 255 | case None => null 256 | } 257 | } 258 | if(r != null && !ignoreRoutes.contains(r)) { 259 | // var resourcePath = addLeadingSlash(r) 260 | val position = Option(api) match { 261 | case Some(api) => api.position 262 | case None => 0 263 | } 264 | val (consumes, produces, protocols, description) = { 265 | if(api != null){ 266 | (Option(api.consumes) match { 267 | case Some(e) if(e != "") => e.split(",").map(_.trim).toList 268 | case _ => cls.getAnnotation(classOf[RequestMapping]).consumes() match { 269 | case e: Array[String] => e.toList 270 | case _ => List() 271 | } 272 | }, 273 | Option(api.produces) match { 274 | case Some(e) if(e != "") => e.split(",").map(_.trim).toList 275 | case _ => cls.getAnnotation(classOf[RequestMapping]).produces() match { 276 | case e: Array[String] => e.toList 277 | case _ => List() 278 | } 279 | }, 280 | Option(api.protocols) match { 281 | case Some(e) if(e != "") => e.split(",").map(_.trim).toList 282 | case _ => List() 283 | }, 284 | api.description match { 285 | case e: String if(e != "") => Some(e) 286 | case _ => None 287 | } 288 | )} 289 | else ((List(), List(), List(), None)) 290 | } 291 | // look for method-level annotated properties 292 | val parentParams: List[Parameter] = (for(field <- getAllFields(cls)) 293 | yield { 294 | // only process fields with @ApiParam, @QueryParam, @HeaderParam, @PathParam 295 | if(field.getAnnotation(classOf[RequestParam]) != null || field.getAnnotation(classOf[RequestHeader]) != null || 296 | field.getAnnotation(classOf[RequestHeader]) != null || field.getAnnotation(classOf[PathVariable]) != null || 297 | field.getAnnotation(classOf[ApiParam]) != null) { 298 | val param = new MutableParameter 299 | param.dataType = field.getType.getName 300 | Option (field.getAnnotation(classOf[ApiParam])) match { 301 | case Some(annotation) => toAllowableValues(annotation.allowableValues) 302 | case _ => 303 | } 304 | val annotations = field.getAnnotations 305 | processParamAnnotations(param, annotations) 306 | } 307 | else None 308 | } 309 | ).flatten.toList 310 | 311 | for(method <- cls.getDeclaredMethods) { 312 | val returnType = findSubresourceType(method) 313 | var path = "" 314 | if(method.getAnnotation(classOf[RequestMapping]) != null) { 315 | val paths = method.getAnnotation(classOf[RequestMapping]).value() 316 | if(paths.size > 0) { 317 | path = paths(0) 318 | } 319 | } 320 | // val endpoint = (parentPath + pathFromMethod(method)).replace("//", "/") 321 | val endpoint = parentPath + api.value + pathFromMethod(method) 322 | Option(returnType.getAnnotation(classOf[Api])) match { 323 | case Some(e) => { 324 | val root = docRoot + api.value + pathFromMethod(method) 325 | parentMethods += method 326 | readRecursive(root, endpoint, returnType, config, operations, parentMethods) 327 | parentMethods -= method 328 | } 329 | case _ => { 330 | readMethod(method, parentParams, parentMethods) match { 331 | case Some(op) => appendOperation(endpoint, path, op, operations) 332 | case None => None 333 | } 334 | } 335 | } 336 | } 337 | // sort them by min position in the operations 338 | val s = (for(op <- operations) yield { 339 | (op, op._3.map(_.position).toList.min) 340 | }).sortWith(_._2 < _._2).toList 341 | val orderedOperations = new ListBuffer[Tuple3[String, String, ListBuffer[Operation]]] 342 | s.foreach(op => { 343 | val ops = op._1._3.sortWith(_.position < _.position) 344 | orderedOperations += Tuple3(op._1._1, op._1._2, ops) 345 | }) 346 | val apis = (for ((endpoint, resourcePath, operationList) <- orderedOperations) yield { 347 | val orderedOperations = new ListBuffer[Operation] 348 | operationList.sortWith(_.position < _.position).foreach(e => orderedOperations += e) 349 | ApiDescription( 350 | addLeadingSlash(endpoint), 351 | None, 352 | orderedOperations.toList) 353 | }).toList 354 | val models = ModelUtil.modelsFromApis(apis) 355 | Some(ApiListing ( 356 | apiVersion = config.apiVersion, 357 | swaggerVersion = config.swaggerVersion, 358 | basePath = config.basePath, 359 | // resourcePath = addLeadingSlash(api.value), 360 | resourcePath = addLeadingSlash(docRoot), 361 | apis = ModelUtil.stripPackages(apis), 362 | models = models, 363 | description = description, 364 | produces = produces, 365 | consumes = consumes, 366 | protocols = protocols, 367 | position = position) 368 | ) 369 | } 370 | else None 371 | } 372 | 373 | def getAllFields(cls: Class[_]): List[Field] = { 374 | var fields = cls.getDeclaredFields.toList 375 | if (cls.getSuperclass != null) { 376 | fields = getAllFields(cls.getSuperclass) ++ fields 377 | } 378 | fields 379 | } 380 | 381 | def pathFromMethod(method: Method): String = { 382 | val requestMapping = method.getAnnotation(classOf[org.springframework.web.bind.annotation.RequestMapping]) 383 | if(requestMapping != null) { 384 | val requestMappingValues = requestMapping.value 385 | if(requestMappingValues.length > 0) 386 | return requestMapping.value()(0) 387 | } 388 | "" 389 | } 390 | 391 | def parseApiParamAnnotation(param: MutableParameter, annotation: ApiParam) { 392 | param.name = readString(annotation.name, param.name) 393 | param.description = Option(readString(annotation.value)) 394 | param.defaultValue = Option(readString(annotation.defaultValue)) 395 | 396 | try { 397 | param.allowableValues = toAllowableValues(annotation.allowableValues) 398 | } catch { 399 | case e: Exception => 400 | LOGGER.error("Allowable values annotation problem in method for parameter " + param.name) 401 | } 402 | param.required = annotation.required 403 | param.allowMultiple = annotation.allowMultiple 404 | param.paramAccess = Option(readString(annotation.access)) 405 | } 406 | 407 | def readString(value: String, defaultValue: String = null, ignoreValue: String = null): String = { 408 | if (defaultValue != null && defaultValue.trim.length > 0) defaultValue 409 | else if (value == null) null 410 | else if (value.trim.length == 0) null 411 | else if (ignoreValue != null && value.equals(ignoreValue)) null 412 | else value.trim 413 | } 414 | 415 | def parseHttpMethod(method: Method, op: ApiOperation): String = { 416 | if (op != null && op.httpMethod() != null && op.httpMethod().trim().length() > 0) 417 | op.httpMethod().trim 418 | else { 419 | val requestMappingAnnotation = method.getAnnotation(classOf[RequestMapping]) 420 | val requestMapping = if(requestMappingAnnotation != null && !requestMappingAnnotation.method().isEmpty) requestMappingAnnotation.method()(0) else "GET" 421 | if(RequestMethod.GET.equals(requestMapping)) "GET" 422 | else if(RequestMethod.DELETE.equals(requestMapping)) "DELETE" 423 | else if(RequestMethod.PATCH.equals(requestMapping)) "PATCH" 424 | else if(RequestMethod.POST.equals(requestMapping)) "POST" 425 | else if(RequestMethod.PUT.equals(requestMapping)) "PUT" 426 | else if(RequestMethod.HEAD.equals(requestMapping)) "HEAD" 427 | else if(RequestMethod.OPTIONS.equals(requestMapping)) "OPTIONS" 428 | else null 429 | } 430 | } 431 | 432 | def addLeadingSlash(e: String): String = { 433 | e.startsWith("/") match { 434 | case true => e 435 | case false => "/" + e 436 | } 437 | } 438 | } 439 | 440 | 441 | class MutableParameter(param: Parameter) { 442 | var name: String = _ 443 | var description: Option[String] = None 444 | var defaultValue: Option[String] = None 445 | var required: Boolean = _ 446 | var allowMultiple: Boolean = false 447 | var dataType: String = _ 448 | var allowableValues: AllowableValues = AnyAllowableValues 449 | var paramType: String = _ 450 | var paramAccess: Option[String] = None 451 | 452 | if(param != null) { 453 | this.name = param.name 454 | this.description = param.description 455 | this.defaultValue = param.defaultValue 456 | this.required = param.required 457 | this.allowMultiple = param.allowMultiple 458 | this.dataType = param.dataType 459 | this.allowableValues = param.allowableValues 460 | this.paramType = param.paramType 461 | this.paramAccess = param.paramAccess 462 | } 463 | 464 | def this() = this(null) 465 | 466 | def asParameter() = { 467 | Parameter(name, 468 | description, 469 | defaultValue, 470 | required, 471 | allowMultiple, 472 | dataType, 473 | allowableValues, 474 | paramType, 475 | paramAccess) 476 | } 477 | } 478 | -------------------------------------------------------------------------------- /src/main/scala/com/knappsack/swagger4springweb/util/ApiListingUtil.scala: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.util 2 | 3 | import com.wordnik.swagger.model.{Authorization, ApiDescription, Model, ApiListing} 4 | 5 | object ApiListingUtil { 6 | 7 | def baseApiListing(apiVersion: String, swaggerVersion: String, basePath: String, resourcePath: String): ApiListing = { 8 | new ApiListing(apiVersion, swaggerVersion, basePath, resourcePath, 9 | List.empty[String], List.empty[String], List.empty[String], List.empty[Authorization], List.empty[ApiDescription], None, None, 0) 10 | } 11 | 12 | def mergeApiListing(apiListing1: ApiListing, apiListing2: ApiListing): ApiListing = { 13 | 14 | val mergedModels: Map[String, Model] = (apiListing1.models, apiListing2.models) match { 15 | case (Some(x), Some(y)) => apiListing1.models.get ++ apiListing2.models.get 16 | case (Some(x), None) => apiListing1.models.get 17 | case (None, Some(y)) => apiListing2.models.get 18 | case (None, None) => Map.empty[String, Model] 19 | } 20 | 21 | new ApiListing(apiListing1.apiVersion, apiListing1.swaggerVersion, apiListing1.basePath, apiListing1.resourcePath, 22 | (apiListing1.produces ++ apiListing2.produces).distinct, (apiListing1.consumes ++ apiListing2.consumes).distinct, 23 | (apiListing1.protocols ++ apiListing2.protocols).distinct, (apiListing1.authorizations ++ apiListing2.authorizations).distinct, 24 | (apiListing1.apis ++ apiListing2.apis).distinct, Option(mergedModels), apiListing1.description, apiListing1.position) 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/scala/com/knappsack/swagger4springweb/util/JavaToScalaUtil.scala: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.util 2 | 3 | import scala.collection.JavaConverters 4 | 5 | object JavaToScalaUtil { 6 | 7 | def toScalaImmutableMap[A, B](javaMap : java.util.Map[A, B]) : scala.collection.immutable.Map[A, B] = { 8 | JavaConverters.mapAsScalaMapConverter(javaMap).asScala.toMap 9 | } 10 | 11 | def toScalaList[T](javaList: java.util.List[T]): List[T] = { 12 | if (javaList != null) { 13 | return scala.collection.JavaConversions.collectionAsScalaIterable(javaList).toList 14 | } 15 | scala.collection.immutable.List.empty 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/scala/com/knappsack/swagger4springweb/util/ScalaToJavaUtil.scala: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.util 2 | 3 | import scalaj.collection.Imports._ 4 | 5 | object ScalaToJavaUtil { 6 | 7 | def toJavaList[A](immutableList : scala.collection.immutable.List[A]) : java.util.List[A] = { 8 | immutableList.asJava 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/com/knappsack/swagger4springweb/AbstractTest.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb; 2 | 3 | import com.wordnik.swagger.model.ApiInfo; 4 | import com.wordnik.swagger.model.AuthorizationType; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | public abstract class AbstractTest { 11 | 12 | public static final String BASE_CONTROLLER_PACKAGE = "com.knappsack.swagger4springweb.testController"; 13 | public static final String EXCLUDE_LABEL = "exclude"; 14 | public static final List END_POINT_PATHS = Arrays.asList("/doc/api/v1/partialExclude", "/doc/api/v1/test", "/doc/api/v1/exclude2", "/doc/com.knappsack.swagger4springweb.testController.NoClassLevelMappingController", "/doc/api/v1/empty"); 15 | public static final ApiInfo API_INFO = new ApiInfo("swagger4spring-web example app", "This is a basic web app for demonstrating swagger4spring-web", "http://localhost:8080/terms", "http://localhost:8080/contact", "MIT", "http://opensource.org/licenses/MIT"); 16 | public static final List AUTHORIZATION_TYPES = new ArrayList<>(); 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/com/knappsack/swagger4springweb/AnnotationUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb; 2 | 3 | import com.knappsack.swagger4springweb.model.AnnotatedParameter; 4 | import com.knappsack.swagger4springweb.testController.MockController; 5 | import com.knappsack.swagger4springweb.util.AnnotationUtils; 6 | import com.wordnik.swagger.annotations.ApiParam; 7 | import org.junit.Test; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | 11 | import javax.servlet.http.HttpServletRequest; 12 | import java.lang.annotation.Annotation; 13 | import java.lang.reflect.Method; 14 | import java.util.List; 15 | import java.util.Set; 16 | 17 | import static junit.framework.Assert.assertEquals; 18 | import static junit.framework.Assert.assertFalse; 19 | import static junit.framework.Assert.assertTrue; 20 | 21 | public class AnnotationUtilsTest extends AbstractTest { 22 | 23 | @Test 24 | public void testHasApiParam() { 25 | Class controllerClass = MockController.class; 26 | try { 27 | boolean hasApiParam = AnnotationUtils.hasApiParam(controllerClass.getMethod("getTestPojos", HttpServletRequest.class, String.class)); 28 | assertTrue(hasApiParam); 29 | } catch (NoSuchMethodException e) { 30 | e.printStackTrace(); 31 | } 32 | } 33 | 34 | @Test 35 | public void testGetMethodRequestMappingValue() { 36 | Class controllerClass = MockController.class; 37 | try { 38 | String requestMappingValue = AnnotationUtils.getMethodRequestMappingValue(controllerClass.getMethod("getTestPojos", HttpServletRequest.class, String.class)); 39 | assertEquals(requestMappingValue, ""); 40 | } catch (NoSuchMethodException e) { 41 | e.printStackTrace(); 42 | } 43 | } 44 | 45 | @Test 46 | public void testGetSwaggerParameterAnnotations() throws NoSuchMethodException { 47 | Class controllerClass = MockController.class; 48 | Method method = controllerClass.getMethod("getTestPojos", HttpServletRequest.class, String.class); 49 | List annotatedParameters = AnnotationUtils.getAnnotatedParameters(method); 50 | assertTrue(annotatedParameters.size() == 1); 51 | for (AnnotatedParameter annotatedParameter : annotatedParameters) { 52 | assertTrue(listContainsType(annotatedParameter.getAnnotations(), ApiParam.class)); 53 | assertTrue(annotatedParameter.getParameterName().equals("testVariable")); 54 | assertTrue(annotatedParameter.getParameterClass().isAssignableFrom(String.class)); 55 | } 56 | } 57 | 58 | @Test 59 | public void testGetSpringMvcParaterAnnotations() throws NoSuchMethodException { 60 | Class controllerClass = MockController.class; 61 | Method method = controllerClass.getMethod("getTestPojosNoSwaggerAnnotations", HttpServletRequest.class, String.class); 62 | List annotatedParameters = AnnotationUtils.getAnnotatedParameters(method); 63 | assertTrue(annotatedParameters.size() == 1); 64 | for (AnnotatedParameter annotatedParameter : annotatedParameters) { 65 | assertTrue(listContainsType(annotatedParameter.getAnnotations(), PathVariable.class)); 66 | assertTrue(annotatedParameter.getParameterName().equals("testVariable")); 67 | assertTrue(annotatedParameter.getParameterClass().isAssignableFrom(String.class)); 68 | } 69 | } 70 | 71 | private boolean listContainsType(List annotations, 72 | Class clazz) { 73 | for (Annotation annotation : annotations) { 74 | if (clazz.isAssignableFrom(annotation.getClass())) { 75 | return true; 76 | } 77 | } 78 | return false; 79 | } 80 | 81 | @Test 82 | public void testMethodHasNoApiParamAnnotation() throws NoSuchMethodException { 83 | Class controllerClass = MockController.class; 84 | Method method = controllerClass.getMethod("getTestPojosNoSwaggerAnnotations", HttpServletRequest.class, String.class); 85 | boolean hasApiParam = AnnotationUtils.hasApiParam(method); 86 | assertFalse(hasApiParam); 87 | } 88 | 89 | @Test 90 | public void testGetMethodsWithAnnotation() { 91 | Set methods = AnnotationUtils.getAnnotatedMethods(MockController.class, RequestMapping.class); 92 | assertTrue(methods.size() == 4); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/test/java/com/knappsack/swagger4springweb/ApiParserTest.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb; 2 | 3 | import com.knappsack.swagger4springweb.parser.ApiParser; 4 | import com.knappsack.swagger4springweb.parser.ApiParserImpl; 5 | import com.knappsack.swagger4springweb.util.ScalaObjectMapper; 6 | import com.knappsack.swagger4springweb.util.ScalaToJavaUtil; 7 | import com.wordnik.swagger.core.SwaggerSpec; 8 | import com.wordnik.swagger.core.util.JsonSerializer; 9 | import com.wordnik.swagger.model.ApiDescription; 10 | import com.wordnik.swagger.model.ApiListing; 11 | import com.wordnik.swagger.model.ApiListingReference; 12 | import com.wordnik.swagger.model.ResourceListing; 13 | import org.junit.Test; 14 | 15 | import java.io.IOException; 16 | import java.util.ArrayList; 17 | import java.util.Arrays; 18 | import java.util.List; 19 | import java.util.Map; 20 | 21 | import static org.junit.Assert.*; 22 | 23 | public class ApiParserTest extends AbstractTest { 24 | 25 | @Test 26 | public void testParseControllerDocumentation() { 27 | Map documentList = createApiParser().createApiListings(); 28 | for (String key : documentList.keySet()) { 29 | ApiListing documentation = documentList.get(key); 30 | ScalaObjectMapper mapper = new ScalaObjectMapper(); 31 | try { 32 | String documentationAsJSON = mapper.writeValueAsString(documentation); 33 | System.out.println(documentationAsJSON); 34 | ApiListing documentationDeserialized = JsonSerializer.asApiListing(documentationAsJSON); 35 | assertNotNull(documentationDeserialized); 36 | assertTrue(documentationDeserialized.swaggerVersion().equals(SwaggerSpec.version())); 37 | assertTrue(documentationDeserialized.apiVersion().equals("v1")); 38 | assertTrue(documentationDeserialized.basePath().equals("http://localhost:8080/api")); 39 | } catch (IOException e) { 40 | e.printStackTrace(); 41 | } 42 | } 43 | } 44 | 45 | @Test 46 | public void testOperationApiExclude() { 47 | ApiParser apiParser = createApiParser(Arrays.asList(BASE_CONTROLLER_PACKAGE + ".exclude")); 48 | Map documents = apiParser.createApiListings(); 49 | 50 | assertEquals(1, documents.size()); // ExcludeClassTestController excluded completely 51 | 52 | // validate that we don't expose any excluded operations in the documents 53 | for (ApiListing documentation : documents.values()) { 54 | assertTrue(ScalaToJavaUtil.toJavaList(documentation.apis()).size() == 2); 55 | for (ApiDescription api : ScalaToJavaUtil.toJavaList(documentation.apis())) { 56 | assertTrue(ScalaToJavaUtil.toJavaList(api.operations()).size() == 1); 57 | } 58 | } 59 | } 60 | 61 | @Test 62 | public void testResourceListing() { 63 | ApiParser apiParser = createApiParser(); 64 | Map documentList = apiParser.createApiListings(); 65 | ResourceListing resourceList = apiParser.getResourceListing(documentList); 66 | 67 | // assertEquals("http://localhost:8080/api", resourceList.basePath()); 68 | // assertEquals("v1", resourceList.apiVersion()); 69 | // assertEquals(END_POINT_PATHS.size(), resourceList.getApis().size()); 70 | // 71 | // for (DocumentationEndPoint endPoint : resourceList.getApis()) { 72 | // assertTrue("did u add a new controller without updating the endPoint paths ???", END_POINT_PATHS.contains(endPoint.getPath())); 73 | // } 74 | } 75 | 76 | @Test 77 | public void testNoClassRequestMapping() { 78 | ApiParser apiParser = createApiParser(); 79 | Map documentList = apiParser.createApiListings(); 80 | ResourceListing resourceList = apiParser.getResourceListing(documentList); 81 | for (ApiListingReference api: ScalaToJavaUtil.toJavaList(resourceList.apis())) { 82 | assertNotNull("could not get api listing for path: " + api.path(), documentList.get(api.path().substring(4))); // each api should be accessible using the ApiListingReference minus /doc 83 | } 84 | } 85 | 86 | private ApiParser createApiParser() { 87 | return createApiParser(Arrays.asList(BASE_CONTROLLER_PACKAGE)); 88 | } 89 | 90 | private ApiParser createApiParser(List controllerPackages) { 91 | return new ApiParserImpl(API_INFO, AUTHORIZATION_TYPES, controllerPackages, "http://localhost:8080/api", "/api", "v1", 92 | new ArrayList(), true, null); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/test/java/com/knappsack/swagger4springweb/controller/ApiDocumentationControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.controller; 2 | 3 | import com.knappsack.swagger4springweb.AbstractTest; 4 | import com.knappsack.swagger4springweb.util.ScalaToJavaUtil; 5 | import com.wordnik.swagger.model.ApiListing; 6 | import com.wordnik.swagger.model.ApiListingReference; 7 | import com.wordnik.swagger.model.ResourceListing; 8 | import org.junit.Test; 9 | import org.springframework.mock.web.MockHttpServletRequest; 10 | import org.springframework.web.servlet.HandlerMapping; 11 | 12 | import static org.junit.Assert.*; 13 | 14 | public class ApiDocumentationControllerTest extends AbstractTest { 15 | 16 | @Test 17 | public void getDocumentation() { 18 | MockHttpServletRequest servletRequest = new MockHttpServletRequest(); 19 | servletRequest.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/api/doc/api/v1/test"); 20 | ApiDocumentationController apiDocumentationController = new ApiDocumentationController(); 21 | apiDocumentationController.setApiVersion("v1"); 22 | apiDocumentationController.setBaseControllerPackage(BASE_CONTROLLER_PACKAGE); 23 | apiDocumentationController.setBasePath("http://localhost/swagger4spring-web-example"); 24 | ApiListing documentation = apiDocumentationController.getDocumentation(servletRequest); 25 | assertNotNull(documentation); 26 | assertEquals(documentation.apiVersion(), "v1"); 27 | assertEquals(documentation.resourcePath(), "/api/v1/test"); 28 | } 29 | 30 | @Test 31 | public void getResourceList() { 32 | MockHttpServletRequest servletRequest = new MockHttpServletRequest(); 33 | servletRequest.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/api/doc/api/v1/test"); 34 | ApiDocumentationController apiDocumentationController = new ApiDocumentationController(); 35 | apiDocumentationController.setApiVersion("v1"); 36 | apiDocumentationController.setBaseControllerPackage(BASE_CONTROLLER_PACKAGE); 37 | apiDocumentationController.setBasePath("http://localhost/swagger4spring-web-example"); 38 | 39 | ResourceListing documentation = apiDocumentationController.getResources(servletRequest); 40 | 41 | assertNotNull(documentation); 42 | assertEquals("v1", documentation.apiVersion()); 43 | assertEquals(3, documentation.apis().size()); 44 | 45 | for (ApiListingReference endPoint : ScalaToJavaUtil.toJavaList(documentation.apis())) { 46 | assertTrue(END_POINT_PATHS.contains(endPoint.path())); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/com/knappsack/swagger4springweb/parser/ApiParameterParserTest.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.parser; 2 | 3 | import com.knappsack.swagger4springweb.AbstractTest; 4 | import com.knappsack.swagger4springweb.testController.MockController; 5 | import com.wordnik.swagger.model.Model; 6 | import com.wordnik.swagger.model.Parameter; 7 | import org.junit.Test; 8 | 9 | import java.lang.reflect.Method; 10 | import java.util.*; 11 | 12 | import static junit.framework.Assert.assertTrue; 13 | 14 | public class ApiParameterParserTest extends AbstractTest { 15 | 16 | @Test 17 | public void documentationParameterTest() throws NoSuchMethodException { 18 | Class controllerClass = MockController.class; 19 | Method method = controllerClass 20 | .getMethod("getTestPojoRequestParams", String.class, Boolean.class, Byte.class, Long.class, 21 | Integer.class, Float.class, Double.class, Date.class); 22 | ApiParameterParser parameterParser = new ApiParameterParser(new ArrayList(), 23 | new HashMap()); 24 | List documentationParameters = parameterParser.parseApiParametersAndArgumentModels(method); 25 | assertTrue(documentationParameters.size() == 8); 26 | Parameter documentationParameter = documentationParameters.get(0); 27 | assertTrue(documentationParameter.dataType().equals("string")); 28 | assertTrue(documentationParameter.name().equals("testVariable")); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/com/knappsack/swagger4springweb/parser/DocumentationPathParserTest.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.parser; 2 | 3 | import com.wordnik.swagger.core.ApiValues; 4 | import com.wordnik.swagger.model.Parameter; 5 | import org.junit.Test; 6 | 7 | import java.util.List; 8 | 9 | import static org.junit.Assert.assertEquals; 10 | import static org.junit.Assert.assertTrue; 11 | 12 | public class DocumentationPathParserTest { 13 | 14 | @Test 15 | public void getPathParametersReturnsCorrectParametersList() { 16 | String resourcePath = "/owner/{ownerName}/pets/{someOther}"; 17 | String[] methodPaths = { "/dogs/{dogId}", "/legacy/path/to/dogs/breed/{dogId}" }; 18 | 19 | List parameters = new ApiPathParser() 20 | .getPathParameters(resourcePath, methodPaths); 21 | 22 | assertEquals(3, parameters.size()); 23 | assertTrue(contains(parameters, "ownerName")); 24 | assertTrue(contains(parameters, "dogId")); 25 | assertTrue(contains(parameters, "someOther")); 26 | } 27 | 28 | @Test 29 | public void getPathParametersReturnsCorrectParameterFields() { 30 | String resourcePath = "/owner/{ownerName}/pets"; 31 | 32 | List parameters = new ApiPathParser().getPathParameters(resourcePath, null); 33 | 34 | assertEquals(1, parameters.size()); 35 | 36 | Parameter documentationParameter = parameters.get(0); 37 | 38 | assertEquals("ownerName", documentationParameter.name()); 39 | assertEquals(true, documentationParameter.required()); 40 | assertEquals(ApiValues.TYPE_PATH(), documentationParameter.paramType()); 41 | assertEquals("string", documentationParameter.dataType()); 42 | // assertEquals(String.class.getName(), documentationParameter.getValueTypeInternal()); 43 | assertEquals(false, documentationParameter.allowMultiple()); 44 | assertEquals("Owner name", documentationParameter.description().get()); 45 | } 46 | 47 | @Test 48 | public void getPathParametersReturnsEmptyListWhenBadParametersArePassed() { 49 | List parameters = new ApiPathParser().getPathParameters(null, null); 50 | assertEquals(parameters.size(), 0); 51 | } 52 | 53 | private boolean contains(List parameters, String parameter) { 54 | for (Parameter documentationParameter : parameters) { 55 | if (parameter.equals(documentationParameter.name())) { 56 | return true; 57 | } 58 | } 59 | return false; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/com/knappsack/swagger4springweb/testController/EmptyTestController.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.testController; 2 | 3 | import com.wordnik.swagger.annotations.Api; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | 7 | @Controller 8 | @RequestMapping("/api/v1/empty") 9 | @Api(value = "Test ApiExcludes", basePath = "/api/v1/empty", description = "An empty controller") 10 | public class EmptyTestController { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/test/java/com/knappsack/swagger4springweb/testController/MockController.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.testController; 2 | 3 | 4 | import com.knappsack.swagger4springweb.testModels.MockPojo; 5 | import com.wordnik.swagger.annotations.*; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.web.bind.annotation.*; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | import java.util.ArrayList; 11 | import java.util.Date; 12 | import java.util.List; 13 | 14 | @Controller 15 | @RequestMapping("/api/v1/test") 16 | @Api(value = "Test operations", basePath = "/api/v1/test", description = "Operations for all tests") 17 | public class MockController { 18 | 19 | @ApiOperation(value = "Find all test pojos", notes = "Get all test pojos for this test.", httpMethod = "GET", response = MockPojo.class) 20 | // @ApiError(code = 500, reason = "Process error") 21 | @RequestMapping(method = RequestMethod.GET, produces = "application/json") 22 | public 23 | @ResponseBody 24 | List getTestPojos(HttpServletRequest request, 25 | @ApiParam(name = "testVariable", value = "String") 26 | @PathVariable String testVariable) { 27 | List mockPojos = new ArrayList(); 28 | MockPojo mockPojo = new MockPojo(); 29 | mockPojo.setId(1); 30 | mockPojo.setName("Test Pojo"); 31 | mockPojo.setDescription("This is a test pojo for testing purposes."); 32 | mockPojos.add(mockPojo); 33 | 34 | return mockPojos; 35 | } 36 | 37 | @RequestMapping(value = "/testPojosNoSwaggerAnnotations", method = RequestMethod.GET, produces = "application/json") 38 | public 39 | @ResponseBody 40 | List getTestPojosNoSwaggerAnnotations(HttpServletRequest request, 41 | @PathVariable String testVariable) { 42 | List mockPojos = new ArrayList(); 43 | MockPojo mockPojo = new MockPojo(); 44 | mockPojo.setId(1); 45 | mockPojo.setName("Test Pojo"); 46 | mockPojo.setDescription("This is a test pojo for testing purposes."); 47 | mockPojos.add(mockPojo); 48 | 49 | return mockPojos; 50 | } 51 | 52 | @RequestMapping(value = "/testPojoRequestParams", method = RequestMethod.GET, produces = "application/json") 53 | public 54 | @ResponseBody 55 | List getTestPojoRequestParams(@RequestParam String testVariable, 56 | @RequestParam Boolean booleanVariable, 57 | @RequestParam Byte byteVariable, 58 | @RequestParam Long longVariable, 59 | @RequestParam Integer integerVariable, 60 | @RequestParam Float floatVariable, 61 | @RequestParam Double doubleVariable, 62 | @RequestParam Date date) { 63 | List mockPojos = new ArrayList(); 64 | 65 | if (testVariable != null && !testVariable.isEmpty()) { 66 | MockPojo mockPojo = new MockPojo(); 67 | mockPojo.setId(1); 68 | mockPojo.setName("Test Pojo"); 69 | mockPojo.setDescription("This is a test pojo for testing purposes."); 70 | mockPojos.add(mockPojo); 71 | } 72 | 73 | return mockPojos; 74 | } 75 | 76 | @RequestMapping(value = "/testPojoRequestParams", method = RequestMethod.GET, produces = "application/json") 77 | public 78 | @ResponseBody 79 | List getTestPojoRequestHeader(HttpServletRequest request, 80 | @RequestHeader(("Accept-Encoding")) String encoding) { 81 | List mockPojos = new ArrayList(); 82 | 83 | MockPojo mockPojo = new MockPojo(); 84 | mockPojo.setId(1); 85 | mockPojo.setName("Test Pojo"); 86 | mockPojo.setDescription("This is a test pojo for testing purposes."); 87 | mockPojos.add(mockPojo); 88 | 89 | return mockPojos; 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/test/java/com/knappsack/swagger4springweb/testController/NoClassLevelMappingController.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.testController; 2 | 3 | import com.knappsack.swagger4springweb.testModels.MockPojo; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RequestMethod; 7 | import org.springframework.web.bind.annotation.ResponseBody; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | @Controller 13 | public class NoClassLevelMappingController { 14 | 15 | @RequestMapping(value = "/api/v1/noClassLevelMapping", method = RequestMethod.GET, produces = "application/json") 16 | public 17 | @ResponseBody 18 | List noRoot() { 19 | return new ArrayList(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/com/knappsack/swagger4springweb/testController/exclude/ExcludeClassTestController.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.testController.exclude; 2 | 3 | import com.knappsack.swagger4springweb.annotation.ApiExclude; 4 | import com.knappsack.swagger4springweb.testModels.MockPojo; 5 | import com.wordnik.swagger.annotations.Api; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RequestMethod; 9 | import org.springframework.web.bind.annotation.ResponseBody; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | @Controller 15 | @RequestMapping("/api/v1/exclude3") 16 | @ApiExclude 17 | @Api(value = "Test ApiExcludes", basePath = "/api/v1/exclude3", description = "controller to exclude") 18 | public class ExcludeClassTestController { 19 | 20 | @RequestMapping(method = RequestMethod.GET, produces = "application/json") 21 | public 22 | @ResponseBody 23 | List apiOperation1ToExclude() { 24 | return new ArrayList(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/com/knappsack/swagger4springweb/testController/exclude/ExcludeSingleOpTestController.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.testController.exclude; 2 | 3 | import com.knappsack.swagger4springweb.AbstractTest; 4 | import com.knappsack.swagger4springweb.annotation.ApiExclude; 5 | import com.knappsack.swagger4springweb.testModels.MockPojo; 6 | import com.wordnik.swagger.annotations.Api; 7 | import com.wordnik.swagger.annotations.ApiOperation; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestMethod; 11 | import org.springframework.web.bind.annotation.ResponseBody; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | @Controller 17 | @RequestMapping("/api/v1/exclude2") 18 | @Api(value = "Test ApiExcludes", basePath = "/api/v1/exclude2", description = "The only operation to exclude") 19 | public class ExcludeSingleOpTestController { 20 | 21 | @ApiExclude 22 | @ApiOperation(value = AbstractTest.EXCLUDE_LABEL) 23 | @RequestMapping(method = RequestMethod.GET, produces = "application/json") 24 | public 25 | @ResponseBody 26 | List apiOperation1ToExclude() { 27 | return new ArrayList(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/com/knappsack/swagger4springweb/testController/exclude/PartialExcludeTestController.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.testController.exclude; 2 | 3 | import com.knappsack.swagger4springweb.AbstractTest; 4 | import com.knappsack.swagger4springweb.annotation.ApiExclude; 5 | import com.knappsack.swagger4springweb.testModels.MockPojo; 6 | import com.wordnik.swagger.annotations.Api; 7 | import com.wordnik.swagger.annotations.ApiOperation; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestMethod; 11 | import org.springframework.web.bind.annotation.ResponseBody; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | @Controller 17 | @RequestMapping("/api/v1/partialExclude") 18 | @Api(value = "Test ApiExcludes", basePath = "/api/v1/partialExclude", description = "Some operations to exclude") 19 | public class PartialExcludeTestController { 20 | 21 | @RequestMapping(method = RequestMethod.GET, produces = "application/json") 22 | public 23 | @ResponseBody 24 | List apiOperation1ToInclude() { 25 | return new ArrayList(); 26 | } 27 | 28 | @ApiOperation(value = AbstractTest.EXCLUDE_LABEL) 29 | @RequestMapping(method = RequestMethod.POST, produces = "application/json") 30 | @ApiExclude 31 | public 32 | @ResponseBody 33 | List apiOperation2ToExclude() { 34 | return new ArrayList(); 35 | } 36 | 37 | @RequestMapping(value = "/resource1", method = RequestMethod.GET, produces = "application/json") 38 | public 39 | @ResponseBody 40 | MockPojo apiOperation3ToInclude() { 41 | return new MockPojo(); 42 | } 43 | 44 | @ApiOperation(value = AbstractTest.EXCLUDE_LABEL) 45 | @RequestMapping(value = "/resource2", method = RequestMethod.GET, produces = "application/json") 46 | @ApiExclude 47 | public 48 | @ResponseBody 49 | MockPojo apiOperation4ToExclude() { 50 | return new MockPojo(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/com/knappsack/swagger4springweb/testModels/MockPojo.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.testModels; 2 | 3 | import com.wordnik.swagger.annotations.ApiModel; 4 | import com.wordnik.swagger.annotations.ApiModelProperty; 5 | 6 | import java.util.Collection; 7 | import java.util.List; 8 | 9 | import javax.xml.bind.annotation.XmlRootElement; 10 | 11 | @XmlRootElement 12 | @ApiModel(value = "MockPojo", description = "A basic pojo for testing API documentation") 13 | public class MockPojo { 14 | 15 | @ApiModelProperty(value = "id", dataType = "long") 16 | private long id; 17 | @ApiModelProperty(value = "name", dataType = "String") 18 | private String name; 19 | @ApiModelProperty(value = "description", dataType = "String") 20 | private String description; 21 | 22 | private Collection children; 23 | 24 | public long getId() { 25 | return id; 26 | } 27 | 28 | public void setId(long id) { 29 | this.id = id; 30 | } 31 | 32 | public String getName() { 33 | return name; 34 | } 35 | 36 | public void setName(String name) { 37 | this.name = name; 38 | } 39 | 40 | public String getDescription() { 41 | return description; 42 | } 43 | 44 | public void setDescription(String description) { 45 | this.description = description; 46 | } 47 | 48 | public Collection getChildren() { 49 | return children; 50 | } 51 | 52 | public void setChildren(List children) { 53 | this.children = children; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/com/knappsack/swagger4springweb/testModels/MockPojoChild.java: -------------------------------------------------------------------------------- 1 | package com.knappsack.swagger4springweb.testModels; 2 | 3 | public class MockPojoChild { 4 | public long id; 5 | public String childName; 6 | 7 | public long getId() { 8 | return id; 9 | } 10 | 11 | public void setId(long id) { 12 | this.id = id; 13 | } 14 | 15 | public String getChildName() { 16 | return childName; 17 | } 18 | 19 | public void setChildName(String childName) { 20 | this.childName = childName; 21 | } 22 | } 23 | --------------------------------------------------------------------------------