diff --git a/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/JaxrsApiReader.scala b/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/JaxrsApiReader.scala index 6af79f4df8..e21a7b3611 100644 --- a/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/JaxrsApiReader.scala +++ b/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/JaxrsApiReader.scala @@ -30,6 +30,13 @@ trait JaxrsApiReader extends ClassReader with ClassReaderUtils { // In case it's not a subresource locator the entity type is returned def findSubresourceType(method: Method): Class[_] + def readRecursive( + docRoot: String, + parentPath: String, cls: Class[_], + config: SwaggerConfig, + operations: ListBuffer[Tuple3[String, String, ListBuffer[Operation]]], + parentMethods: ListBuffer[Method]): Option[ApiListing] + def processDataType(paramType: Class[_], genericParamType: Type) = { paramType.getName match { case "[I" => "Array[int]" @@ -79,7 +86,7 @@ trait JaxrsApiReader extends ClassReader with ClassReaderUtils { parentMethods: ListBuffer[Method] ) = { val api = method.getAnnotation(classOf[Api]) - val responseClass = { + val responseClass = if(apiOperation != null){ val baseName = apiOperation.response.getName val output = apiOperation.responseContainer match { case "" => baseName @@ -87,6 +94,9 @@ trait JaxrsApiReader extends ClassReader with ClassReaderUtils { } output } + else { + "void" + } var paramAnnotations: Array[Array[java.lang.annotation.Annotation]] = null var paramTypes: Array[java.lang.Class[_]] = null @@ -102,30 +112,35 @@ trait JaxrsApiReader extends ClassReader with ClassReaderUtils { genericParamTypes = parentMethods.map(pm => pm.getGenericParameterTypes).reduceRight(_ ++ _) ++ method.getGenericParameterTypes } - val produces = Option(apiOperation.produces) match { - case Some(e) if(e != "") => e.split(",").map(_.trim).toList - case _ => method.getAnnotation(classOf[Produces]) match { - case e: Produces => e.value.toList - case _ => List() - } - } - val consumes = Option(apiOperation.consumes) match { - case Some(e) if(e != "") => e.split(",").map(_.trim).toList - case _ => method.getAnnotation(classOf[Consumes]) match { - case e: Consumes => e.value.toList - case _ => List() + val (produces, consumes, protocols, authorizations) = { + if(apiOperation != null) { + (Option(apiOperation.produces) match { + case Some(e) if(e != "") => e.split(",").map(_.trim).toList + case _ => method.getAnnotation(classOf[Produces]) match { + case e: Produces => e.value.toList + case _ => List() + } + }, + Option(apiOperation.consumes) match { + case Some(e) if(e != "") => e.split(",").map(_.trim).toList + case _ => method.getAnnotation(classOf[Consumes]) match { + case e: Consumes => e.value.toList + case _ => List() + } + }, + Option(apiOperation.protocols) match { + case Some(e) if(e != "") => e.split(",").map(_.trim).toList + case _ => List() + }, + Option(apiOperation.authorizations) match { + case Some(e) => (for(a <- e) yield { + val scopes = (for(s <- a.scopes) yield com.wordnik.swagger.model.AuthorizationScope(s.scope, s.description)).toArray + new com.wordnik.swagger.model.Authorization(a.value, scopes) + }).toList + case _ => List() + }) } - } - val protocols = Option(apiOperation.protocols) match { - case Some(e) if(e != "") => e.split(",").map(_.trim).toList - case _ => List() - } - val authorizations:List[com.wordnik.swagger.model.Authorization] = Option(apiOperation.authorizations) match { - case Some(e) => (for(a <- e) yield { - val scopes = (for(s <- a.scopes) yield com.wordnik.swagger.model.AuthorizationScope(s.scope, s.description)).toArray - new com.wordnik.swagger.model.Authorization(a.value, scopes) - }).toList - case _ => List() + else((List(), List(), List(), List())) } val params = parentParams ++ (for((annotations, paramType, genericParamType) <- (paramAnnotations, paramTypes, genericParamTypes).zipped.toList) yield { if(annotations.length > 0) { @@ -169,13 +184,18 @@ trait JaxrsApiReader extends ClassReader with ClassReaderUtils { } } + val (summary, notes, position) = { + if(apiOperation != null) (apiOperation.value, apiOperation.notes, apiOperation.position) + else ("","",0) + } + Operation( parseHttpMethod(method, apiOperation), - apiOperation.value, - apiOperation.notes, + summary, + notes, responseClass, method.getName, - apiOperation.position, + position, produces, consumes, protocols, @@ -185,7 +205,7 @@ trait JaxrsApiReader extends ClassReader with ClassReaderUtils { Option(isDeprecated)) } - def readMethod(method: Method, parentParams: List[Parameter], parentMethods: ListBuffer[Method]) = { + def readMethod(method: Method, parentParams: List[Parameter], parentMethods: ListBuffer[Method]): Option[Operation] = { val apiOperation = method.getAnnotation(classOf[ApiOperation]) val responseAnnotation = method.getAnnotation(classOf[ApiResponses]) val apiResponses = { @@ -201,7 +221,12 @@ trait JaxrsApiReader extends ClassReader with ClassReaderUtils { } val isDeprecated = Option(method.getAnnotation(classOf[Deprecated])).map(m => "true").getOrElse(null) - parseOperation(method, apiOperation, apiResponses, isDeprecated, parentParams, parentMethods) + val hidden = if(apiOperation != null) + apiOperation.hidden + else false + + if(!hidden) Some(parseOperation(method, apiOperation, apiResponses, isDeprecated, parentParams, parentMethods)) + else None } def appendOperation(endpoint: String, path: String, op: Operation, operations: ListBuffer[Tuple3[String, String, ListBuffer[Operation]]]) = { @@ -218,128 +243,9 @@ trait JaxrsApiReader extends ClassReader with ClassReaderUtils { case _ => "" } } - readRecursive(docRoot, parentPath.replace("//","/"), cls, config, new ListBuffer[Tuple3[String, String, ListBuffer[Operation]]], new ListBuffer[Method]) } - 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 + /*api.value + */ 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 authorizations:List[com.wordnik.swagger.model.Authorization] = Option(api.authorizations) match { - case Some(e) => (for(a <- e) yield { - val scopes = (for(s <- a.scopes) yield com.wordnik.swagger.model.AuthorizationScope(s.scope, s.description)).toArray - new com.wordnik.swagger.model.Authorization(a.value, scopes) - }).toList - case _ => List() - } - 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, - authorizations = authorizations, - position = api.position) - ) - } - else None - } - def getAllFields(cls: Class[_]): List[Field] = { var fields = cls.getDeclaredFields().toList; if (cls.getSuperclass() != null) { @@ -379,7 +285,7 @@ trait JaxrsApiReader extends ClassReader with ClassReaderUtils { } def parseHttpMethod(method: Method, op: ApiOperation): String = { - if (op.httpMethod() != null && op.httpMethod().trim().length() > 0) + if (op != null && op.httpMethod() != null && op.httpMethod().trim().length() > 0) op.httpMethod().trim else { if(method.getAnnotation(classOf[GET]) != null) "GET" diff --git a/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/config/DefaultJaxrsConfig.scala b/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/config/DefaultJaxrsConfig.scala index 101bdb1a51..175cda4c03 100644 --- a/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/config/DefaultJaxrsConfig.scala +++ b/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/config/DefaultJaxrsConfig.scala @@ -2,7 +2,7 @@ package com.wordnik.swagger.jaxrs.config import com.wordnik.swagger.config.{ ConfigFactory, ScannerFactory } -import com.wordnik.swagger.jaxrs.reader.DefaultJaxrsApiReader +import com.wordnik.swagger.jaxrs.reader._ import com.wordnik.swagger.reader._ import javax.servlet.ServletConfig @@ -15,6 +15,16 @@ class DefaultJaxrsConfig extends HttpServlet { implicit val config = servletConfig ConfigFactory.config = new WebXMLReader() ScannerFactory.scanner = Some(new DefaultJaxrsScanner()) - ClassReaders.reader = Some(new DefaultJaxrsApiReader) + servletConfig.getInitParameter("scan.all.resources") match { + case "true" => { + val reader = new BasicJaxrsReader + Option(servletConfig.getInitParameter("ignore.routes")) match { + case Some(e) => reader.ignoredRoutes = e.split(",").toSet + case _ => + } + ClassReaders.reader = Some(reader) + } + case _ => ClassReaders.reader = Some(new DefaultJaxrsApiReader) + } } } diff --git a/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/listing/ApiListingResource.scala b/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/listing/ApiListingResource.scala index a7e8a47575..14486b768d 100644 --- a/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/listing/ApiListingResource.scala +++ b/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/listing/ApiListingResource.scala @@ -8,5 +8,5 @@ import javax.ws.rs.core.MediaType @Path("/api-docs") @Api("/api-docs") -@Produces(Array("application/json; charset=utf8")) +@Produces(Array("application/json; charset=utf-8")) class ApiListingResourceJSON extends ApiListingResource diff --git a/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/reader/DefaultJaxrsReader.scala b/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/reader/DefaultJaxrsReader.scala index c0764efa5f..8077116912 100644 --- a/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/reader/DefaultJaxrsReader.scala +++ b/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/reader/DefaultJaxrsReader.scala @@ -2,6 +2,8 @@ package com.wordnik.swagger.jaxrs.reader import com.wordnik.swagger.model._ import com.wordnik.swagger.jaxrs._ +import com.wordnik.swagger.config._ +import com.wordnik.swagger.core.util.ModelUtil import com.wordnik.swagger.annotations._ import com.wordnik.swagger.core.ApiValues._ @@ -11,7 +13,120 @@ import java.lang.annotation.Annotation import javax.ws.rs._ import javax.ws.rs.core.Context +import scala.collection.mutable.{ ListBuffer, HashMap, HashSet } + class DefaultJaxrsApiReader extends JaxrsApiReader { + 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) { + readMethod(method, parentParams, parentMethods) match { + case Some(op) => appendOperation(endpoint, path, op, operations) + case None => + } + } + } + } + } + // 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 + } + // decorates a Parameter based on annotations, returns None if param should be ignored def processParamAnnotations(mutable: MutableParameter, paramAnnotations: Array[Annotation]): Option[Parameter] = { var shouldIgnore = false