Repository: servicemix-bundles Updated Branches: refs/heads/master 6df3b3794 -> 2638e2e9b
[SM-2480]swagger-jaxrs should support multiple swagger enabled endpoints in OSGi container Project: http://git-wip-us.apache.org/repos/asf/servicemix-bundles/repo Commit: http://git-wip-us.apache.org/repos/asf/servicemix-bundles/commit/2638e2e9 Tree: http://git-wip-us.apache.org/repos/asf/servicemix-bundles/tree/2638e2e9 Diff: http://git-wip-us.apache.org/repos/asf/servicemix-bundles/diff/2638e2e9 Branch: refs/heads/master Commit: 2638e2e9bf19947106c5eecc2939633e7eb5be2c Parents: 6df3b37 Author: Freeman Fang <[email protected]> Authored: Mon Mar 16 15:19:15 2015 +0800 Committer: Freeman Fang <[email protected]> Committed: Mon Mar 16 15:19:15 2015 +0800 ---------------------------------------------------------------------- pom.xml | 1 + swagger-jaxrs-1.3.2/pom.xml | 26 +++ .../swagger/jaxrs/listing/ApiListing.scala | 160 +++++++++++++++++++ 3 files changed, 187 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/servicemix-bundles/blob/2638e2e9/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index 22f104a..3f38395 100644 --- a/pom.xml +++ b/pom.xml @@ -107,6 +107,7 @@ <module>lightcouch-0.1.6</module> <module>htmlunit-2.15</module> <module>opensaml-3.1.0</module> + <module>swagger-jaxrs-1.3.2</module> </modules> </project> http://git-wip-us.apache.org/repos/asf/servicemix-bundles/blob/2638e2e9/swagger-jaxrs-1.3.2/pom.xml ---------------------------------------------------------------------- diff --git a/swagger-jaxrs-1.3.2/pom.xml b/swagger-jaxrs-1.3.2/pom.xml index c8dafa9..711da52 100644 --- a/swagger-jaxrs-1.3.2/pom.xml +++ b/swagger-jaxrs-1.3.2/pom.xml @@ -63,11 +63,37 @@ <classifier>sources</classifier> <optional>true</optional> </dependency> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>servlet-api</artifactId> + <version>2.5</version> + </dependency> </dependencies> <build> <plugins> <plugin> + <groupId>net.alchim31.maven</groupId> + <artifactId>scala-maven-plugin</artifactId> + <executions> + <execution> + <id>scala-compile-first</id> + <phase>process-resources</phase> + <goals> + <goal>add-source</goal> + <goal>compile</goal> + </goals> + </execution> + <execution> + <id>scala-test-compile</id> + <phase>process-test-resources</phase> + <goals> + <goal>testCompile</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <executions> http://git-wip-us.apache.org/repos/asf/servicemix-bundles/blob/2638e2e9/swagger-jaxrs-1.3.2/src/main/scala/com/wordnik/swagger/jaxrs/listing/ApiListing.scala ---------------------------------------------------------------------- diff --git a/swagger-jaxrs-1.3.2/src/main/scala/com/wordnik/swagger/jaxrs/listing/ApiListing.scala b/swagger-jaxrs-1.3.2/src/main/scala/com/wordnik/swagger/jaxrs/listing/ApiListing.scala new file mode 100644 index 0000000..1ea4d77 --- /dev/null +++ b/swagger-jaxrs-1.3.2/src/main/scala/com/wordnik/swagger/jaxrs/listing/ApiListing.scala @@ -0,0 +1,160 @@ +package com.wordnik.swagger.jaxrs.listing + +import com.wordnik.swagger.config._ +import com.wordnik.swagger.reader._ +import com.wordnik.swagger.core.util._ +import com.wordnik.swagger.model._ +import com.wordnik.swagger.core.filter._ +import com.wordnik.swagger.annotations._ +import com.wordnik.swagger.jaxrs._ +import com.wordnik.swagger.jaxrs.config._ + +import org.slf4j.LoggerFactory + +import java.lang.annotation.Annotation +import java.lang.reflect.Method + +import javax.ws.rs.core.{ UriInfo, HttpHeaders, Context, Response, MediaType, Application, MultivaluedMap } +import javax.ws.rs.core.Response._ +import javax.ws.rs._ +import javax.ws.rs.ext.Provider + +import javax.servlet.ServletConfig + +import scala.collection.mutable.LinkedHashMap + +import scala.collection.JavaConverters._ +import scala.collection.mutable.ListBuffer + +object ApiListingCache { + private val LOGGER = LoggerFactory.getLogger(ApiListingCache.getClass) + + var _cache: Option[Map[String, ApiListing]] = None + + def listing(docRoot: String, app: Application, sc: ServletConfig): Option[Map[String, ApiListing]] = { + LOGGER.debug("loading cache") + ClassReaders.reader.map{reader => + val scanner = sc.getServletContext().getAttribute("SCANNER"); + val classes = scanner.asInstanceOf[JaxrsScanner].classesFromContext(app, null) + // For each top level resource, parse it and look for swagger annotations. + val listings = (for(cls <- classes) yield reader.read(docRoot, cls, ConfigFactory.config)).flatten.toList + _cache = Some((listings.map(m => { + //always start with "/" + val resourcePath = m.resourcePath.startsWith ("/") match { + case true => m.resourcePath + case false => "/" + m.resourcePath + } + LOGGER.debug("adding resource path " + resourcePath) + (resourcePath, m) + })).toMap) + //}) + } + _cache + + if(_cache != None) + LOGGER.debug("cache has " + _cache.get.keys + " keys") + else + LOGGER.debug("cache is empty") + _cache + } + + def invalidateCache() = { + _cache = None + } +} + +class ApiListingResource { + private val LOGGER = LoggerFactory.getLogger(classOf[ApiListingResource]) + + @GET + def resourceListing ( + @Context app: Application, + @Context sc: ServletConfig, + @Context headers: HttpHeaders, + @Context uriInfo: UriInfo + ): Response = { + val docRoot = this.getClass.getAnnotation(classOf[Path]).value + val f = new SpecFilter + val listings = ApiListingCache.listing(docRoot, app, sc).map(specs => { + (for(spec <- specs.values) + yield f.filter(spec, FilterFactory.filter, paramsToMap(uriInfo.getQueryParameters), cookiesToMap(headers), headersToMap(headers)) + ).filter(m => m.apis.size > 0) + }) + val references = (for(listing <- listings.getOrElse(List())) yield { + ApiListingReference(listing.resourcePath, listing.description, listing.position) + }).toList.sortWith(_.position < _.position) + + val config = ConfigFactory.config + val resourceListing = ResourceListing(config.apiVersion, + config.swaggerVersion, + references, + config.authorizations, + config.info + ) + Response.ok(resourceListing).build + } + + /** + * individual api listing + **/ + @GET + @Path("/{route: .+}") + def apiDeclaration ( + @PathParam("route") route: String, + @Context app: Application, + @Context sc: ServletConfig, + @Context headers: HttpHeaders, + @Context uriInfo: UriInfo + ): Response = { + LOGGER.debug("requested apiDeclaration for " + route) + val docRoot = this.getClass.getAnnotation(classOf[Path]).value + val f = new SpecFilter + val pathPart = cleanRoute(route) + LOGGER.debug("requested route " + pathPart) + val listings = ApiListingCache.listing(docRoot, app, sc).map(specs => { + (for(spec <- specs.values) yield { + LOGGER.debug("inspecting path " + spec.resourcePath) + f.filter(spec, FilterFactory.filter, paramsToMap(uriInfo.getQueryParameters), cookiesToMap(headers), headersToMap(headers)) + }).filter(m => { + val resourcePath = m.resourcePath match { + case e: String if(e.startsWith("/")) => e + case e: String => "/" + e + } + resourcePath == pathPart + }) + }).toList.flatten + + listings.size match { + case 1 => Response.ok(listings(0)).build + case _ => Response.status(404).build + } + } + + // ensure leading slash, remove trailing + def cleanRoute(route: String) = { + val cleanStart = { + if(route.startsWith("/")) route + else "/" + route + } + if(cleanStart.endsWith("/")) cleanStart.substring(0, cleanStart.length - 1) + else cleanStart + } + + def invalidateCache() = { + ApiListingCache.invalidateCache() + } + + def paramsToMap(params: MultivaluedMap[String, String]): Map[String, List[String]] = { + (for((key, list) <- params.asScala) yield (key, list.asScala.toList)).toMap + } + + def cookiesToMap(headers: HttpHeaders): Map[String, String] = { + Option(headers).map(h => { + (for((name, cookie) <- h.getCookies.asScala) yield (name, cookie.getValue)).toMap + }).getOrElse(Map()) + } + + def headersToMap(headers: HttpHeaders): Map[String, List[String]] = { + (for((key, values) <- headers.getRequestHeaders.asScala) yield (key, values.asScala.toList)).toMap + } +}
