From 697a4bdb4c1d99e27ff863c4db7304a073b36945 Mon Sep 17 00:00:00 2001 From: Tony Tam Date: Fri, 14 Mar 2014 17:06:42 -0700 Subject: [PATCH] support for #489 --- .../swagger/jersey/JerseyApiReader.scala | 111 +++++++++++++++++ .../swagger/jersey/JerseyApiReader.scala | 112 ++++++++++++++++++ .../SubresourceLocatorChildTest.scala | 2 +- 3 files changed, 224 insertions(+), 1 deletion(-) diff --git a/modules/swagger-jersey-jaxrs/src/main/scala/com/wordnik/swagger/jersey/JerseyApiReader.scala b/modules/swagger-jersey-jaxrs/src/main/scala/com/wordnik/swagger/jersey/JerseyApiReader.scala index 1e116fe7d0..093a112449 100644 --- a/modules/swagger-jersey-jaxrs/src/main/scala/com/wordnik/swagger/jersey/JerseyApiReader.scala +++ b/modules/swagger-jersey-jaxrs/src/main/scala/com/wordnik/swagger/jersey/JerseyApiReader.scala @@ -19,6 +19,7 @@ package com.wordnik.swagger.jersey import com.wordnik.swagger.jaxrs._ import com.wordnik.swagger.core._ import com.wordnik.swagger.model._ +import com.wordnik.swagger.config._ import com.wordnik.swagger.annotations._ import com.wordnik.swagger.core.util._ @@ -37,8 +38,118 @@ import com.sun.jersey.core.header.FormDataContentDisposition import com.sun.jersey.multipart.FormDataParam import com.sun.jersey.api.core.InjectParam +import scala.collection.mutable.{ ListBuffer, HashMap, HashSet } + class JerseyApiReader extends JaxrsApiReader { private val LOGGER = LoggerFactory.getLogger(classOf[JerseyApiReader]) + def readRecursive( + docRoot: String, + parentPath: String, cls: Class[_], + config: SwaggerConfig, + operations: ListBuffer[Tuple3[String, String, ListBuffer[Operation]]], + parentMethods: ListBuffer[Method]): Option[ApiListing] = { + val api = cls.getAnnotation(classOf[Api]) + + // must have @Api annotation to process! + if(api != null) { + val consumes = Option(api.consumes) match { + case Some(e) if(e != "") => e.split(",").map(_.trim).toList + case _ => cls.getAnnotation(classOf[Consumes]) match { + case e: Consumes => e.value.toList + case _ => List() + } + } + val produces = Option(api.produces) match { + case Some(e) if(e != "") => e.split(",").map(_.trim).toList + case _ => cls.getAnnotation(classOf[Produces]) match { + case e: Produces => e.value.toList + case _ => List() + } + } + val protocols = Option(api.protocols) match { + case Some(e) if(e != "") => e.split(",").map(_.trim).toList + case _ => List() + } + val description = api.description match { + case e: String if(e != "") => Some(e) + case _ => None + } + // look for method-level annotated properties + val parentParams: List[Parameter] = (for(field <- getAllFields(cls)) + yield { + // only process fields with @ApiParam, @QueryParam, @HeaderParam, @PathParam + if(field.getAnnotation(classOf[QueryParam]) != null || field.getAnnotation(classOf[HeaderParam]) != null || + field.getAnnotation(classOf[HeaderParam]) != null || field.getAnnotation(classOf[PathParam]) != null || + field.getAnnotation(classOf[ApiParam]) != null) { + val param = new MutableParameter + param.dataType = field.getType.getName + Option (field.getAnnotation(classOf[ApiParam])) match { + case Some(annotation) => toAllowableValues(annotation.allowableValues) + case _ => + } + val annotations = field.getAnnotations + processParamAnnotations(param, annotations) + } + else None + } + ).flatten.toList + + for(method <- cls.getMethods) { + val returnType = findSubresourceType(method) + val path = method.getAnnotation(classOf[Path]) match { + case e: Path => e.value() + case _ => "" + } + val endpoint = (parentPath + pathFromMethod(method)).replace("//", "/") + Option(returnType.getAnnotation(classOf[Api])) match { + case Some(e) => { + val root = docRoot + api.value + pathFromMethod(method) + parentMethods += method + readRecursive(root, endpoint, returnType, config, operations, parentMethods) + parentMethods -= method + } + case _ => { + if(method.getAnnotation(classOf[ApiOperation]) != null) { + val op = readMethod(method, parentParams, parentMethods) + appendOperation(endpoint, path, op, operations) + } + } + } + } + // sort them by min position in the operations + val s = (for(op <- operations) yield { + (op, op._3.map(_.position).toList.min) + }).sortWith(_._2 < _._2).toList + val orderedOperations = new ListBuffer[Tuple3[String, String, ListBuffer[Operation]]] + s.foreach(op => { + val ops = op._1._3.sortWith(_.position < _.position) + orderedOperations += Tuple3(op._1._1, op._1._2, ops) + }) + val apis = (for ((endpoint, resourcePath, operationList) <- orderedOperations) yield { + val orderedOperations = new ListBuffer[Operation] + operationList.sortWith(_.position < _.position).foreach(e => orderedOperations += e) + ApiDescription( + addLeadingSlash(endpoint), + None, + orderedOperations.toList) + }).toList + val models = ModelUtil.modelsFromApis(apis) + Some(ApiListing ( + apiVersion = config.apiVersion, + swaggerVersion = config.swaggerVersion, + basePath = config.basePath, + resourcePath = addLeadingSlash(api.value), + apis = ModelUtil.stripPackages(apis), + models = models, + description = description, + produces = produces, + consumes = consumes, + protocols = protocols, + position = api.position) + ) + } + else None + } def processParamAnnotations(mutable: MutableParameter, paramAnnotations: Array[Annotation]): Option[Parameter] = { var shouldIgnore = false diff --git a/modules/swagger-jersey2-jaxrs/src/main/scala/com/wordnik/swagger/jersey/JerseyApiReader.scala b/modules/swagger-jersey2-jaxrs/src/main/scala/com/wordnik/swagger/jersey/JerseyApiReader.scala index ffd2ec2809..dd3a68f193 100644 --- a/modules/swagger-jersey2-jaxrs/src/main/scala/com/wordnik/swagger/jersey/JerseyApiReader.scala +++ b/modules/swagger-jersey2-jaxrs/src/main/scala/com/wordnik/swagger/jersey/JerseyApiReader.scala @@ -19,6 +19,7 @@ package com.wordnik.swagger.jersey import com.wordnik.swagger.jaxrs._ import com.wordnik.swagger.core._ import com.wordnik.swagger.model._ +import com.wordnik.swagger.config._ import com.wordnik.swagger.annotations._ import com.wordnik.swagger.core.util._ @@ -36,9 +37,120 @@ import java.lang.annotation.Annotation import org.glassfish.jersey.media.multipart.FormDataContentDisposition import org.glassfish.jersey.media.multipart.FormDataParam +import scala.collection.mutable.{ ListBuffer, HashMap, HashSet } + class JerseyApiReader extends JaxrsApiReader { private val LOGGER = LoggerFactory.getLogger(classOf[JerseyApiReader]) + def readRecursive( + docRoot: String, + parentPath: String, cls: Class[_], + config: SwaggerConfig, + operations: ListBuffer[Tuple3[String, String, ListBuffer[Operation]]], + parentMethods: ListBuffer[Method]): Option[ApiListing] = { + val api = cls.getAnnotation(classOf[Api]) + + // must have @Api annotation to process! + if(api != null) { + val consumes = Option(api.consumes) match { + case Some(e) if(e != "") => e.split(",").map(_.trim).toList + case _ => cls.getAnnotation(classOf[Consumes]) match { + case e: Consumes => e.value.toList + case _ => List() + } + } + val produces = Option(api.produces) match { + case Some(e) if(e != "") => e.split(",").map(_.trim).toList + case _ => cls.getAnnotation(classOf[Produces]) match { + case e: Produces => e.value.toList + case _ => List() + } + } + val protocols = Option(api.protocols) match { + case Some(e) if(e != "") => e.split(",").map(_.trim).toList + case _ => List() + } + val description = api.description match { + case e: String if(e != "") => Some(e) + case _ => None + } + // look for method-level annotated properties + val parentParams: List[Parameter] = (for(field <- getAllFields(cls)) + yield { + // only process fields with @ApiParam, @QueryParam, @HeaderParam, @PathParam + if(field.getAnnotation(classOf[QueryParam]) != null || field.getAnnotation(classOf[HeaderParam]) != null || + field.getAnnotation(classOf[HeaderParam]) != null || field.getAnnotation(classOf[PathParam]) != null || + field.getAnnotation(classOf[ApiParam]) != null) { + val param = new MutableParameter + param.dataType = field.getType.getName + Option (field.getAnnotation(classOf[ApiParam])) match { + case Some(annotation) => toAllowableValues(annotation.allowableValues) + case _ => + } + val annotations = field.getAnnotations + processParamAnnotations(param, annotations) + } + else None + } + ).flatten.toList + + for(method <- cls.getMethods) { + val returnType = findSubresourceType(method) + val path = method.getAnnotation(classOf[Path]) match { + case e: Path => e.value() + case _ => "" + } + val endpoint = (parentPath + pathFromMethod(method)).replace("//", "/") + Option(returnType.getAnnotation(classOf[Api])) match { + case Some(e) => { + val root = docRoot + api.value + pathFromMethod(method) + parentMethods += method + readRecursive(root, endpoint, returnType, config, operations, parentMethods) + parentMethods -= method + } + case _ => { + if(method.getAnnotation(classOf[ApiOperation]) != null) { + val op = readMethod(method, parentParams, parentMethods) + appendOperation(endpoint, path, op, operations) + } + } + } + } + // sort them by min position in the operations + val s = (for(op <- operations) yield { + (op, op._3.map(_.position).toList.min) + }).sortWith(_._2 < _._2).toList + val orderedOperations = new ListBuffer[Tuple3[String, String, ListBuffer[Operation]]] + s.foreach(op => { + val ops = op._1._3.sortWith(_.position < _.position) + orderedOperations += Tuple3(op._1._1, op._1._2, ops) + }) + val apis = (for ((endpoint, resourcePath, operationList) <- orderedOperations) yield { + val orderedOperations = new ListBuffer[Operation] + operationList.sortWith(_.position < _.position).foreach(e => orderedOperations += e) + ApiDescription( + addLeadingSlash(endpoint), + None, + orderedOperations.toList) + }).toList + val models = ModelUtil.modelsFromApis(apis) + Some(ApiListing ( + apiVersion = config.apiVersion, + swaggerVersion = config.swaggerVersion, + basePath = config.basePath, + resourcePath = addLeadingSlash(api.value), + apis = ModelUtil.stripPackages(apis), + models = models, + description = description, + produces = produces, + consumes = consumes, + protocols = protocols, + position = api.position) + ) + } + else None + } + def processParamAnnotations(mutable: MutableParameter, paramAnnotations: Array[Annotation]): Option[Parameter] = { var shouldIgnore = false for (pa <- paramAnnotations) { diff --git a/modules/swagger-jersey2-jaxrs/src/test/scala/testresources/SubresourceLocatorChildTest.scala b/modules/swagger-jersey2-jaxrs/src/test/scala/testresources/SubresourceLocatorChildTest.scala index 50fe50650a..0f9ffc454d 100644 --- a/modules/swagger-jersey2-jaxrs/src/test/scala/testresources/SubresourceLocatorChildTest.scala +++ b/modules/swagger-jersey2-jaxrs/src/test/scala/testresources/SubresourceLocatorChildTest.scala @@ -7,7 +7,7 @@ import javax.ws.rs.core.Response @Api(value = "/child", description = "Media Type Test", - produces = "application/json; charset=utf8", + produces = "application/json; charset=utf-8", protocols = "http, https") class SubresourceLocatorChildTest { @GET