From 1cc99a10feabbc4f7b2d1666c41d8a7ac117b537 Mon Sep 17 00:00:00 2001 From: UEC Date: Mon, 22 Sep 2014 10:57:42 +0300 Subject: [PATCH 01/11] Test case --- .../annotation/ApiCategory.java | 18 ++++++++ .../swagger4springweb/ApiCategoryTest.java | 44 +++++++++++++++++++ .../category/BlackController.java | 24 ++++++++++ .../category/BlueController.java | 22 ++++++++++ .../category/WhiteController.java | 22 ++++++++++ 5 files changed, 130 insertions(+) create mode 100644 src/main/java/com/knappsack/swagger4springweb/annotation/ApiCategory.java create mode 100644 src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java create mode 100644 src/test/java/com/knappsack/swagger4springweb/testController/category/BlackController.java create mode 100644 src/test/java/com/knappsack/swagger4springweb/testController/category/BlueController.java create mode 100644 src/test/java/com/knappsack/swagger4springweb/testController/category/WhiteController.java diff --git a/src/main/java/com/knappsack/swagger4springweb/annotation/ApiCategory.java b/src/main/java/com/knappsack/swagger4springweb/annotation/ApiCategory.java new file mode 100644 index 0000000..ed6540c --- /dev/null +++ b/src/main/java/com/knappsack/swagger4springweb/annotation/ApiCategory.java @@ -0,0 +1,18 @@ +package com.knappsack.swagger4springweb.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation which indicates if this Api should be added to a category + * + * This annotation is applicable to the controller class + */ +@Target(value = {ElementType.METHOD, ElementType.TYPE}) +@Retention(value = RetentionPolicy.RUNTIME) +public @interface ApiCategory { + java.lang.String value(); + +} diff --git a/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java b/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java new file mode 100644 index 0000000..143c728 --- /dev/null +++ b/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java @@ -0,0 +1,44 @@ +package com.knappsack.swagger4springweb; + +import com.knappsack.swagger4springweb.parser.ApiParser; +import com.knappsack.swagger4springweb.parser.ApiParserImpl; +import com.wordnik.swagger.model.ApiListing; +import junit.framework.*; +import org.junit.*; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + + +public class ApiCategoryTest extends AbstractTest { + + @Test + public void category() { + + ApiParser apiParser = createApiParser(); + Map documents = apiParser.createApiListings(); + + assertEquals(2, documents.size()); + assertTrue(documents.containsKey("dark")); + assertTrue(documents.containsKey("light")); + + } + + + private ApiParser createApiParser() { + return createApiParser(Arrays.asList(BASE_CONTROLLER_PACKAGE + ".category")); + } + + private ApiParser createApiParser(List controllerPackages) { + return new ApiParserImpl(API_INFO, controllerPackages, "http://localhost:8080/api", "/api", "v1", + new ArrayList(), true, null); + } + +} diff --git a/src/test/java/com/knappsack/swagger4springweb/testController/category/BlackController.java b/src/test/java/com/knappsack/swagger4springweb/testController/category/BlackController.java new file mode 100644 index 0000000..a0ac7a9 --- /dev/null +++ b/src/test/java/com/knappsack/swagger4springweb/testController/category/BlackController.java @@ -0,0 +1,24 @@ +package com.knappsack.swagger4springweb.testController.category; + + +import com.knappsack.swagger4springweb.annotation.ApiCategory; +import com.knappsack.swagger4springweb.annotation.ApiExclude; +import com.wordnik.swagger.annotations.Api; +import com.wordnik.swagger.annotations.ApiOperation; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +import static org.springframework.web.bind.annotation.RequestMethod.GET; + +@Controller +@RequestMapping("/api/v1/black") +@Api(value = "Test Category, black") +@ApiCategory("dark") +public class BlackController { + + @ApiOperation("Black is earth") + @RequestMapping(value = "earth", method = GET) + public String earth() { + return "earth"; + } +} diff --git a/src/test/java/com/knappsack/swagger4springweb/testController/category/BlueController.java b/src/test/java/com/knappsack/swagger4springweb/testController/category/BlueController.java new file mode 100644 index 0000000..75e580b --- /dev/null +++ b/src/test/java/com/knappsack/swagger4springweb/testController/category/BlueController.java @@ -0,0 +1,22 @@ +package com.knappsack.swagger4springweb.testController.category; + +import com.knappsack.swagger4springweb.annotation.ApiCategory; +import com.wordnik.swagger.annotations.Api; +import com.wordnik.swagger.annotations.ApiOperation; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +import static org.springframework.web.bind.annotation.RequestMethod.GET; + +@Controller +@RequestMapping("/api/v1/blue") +@Api(value = "Test Category, blue") +@ApiCategory("dark") +public class BlueController { + + @ApiOperation("Blue is sky") + @RequestMapping(value = "/sky", method = GET) + public String sky() { + return "sky"; + } +} diff --git a/src/test/java/com/knappsack/swagger4springweb/testController/category/WhiteController.java b/src/test/java/com/knappsack/swagger4springweb/testController/category/WhiteController.java new file mode 100644 index 0000000..ebb16ea --- /dev/null +++ b/src/test/java/com/knappsack/swagger4springweb/testController/category/WhiteController.java @@ -0,0 +1,22 @@ +package com.knappsack.swagger4springweb.testController.category; + +import com.knappsack.swagger4springweb.annotation.ApiCategory; +import com.wordnik.swagger.annotations.Api; +import com.wordnik.swagger.annotations.ApiOperation; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +import static org.springframework.web.bind.annotation.RequestMethod.GET; + +@Controller +@RequestMapping("/api/v1/white") +@Api(value = "Test Category, white") +@ApiCategory("light") +public class WhiteController { + + @ApiOperation("White is fortune") + @RequestMapping(value = "fortune", method = GET) + public String fortune() { + return "fortune"; + } +} From 66c504b003868a406724a17db3a85cad32794f1d Mon Sep 17 00:00:00 2001 From: UEC Date: Mon, 22 Sep 2014 11:37:39 +0300 Subject: [PATCH 02/11] Api parser checks ApiCategory annotation --- .../parser/ApiParserImpl.java | 6 ++++++ .../swagger4springweb/ApiCategoryTest.java | 16 ++++++++++++++- .../category/NoColorController.java | 20 +++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/knappsack/swagger4springweb/testController/category/NoColorController.java diff --git a/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java b/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java index 765ba99..1860f1b 100644 --- a/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java +++ b/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java @@ -1,5 +1,6 @@ package com.knappsack.swagger4springweb.parser; +import com.knappsack.swagger4springweb.annotation.ApiCategory; import com.knappsack.swagger4springweb.controller.ApiDocumentationController; import com.knappsack.swagger4springweb.filter.ApiExcludeFilter; import com.knappsack.swagger4springweb.filter.Filter; @@ -133,6 +134,11 @@ private Map processControllers(Set> controllerClass if (apiListing.apis() == null) { apiListing = processMethods(requestMappingMethods, controllerClass, apiListing, description); } + ApiCategory apiCategory = controllerClass.getAnnotation(ApiCategory.class); + if (apiCategory != null && apiCategory.value() != null) { + apiListingMap.put(apiCategory.value(), apiListing); + continue; + } // controllers without any operations are excluded from the apiListingMap list if (apiListing.apis() != null && !apiListing.apis().isEmpty()) { diff --git a/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java b/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java index 143c728..9717074 100644 --- a/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java +++ b/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java @@ -2,11 +2,14 @@ import com.knappsack.swagger4springweb.parser.ApiParser; import com.knappsack.swagger4springweb.parser.ApiParserImpl; +import com.wordnik.swagger.model.ApiDescription; import com.wordnik.swagger.model.ApiListing; import junit.framework.*; import org.junit.*; import org.junit.Test; import org.junit.runner.RunWith; +import scala.Function1; +import scala.collection.Iterator; import java.util.ArrayList; import java.util.Arrays; @@ -25,10 +28,21 @@ public void category() { ApiParser apiParser = createApiParser(); Map documents = apiParser.createApiListings(); - assertEquals(2, documents.size()); + assertEquals("api keys expected", 2, documents.size()); assertTrue(documents.containsKey("dark")); assertTrue(documents.containsKey("light")); + scala.collection.immutable.List dark = documents.get("dark").apis(); +// assertEquals("dark colors expected", 2, dark.size()); + + boolean foundBlack, foundBlue; + for (Iterator it = dark.iterator(); it.hasNext();) { + ApiDescription description = it.next(); + description.path().equals("sky"); + System.out.println(description.path()); + + } + } diff --git a/src/test/java/com/knappsack/swagger4springweb/testController/category/NoColorController.java b/src/test/java/com/knappsack/swagger4springweb/testController/category/NoColorController.java new file mode 100644 index 0000000..0454112 --- /dev/null +++ b/src/test/java/com/knappsack/swagger4springweb/testController/category/NoColorController.java @@ -0,0 +1,20 @@ +package com.knappsack.swagger4springweb.testController.category; + + +import com.knappsack.swagger4springweb.annotation.ApiCategory; +import com.wordnik.swagger.annotations.Api; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +import static org.springframework.web.bind.annotation.RequestMethod.GET; + +@Controller +@RequestMapping("/api/v1/nocolor") +@Api(value = "Test Category, this controller does not have ApiCategory annotation, thus will be mapped as earlier") +public class NoColorController { + + @RequestMapping(value = "/invisible", method = GET) + public String invisible() { + return "invisible"; + } +} From e888406059850e50c444383344de30760c7111ff Mon Sep 17 00:00:00 2001 From: UEC Date: Mon, 22 Sep 2014 18:06:00 +0300 Subject: [PATCH 03/11] @ApiCategory basic grouping --- .../annotation/ApiCategory.java | 1 + .../parser/ApiParserImpl.java | 47 ++++++++++++++----- .../util/ApiListingUtil.scala | 10 ++++ .../swagger4springweb/ApiCategoryTest.java | 37 ++++++++------- .../category/BlackController.java | 12 ++--- .../category/WhiteController.java | 11 +++-- 6 files changed, 77 insertions(+), 41 deletions(-) create mode 100644 src/main/scala/com/knappsack/swagger4springweb/util/ApiListingUtil.scala diff --git a/src/main/java/com/knappsack/swagger4springweb/annotation/ApiCategory.java b/src/main/java/com/knappsack/swagger4springweb/annotation/ApiCategory.java index ed6540c..0adf4af 100644 --- a/src/main/java/com/knappsack/swagger4springweb/annotation/ApiCategory.java +++ b/src/main/java/com/knappsack/swagger4springweb/annotation/ApiCategory.java @@ -15,4 +15,5 @@ public @interface ApiCategory { java.lang.String value(); + // TODO Description } diff --git a/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java b/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java index 1860f1b..298bd75 100644 --- a/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java +++ b/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java @@ -1,28 +1,44 @@ package com.knappsack.swagger4springweb.parser; +import static org.reflections.ReflectionUtils.withAnnotation; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.reflections.Reflections; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + import com.knappsack.swagger4springweb.annotation.ApiCategory; import com.knappsack.swagger4springweb.controller.ApiDocumentationController; import com.knappsack.swagger4springweb.filter.ApiExcludeFilter; import com.knappsack.swagger4springweb.filter.Filter; import com.knappsack.swagger4springweb.util.AnnotationUtils; import com.knappsack.swagger4springweb.util.JavaToScalaUtil; +import com.knappsack.swagger4springweb.util.ApiListingUtil; import com.knappsack.swagger4springweb.util.ScalaToJavaUtil; import com.wordnik.swagger.annotations.Api; import com.wordnik.swagger.config.SwaggerConfig; -import com.wordnik.swagger.model.*; -import org.reflections.Reflections; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import scala.Option; +import com.wordnik.swagger.model.ApiDescription; +import com.wordnik.swagger.model.ApiInfo; +import com.wordnik.swagger.model.ApiListing; +import com.wordnik.swagger.model.ApiListingReference; +import com.wordnik.swagger.model.Model; +import com.wordnik.swagger.model.Operation; +import com.wordnik.swagger.model.ResourceListing; -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.*; - -import static org.reflections.ReflectionUtils.withAnnotation; +import scala.Option; public class ApiParserImpl implements ApiParser { @@ -136,6 +152,11 @@ private Map processControllers(Set> controllerClass } ApiCategory apiCategory = controllerClass.getAnnotation(ApiCategory.class); if (apiCategory != null && apiCategory.value() != null) { + ApiListing existingApiListing = apiListingMap.get(apiCategory.value()); + if (existingApiListing != null) { + apiListing = ApiListingUtil.combine(existingApiListing, apiListing); + } + apiListingMap.put(apiCategory.value(), apiListing); continue; } diff --git a/src/main/scala/com/knappsack/swagger4springweb/util/ApiListingUtil.scala b/src/main/scala/com/knappsack/swagger4springweb/util/ApiListingUtil.scala new file mode 100644 index 0000000..7221463 --- /dev/null +++ b/src/main/scala/com/knappsack/swagger4springweb/util/ApiListingUtil.scala @@ -0,0 +1,10 @@ +package com.knappsack.swagger4springweb.util + +import com.wordnik.swagger.model.ApiListing + +/** + * @author Allar Tammik + */ +object ApiListingUtil { + def combine(main: ApiListing, additional : ApiListing) : ApiListing = main.copy(apis = main.apis ++ additional.apis) +} diff --git a/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java b/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java index 9717074..d687b30 100644 --- a/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java +++ b/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java @@ -1,23 +1,21 @@ package com.knappsack.swagger4springweb; -import com.knappsack.swagger4springweb.parser.ApiParser; -import com.knappsack.swagger4springweb.parser.ApiParserImpl; -import com.wordnik.swagger.model.ApiDescription; -import com.wordnik.swagger.model.ApiListing; -import junit.framework.*; -import org.junit.*; -import org.junit.Test; -import org.junit.runner.RunWith; -import scala.Function1; -import scala.collection.Iterator; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import org.junit.Test; + +import com.knappsack.swagger4springweb.parser.ApiParser; +import com.knappsack.swagger4springweb.parser.ApiParserImpl; +import com.wordnik.swagger.model.ApiDescription; +import com.wordnik.swagger.model.ApiListing; + +import scala.collection.Iterator; public class ApiCategoryTest extends AbstractTest { @@ -33,16 +31,23 @@ public void category() { assertTrue(documents.containsKey("light")); scala.collection.immutable.List dark = documents.get("dark").apis(); -// assertEquals("dark colors expected", 2, dark.size()); + assertEquals("dark colors expected", 2, dark.size()); - boolean foundBlack, foundBlue; + boolean foundBlack = false, foundBlue = false; for (Iterator it = dark.iterator(); it.hasNext();) { ApiDescription description = it.next(); - description.path().equals("sky"); - System.out.println(description.path()); + if (description.operations().iterator().next().nickname().equals("sky")) { + foundBlue = true; + } + if (description.operations().iterator().next().nickname().equals("earth")) { + foundBlack = true; + } } + assertTrue("found black", foundBlack); + assertTrue("found blue", foundBlue); + } diff --git a/src/test/java/com/knappsack/swagger4springweb/testController/category/BlackController.java b/src/test/java/com/knappsack/swagger4springweb/testController/category/BlackController.java index a0ac7a9..9fed05f 100644 --- a/src/test/java/com/knappsack/swagger4springweb/testController/category/BlackController.java +++ b/src/test/java/com/knappsack/swagger4springweb/testController/category/BlackController.java @@ -1,23 +1,21 @@ package com.knappsack.swagger4springweb.testController.category; -import com.knappsack.swagger4springweb.annotation.ApiCategory; -import com.knappsack.swagger4springweb.annotation.ApiExclude; -import com.wordnik.swagger.annotations.Api; -import com.wordnik.swagger.annotations.ApiOperation; +import static org.springframework.web.bind.annotation.RequestMethod.GET; + import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; -import static org.springframework.web.bind.annotation.RequestMethod.GET; +import com.knappsack.swagger4springweb.annotation.ApiCategory; +import com.wordnik.swagger.annotations.ApiOperation; @Controller @RequestMapping("/api/v1/black") -@Api(value = "Test Category, black") @ApiCategory("dark") public class BlackController { @ApiOperation("Black is earth") - @RequestMapping(value = "earth", method = GET) + @RequestMapping(value = "/earth", method = GET) public String earth() { return "earth"; } diff --git a/src/test/java/com/knappsack/swagger4springweb/testController/category/WhiteController.java b/src/test/java/com/knappsack/swagger4springweb/testController/category/WhiteController.java index ebb16ea..c526940 100644 --- a/src/test/java/com/knappsack/swagger4springweb/testController/category/WhiteController.java +++ b/src/test/java/com/knappsack/swagger4springweb/testController/category/WhiteController.java @@ -1,12 +1,13 @@ package com.knappsack.swagger4springweb.testController.category; -import com.knappsack.swagger4springweb.annotation.ApiCategory; -import com.wordnik.swagger.annotations.Api; -import com.wordnik.swagger.annotations.ApiOperation; +import static org.springframework.web.bind.annotation.RequestMethod.GET; + import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; -import static org.springframework.web.bind.annotation.RequestMethod.GET; +import com.knappsack.swagger4springweb.annotation.ApiCategory; +import com.wordnik.swagger.annotations.Api; +import com.wordnik.swagger.annotations.ApiOperation; @Controller @RequestMapping("/api/v1/white") @@ -15,7 +16,7 @@ public class WhiteController { @ApiOperation("White is fortune") - @RequestMapping(value = "fortune", method = GET) + @RequestMapping(value = "/fortune", method = GET) public String fortune() { return "fortune"; } From 21ff37e3f08a632d185230b6d796661f94928bd1 Mon Sep 17 00:00:00 2001 From: UEC Date: Mon, 22 Sep 2014 18:10:35 +0300 Subject: [PATCH 04/11] @ApiCategory description --- .../knappsack/swagger4springweb/annotation/ApiCategory.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/knappsack/swagger4springweb/annotation/ApiCategory.java b/src/main/java/com/knappsack/swagger4springweb/annotation/ApiCategory.java index 0adf4af..0628e89 100644 --- a/src/main/java/com/knappsack/swagger4springweb/annotation/ApiCategory.java +++ b/src/main/java/com/knappsack/swagger4springweb/annotation/ApiCategory.java @@ -14,6 +14,5 @@ @Retention(value = RetentionPolicy.RUNTIME) public @interface ApiCategory { java.lang.String value(); - - // TODO Description + java.lang.String description() default ""; } From d0ea84e654ac4227e7eb1507401b0acf6878fbaa Mon Sep 17 00:00:00 2001 From: UEC Date: Tue, 23 Sep 2014 14:53:34 +0300 Subject: [PATCH 05/11] @ApiCategory now fully works and tested --- .../parser/ApiParserImpl.java | 24 +++++++----- .../swagger4springweb/AbstractTest.java | 6 +-- .../swagger4springweb/ApiCategoryTest.java | 39 +++++++++++++++---- .../ApiDocumentationControllerTest.java | 14 ++++--- .../category/BlueController.java | 11 +++--- .../category/NoColorController.java | 10 +++-- .../category/WhiteController.java | 2 +- 7 files changed, 71 insertions(+), 35 deletions(-) diff --git a/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java b/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java index 298bd75..a1bf9e7 100644 --- a/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java +++ b/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java @@ -25,8 +25,8 @@ import com.knappsack.swagger4springweb.filter.ApiExcludeFilter; import com.knappsack.swagger4springweb.filter.Filter; import com.knappsack.swagger4springweb.util.AnnotationUtils; -import com.knappsack.swagger4springweb.util.JavaToScalaUtil; import com.knappsack.swagger4springweb.util.ApiListingUtil; +import com.knappsack.swagger4springweb.util.JavaToScalaUtil; import com.knappsack.swagger4springweb.util.ScalaToJavaUtil; import com.wordnik.swagger.annotations.Api; import com.wordnik.swagger.config.SwaggerConfig; @@ -87,7 +87,7 @@ public ResourceListing getResourceListing(Map apiListingMap) List apiListingReferences = new ArrayList(); for (String key : apiListingMap.keySet()) { ApiListing apiListing = apiListingMap.get(key); - String docPath = "/doc"; //servletPath + "/doc"; //"/api/doc"; + String docPath = "/doc"; //servletPath + "/doc"; //"/api/doc"; ApiListingReference apiListingReference = new ApiListingReference(docPath + key, apiListing.description(), apiListing.position()); @@ -115,7 +115,7 @@ else if (o1.position() > o2.position()) swaggerConfig.info()); } - public Map createApiListings() { + public Map createApiListings() { Set> controllerClasses = new HashSet>(); for (String controllerPackage : controllerPackages) { Reflections reflections = new Reflections(controllerPackage); @@ -152,12 +152,13 @@ private Map processControllers(Set> controllerClass } ApiCategory apiCategory = controllerClass.getAnnotation(ApiCategory.class); if (apiCategory != null && apiCategory.value() != null) { - ApiListing existingApiListing = apiListingMap.get(apiCategory.value()); + String key = prependSlashIfMissing(apiCategory.value()); + ApiListing existingApiListing = apiListingMap.get(key); if (existingApiListing != null) { apiListing = ApiListingUtil.combine(existingApiListing, apiListing); } - apiListingMap.put(apiCategory.value(), apiListing); + apiListingMap.put(key, apiListing); continue; } @@ -186,11 +187,9 @@ private ApiListing processControllerApi(Class controllerClass) { resourcePath = controllerClass.getName(); } } - if (!resourcePath.startsWith("/")) { - resourcePath = "/" + resourcePath; - } + resourcePath = prependSlashIfMissing(resourcePath); - String docRoot = resourcePath; + String docRoot = resourcePath; if(docRoot.contains(controllerClass.getName())) { docRoot = docRoot.replace(controllerClass.getName(), ""); } @@ -296,4 +295,11 @@ private boolean ignore(Method method) { return false; } + private String prependSlashIfMissing(String key) { + if (!key.startsWith("/")) { + key = "/" + key; + } + return key; + } + } diff --git a/src/test/java/com/knappsack/swagger4springweb/AbstractTest.java b/src/test/java/com/knappsack/swagger4springweb/AbstractTest.java index 4a0b48a..67f9f8e 100644 --- a/src/test/java/com/knappsack/swagger4springweb/AbstractTest.java +++ b/src/test/java/com/knappsack/swagger4springweb/AbstractTest.java @@ -1,14 +1,14 @@ package com.knappsack.swagger4springweb; -import com.wordnik.swagger.model.ApiInfo; - import java.util.Arrays; import java.util.List; +import com.wordnik.swagger.model.ApiInfo; + public abstract class AbstractTest { public static final String BASE_CONTROLLER_PACKAGE = "com.knappsack.swagger4springweb.testController"; public static final String EXCLUDE_LABEL = "exclude"; - 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"); + 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/nocolor", "/doc/dark", "/doc/light"); 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"); } diff --git a/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java b/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java index d687b30..cdede15 100644 --- a/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java +++ b/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java @@ -14,6 +14,8 @@ import com.knappsack.swagger4springweb.parser.ApiParserImpl; import com.wordnik.swagger.model.ApiDescription; import com.wordnik.swagger.model.ApiListing; +import com.wordnik.swagger.model.ApiListingReference; +import com.wordnik.swagger.model.ResourceListing; import scala.collection.Iterator; @@ -26,15 +28,15 @@ public void category() { ApiParser apiParser = createApiParser(); Map documents = apiParser.createApiListings(); - assertEquals("api keys expected", 2, documents.size()); - assertTrue(documents.containsKey("dark")); - assertTrue(documents.containsKey("light")); + assertEquals("api keys expected", 3, documents.size()); + assertTrue(documents.containsKey("/dark")); + assertTrue(documents.containsKey("/light")); - scala.collection.immutable.List dark = documents.get("dark").apis(); + scala.collection.immutable.List dark = documents.get("/dark").apis(); assertEquals("dark colors expected", 2, dark.size()); boolean foundBlack = false, foundBlue = false; - for (Iterator it = dark.iterator(); it.hasNext();) { + for (Iterator it = dark.iterator(); it.hasNext(); ) { ApiDescription description = it.next(); if (description.operations().iterator().next().nickname().equals("sky")) { @@ -50,14 +52,37 @@ public void category() { } + @Test + public void resourceListing() { + ApiParser apiParser = createApiParser(); + ResourceListing resourceListing = apiParser.getResourceListing(apiParser.createApiListings()); + assertEquals(3, resourceListing.apis().size()); + scala.collection.immutable.List apis = resourceListing.apis(); + + + boolean foundDark = false; + boolean foundLight = false; + boolean foundNoColor = false; + for (Iterator it = apis.iterator(); it.hasNext(); ) { + + ApiListingReference apiListingReference = it.next(); + if (apiListingReference.path().equals("/doc/dark")) foundDark = true; + if (apiListingReference.path().equals("/doc/light")) foundLight = true; + if (apiListingReference.path().equals("/doc/api/v1/nocolor")) foundNoColor = true; + } + assertTrue("found dark category", foundDark); + assertTrue("found light category", foundLight); + assertTrue("found no color category", foundNoColor); + } + private ApiParser createApiParser() { - return createApiParser(Arrays.asList(BASE_CONTROLLER_PACKAGE + ".category")); + return createApiParser(Arrays.asList(BASE_CONTROLLER_PACKAGE + ".category")); } private ApiParser createApiParser(List controllerPackages) { return new ApiParserImpl(API_INFO, controllerPackages, "http://localhost:8080/api", "/api", "v1", - new ArrayList(), true, null); + new ArrayList(), true, null); } } diff --git a/src/test/java/com/knappsack/swagger4springweb/controller/ApiDocumentationControllerTest.java b/src/test/java/com/knappsack/swagger4springweb/controller/ApiDocumentationControllerTest.java index 027844d..ca726e8 100644 --- a/src/test/java/com/knappsack/swagger4springweb/controller/ApiDocumentationControllerTest.java +++ b/src/test/java/com/knappsack/swagger4springweb/controller/ApiDocumentationControllerTest.java @@ -1,15 +1,18 @@ package com.knappsack.swagger4springweb.controller; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.web.servlet.HandlerMapping; + import com.knappsack.swagger4springweb.AbstractTest; import com.knappsack.swagger4springweb.util.ScalaToJavaUtil; import com.wordnik.swagger.model.ApiListing; import com.wordnik.swagger.model.ApiListingReference; import com.wordnik.swagger.model.ResourceListing; -import org.junit.Test; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.web.servlet.HandlerMapping; - -import static org.junit.Assert.*; public class ApiDocumentationControllerTest extends AbstractTest { @@ -41,7 +44,6 @@ public void getResourceList() { assertNotNull(documentation); assertEquals("v1", documentation.apiVersion()); assertEquals(END_POINT_PATHS.size(), documentation.apis().size()); - assertEquals("/doc/api/v1/test", documentation.apis().apply(0).path()); for (ApiListingReference endPoint : ScalaToJavaUtil.toJavaList(documentation.apis())) { assertTrue(END_POINT_PATHS.contains(endPoint.path())); diff --git a/src/test/java/com/knappsack/swagger4springweb/testController/category/BlueController.java b/src/test/java/com/knappsack/swagger4springweb/testController/category/BlueController.java index 75e580b..cc59c2d 100644 --- a/src/test/java/com/knappsack/swagger4springweb/testController/category/BlueController.java +++ b/src/test/java/com/knappsack/swagger4springweb/testController/category/BlueController.java @@ -1,16 +1,17 @@ package com.knappsack.swagger4springweb.testController.category; -import com.knappsack.swagger4springweb.annotation.ApiCategory; -import com.wordnik.swagger.annotations.Api; -import com.wordnik.swagger.annotations.ApiOperation; +import static org.springframework.web.bind.annotation.RequestMethod.GET; + import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; -import static org.springframework.web.bind.annotation.RequestMethod.GET; +import com.knappsack.swagger4springweb.annotation.ApiCategory; +import com.wordnik.swagger.annotations.Api; +import com.wordnik.swagger.annotations.ApiOperation; @Controller @RequestMapping("/api/v1/blue") -@Api(value = "Test Category, blue") +@Api(value = "/api/v1/blue", description = "Test Category, blue") // this will be ignored due to @ApiCategory @ApiCategory("dark") public class BlueController { diff --git a/src/test/java/com/knappsack/swagger4springweb/testController/category/NoColorController.java b/src/test/java/com/knappsack/swagger4springweb/testController/category/NoColorController.java index 0454112..c07cf0c 100644 --- a/src/test/java/com/knappsack/swagger4springweb/testController/category/NoColorController.java +++ b/src/test/java/com/knappsack/swagger4springweb/testController/category/NoColorController.java @@ -1,19 +1,21 @@ package com.knappsack.swagger4springweb.testController.category; -import com.knappsack.swagger4springweb.annotation.ApiCategory; -import com.wordnik.swagger.annotations.Api; +import static org.springframework.web.bind.annotation.RequestMethod.GET; + import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; -import static org.springframework.web.bind.annotation.RequestMethod.GET; +import com.wordnik.swagger.annotations.Api; +import com.wordnik.swagger.annotations.ApiOperation; @Controller @RequestMapping("/api/v1/nocolor") -@Api(value = "Test Category, this controller does not have ApiCategory annotation, thus will be mapped as earlier") +@Api(value = "/api/v1/nocolor", description = "Test Category, this controller does not have ApiCategory annotation, thus will be mapped as earlier") public class NoColorController { @RequestMapping(value = "/invisible", method = GET) + @ApiOperation("I am invisible") public String invisible() { return "invisible"; } diff --git a/src/test/java/com/knappsack/swagger4springweb/testController/category/WhiteController.java b/src/test/java/com/knappsack/swagger4springweb/testController/category/WhiteController.java index c526940..ca08dc6 100644 --- a/src/test/java/com/knappsack/swagger4springweb/testController/category/WhiteController.java +++ b/src/test/java/com/knappsack/swagger4springweb/testController/category/WhiteController.java @@ -11,7 +11,7 @@ @Controller @RequestMapping("/api/v1/white") -@Api(value = "Test Category, white") +@Api(value = "/api/v1/white",description = "Test Category, white") @ApiCategory("light") public class WhiteController { From f39f3f7200b40a1b6623497729e0b08ff3c4bc9d Mon Sep 17 00:00:00 2001 From: UEC Date: Tue, 23 Sep 2014 17:47:00 +0300 Subject: [PATCH 06/11] Added alphabetic ordering --- .../parser/ApiParserImpl.java | 12 ++++++++--- .../util/ApiListingUtil.scala | 1 + .../swagger4springweb/ApiCategoryTest.java | 20 +++++-------------- .../category/BlackController.java | 6 ++++++ 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java b/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java index a1bf9e7..14a8631 100644 --- a/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java +++ b/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeMap; import org.reflections.Reflections; import org.slf4j.Logger; @@ -46,7 +47,7 @@ public class ApiParserImpl implements ApiParser { private static final String swaggerVersion = com.wordnik.swagger.core.SwaggerSpec.version(); - private final Map apiListingMap = new HashMap(); + private final Map apiListingMap = new TreeMap<>(); private final List controllerPackages; private final List ignorableAnnotations; @@ -158,19 +159,24 @@ private Map processControllers(Set> controllerClass apiListing = ApiListingUtil.combine(existingApiListing, apiListing); } - apiListingMap.put(key, apiListing); + addApiListingToMap(apiListing, key); continue; } // controllers without any operations are excluded from the apiListingMap list if (apiListing.apis() != null && !apiListing.apis().isEmpty()) { - apiListingMap.put(apiListing.resourcePath(), apiListing); + addApiListingToMap(apiListing, apiListing.resourcePath()); } } return apiListingMap; } + private void addApiListingToMap(ApiListing apiListing, String key) { + apiListingMap.put(key, ApiListingUtil.sortApisByPath(apiListing)); + } + + private ApiListing processControllerApi(Class controllerClass) { String resourcePath = ""; Api controllerApi = controllerClass.getAnnotation(Api.class); diff --git a/src/main/scala/com/knappsack/swagger4springweb/util/ApiListingUtil.scala b/src/main/scala/com/knappsack/swagger4springweb/util/ApiListingUtil.scala index 7221463..9c6c6f5 100644 --- a/src/main/scala/com/knappsack/swagger4springweb/util/ApiListingUtil.scala +++ b/src/main/scala/com/knappsack/swagger4springweb/util/ApiListingUtil.scala @@ -7,4 +7,5 @@ import com.wordnik.swagger.model.ApiListing */ object ApiListingUtil { def combine(main: ApiListing, additional : ApiListing) : ApiListing = main.copy(apis = main.apis ++ additional.apis) + def sortApisByPath(obj: ApiListing) : ApiListing = obj.copy(apis = obj.apis.sortBy(api => api.path)) } diff --git a/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java b/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java index cdede15..cdb61f4 100644 --- a/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java +++ b/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java @@ -33,22 +33,12 @@ public void category() { assertTrue(documents.containsKey("/light")); scala.collection.immutable.List dark = documents.get("/dark").apis(); - assertEquals("dark colors expected", 2, dark.size()); - - boolean foundBlack = false, foundBlue = false; - for (Iterator it = dark.iterator(); it.hasNext(); ) { - ApiDescription description = it.next(); - - if (description.operations().iterator().next().nickname().equals("sky")) { - foundBlue = true; - } - if (description.operations().iterator().next().nickname().equals("earth")) { - foundBlack = true; - } - } + assertEquals("dark colors expected", 3, dark.size()); - assertTrue("found black", foundBlack); - assertTrue("found blue", foundBlue); + // Validate alphabetic ordering + assertTrue("earth should be first", dark.apply(0).path().equals("/api/v1/black/earth")); + assertTrue("x-planet should be second", dark.apply(1).path().equals("/api/v1/black/x-planet")); + assertTrue("sky should be third", dark.apply(2).path().equals("/api/v1/blue/sky")); } diff --git a/src/test/java/com/knappsack/swagger4springweb/testController/category/BlackController.java b/src/test/java/com/knappsack/swagger4springweb/testController/category/BlackController.java index 9fed05f..e80c12c 100644 --- a/src/test/java/com/knappsack/swagger4springweb/testController/category/BlackController.java +++ b/src/test/java/com/knappsack/swagger4springweb/testController/category/BlackController.java @@ -14,6 +14,12 @@ @ApiCategory("dark") public class BlackController { + @ApiOperation("This is for testing ordering") + @RequestMapping(value = "/x-planet", method = GET) + public String xPlanet() { + return "x-planet"; + } + @ApiOperation("Black is earth") @RequestMapping(value = "/earth", method = GET) public String earth() { From 375cff3bbc27fd2e224ccc0e82fdbb81db40ee5e Mon Sep 17 00:00:00 2001 From: UEC Date: Tue, 23 Sep 2014 19:31:36 +0300 Subject: [PATCH 07/11] Added support for predefined @ApiCategory annotations --- .../annotation/ApiCategory.java | 5 +++-- .../parser/ApiParserImpl.java | 14 +++++++++++++- .../category/BlackController.java | 3 +-- .../category/BlueController.java | 3 +-- .../testController/category/DarkCategory.java | 18 ++++++++++++++++++ .../testController/category/LightCategory.java | 18 ++++++++++++++++++ .../category/WhiteController.java | 3 +-- 7 files changed, 55 insertions(+), 9 deletions(-) create mode 100644 src/test/java/com/knappsack/swagger4springweb/testController/category/DarkCategory.java create mode 100644 src/test/java/com/knappsack/swagger4springweb/testController/category/LightCategory.java diff --git a/src/main/java/com/knappsack/swagger4springweb/annotation/ApiCategory.java b/src/main/java/com/knappsack/swagger4springweb/annotation/ApiCategory.java index 0628e89..207f89c 100644 --- a/src/main/java/com/knappsack/swagger4springweb/annotation/ApiCategory.java +++ b/src/main/java/com/knappsack/swagger4springweb/annotation/ApiCategory.java @@ -7,12 +7,13 @@ /** * An annotation which indicates if this Api should be added to a category - * + *

* This annotation is applicable to the controller class */ -@Target(value = {ElementType.METHOD, ElementType.TYPE}) +@Target(ElementType.ANNOTATION_TYPE) @Retention(value = RetentionPolicy.RUNTIME) public @interface ApiCategory { java.lang.String value(); + java.lang.String description() default ""; } diff --git a/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java b/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java index 14a8631..b4568a5 100644 --- a/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java +++ b/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java @@ -2,6 +2,7 @@ import static org.reflections.ReflectionUtils.withAnnotation; +import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; @@ -151,7 +152,7 @@ private Map processControllers(Set> controllerClass if (apiListing.apis() == null) { apiListing = processMethods(requestMappingMethods, controllerClass, apiListing, description); } - ApiCategory apiCategory = controllerClass.getAnnotation(ApiCategory.class); + ApiCategory apiCategory = getApiCategoryAnnotation(controllerClass); if (apiCategory != null && apiCategory.value() != null) { String key = prependSlashIfMissing(apiCategory.value()); ApiListing existingApiListing = apiListingMap.get(key); @@ -172,6 +173,17 @@ private Map processControllers(Set> controllerClass return apiListingMap; } + private ApiCategory getApiCategoryAnnotation(Class controllerClass) { + Annotation[] annotations = controllerClass.getAnnotations(); + for (Annotation annotation : annotations) { + ApiCategory value = org.springframework.core.annotation.AnnotationUtils.getAnnotation(annotation, ApiCategory.class); + if (value != null) { + return value; + } + } + return null; + } + private void addApiListingToMap(ApiListing apiListing, String key) { apiListingMap.put(key, ApiListingUtil.sortApisByPath(apiListing)); } diff --git a/src/test/java/com/knappsack/swagger4springweb/testController/category/BlackController.java b/src/test/java/com/knappsack/swagger4springweb/testController/category/BlackController.java index e80c12c..9993558 100644 --- a/src/test/java/com/knappsack/swagger4springweb/testController/category/BlackController.java +++ b/src/test/java/com/knappsack/swagger4springweb/testController/category/BlackController.java @@ -6,12 +6,11 @@ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; -import com.knappsack.swagger4springweb.annotation.ApiCategory; import com.wordnik.swagger.annotations.ApiOperation; @Controller @RequestMapping("/api/v1/black") -@ApiCategory("dark") +@DarkCategory public class BlackController { @ApiOperation("This is for testing ordering") diff --git a/src/test/java/com/knappsack/swagger4springweb/testController/category/BlueController.java b/src/test/java/com/knappsack/swagger4springweb/testController/category/BlueController.java index cc59c2d..6f53a2e 100644 --- a/src/test/java/com/knappsack/swagger4springweb/testController/category/BlueController.java +++ b/src/test/java/com/knappsack/swagger4springweb/testController/category/BlueController.java @@ -5,14 +5,13 @@ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; -import com.knappsack.swagger4springweb.annotation.ApiCategory; import com.wordnik.swagger.annotations.Api; import com.wordnik.swagger.annotations.ApiOperation; @Controller @RequestMapping("/api/v1/blue") @Api(value = "/api/v1/blue", description = "Test Category, blue") // this will be ignored due to @ApiCategory -@ApiCategory("dark") +@DarkCategory public class BlueController { @ApiOperation("Blue is sky") diff --git a/src/test/java/com/knappsack/swagger4springweb/testController/category/DarkCategory.java b/src/test/java/com/knappsack/swagger4springweb/testController/category/DarkCategory.java new file mode 100644 index 0000000..991fdeb --- /dev/null +++ b/src/test/java/com/knappsack/swagger4springweb/testController/category/DarkCategory.java @@ -0,0 +1,18 @@ +package com.knappsack.swagger4springweb.testController.category; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.knappsack.swagger4springweb.annotation.ApiCategory; + +/** + * @author Allar Tammik + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@ApiCategory(value = "dark", description = "This is so dark") +public @interface DarkCategory { + String value() default ""; +} diff --git a/src/test/java/com/knappsack/swagger4springweb/testController/category/LightCategory.java b/src/test/java/com/knappsack/swagger4springweb/testController/category/LightCategory.java new file mode 100644 index 0000000..2a5f56d --- /dev/null +++ b/src/test/java/com/knappsack/swagger4springweb/testController/category/LightCategory.java @@ -0,0 +1,18 @@ +package com.knappsack.swagger4springweb.testController.category; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.knappsack.swagger4springweb.annotation.ApiCategory; + +/** + * @author Allar Tammik + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@ApiCategory(value = "light", description = "This is so bright") +public @interface LightCategory { + String value() default ""; +} diff --git a/src/test/java/com/knappsack/swagger4springweb/testController/category/WhiteController.java b/src/test/java/com/knappsack/swagger4springweb/testController/category/WhiteController.java index ca08dc6..130b156 100644 --- a/src/test/java/com/knappsack/swagger4springweb/testController/category/WhiteController.java +++ b/src/test/java/com/knappsack/swagger4springweb/testController/category/WhiteController.java @@ -5,14 +5,13 @@ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; -import com.knappsack.swagger4springweb.annotation.ApiCategory; import com.wordnik.swagger.annotations.Api; import com.wordnik.swagger.annotations.ApiOperation; @Controller @RequestMapping("/api/v1/white") @Api(value = "/api/v1/white",description = "Test Category, white") -@ApiCategory("light") +@LightCategory public class WhiteController { @ApiOperation("White is fortune") From 9ef570bedffb3eabcf5b4ad3523f2f6b5ff1b68b Mon Sep 17 00:00:00 2001 From: UEC Date: Wed, 24 Sep 2014 09:19:22 +0300 Subject: [PATCH 08/11] Removed unnecessary value --- .../swagger4springweb/testController/category/DarkCategory.java | 1 - .../swagger4springweb/testController/category/LightCategory.java | 1 - 2 files changed, 2 deletions(-) diff --git a/src/test/java/com/knappsack/swagger4springweb/testController/category/DarkCategory.java b/src/test/java/com/knappsack/swagger4springweb/testController/category/DarkCategory.java index 991fdeb..377ca39 100644 --- a/src/test/java/com/knappsack/swagger4springweb/testController/category/DarkCategory.java +++ b/src/test/java/com/knappsack/swagger4springweb/testController/category/DarkCategory.java @@ -14,5 +14,4 @@ @Target(ElementType.TYPE) @ApiCategory(value = "dark", description = "This is so dark") public @interface DarkCategory { - String value() default ""; } diff --git a/src/test/java/com/knappsack/swagger4springweb/testController/category/LightCategory.java b/src/test/java/com/knappsack/swagger4springweb/testController/category/LightCategory.java index 2a5f56d..cf45c20 100644 --- a/src/test/java/com/knappsack/swagger4springweb/testController/category/LightCategory.java +++ b/src/test/java/com/knappsack/swagger4springweb/testController/category/LightCategory.java @@ -14,5 +14,4 @@ @Target(ElementType.TYPE) @ApiCategory(value = "light", description = "This is so bright") public @interface LightCategory { - String value() default ""; } From 281ebf7efd056438ea7dc3910805af7fc27d04bc Mon Sep 17 00:00:00 2001 From: UEC Date: Wed, 24 Sep 2014 09:39:02 +0300 Subject: [PATCH 09/11] @ApiCategory description support --- .../parser/ApiParserImpl.java | 23 ++++--------- .../util/AnnotationUtils.java | 34 +++++++++++++++---- .../swagger4springweb/ApiCategoryTest.java | 2 ++ 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java b/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java index b4568a5..11dc958 100644 --- a/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java +++ b/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java @@ -2,7 +2,6 @@ import static org.reflections.ReflectionUtils.withAnnotation; -import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; @@ -144,15 +143,18 @@ private Map processControllers(Set> controllerClass .getAllMethods(controllerClass, withAnnotation(RequestMapping.class)); ApiListing apiListing = processControllerApi(controllerClass); String description = ""; - Api controllerApi = controllerClass.getAnnotation(Api.class); - if (controllerApi != null) { + ApiCategory apiCategory = AnnotationUtils.getAnnotationAnnotation(ApiCategory.class, controllerClass); + if (apiCategory != null) { + description = apiCategory.description(); + } else { + Api controllerApi = controllerClass.getAnnotation(Api.class); + if (controllerApi != null) { description = controllerApi.description(); + } } - if (apiListing.apis() == null) { apiListing = processMethods(requestMappingMethods, controllerClass, apiListing, description); } - ApiCategory apiCategory = getApiCategoryAnnotation(controllerClass); if (apiCategory != null && apiCategory.value() != null) { String key = prependSlashIfMissing(apiCategory.value()); ApiListing existingApiListing = apiListingMap.get(key); @@ -173,17 +175,6 @@ private Map processControllers(Set> controllerClass return apiListingMap; } - private ApiCategory getApiCategoryAnnotation(Class controllerClass) { - Annotation[] annotations = controllerClass.getAnnotations(); - for (Annotation annotation : annotations) { - ApiCategory value = org.springframework.core.annotation.AnnotationUtils.getAnnotation(annotation, ApiCategory.class); - if (value != null) { - return value; - } - } - return null; - } - private void addApiListingToMap(ApiListing apiListing, String key) { apiListingMap.put(key, ApiListingUtil.sortApisByPath(apiListing)); } diff --git a/src/main/java/com/knappsack/swagger4springweb/util/AnnotationUtils.java b/src/main/java/com/knappsack/swagger4springweb/util/AnnotationUtils.java index 5e3df60..7aba25d 100644 --- a/src/main/java/com/knappsack/swagger4springweb/util/AnnotationUtils.java +++ b/src/main/java/com/knappsack/swagger4springweb/util/AnnotationUtils.java @@ -1,18 +1,19 @@ package com.knappsack.swagger4springweb.util; -import com.google.common.collect.Lists; -import com.knappsack.swagger4springweb.model.AnnotatedParameter; -import com.thoughtworks.paranamer.BytecodeReadingParanamer; -import com.thoughtworks.paranamer.Paranamer; -import com.wordnik.swagger.annotations.ApiParam; -import org.springframework.web.bind.annotation.RequestMapping; - import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; +import org.springframework.web.bind.annotation.RequestMapping; + +import com.google.common.collect.Lists; +import com.knappsack.swagger4springweb.model.AnnotatedParameter; +import com.thoughtworks.paranamer.BytecodeReadingParanamer; +import com.thoughtworks.paranamer.Paranamer; +import com.wordnik.swagger.annotations.ApiParam; + public class AnnotationUtils { /** @@ -84,4 +85,23 @@ public static List getAnnotatedParameters(Method method) { } return annotatedParameters; } + + + public static T getAnnotationAnnotation(Class annotationClass, Class controllerClass) { + Annotation[] annotations = controllerClass.getAnnotations(); + for (Annotation annotation : annotations) { + T value = getAnnotation(annotation, annotationClass); + if (value != null) { + return value; + } + } + return null; + } + + private static T getAnnotation(Annotation ann, Class annotationType) { + if (annotationType.isInstance(ann)) { + return (T) ann; + } + return ann.annotationType().getAnnotation(annotationType); + } } diff --git a/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java b/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java index cdb61f4..2a519e8 100644 --- a/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java +++ b/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java @@ -40,6 +40,8 @@ public void category() { assertTrue("x-planet should be second", dark.apply(1).path().equals("/api/v1/black/x-planet")); assertTrue("sky should be third", dark.apply(2).path().equals("/api/v1/blue/sky")); + assertEquals("This is so dark", dark.apply(0).description().get()); + } @Test From 3cf7c2c888a7d9322ba7bb140aac0af7ef551fbc Mon Sep 17 00:00:00 2001 From: UEC Date: Wed, 24 Sep 2014 10:53:38 +0300 Subject: [PATCH 10/11] @ApiCategory description support fixed --- .../parser/ApiParserImpl.java | 24 +++++++++++++++++-- .../util/ApiListingUtil.scala | 1 + .../swagger4springweb/ApiCategoryTest.java | 6 +++-- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java b/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java index 11dc958..c964b4d 100644 --- a/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java +++ b/src/main/java/com/knappsack/swagger4springweb/parser/ApiParserImpl.java @@ -40,6 +40,7 @@ import com.wordnik.swagger.model.ResourceListing; import scala.Option; +import scala.Some; public class ApiParserImpl implements ApiParser { @@ -204,12 +205,31 @@ private ApiListing processControllerApi(Class controllerClass) { } SpringApiReader reader = new SpringApiReader(); Option apiListingOption = reader.read(docRoot, controllerClass, swaggerConfig); + ApiListing apiListing = null; + Option descriptionOption = null; + String categoryValue = null; + + ApiCategory category = AnnotationUtils.getAnnotationAnnotation(ApiCategory.class, controllerClass); + String description = null; + if (category != null) { + categoryValue = category.value(); + description = category.description(); + descriptionOption = Some.apply(description); + } + if (apiListingOption.nonEmpty()) { - apiListing = apiListingOption.get(); + apiListing = apiListingOption.get(); + apiListing = ApiListingUtil.changeDescription(apiListing, description); } //Allow for multiple controllers having the same resource path. + if (categoryValue != null) { + ApiListing existingApiListing = apiListingMap.get(categoryValue); + if (existingApiListing != null) { + return existingApiListing; + } + } ApiListing existingApiListing = apiListingMap.get(resourcePath); if (existingApiListing != null) { return existingApiListing; @@ -220,7 +240,7 @@ private ApiListing processControllerApi(Class controllerClass) { } return new ApiListing(apiVersion, swaggerVersion, basePath, resourcePath, null, null, null, null, null, null, - null, 0); + descriptionOption, 0); } private ApiListing processMethods(Collection methods, Class controllerClass, ApiListing apiListing, String description) { diff --git a/src/main/scala/com/knappsack/swagger4springweb/util/ApiListingUtil.scala b/src/main/scala/com/knappsack/swagger4springweb/util/ApiListingUtil.scala index 9c6c6f5..b622b83 100644 --- a/src/main/scala/com/knappsack/swagger4springweb/util/ApiListingUtil.scala +++ b/src/main/scala/com/knappsack/swagger4springweb/util/ApiListingUtil.scala @@ -8,4 +8,5 @@ import com.wordnik.swagger.model.ApiListing object ApiListingUtil { def combine(main: ApiListing, additional : ApiListing) : ApiListing = main.copy(apis = main.apis ++ additional.apis) def sortApisByPath(obj: ApiListing) : ApiListing = obj.copy(apis = obj.apis.sortBy(api => api.path)) + def changeDescription(obj: ApiListing, desc: String) : ApiListing = obj.copy(description = Some(desc)) } diff --git a/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java b/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java index 2a519e8..b2be9f6 100644 --- a/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java +++ b/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java @@ -32,7 +32,9 @@ public void category() { assertTrue(documents.containsKey("/dark")); assertTrue(documents.containsKey("/light")); - scala.collection.immutable.List dark = documents.get("/dark").apis(); + ApiListing darkApiListing = documents.get("/dark"); + + scala.collection.immutable.List dark = darkApiListing.apis(); assertEquals("dark colors expected", 3, dark.size()); // Validate alphabetic ordering @@ -40,7 +42,7 @@ public void category() { assertTrue("x-planet should be second", dark.apply(1).path().equals("/api/v1/black/x-planet")); assertTrue("sky should be third", dark.apply(2).path().equals("/api/v1/blue/sky")); - assertEquals("This is so dark", dark.apply(0).description().get()); + assertEquals("This is so dark", darkApiListing.description().get()); } From ad02fcd210f0bf469a0896f5e3dc05ae04b6d10d Mon Sep 17 00:00:00 2001 From: UEC Date: Wed, 24 Sep 2014 13:26:24 +0300 Subject: [PATCH 11/11] Method return type wildcard support --- .../parser/ApiOperationParser.java | 36 ++++++++++++------- .../swagger4springweb/ApiCategoryTest.java | 7 ++-- .../category/BlackController.java | 10 ++++++ 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/knappsack/swagger4springweb/parser/ApiOperationParser.java b/src/main/java/com/knappsack/swagger4springweb/parser/ApiOperationParser.java index a1d1d21..42e0933 100644 --- a/src/main/java/com/knappsack/swagger4springweb/parser/ApiOperationParser.java +++ b/src/main/java/com/knappsack/swagger4springweb/parser/ApiOperationParser.java @@ -1,27 +1,35 @@ package com.knappsack.swagger4springweb.parser; -import com.knappsack.swagger4springweb.util.JavaToScalaUtil; -import com.wordnik.swagger.annotations.*; -import com.wordnik.swagger.converter.ModelConverters; -import com.wordnik.swagger.model.*; -import com.wordnik.swagger.model.Authorization; -import com.wordnik.swagger.model.AuthorizationScope; -import org.apache.commons.lang.ArrayUtils; -import org.springframework.http.HttpMethod; -import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import scala.Option; +import static java.lang.String.format; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; -import static java.lang.String.format; +import org.apache.commons.lang.ArrayUtils; +import org.springframework.http.HttpMethod; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import com.knappsack.swagger4springweb.util.JavaToScalaUtil; +import com.wordnik.swagger.annotations.ApiOperation; +import com.wordnik.swagger.annotations.ApiResponse; +import com.wordnik.swagger.annotations.ApiResponses; +import com.wordnik.swagger.converter.ModelConverters; +import com.wordnik.swagger.model.Authorization; +import com.wordnik.swagger.model.AuthorizationScope; +import com.wordnik.swagger.model.Model; +import com.wordnik.swagger.model.Operation; +import com.wordnik.swagger.model.Parameter; +import com.wordnik.swagger.model.ResponseMessage; + +import scala.Option; public class ApiOperationParser { @@ -51,6 +59,8 @@ public Operation parseDocumentationOperation(Method method) { final Type type = parameterizedType.getActualTypeArguments()[0]; if (type instanceof ParameterizedType) { documentationOperation.setResponseClass((Class) ((ParameterizedType) type).getRawType()); + } else if (type instanceof WildcardType) { + documentationOperation.setResponseClass(Object.class); } else { documentationOperation.setResponseClass((Class) type); } diff --git a/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java b/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java index b2be9f6..0afeadd 100644 --- a/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java +++ b/src/test/java/com/knappsack/swagger4springweb/ApiCategoryTest.java @@ -35,12 +35,13 @@ public void category() { ApiListing darkApiListing = documents.get("/dark"); scala.collection.immutable.List dark = darkApiListing.apis(); - assertEquals("dark colors expected", 3, dark.size()); + assertEquals("dark colors expected", 4, dark.size()); // Validate alphabetic ordering assertTrue("earth should be first", dark.apply(0).path().equals("/api/v1/black/earth")); - assertTrue("x-planet should be second", dark.apply(1).path().equals("/api/v1/black/x-planet")); - assertTrue("sky should be third", dark.apply(2).path().equals("/api/v1/blue/sky")); + assertTrue("wildcard should be second", dark.apply(1).path().equals("/api/v1/black/wildcard")); + assertTrue("x-planet should be 3rd", dark.apply(2).path().equals("/api/v1/black/x-planet")); + assertTrue("sky should be 4th", dark.apply(3).path().equals("/api/v1/blue/sky")); assertEquals("This is so dark", darkApiListing.description().get()); diff --git a/src/test/java/com/knappsack/swagger4springweb/testController/category/BlackController.java b/src/test/java/com/knappsack/swagger4springweb/testController/category/BlackController.java index 9993558..8dd449f 100644 --- a/src/test/java/com/knappsack/swagger4springweb/testController/category/BlackController.java +++ b/src/test/java/com/knappsack/swagger4springweb/testController/category/BlackController.java @@ -1,8 +1,11 @@ package com.knappsack.swagger4springweb.testController.category; +import static java.util.Arrays.asList; import static org.springframework.web.bind.annotation.RequestMethod.GET; +import java.util.List; + import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -24,4 +27,11 @@ public String xPlanet() { public String earth() { return "earth"; } + + @ApiOperation("Wildcard list") + @RequestMapping(value = "/wildcard", method = GET) + public List genericType() { + return asList(6L, 4L); + } + }