Skip to content

Commit 2c20d34

Browse files
committed
added ability to parse non-annotated resources per #489
1 parent 396542b commit 2c20d34

File tree

4 files changed

+183
-152
lines changed

4 files changed

+183
-152
lines changed

modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/JaxrsApiReader.scala

+55-149
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ trait JaxrsApiReader extends ClassReader with ClassReaderUtils {
3030
// In case it's not a subresource locator the entity type is returned
3131
def findSubresourceType(method: Method): Class[_]
3232

33+
def readRecursive(
34+
docRoot: String,
35+
parentPath: String, cls: Class[_],
36+
config: SwaggerConfig,
37+
operations: ListBuffer[Tuple3[String, String, ListBuffer[Operation]]],
38+
parentMethods: ListBuffer[Method]): Option[ApiListing]
39+
3340
def processDataType(paramType: Class[_], genericParamType: Type) = {
3441
paramType.getName match {
3542
case "[I" => "Array[int]"
@@ -79,14 +86,17 @@ trait JaxrsApiReader extends ClassReader with ClassReaderUtils {
7986
parentMethods: ListBuffer[Method]
8087
) = {
8188
val api = method.getAnnotation(classOf[Api])
82-
val responseClass = {
89+
val responseClass = if(apiOperation != null){
8390
val baseName = apiOperation.response.getName
8491
val output = apiOperation.responseContainer match {
8592
case "" => baseName
8693
case e: String => "%s[%s]".format(e, baseName)
8794
}
8895
output
8996
}
97+
else {
98+
"void"
99+
}
90100

91101
var paramAnnotations: Array[Array[java.lang.annotation.Annotation]] = null
92102
var paramTypes: Array[java.lang.Class[_]] = null
@@ -102,30 +112,35 @@ trait JaxrsApiReader extends ClassReader with ClassReaderUtils {
102112
genericParamTypes = parentMethods.map(pm => pm.getGenericParameterTypes).reduceRight(_ ++ _) ++ method.getGenericParameterTypes
103113
}
104114

105-
val produces = Option(apiOperation.produces) match {
106-
case Some(e) if(e != "") => e.split(",").map(_.trim).toList
107-
case _ => method.getAnnotation(classOf[Produces]) match {
108-
case e: Produces => e.value.toList
109-
case _ => List()
110-
}
111-
}
112-
val consumes = Option(apiOperation.consumes) match {
113-
case Some(e) if(e != "") => e.split(",").map(_.trim).toList
114-
case _ => method.getAnnotation(classOf[Consumes]) match {
115-
case e: Consumes => e.value.toList
116-
case _ => List()
115+
val (produces, consumes, protocols, authorizations) = {
116+
if(apiOperation != null) {
117+
(Option(apiOperation.produces) match {
118+
case Some(e) if(e != "") => e.split(",").map(_.trim).toList
119+
case _ => method.getAnnotation(classOf[Produces]) match {
120+
case e: Produces => e.value.toList
121+
case _ => List()
122+
}
123+
},
124+
Option(apiOperation.consumes) match {
125+
case Some(e) if(e != "") => e.split(",").map(_.trim).toList
126+
case _ => method.getAnnotation(classOf[Consumes]) match {
127+
case e: Consumes => e.value.toList
128+
case _ => List()
129+
}
130+
},
131+
Option(apiOperation.protocols) match {
132+
case Some(e) if(e != "") => e.split(",").map(_.trim).toList
133+
case _ => List()
134+
},
135+
Option(apiOperation.authorizations) match {
136+
case Some(e) => (for(a <- e) yield {
137+
val scopes = (for(s <- a.scopes) yield com.wordnik.swagger.model.AuthorizationScope(s.scope, s.description)).toArray
138+
new com.wordnik.swagger.model.Authorization(a.value, scopes)
139+
}).toList
140+
case _ => List()
141+
})
117142
}
118-
}
119-
val protocols = Option(apiOperation.protocols) match {
120-
case Some(e) if(e != "") => e.split(",").map(_.trim).toList
121-
case _ => List()
122-
}
123-
val authorizations:List[com.wordnik.swagger.model.Authorization] = Option(apiOperation.authorizations) match {
124-
case Some(e) => (for(a <- e) yield {
125-
val scopes = (for(s <- a.scopes) yield com.wordnik.swagger.model.AuthorizationScope(s.scope, s.description)).toArray
126-
new com.wordnik.swagger.model.Authorization(a.value, scopes)
127-
}).toList
128-
case _ => List()
143+
else((List(), List(), List(), List()))
129144
}
130145
val params = parentParams ++ (for((annotations, paramType, genericParamType) <- (paramAnnotations, paramTypes, genericParamTypes).zipped.toList) yield {
131146
if(annotations.length > 0) {
@@ -169,13 +184,18 @@ trait JaxrsApiReader extends ClassReader with ClassReaderUtils {
169184
}
170185
}
171186

187+
val (summary, notes, position) = {
188+
if(apiOperation != null) (apiOperation.value, apiOperation.notes, apiOperation.position)
189+
else ("","",0)
190+
}
191+
172192
Operation(
173193
parseHttpMethod(method, apiOperation),
174-
apiOperation.value,
175-
apiOperation.notes,
194+
summary,
195+
notes,
176196
responseClass,
177197
method.getName,
178-
apiOperation.position,
198+
position,
179199
produces,
180200
consumes,
181201
protocols,
@@ -185,7 +205,7 @@ trait JaxrsApiReader extends ClassReader with ClassReaderUtils {
185205
Option(isDeprecated))
186206
}
187207

188-
def readMethod(method: Method, parentParams: List[Parameter], parentMethods: ListBuffer[Method]) = {
208+
def readMethod(method: Method, parentParams: List[Parameter], parentMethods: ListBuffer[Method]): Option[Operation] = {
189209
val apiOperation = method.getAnnotation(classOf[ApiOperation])
190210
val responseAnnotation = method.getAnnotation(classOf[ApiResponses])
191211
val apiResponses = {
@@ -201,7 +221,12 @@ trait JaxrsApiReader extends ClassReader with ClassReaderUtils {
201221
}
202222
val isDeprecated = Option(method.getAnnotation(classOf[Deprecated])).map(m => "true").getOrElse(null)
203223

204-
parseOperation(method, apiOperation, apiResponses, isDeprecated, parentParams, parentMethods)
224+
val hidden = if(apiOperation != null)
225+
apiOperation.hidden
226+
else false
227+
228+
if(!hidden) Some(parseOperation(method, apiOperation, apiResponses, isDeprecated, parentParams, parentMethods))
229+
else None
205230
}
206231

207232
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 {
218243
case _ => ""
219244
}
220245
}
221-
222246
readRecursive(docRoot, parentPath.replace("//","/"), cls, config, new ListBuffer[Tuple3[String, String, ListBuffer[Operation]]], new ListBuffer[Method])
223247
}
224248

225-
def readRecursive(
226-
docRoot: String,
227-
parentPath: String, cls: Class[_],
228-
config: SwaggerConfig,
229-
operations: ListBuffer[Tuple3[String, String, ListBuffer[Operation]]],
230-
parentMethods: ListBuffer[Method]): Option[ApiListing] = {
231-
val api = cls.getAnnotation(classOf[Api])
232-
233-
// must have @Api annotation to process!
234-
if(api != null) {
235-
val consumes = Option(api.consumes) match {
236-
case Some(e) if(e != "") => e.split(",").map(_.trim).toList
237-
case _ => cls.getAnnotation(classOf[Consumes]) match {
238-
case e: Consumes => e.value.toList
239-
case _ => List()
240-
}
241-
}
242-
val produces = Option(api.produces) match {
243-
case Some(e) if(e != "") => e.split(",").map(_.trim).toList
244-
case _ => cls.getAnnotation(classOf[Produces]) match {
245-
case e: Produces => e.value.toList
246-
case _ => List()
247-
}
248-
}
249-
val protocols = Option(api.protocols) match {
250-
case Some(e) if(e != "") => e.split(",").map(_.trim).toList
251-
case _ => List()
252-
}
253-
val description = api.description match {
254-
case e: String if(e != "") => Some(e)
255-
case _ => None
256-
}
257-
// look for method-level annotated properties
258-
val parentParams: List[Parameter] = (for(field <- getAllFields(cls))
259-
yield {
260-
// only process fields with @ApiParam, @QueryParam, @HeaderParam, @PathParam
261-
if(field.getAnnotation(classOf[QueryParam]) != null || field.getAnnotation(classOf[HeaderParam]) != null ||
262-
field.getAnnotation(classOf[HeaderParam]) != null || field.getAnnotation(classOf[PathParam]) != null ||
263-
field.getAnnotation(classOf[ApiParam]) != null) {
264-
val param = new MutableParameter
265-
param.dataType = field.getType.getName
266-
Option (field.getAnnotation(classOf[ApiParam])) match {
267-
case Some(annotation) => toAllowableValues(annotation.allowableValues)
268-
case _ =>
269-
}
270-
val annotations = field.getAnnotations
271-
processParamAnnotations(param, annotations)
272-
}
273-
else None
274-
}
275-
).flatten.toList
276-
277-
for(method <- cls.getMethods) {
278-
val returnType = findSubresourceType(method)
279-
val path = method.getAnnotation(classOf[Path]) match {
280-
case e: Path => e.value()
281-
case _ => ""
282-
}
283-
val endpoint = (parentPath + /*api.value + */ pathFromMethod(method)).replace("//", "/")
284-
Option(returnType.getAnnotation(classOf[Api])) match {
285-
case Some(e) => {
286-
val root = docRoot + api.value + pathFromMethod(method)
287-
parentMethods += method
288-
readRecursive(root, endpoint, returnType, config, operations, parentMethods)
289-
parentMethods -= method
290-
}
291-
case _ => {
292-
if(method.getAnnotation(classOf[ApiOperation]) != null) {
293-
val op = readMethod(method, parentParams, parentMethods)
294-
appendOperation(endpoint, path, op, operations)
295-
}
296-
}
297-
}
298-
}
299-
// sort them by min position in the operations
300-
val s = (for(op <- operations) yield {
301-
(op, op._3.map(_.position).toList.min)
302-
}).sortWith(_._2 < _._2).toList
303-
val orderedOperations = new ListBuffer[Tuple3[String, String, ListBuffer[Operation]]]
304-
s.foreach(op => {
305-
val ops = op._1._3.sortWith(_.position < _.position)
306-
orderedOperations += Tuple3(op._1._1, op._1._2, ops)
307-
})
308-
val apis = (for ((endpoint, resourcePath, operationList) <- orderedOperations) yield {
309-
val orderedOperations = new ListBuffer[Operation]
310-
operationList.sortWith(_.position < _.position).foreach(e => orderedOperations += e)
311-
ApiDescription(
312-
addLeadingSlash(endpoint),
313-
None,
314-
orderedOperations.toList)
315-
}).toList
316-
317-
val authorizations:List[com.wordnik.swagger.model.Authorization] = Option(api.authorizations) match {
318-
case Some(e) => (for(a <- e) yield {
319-
val scopes = (for(s <- a.scopes) yield com.wordnik.swagger.model.AuthorizationScope(s.scope, s.description)).toArray
320-
new com.wordnik.swagger.model.Authorization(a.value, scopes)
321-
}).toList
322-
case _ => List()
323-
}
324-
val models = ModelUtil.modelsFromApis(apis)
325-
Some(ApiListing (
326-
apiVersion = config.apiVersion,
327-
swaggerVersion = config.swaggerVersion,
328-
basePath = config.basePath,
329-
resourcePath = addLeadingSlash(api.value),
330-
apis = ModelUtil.stripPackages(apis),
331-
models = models,
332-
description = description,
333-
produces = produces,
334-
consumes = consumes,
335-
protocols = protocols,
336-
authorizations = authorizations,
337-
position = api.position)
338-
)
339-
}
340-
else None
341-
}
342-
343249
def getAllFields(cls: Class[_]): List[Field] = {
344250
var fields = cls.getDeclaredFields().toList;
345251
if (cls.getSuperclass() != null) {
@@ -379,7 +285,7 @@ trait JaxrsApiReader extends ClassReader with ClassReaderUtils {
379285
}
380286

381287
def parseHttpMethod(method: Method, op: ApiOperation): String = {
382-
if (op.httpMethod() != null && op.httpMethod().trim().length() > 0)
288+
if (op != null && op.httpMethod() != null && op.httpMethod().trim().length() > 0)
383289
op.httpMethod().trim
384290
else {
385291
if(method.getAnnotation(classOf[GET]) != null) "GET"

modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/config/DefaultJaxrsConfig.scala

+12-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package com.wordnik.swagger.jaxrs.config
22

33

44
import com.wordnik.swagger.config.{ ConfigFactory, ScannerFactory }
5-
import com.wordnik.swagger.jaxrs.reader.DefaultJaxrsApiReader
5+
import com.wordnik.swagger.jaxrs.reader._
66
import com.wordnik.swagger.reader._
77

88
import javax.servlet.ServletConfig
@@ -15,6 +15,16 @@ class DefaultJaxrsConfig extends HttpServlet {
1515
implicit val config = servletConfig
1616
ConfigFactory.config = new WebXMLReader()
1717
ScannerFactory.scanner = Some(new DefaultJaxrsScanner())
18-
ClassReaders.reader = Some(new DefaultJaxrsApiReader)
18+
servletConfig.getInitParameter("scan.all.resources") match {
19+
case "true" => {
20+
val reader = new BasicJaxrsReader
21+
Option(servletConfig.getInitParameter("ignore.routes")) match {
22+
case Some(e) => reader.ignoredRoutes = e.split(",").toSet
23+
case _ =>
24+
}
25+
ClassReaders.reader = Some(reader)
26+
}
27+
case _ => ClassReaders.reader = Some(new DefaultJaxrsApiReader)
28+
}
1929
}
2030
}

modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/listing/ApiListingResource.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ import javax.ws.rs.core.MediaType
88

99
@Path("/api-docs")
1010
@Api("/api-docs")
11-
@Produces(Array("application/json; charset=utf8"))
11+
@Produces(Array("application/json; charset=utf-8"))
1212
class ApiListingResourceJSON extends ApiListingResource

0 commit comments

Comments
 (0)