├── .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 extends Annotation> 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 extends Annotation> 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 |
--------------------------------------------------------------------------------