Skip to content

Commit 396542b

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

File tree

3 files changed

+266
-0
lines changed

3 files changed

+266
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
package com.wordnik.swagger.jaxrs.reader
2+
3+
import com.wordnik.swagger.model._
4+
import com.wordnik.swagger.jaxrs._
5+
import com.wordnik.swagger.config._
6+
import com.wordnik.swagger.core.util.ModelUtil
7+
import com.wordnik.swagger.annotations._
8+
import com.wordnik.swagger.core.ApiValues._
9+
10+
import java.lang.reflect.{ Method, Type }
11+
import java.lang.annotation.Annotation
12+
13+
import javax.ws.rs._
14+
import javax.ws.rs.core.Context
15+
16+
import scala.collection.mutable.{ ListBuffer, HashMap, HashSet }
17+
18+
class BasicJaxrsReader extends JaxrsApiReader {
19+
var ignoredRoutes: Set[String] = Set()
20+
21+
def ignoreRoutes = ignoredRoutes
22+
def readRecursive(
23+
docRoot: String,
24+
parentPath: String, cls: Class[_],
25+
config: SwaggerConfig,
26+
operations: ListBuffer[Tuple3[String, String, ListBuffer[Operation]]],
27+
parentMethods: ListBuffer[Method]): Option[ApiListing] = {
28+
val api = cls.getAnnotation(classOf[Api])
29+
val pathAnnotation = cls.getAnnotation(classOf[Path])
30+
31+
val r = Option(api) match {
32+
case Some(api) => api.value
33+
case None => Option(pathAnnotation) match {
34+
case Some(p) => p.value
35+
case None => null
36+
}
37+
}
38+
if(r != null && !ignoreRoutes.contains(r)) {
39+
var resourcePath = addLeadingSlash(r)
40+
val position = Option(api) match {
41+
case Some(api) => api.position
42+
case None => 0
43+
}
44+
val (consumes, produces, protocols, description) = {
45+
if(api != null){
46+
(Option(api.consumes) match {
47+
case Some(e) if(e != "") => e.split(",").map(_.trim).toList
48+
case _ => cls.getAnnotation(classOf[Consumes]) match {
49+
case e: Consumes => e.value.toList
50+
case _ => List()
51+
}
52+
},
53+
Option(api.produces) match {
54+
case Some(e) if(e != "") => e.split(",").map(_.trim).toList
55+
case _ => cls.getAnnotation(classOf[Produces]) match {
56+
case e: Produces => e.value.toList
57+
case _ => List()
58+
}
59+
},
60+
Option(api.protocols) match {
61+
case Some(e) if(e != "") => e.split(",").map(_.trim).toList
62+
case _ => List()
63+
},
64+
api.description match {
65+
case e: String if(e != "") => Some(e)
66+
case _ => None
67+
}
68+
)}
69+
else ((List(), List(), List(), None))
70+
}
71+
// look for method-level annotated properties
72+
val parentParams: List[Parameter] = (for(field <- getAllFields(cls))
73+
yield {
74+
// only process fields with @ApiParam, @QueryParam, @HeaderParam, @PathParam
75+
if(field.getAnnotation(classOf[QueryParam]) != null || field.getAnnotation(classOf[HeaderParam]) != null ||
76+
field.getAnnotation(classOf[HeaderParam]) != null || field.getAnnotation(classOf[PathParam]) != null ||
77+
field.getAnnotation(classOf[ApiParam]) != null) {
78+
val param = new MutableParameter
79+
param.dataType = field.getType.getName
80+
Option (field.getAnnotation(classOf[ApiParam])) match {
81+
case Some(annotation) => toAllowableValues(annotation.allowableValues)
82+
case _ =>
83+
}
84+
val annotations = field.getAnnotations
85+
processParamAnnotations(param, annotations)
86+
}
87+
else None
88+
}
89+
).flatten.toList
90+
91+
for(method <- cls.getMethods) {
92+
val returnType = findSubresourceType(method)
93+
val path = method.getAnnotation(classOf[Path]) match {
94+
case e: Path => e.value()
95+
case _ => ""
96+
}
97+
val endpoint = (parentPath + pathFromMethod(method)).replace("//", "/")
98+
Option(returnType.getAnnotation(classOf[Api])) match {
99+
case Some(e) => {
100+
val root = docRoot + api.value + pathFromMethod(method)
101+
parentMethods += method
102+
readRecursive(root, endpoint, returnType, config, operations, parentMethods)
103+
parentMethods -= method
104+
}
105+
case _ => {
106+
readMethod(method, parentParams, parentMethods) match {
107+
case Some(op) => appendOperation(endpoint, path, op, operations)
108+
case None => None
109+
}
110+
}
111+
}
112+
}
113+
// sort them by min position in the operations
114+
val s = (for(op <- operations) yield {
115+
(op, op._3.map(_.position).toList.min)
116+
}).sortWith(_._2 < _._2).toList
117+
val orderedOperations = new ListBuffer[Tuple3[String, String, ListBuffer[Operation]]]
118+
s.foreach(op => {
119+
val ops = op._1._3.sortWith(_.position < _.position)
120+
orderedOperations += Tuple3(op._1._1, op._1._2, ops)
121+
})
122+
val apis = (for ((endpoint, resourcePath, operationList) <- orderedOperations) yield {
123+
val orderedOperations = new ListBuffer[Operation]
124+
operationList.sortWith(_.position < _.position).foreach(e => orderedOperations += e)
125+
ApiDescription(
126+
addLeadingSlash(endpoint),
127+
None,
128+
orderedOperations.toList)
129+
}).toList
130+
val models = ModelUtil.modelsFromApis(apis)
131+
Some(ApiListing (
132+
apiVersion = config.apiVersion,
133+
swaggerVersion = config.swaggerVersion,
134+
basePath = config.basePath,
135+
resourcePath = addLeadingSlash(resourcePath),
136+
apis = ModelUtil.stripPackages(apis),
137+
models = models,
138+
description = description,
139+
produces = produces,
140+
consumes = consumes,
141+
protocols = protocols,
142+
position = position)
143+
)
144+
}
145+
else None
146+
}
147+
148+
// decorates a Parameter based on annotations, returns None if param should be ignored
149+
def processParamAnnotations(mutable: MutableParameter, paramAnnotations: Array[Annotation]): Option[Parameter] = {
150+
var shouldIgnore = false
151+
for (pa <- paramAnnotations) {
152+
pa match {
153+
case e: ApiParam => parseApiParamAnnotation(mutable, e)
154+
case e: QueryParam => {
155+
mutable.name = readString(e.value, mutable.name)
156+
mutable.paramType = readString(TYPE_QUERY, mutable.paramType)
157+
}
158+
case e: PathParam => {
159+
mutable.name = readString(e.value, mutable.name)
160+
mutable.required = true
161+
mutable.paramType = readString(TYPE_PATH, mutable.paramType)
162+
}
163+
case e: MatrixParam => {
164+
mutable.name = readString(e.value, mutable.name)
165+
mutable.paramType = readString(TYPE_MATRIX, mutable.paramType)
166+
}
167+
case e: HeaderParam => {
168+
mutable.name = readString(e.value, mutable.name)
169+
mutable.paramType = readString(TYPE_HEADER, mutable.paramType)
170+
}
171+
case e: FormParam => {
172+
mutable.name = readString(e.value, mutable.name)
173+
mutable.paramType = readString(TYPE_FORM, mutable.paramType)
174+
}
175+
case e: CookieParam => {
176+
mutable.name = readString(e.value, mutable.name)
177+
mutable.paramType = readString(TYPE_COOKIE, mutable.paramType)
178+
}
179+
case e: DefaultValue => {
180+
mutable.defaultValue = Option(readString(e.value))
181+
}
182+
case e: Context => shouldIgnore = true
183+
case _ =>
184+
}
185+
}
186+
if(!shouldIgnore) {
187+
if(mutable.paramType == null) {
188+
mutable.paramType = TYPE_BODY
189+
mutable.name = TYPE_BODY
190+
}
191+
Some(mutable.asParameter)
192+
}
193+
else None
194+
}
195+
196+
def findSubresourceType(method: Method): Class[_] = {
197+
method.getReturnType
198+
}
199+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import testresources._
2+
3+
import com.wordnik.swagger.jaxrs.reader._
4+
import com.wordnik.swagger.core.util._
5+
import com.wordnik.swagger.model._
6+
import com.wordnik.swagger.config._
7+
import com.wordnik.swagger.core.filter._
8+
9+
import java.lang.reflect.Method
10+
11+
import java.util.Date
12+
13+
import org.junit.runner.RunWith
14+
import org.scalatest.junit.JUnitRunner
15+
import org.scalatest.FlatSpec
16+
import org.scalatest.matchers.ShouldMatchers
17+
18+
import scala.collection.mutable.ListBuffer
19+
20+
@RunWith(classOf[JUnitRunner])
21+
class NonAnnotatedResourceTest extends FlatSpec with ShouldMatchers {
22+
it should "read a resource without Api annotations" in {
23+
val reader = new BasicJaxrsReader
24+
val config = new SwaggerConfig()
25+
val apiResource = reader.read("/api-docs", classOf[NonAnnotatedResource], config).getOrElse(fail("should not be None"))
26+
27+
apiResource.apis.map(p => println(p.path))
28+
29+
apiResource.apis.size should be (2)
30+
val api = apiResource.apis.head
31+
api.path should be ("/basic/{id}")
32+
33+
val ops = api.operations
34+
ops.size should be (2)
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package testresources
2+
3+
import testmodels._
4+
import com.wordnik.swagger.core._
5+
import com.wordnik.swagger.annotations._
6+
7+
import javax.ws.rs._
8+
import javax.ws.rs.core.Response
9+
10+
import javax.xml.bind.annotation._
11+
12+
import scala.reflect.BeanProperty
13+
14+
@Path("/basic")
15+
class NonAnnotatedResource {
16+
@GET
17+
@Path("/{id}")
18+
def getTest(
19+
@QueryParam("id") id: String) = {
20+
val out = new Sample
21+
out.name = "foo"
22+
out.value = "bar"
23+
Response.ok.entity(out).build
24+
}
25+
26+
@PUT
27+
@Path("/{id}")
28+
def updateTest(
29+
sample: Sample) = {
30+
}
31+
}

0 commit comments

Comments
 (0)