Skip to content
Browse files

Fix scalate and async requests, fix gzip output

  • Loading branch information...
2 parents c4b9edd + 3af54a4 commit 4f7f91be990791504b6da52cbb227bfe01c42aa7 @casualjim casualjim committed Feb 24, 2013
View
12 common/src/main/scala/org/scalatra/servlet/HasMultipartConfig.scala
@@ -27,13 +27,21 @@ trait HasMultipartConfig extends Initializable { self: { def servletContext: Ser
}
}
- def multipartConfig: MultipartConfig = multipartConfigFromContext getOrElse DefaultMultipartConfig
+ def multipartConfig: MultipartConfig = try {
+ multipartConfigFromContext getOrElse DefaultMultipartConfig
+ } catch {
+ case e: Throwable =>
+ System.err.println("Couldn't get the multipart config from the servlet context because: ")
+ e.printStackTrace()
+ DefaultMultipartConfig
+ }
private[this] var providedConfig: Option[MultipartConfig] = None
abstract override def initialize(config: ConfigT) {
super.initialize(config)
- providedConfig foreach { _ apply servletContext }
+
+ providedConfig foreach { _ apply config.context }
}
def configureMultipartHandling(config: MultipartConfig) {
View
47 core/src/main/scala/org/scalatra/FutureSupport.scala
@@ -7,6 +7,7 @@ import javax.servlet.{ServletContext, AsyncEvent, AsyncListener}
import servlet.AsyncSupport
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
import scala.concurrent.{ExecutionContext, Future}
+import scala.util.{Failure, Success, Try}
object AsyncResult {
val DefaultTimeout = Timeout(30 seconds)
@@ -32,15 +33,27 @@ trait FutureSupport extends AsyncSupport {
// In the meantime, this gives us enough control for our test.
// IPC: it may not be perfect but I need to be able to configure this timeout in an application
// This is a Duration instead of a timeout because a duration has the concept of infinity
+ @deprecated("Override the `timeout` method on a `org.scalatra.AsyncResult` instead.", "2.2")
protected def asyncTimeout: Duration = 30 seconds
- override protected def isAsyncExecutable(result: Any) = classOf[Future[_]].isAssignableFrom(result.getClass)
+ override protected def isAsyncExecutable(result: Any) =
+ classOf[Future[_]].isAssignableFrom(result.getClass) ||
+ classOf[AsyncResult].isAssignableFrom(result.getClass)
override protected def renderResponse(actionResult: Any) {
actionResult match {
- case r: AsyncResult handleFuture(r.is, r.timeout)
- case f: Future[_] handleFuture(f, asyncTimeout)
+ case r: AsyncResult
+ val req = request
+ setMultiparams(matchedRoute(req), multiParams(req))
+ val prelude = new AsyncResult {
+ val is: Future[_] = Future { setMultiparams(matchedRoute, multiParams) }
+ }
+ handleFuture(prelude.is flatMap (_ => r.is) , r.timeout)
+ case f: Future[_]
+ val req = request
+ setMultiparams(matchedRoute(req), multiParams(req))
+ handleFuture(Future { setMultiparams(matchedRoute(req), multiParams(req))(req) } flatMap (_ => f), asyncTimeout)
case a super.renderResponse(a)
}
}
@@ -53,7 +66,6 @@ trait FutureSupport extends AsyncSupport {
else
context.setTimeout(-1)
context addListener (new AsyncListener {
- def onComplete(event: AsyncEvent) {}
def onTimeout(event: AsyncEvent) {
onAsyncEvent(event) {
@@ -64,23 +76,34 @@ trait FutureSupport extends AsyncSupport {
}
}
+ def onComplete(event: AsyncEvent) {}
def onError(event: AsyncEvent) {}
-
def onStartAsync(event: AsyncEvent) {}
})
f onComplete {
case t {
withinAsyncContext(context) {
if (gotResponseAlready.compareAndSet(false, true)) {
- t map { result =>
- runFilters(routes.afterFilters)
- super.renderResponse(result)
- } recover {
- case e: HaltException renderHaltException(e)
- case e => renderResponse(errorHandler(e))
+ try {
+ t map { result
+ renderResponse(result)
+ } recover {
+ case e: HaltException
+ renderHaltException(e)
+ case e
+ try {
+ renderResponse(errorHandler(e))
+ } catch {
+ case e: Throwable =>
+ ScalatraBase.runCallbacks(Failure(e))
+ renderUncaughtException(e)
+ ScalatraBase.runRenderCallbacks(Failure(e))
+ }
+ }
+ } finally {
+ context.complete()
}
- context.complete()
}
}
}
View
72 core/src/main/scala/org/scalatra/GZipSupport.scala
@@ -1,6 +1,5 @@
package org.scalatra
-import java.io.OutputStream
import java.io.PrintWriter
import java.util.zip.GZIPOutputStream
import javax.servlet.ServletOutputStream
@@ -15,66 +14,37 @@ trait GZipSupport extends Handler {
self: ScalatraBase =>
abstract override def handle(req: HttpServletRequest, res: HttpServletResponse) {
-
- if (isGzip(req)) {
- var w: PrintWriter = null
- var s = new ContentLengthOutputStream(res.getOutputStream())
-
- val response = new HttpServletResponseWrapper(res) {
- override def getOutputStream(): ServletOutputStream = {
- val gzip = new GZIPOutputStream(s)
- w = new PrintWriter(gzip)
- return new ServletOutputStream {
- override def write(b: Int) = gzip.write(b)
- }
+ withRequestResponse(req, res) {
+ if (isGzip) {
+ val gzos = new GZIPOutputStream(res.getOutputStream)
+ val w = new PrintWriter(gzos)
+ val gzsos = new ServletOutputStream { def write(b: Int) { gzos.write(b) } }
+
+ val response = new HttpServletResponseWrapper(res) {
+ override def getOutputStream: ServletOutputStream = gzsos
+ override def getWriter: PrintWriter = w
+ override def setContentLength(i: Int) = {} // ignoring content length as it won't be the same when gzipped
}
- override def getWriter(): PrintWriter = {
- w = new PrintWriter(new GZIPOutputStream(s));
- return w
- }
- override def setContentLength(i: Int) = {} // ignoring content length as it wont be the same when gzipped
- }
- super.handle(req, response)
+ ScalatraBase onCompleted { _ => response.addHeader("Content-Encoding", "gzip") }
- if (w != null) {
- response.addHeader("Content-Encoding", "gzip")
-
- w.flush
- w.close
+ ScalatraBase onRenderedCompleted { _ =>
+ w.flush()
+ w.close()
+ }
- response.setContentLength(s.length)
+ withRequestResponse(req, response) { super.handle(req, response) }
+ } else {
+ super.handle(req, res)
}
- } else {
- super.handle(req, res)
}
}
/**
* Returns true if Accept-Encoding contains gzip.
*/
- private def isGzip(request: HttpServletRequest): Boolean = {
- val encoding: java.util.Enumeration[_] = request.getHeaders("Accept-Encoding")
-
- while (encoding.hasMoreElements) {
- if (encoding.nextElement().toString.contains("gzip")) {
- return true
- }
- }
-
- return false
- }
-}
-
-/**
- * Wrapper output stream that counts the content length.
- */
-private class ContentLengthOutputStream(s: OutputStream) extends OutputStream {
- var length = 0
- private val stream = s
-
- override def write(b: Int) = {
- stream.write(b)
- length += 1
+ private[this] def isGzip(implicit request: HttpServletRequest): Boolean = {
+ import util.RicherString._
+ request.header("Accept-Encoding").flatMap(_.blankOption.map(_.toUpperCase)) == Some("GZIP")
}
}
View
159 core/src/main/scala/org/scalatra/ScalatraBase.scala
@@ -9,10 +9,11 @@ import scala.annotation.tailrec
import util._
import util.RicherString._
import rl.UrlCodingUtils
-import javax.servlet.ServletContext
-import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
+import javax.servlet.{Filter, ServletContext}
+import javax.servlet.http.{HttpServlet, HttpServletRequest, HttpServletResponse}
import scala.util.control.Exception._
import org.scalatra.ScalatraBase._
+import scala.util.{Failure, Try, Success}
object UriDecoder {
def firstStep(uri: String) = UrlCodingUtils.urlDecode(UrlCodingUtils.ensureUrlEncoding(uri), toSkip = PathPatternParser.PathReservedCharacters)
@@ -32,6 +33,38 @@ object ScalatraBase {
val PortKey = "org.scalatra.Port"
val ForceHttpsKey = "org.scalatra.ForceHttps"
+ private[this] val KeyPrefix = classOf[FutureSupport].getName
+ val Callbacks = s"$KeyPrefix.callbacks"
+ val RenderCallbacks = s"$KeyPrefix.renderCallbacks"
+ val IsAsyncKey = s"$KeyPrefix.isAsync"
+
+ import servlet.ServletApiImplicits._
+ def isAsyncResponse(implicit request: HttpServletRequest) = request.get(IsAsyncKey).getOrElse(false)
+
+ def onSuccess(fn: Any => Unit)(implicit request: HttpServletRequest) = addCallback(_.foreach(fn))
+ def onFailure(fn: Throwable => Unit)(implicit request: HttpServletRequest) = addCallback(_.failed.foreach(fn))
+ def onCompleted(fn: Try[Any] => Unit)(implicit request: HttpServletRequest) = addCallback(fn)
+ def onRenderedSuccess(fn: Any => Unit)(implicit request: HttpServletRequest) = addRenderCallback(_.foreach(fn))
+ def onRenderedFailure(fn: Throwable => Unit)(implicit request: HttpServletRequest) = addRenderCallback(_.failed.foreach(fn))
+ def onRenderedCompleted(fn: Try[Any] => Unit)(implicit request: HttpServletRequest) = addRenderCallback(fn)
+
+ def callbacks(implicit request: HttpServletRequest) =
+ request.getOrElse(Callbacks, List.empty[Try[Any] => Unit]).asInstanceOf[List[Try[Any] => Unit]]
+
+ def addCallback(callback: Try[Any] => Unit)(implicit request: HttpServletRequest) {
+ request(Callbacks) = callback :: callbacks
+ }
+
+ def runCallbacks(data: Try[Any])(implicit request: HttpServletRequest) = callbacks.reverse foreach (_(data))
+ def renderCallbacks(implicit request: HttpServletRequest) =
+ request.getOrElse(RenderCallbacks, List.empty[Try[Any] => Unit]).asInstanceOf[List[Try[Any] => Unit]]
+
+ def addRenderCallback(callback: Try[Any] => Unit)(implicit request: HttpServletRequest) {
+ request(RenderCallbacks) = callback :: callbacks
+ }
+
+ def runRenderCallbacks(data: Try[Any])(implicit request: HttpServletRequest) = renderCallbacks.reverse foreach (_(data))
+
import collection.JavaConverters._
def getServletRegistration(app: ScalatraBase) = {
@@ -106,39 +139,63 @@ trait ScalatraBase extends ScalatraContext with CoreDsl with DynamicScope with I
* $ 5. The action result is passed to `renderResponse`.
*/
protected def executeRoutes() {
+
var result: Any = null
- try {
- val prehandleException = request.get("org.scalatra.PrehandleException")
+ var rendered = true
+
+ def runActions = {
+ val prehandleException = request.get(PrehandleExceptionKey)
if (prehandleException.isEmpty) {
+ val (rq, rs) = (request, response)
+ onCompleted { _ =>
+ withRequestResponse(rq, rs) {
+ this match {
+ case f: Filter if !rq.contains("org.scalatra.ScalatraFilter.afterFilters.Run") =>
+ rq("org.scalatra.ScalatraFilter.afterFilters.Run") = new {}
+ runFilters(routes.afterFilters)
+ case f: HttpServlet if !rq.contains("org.scalatra.ScalatraServlet.afterFilters.Run") =>
+ rq("org.scalatra.ScalatraServlet.afterFilters.Run") = new {}
+ runFilters(routes.afterFilters)
+ case _ =>
+ }
+ }
+ }
runFilters(routes.beforeFilters)
val actionResult = runRoutes(routes(request.requestMethod)).headOption
// Give the status code handler a chance to override the actionResult
- result = handleStatusCode(status) getOrElse {
+ val r = handleStatusCode(status) getOrElse {
actionResult orElse matchOtherMethods() getOrElse doNotFound()
}
+ rendered = false
+ r
} else {
throw prehandleException.get.asInstanceOf[Exception]
}
}
- catch {
+
+ cradleHalt(result = runActions, e => {
+ cradleHalt({
+ result = errorHandler(e)
+ rendered = false
+ }, e => {
+ runCallbacks(Failure(e))
+ renderUncaughtException(e)
+ runRenderCallbacks(Failure(e))
+ })
+ })
+
+ if (!rendered) renderResponse(result)
+ }
+
+ private[this] def cradleHalt(body: => Any, error: Throwable => Any) = {
+ try { body } catch {
case e: HaltException => renderHaltException(e)
- case e: Throwable => {
- try {
- result = errorHandler(e)
- } catch {
- case e2: HaltException => renderHaltException(e2)
- }
- }
+ case e: Throwable => error(e)
}
- finally {
- if (result == null || !isAsyncExecutable(result)) {
- runFilters(routes.afterFilters)
- }
- }
-
- renderResponse(result)
}
+ private[scalatra] def renderUncaughtException(e: Throwable)(implicit request: HttpServletRequest, response: HttpServletResponse) { throw e }
+
protected def isAsyncExecutable(result: Any) = false
/**
@@ -164,12 +221,15 @@ trait ScalatraBase extends ScalatraContext with CoreDsl with DynamicScope with I
} yield (actionResult, matchedRoute)
result map {
case (actionResult, matchedRoute) =>
+ request("org.scalatra.MatchedRoute") = matchedRoute
setMultiparams(Some(matchedRoute), multiParams)
actionResult
}
- result.map(_._1)
}
+ private[scalatra] def matchedRoute(implicit request: HttpServletRequest) =
+ request.get("org.scalatra.MatchedRoute").map(_.asInstanceOf[MatchedRoute])
+
/**
* Invokes a route or filter. The multiParams gathered from the route
* matchers are merged into the existing route params, and then the action
@@ -265,7 +325,7 @@ trait ScalatraBase extends ScalatraContext with CoreDsl with DynamicScope with I
}
- def setMultiparams[S](matchedRoute: Option[MatchedRoute], originalParams: MultiParams) {
+ protected def setMultiparams[S](matchedRoute: Option[MatchedRoute], originalParams: MultiParams)(implicit request: HttpServletRequest) {
val routeParams = matchedRoute.map(_.multiParams).getOrElse(Map.empty).map {
case (key, values) =>
key -> values.map(UriDecoder.secondStep(_))
@@ -314,11 +374,16 @@ trait ScalatraBase extends ScalatraContext with CoreDsl with DynamicScope with I
* @see #renderPipeline
*/
protected def renderResponseBody(actionResult: Any) {
- @tailrec def loop(ar: Any): Any = ar match {
- case _: Unit | Unit =>
- case a => loop(renderPipeline.lift(a) getOrElse())
+ try {
+ runCallbacks(Success(actionResult))
+ @tailrec def loop(ar: Any): Any = ar match {
+ case _: Unit | Unit => runRenderCallbacks(Success(actionResult))
+ case a => loop(renderPipeline.lift(a) getOrElse())
+ }
+ loop(actionResult)
+ } catch {
+ case e: Throwable => renderUncaughtException(e)
}
- loop(actionResult)
}
/**
@@ -349,6 +414,7 @@ trait ScalatraBase extends ScalatraContext with CoreDsl with DynamicScope with I
using(new FileInputStream(file)) {
in => zeroCopy(in, response.outputStream)
}
+ // If an action returns Unit, it assumes responsibility for the response
case _: Unit | Unit | null =>
// If an action returns Unit, it assumes responsibility for the response
case ActionResult(ResponseStatus(404, _), _: Unit | Unit, _) => doNotFound()
@@ -403,21 +469,27 @@ trait ScalatraBase extends ScalatraContext with CoreDsl with DynamicScope with I
new BooleanBlockRouteMatcher(block)
protected def renderHaltException(e: HaltException) {
- var rendered = false
- e match {
- case HaltException(Some(404), _, _, _: Unit | Unit) | HaltException(_, _, _, ActionResult(ResponseStatus(404, _), _: Unit | Unit, _)) =>
- renderResponse(doNotFound())
- rendered = true
- case HaltException(Some(status), Some(reason), _, _) =>
- response.status = ResponseStatus(status, reason)
- case HaltException(Some(status), None, _, _) =>
- response.status = ResponseStatus(status)
- case HaltException(None, _, _, _) => // leave status line alone
- }
- e.headers foreach {
- case (name, value) => response.addHeader(name, value)
+ try {
+ var rendered = false
+ e match {
+ case HaltException(Some(404), _, _, _: Unit | Unit) | HaltException(_, _, _, ActionResult(ResponseStatus(404, _), _: Unit | Unit, _)) =>
+ renderResponse(doNotFound())
+ rendered = true
+ case HaltException(Some(status), Some(reason), _, _) =>
+ response.status = ResponseStatus(status, reason)
+ case HaltException(Some(status), None, _, _) =>
+ response.status = ResponseStatus(status)
+ case HaltException(None, _, _, _) => // leave status line alone
+ }
+ e.headers foreach {
+ case (name, value) => response.addHeader(name, value)
+ }
+ if (!rendered) renderResponse(e.body)
+ } catch {
+ case e: Throwable =>
+ runCallbacks(Failure(e))
+ renderUncaughtException(e)
}
- if (!rendered) renderResponse(e.body)
}
def get(transformers: RouteTransformer*)(action: => Any) = addRoute(Get, transformers, action)
@@ -450,7 +522,7 @@ trait ScalatraBase extends ScalatraContext with CoreDsl with DynamicScope with I
* @see org.scalatra.ScalatraKernel#removeRoute
*/
protected def addRoute(method: HttpMethod, transformers: Seq[RouteTransformer], action: => Any): Route = {
- val route = Route(transformers, () => action, () => routeBasePath)
+ val route = Route(transformers, () => action, (req: HttpServletRequest) => routeBasePath(req))
routes.prependRoute(method, route)
route
}
@@ -470,7 +542,7 @@ trait ScalatraBase extends ScalatraContext with CoreDsl with DynamicScope with I
}
protected[scalatra] def addStatusRoute(codes: Range, action: => Any) {
- val route = Route(Seq.empty, () => action, () => routeBasePath)
+ val route = Route(Seq.empty, () => action, (req: HttpServletRequest) => routeBasePath(req))
routes.addStatusRoute(codes, route)
}
@@ -573,8 +645,7 @@ trait ScalatraBase extends ScalatraContext with CoreDsl with DynamicScope with I
* Sends a redirect response and immediately halts the current action.
*/
def redirect(uri: String)(implicit request: HttpServletRequest, response: HttpServletResponse) {
- response.redirect(fullUrl(uri, includeServletPath = false))
- halt()
+ halt(Found(fullUrl(uri, includeServletPath = false)))
}
/**
View
69 core/src/main/scala/org/scalatra/ScalatraServlet.scala
@@ -4,33 +4,27 @@ import servlet.ServletBase
import javax.servlet._
import javax.servlet.http._
import org.scalatra.util.RicherString._
-import java.io.File
+import scala.util.control.Exception.catching
object ScalatraServlet {
import servlet.ServletApiImplicits._
def requestPath(request: HttpServletRequest) = {
- def getRequestPath = request.getRequestURI match {
- case requestURI: String =>
- var uri = requestURI
- if (request.getContextPath != null && request.getContextPath.trim.nonEmpty) uri = uri.substring(request.getContextPath.length)
- if (request.getServletPath != null && request.getServletPath.trim.nonEmpty) uri = uri.substring(request.getServletPath.length)
- if (uri.isEmpty) {
- uri = "/"
- } else {
- val pos = uri.indexOf(';')
- if (pos >= 0) uri = uri.substring(0, pos)
- }
- UriDecoder.firstStep(uri)
- case null => "/"
+ require(request != null, "The request can't be null for getting the request path")
+ def getRequestPath(r: HttpServletRequest) = {
+ val u = (catching(classOf[NullPointerException]) opt { r.getRequestURI } getOrElse "/")
+ val u2 = (u.blankOption map { uri =>
+ val uu = if (r.getContextPath.nonBlank) uri.substring(r.getContextPath.length) else uri
+ if (r.getServletPath.nonBlank) uu.substring(r.getServletPath.length) else uu
+ } flatMap(_.blankOption) getOrElse "/")
+ val pos = u2.indexOf(';')
+ val u3 = if (pos > -1) u2.substring(0, pos) else u2
+ UriDecoder.firstStep(u3)
}
- request.get("org.scalatra.ScalatraServlet.requestPath") match {
- case Some(uri) => uri.toString
- case _ => {
- val requestPath = getRequestPath
- request.setAttribute("org.scalatra.ScalatraServlet.requestPath", requestPath)
- requestPath.toString
- }
+ request.get("org.scalatra.ScalatraServlet.requestPath") map (_.toString) getOrElse {
+ val requestPath = getRequestPath(request)
+ request("org.scalatra.ScalatraServlet.requestPath") = requestPath
+ requestPath
}
}
}
@@ -66,36 +60,13 @@ abstract class ScalatraServlet
* All other servlet mappings likely want to return request.getServletPath.
* Custom implementations are allowed for unusual cases.
*/
- def requestPath(implicit request: HttpServletRequest) = {
- def getRequestPath = request.getRequestURI match {
- case requestURI: String =>
- var uri = requestURI
- if (request.getContextPath != null && request.getContextPath.trim.nonEmpty) uri = uri.substring(request.getContextPath.length)
- if (request.getServletPath != null && request.getServletPath.trim.nonEmpty) uri = uri.substring(request.getServletPath.length)
- if (uri.isEmpty) {
- uri = "/"
- } else {
- val pos = uri.indexOf(';')
- if (pos >= 0) uri = uri.substring(0, pos)
- }
- UriDecoder.firstStep(uri)
- case null => "/"
- }
-
- request.get("org.scalatra.ScalatraServlet.requestPath") match {
- case Some(uri) => uri.toString
- case _ => {
- val requestPath = getRequestPath
- request.setAttribute("org.scalatra.ScalatraServlet.requestPath", requestPath)
- requestPath.toString
- }
- }
- }
+ def requestPath(implicit request: HttpServletRequest) = ScalatraServlet.requestPath(request)
protected def routeBasePath(implicit request: HttpServletRequest) = {
- if (request == null)
- throw new IllegalStateException("routeBasePath requires an active request to determine the servlet path")
- request.getContextPath + request.getServletPath
+ require(config != null, "routeBasePath requires the servlet to be initialized")
+ require(request != null, "routeBasePath requires an active request to determine the servlet path")
+
+ servletContext.getContextPath + request.getServletPath
}
/**
View
11 core/src/main/scala/org/scalatra/UrlGenerator.scala
@@ -1,5 +1,7 @@
package org.scalatra
+import javax.servlet.http.HttpServletRequest
+
/**
* Adds support for generating URIs from routes and their params.
*/
@@ -15,7 +17,7 @@ trait UrlGeneratorSupport {
* @throws IllegalStateException if the route's base path cannot be
* determined. This may occur outside of an HTTP request's lifecycle.
*/
- def url(route: Route, params: Pair[String, String]*): String =
+ def url(route: Route, params: Pair[String, String]*)(implicit req: HttpServletRequest): String =
url(route, params.toMap, Seq.empty)
/**
@@ -29,7 +31,7 @@ trait UrlGeneratorSupport {
* @throws IllegalStateException if the route's base path cannot be
* determined. This may occur outside of an HTTP request's lifecycle.
*/
- def url(route: Route, splat: String, moreSplats: String*): String =
+ def url(route: Route, splat: String, moreSplats: String*)(implicit req: HttpServletRequest): String =
url(route, Map[String, String](), splat +: moreSplats)
/**
@@ -47,9 +49,10 @@ trait UrlGeneratorSupport {
route: Route,
params: Map[String, String],
splats: Iterable[String]
- ): String =
+ )(implicit req: HttpServletRequest): String =
route.reversibleMatcher match {
- case Some(matcher: ReversibleRouteMatcher) => route.contextPath() + matcher.reverse(params, splats.toList)
+ case Some(matcher: ReversibleRouteMatcher) =>
+ route.contextPath(req) + matcher.reverse(params, splats.toList)
case _ =>
throw new Exception("Route \"%s\" is not reversible" format (route))
}
View
32 core/src/main/scala/org/scalatra/flashMap.scala
@@ -135,28 +135,34 @@ trait FlashMapSupport extends Handler {
abstract override def handle(req: HttpServletRequest, res: HttpServletResponse) {
withRequest(req) {
val f = flash
- val isOutermost = !req.contains(LockKey)
+ val isOutermost = !request.contains(LockKey)
+
+ ScalatraBase onCompleted { _ =>
+ /*
+ * http://github.com/scalatra/scalatra/issues/41
+ * http://github.com/scalatra/scalatra/issues/57
+ *
+ * Only the outermost FlashMapSupport sweeps it at the end.
+ * This deals with both nested filters and redirects to other servlets.
+ */
+ if (isOutermost) {
+ f.sweep()
+ }
+ flashMapSetSession(f)
+ }
+
if (isOutermost) {
req(LockKey) = "locked"
if (sweepUnusedFlashEntries(req)) {
f.flag()
}
}
+
+
super.handle(req, res)
- /*
- * http://github.com/scalatra/scalatra/issues/41
- * http://github.com/scalatra/scalatra/issues/57
- *
- * Only the outermost FlashMapSupport sweeps it at the end.
- * This deals with both nested filters and redirects to other servlets.
- */
- if (isOutermost) {
- f.sweep()
- }
- flashMapSetSession(f)
}
}
-
+
/**
* Override to implement custom session retriever, or sanity checks if session is still active
* @param f
View
1 core/src/main/scala/org/scalatra/package.scala
@@ -26,6 +26,7 @@ package object scalatra
val EnvironmentKey = "org.scalatra.environment"
val MultiParamsKey = "org.scalatra.MultiParams"
+
@deprecated("Use org.scalatra.servlet.ServletBase if you depend on the Servlet API, or org.scalatra.ScalatraBase if you don't.", "2.1.0")
type ScalatraKernel = servlet.ServletBase
View
7 core/src/main/scala/org/scalatra/route.scala
@@ -2,6 +2,7 @@ package org.scalatra
import util.MultiMap
import javax.activation.MimetypesFileTypeMap
+import javax.servlet.http.HttpServletRequest
/**
* A route is a set of matchers and an action. A route is considered to match
@@ -12,7 +13,7 @@ import javax.activation.MimetypesFileTypeMap
case class Route(
routeMatchers: Seq[RouteMatcher] = Seq.empty,
action: Action,
- contextPath: () => String = () => "",
+ contextPath: HttpServletRequest => String = _ => "",
metadata: Map[Symbol, Any] = Map.empty
)
{
@@ -49,9 +50,9 @@ case class Route(
object Route {
def apply(transformers: Seq[RouteTransformer], action: Action): Route =
- apply(transformers, action, () => "")
+ apply(transformers, action, (_: HttpServletRequest) => "")
- def apply(transformers: Seq[RouteTransformer], action: Action, contextPath: () => String): Route = {
+ def apply(transformers: Seq[RouteTransformer], action: Action, contextPath: HttpServletRequest => String): Route = {
val route = Route(action = action, contextPath = contextPath)
transformers.foldLeft(route){ (route, transformer) => transformer(route) }
}
View
27 core/src/test/scala/org/scalatra/FilterTest.scala
@@ -2,7 +2,9 @@ package org.scalatra
import org.scalatest.BeforeAndAfterEach
import test.scalatest.ScalatraFunSuite
+import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
+class ScalatraExpectedFilterException extends RuntimeException
class FilterTestServlet extends ScalatraServlet {
var beforeCount = 0
var afterCount = 0
@@ -18,19 +20,24 @@ class FilterTestServlet extends ScalatraServlet {
after() {
afterCount += 1
params.get("after") match {
- case Some(x) => response.getWriter.write(x)
+ case Some(x) =>
+ response.getWriter.write(x)
case None =>
}
}
get("/") {}
- get("/before-counter") { beforeCount.toString }
+ get("/before-counter") {
+ beforeCount.toString
+ }
- get("/after-counter") { afterCount.toString }
+ get("/after-counter") {
+ afterCount.toString
+ }
get("/demons-be-here") {
- throw new RuntimeException
+ throw new ScalatraExpectedFilterException
}
post("/reset-counters") {
@@ -53,28 +60,30 @@ class FilterTestFilter extends ScalatraFilter {
beforeCount = 0
pass
}
+
}
class MultipleFilterTestServlet extends ScalatraServlet {
before() {
- response.getWriter.print("one\n")
+ response.writer.print("one\n")
}
before() {
- response.getWriter.print("two\n")
+ response.writer.print("two\n")
}
get("/") {
- response.getWriter.print("three\n")
+ response.writer.print("three\n")
}
after() {
- response.getWriter.print("four\n")
+ response.writer.print("four\n")
}
after() {
- response.getWriter.print("five\n")
+ response.writer.print("five\n")
}
+
}
class FilterTest extends ScalatraFunSuite with BeforeAndAfterEach {
View
17 core/src/test/scala/org/scalatra/GZipSupportTest.scala
@@ -21,6 +21,11 @@ trait GZipSupportAppBase extends ScalatraBase with GZipSupport {
post("/") {
Helper.body
}
+
+ error {
+ case t =>
+ t.printStackTrace()
+ }
}
/**
@@ -37,13 +42,13 @@ abstract class GZipSupportTest extends ScalatraFunSuite with ShouldMatchers {
get("/", Seq.empty, Map("Accept-Encoding" -> "gzip")) {
header("Content-Encoding") should include("gzip")
val uncompressed = Helper.uncompress(response.bodyBytes)
- uncompressed should equal(Helper.body);
+ uncompressed should equal(Helper.body)
}
post("/", Seq.empty, Map("Accept-Encoding" -> "gzip")) {
header("Content-Encoding") should include("gzip")
val uncompressed = Helper.uncompress(response.bodyBytes)
- uncompressed should equal(Helper.body);
+ uncompressed should equal(Helper.body)
}
}
}
@@ -53,13 +58,13 @@ abstract class GZipSupportTest extends ScalatraFunSuite with ShouldMatchers {
get("/") {
val contentEncoding = response.getHeader("Content-Encoding")
assert(contentEncoding == null || !contentEncoding.contains("gzip"))
- body should equal(Helper.body);
+ body should equal(Helper.body)
}
post("/") {
val contentEncoding = response.getHeader("Content-Encoding")
assert(contentEncoding == null || !contentEncoding.contains("gzip"))
- body should equal(Helper.body);
+ body should equal(Helper.body)
}
}
}
@@ -75,15 +80,15 @@ private object Helper {
* Uncompresses an array to a string with gzip.
*/
def uncompress(bytes: Array[Byte]): String = {
- return convertStreamToString(new GZIPInputStream(new ByteArrayInputStream(bytes)))
+ convertStreamToString(new GZIPInputStream(new ByteArrayInputStream(bytes)))
}
/**
* Returns a string with the content from the input stream.
*/
private def convertStreamToString(is: InputStream): String = {
val scanner = new java.util.Scanner(is, "UTF-8").useDelimiter("\\A")
- if (scanner.hasNext()) {
+ if (scanner.hasNext) {
scanner.next()
} else {
""
View
2 project/build.scala
@@ -277,7 +277,7 @@ object ScalatraBuild extends Build {
lazy val logbackClassic = "ch.qos.logback" % "logback-classic" % "1.0.9"
lazy val mimeUtil = "eu.medsea.mimeutil" % "mime-util" % "2.1.3" exclude("org.slf4j", "slf4j-log4j12") exclude("log4j", "log4j")
lazy val mockitoAll = "org.mockito" % "mockito-all" % "1.9.5"
- lazy val rl = "org.scalatra.rl" %% "rl" % "0.4.2"
+ lazy val rl = "org.scalatra.rl" %% "rl" % "0.4.3"
lazy val scalajCollection = "org.scalaj" %% "scalaj-collection" % "1.2"
lazy val scalate: MM = sv => "org.fusesource.scalate" % scalateArtifact(sv) % scalateVersion(sv)
lazy val scalatest: MM = sv => "org.scalatest" %% "scalatest" % scalatestVersion(sv)
View
26 scalate/src/main/scala/org/scalatra/scalate/ScalateSupport.scala
@@ -75,17 +75,6 @@ trait ScalateSupport extends ScalatraKernel {
*/
trait ScalatraTemplateEngine {
this: TemplateEngine =>
- /**
- * Returns a ServletRenderContext constructed from the current
- * request and response.
- */
- override def createRenderContext(uri: String, out: PrintWriter) = {
- val ctx = ScalateSupport.this.createRenderContext(out = out)
- ScalateSupport.this.templateAttributes foreach {
- case (name, value) => ctx.setAttribute(name, Some(value))
- }
- ctx
- }
/**
* Delegates to the ScalatraKernel's isDevelopmentMode flag.
@@ -107,8 +96,9 @@ trait ScalateSupport extends ScalatraKernel {
* If you return something other than a ScalatraRenderContext, you will
* also want to redefine that binding.
*/
- protected def createRenderContext(req: HttpServletRequest = request, resp: HttpServletResponse = response, out: PrintWriter = response.getWriter): RenderContext =
+ protected def createRenderContext(req: HttpServletRequest = request, resp: HttpServletResponse = response, out: PrintWriter = response.getWriter): RenderContext = {
new ScalatraRenderContext(this, templateEngine, out, req, resp)
+ }
/**
* Creates a render context and renders directly to that. No template
@@ -136,9 +126,15 @@ trait ScalateSupport extends ScalatraKernel {
}
}
+
+ override private[scalatra] def renderUncaughtException(e: Throwable)(implicit request: HttpServletRequest, response: HttpServletResponse) {
+ if (isScalateErrorPageEnabled) renderScalateErrorPage(request, response, e)
+ else super.renderUncaughtException(e)
+ }
+
// Hack: Have to pass it the request and response, because we're outside the
// scope of the super handler.
- private def renderScalateErrorPage(req: HttpServletRequest, resp: HttpServletResponse, e: Throwable) = {
+ private[this] def renderScalateErrorPage(req: HttpServletRequest, resp: HttpServletResponse, e: Throwable) = {
resp.setStatus(500)
resp.setContentType("text/html")
val errorPage = templateEngine.load("/WEB-INF/scalate/errors/500.scaml")
@@ -155,7 +151,7 @@ trait ScalateSupport extends ScalatraKernel {
/**
* The default template format.
*/
- protected def defaultTemplateFormat: String = "scaml"
+ protected def defaultTemplateFormat: String = "jade"
/**
* The default path to search for templates. Left as a def so it can be
@@ -236,7 +232,7 @@ trait ScalateSupport extends ScalatraKernel {
* @param attributes Attributes to path to the render context. Disable
* layouts by passing `layout -> ""`.
*/
- protected def layoutTemplate(path: String, attributes: (String, Any)*): String =
+ protected def layoutTemplate(path: String, attributes: (String, Any)*)(implicit request: HttpServletRequest, response: HttpServletResponse): String =
layoutTemplateAs(templateEngine.extensions)(path, attributes :_*)
/**
View
6 scalate/src/main/scala/org/scalatra/scalate/ScalateUrlGeneratorSupport.scala
@@ -19,17 +19,17 @@ trait ScalateUrlGeneratorSupport extends ScalateSupport {
override protected def createTemplateEngine(config: ConfigT) = {
val engine = super.createTemplateEngine(config)
- val generatorBinding = Binding("urlGenerator", classOf[UrlGeneratorSupport].getName, true)
+// val generatorBinding = Binding("urlGenerator", classOf[UrlGeneratorSupport].getName, true)
val routeBindings = this.reflectRoutes.keys map (Binding(_, classOf[Route].getName))
- engine.bindings = generatorBinding :: engine.bindings ::: routeBindings.toList
+ engine.bindings = engine.bindings ::: routeBindings.toList
engine
}
override protected def createRenderContext(req: HttpServletRequest = request, res: HttpServletResponse = response, out: PrintWriter = response.getWriter) = {
val context = super.createRenderContext(req, res, out)
for ((name, route) <- this.reflectRoutes)
context.attributes.update(name, route)
- context.attributes.update("urlGenerator", UrlGenerator)
+// context.attributes.update("urlGenerator", UrlGenerator)
context
}
}
View
76 scalate/src/main/scala/org/scalatra/scalate/ScalatraRenderContext.scala
@@ -7,6 +7,8 @@ import java.io.PrintWriter
import javax.servlet.http.{HttpServletResponse, HttpServletRequest, HttpSession}
import org.fusesource.scalate.TemplateEngine
import org.fusesource.scalate.servlet.ServletRenderContext
+import javax.servlet.ServletContext
+import servlet.ServletApiImplicits._
/**
* A render context integrated with Scalatra. Exposes a few extra
@@ -70,4 +72,78 @@ class ScalatraRenderContext(
case csrfTokenSupport: XsrfTokenSupport => csrfTokenSupport.xsrfToken(request)
case _ => ""
}
+
+ /**
+ * Calculate a URL for a reversible route and some params.
+ *
+ * @param route a reversible route
+ * @param params a list of named param/value pairs
+ * @return a URI that matches the route for the given params
+ * @throws Exception if the route is not reversible
+ * @throws IllegalStateException if the route's base path cannot be
+ * determined. This may occur outside of an HTTP request's lifecycle.
+ */
+ def url(route: Route, params: Pair[String, String]*): String =
+ url(route, params.toMap, Seq.empty)
+
+ /**
+ * Calculate a URL for a reversible route and some splats.
+ *
+ * @param route a reversible route
+ * @param splat the first splat parameter
+ * @param moreSplats any splat parameters beyond the first
+ * @return a URI that matches the route for the given splats
+ * @throws Exception if the route is not reversible
+ * @throws IllegalStateException if the route's base path cannot be
+ * determined. This may occur outside of an HTTP request's lifecycle.
+ */
+ def url(route: Route, splat: String, moreSplats: String*): String =
+ url(route, Map[String, String](), splat +: moreSplats)
+
+ /**
+ * Calculate a URL for a reversible route, some params, and some splats.
+ *
+ * @param route a reversible route
+ * @param params a map of param/value pairs
+ * @param splats a series of splat parameters
+ * @return a URI that matches the route for the given splats
+ * @throws Exception if the route is not reversible
+ * @throws IllegalStateException if the route's base path cannot be
+ * determined. This may occur outside of an HTTP request's lifecycle.
+ */
+ def url(
+ route: Route,
+ params: Map[String, String],
+ splats: Iterable[String]
+ ): String = {
+
+ route.reversibleMatcher match {
+ case Some(matcher: ReversibleRouteMatcher) =>
+ withRouteMultiParams(MatchedRoute(route.action, multiParams)) {
+ println("multiparams " + multiParams)
+ route.contextPath(request) + matcher.reverse(params, splats.toList)
+ }
+ case _ =>
+ throw new Exception("Route \"%s\" is not reversible" format (route))
+ }
+ }
+
+
+ private[this] def withRouteMultiParams[S](matchedRoute: MatchedRoute)(thunk: => S): S = {
+ val originalParams = multiParams
+ setMultiparams(matchedRoute, originalParams)
+ try {
+ thunk
+ } finally {
+ request(MultiParamsKey) = originalParams
+ }
+ }
+
+ def setMultiparams[S](matchedRoute: MatchedRoute, originalParams: MultiParams) {
+ val routeParams = matchedRoute.multiParams map {
+ case (key, values) =>
+ key -> values.map(UriDecoder.secondStep(_))
+ }
+ request(MultiParamsKey) = originalParams ++ routeParams
+ }
}
View
271 scalate/src/test/scala/org/scalatra/scalate/ScalateFuturesSupportSpec.scala
@@ -0,0 +1,271 @@
+package org.scalatra.scalate
+
+import org.scalatra._
+import scala.concurrent.{ExecutionContext, Future}
+import test.specs2.{MutableScalatraSpec, ScalatraSpec}
+import org.fusesource.scalate.layout.DefaultLayoutStrategy
+import scala.concurrent.duration._
+import org.specs2.time.NoTimeConversions
+import java.util.concurrent.{ExecutorService, ThreadFactory, Executors}
+import org.specs2.specification.{Step, Fragments}
+
+class DaemonThreadFactory extends ThreadFactory {
+ def newThread(r: Runnable): Thread = {
+ val thread = new Thread(r)
+ thread setDaemon true
+ thread
+ }
+}
+
+object DaemonThreadFactory {
+ def newPool() = Executors.newCachedThreadPool(new DaemonThreadFactory)
+}
+
+class ScalateFuturesSupportServlet(exec: ExecutorService) extends ScalatraServlet with ScalateSupport with ScalateUrlGeneratorSupport with FlashMapSupport with FutureSupport {
+ protected implicit val executor = ExecutionContext.fromExecutorService(exec)
+
+ get("/barf") {
+ new AsyncResult { val is = Future { throw new RuntimeException } }
+ }
+
+ get("/happy-happy") {
+ new AsyncResult { val is = Future { "puppy dogs" } }
+ }
+
+ get("/simple-template") {
+ new AsyncResult { val is = Future { layoutTemplate("/simple.jade") } }
+ }
+
+ get("/params") {
+ new AsyncResult { val is = Future { layoutTemplate("/params.jade", "foo" -> "Configurable") } }
+ }
+
+ get("/jade-template") {
+ new AsyncResult { val is = Future { jade("simple") } }
+ }
+
+ get("/jade-params") {
+ new AsyncResult { val is = Future { jade("params", "foo" -> "Configurable") } }
+ }
+
+ get("/scaml-template") {
+ new AsyncResult { val is = Future { scaml("simple") } }
+ }
+
+ get("/scaml-params") {
+ new AsyncResult { val is = Future { scaml("params", "foo" -> "Configurable") } }
+ }
+
+ get("/ssp-template") {
+ new AsyncResult { val is = Future { ssp("simple") } }
+ }
+
+ get("/ssp-params") {
+ new AsyncResult { val is = Future { ssp("params", "foo" -> "Configurable") } }
+ }
+
+ get("/mustache-template") {
+ new AsyncResult { val is = Future { mustache("simple") } }
+ }
+
+ get("/mustache-params") {
+ new AsyncResult { val is = Future { mustache("params", "foo" -> "Configurable") } }
+ }
+
+ get("/layout-strategy") {
+ new AsyncResult { val is = Future { templateEngine.layoutStrategy.asInstanceOf[DefaultLayoutStrategy].defaultLayouts.sortWith(_<_) mkString ";" } }
+ }
+
+ val urlGeneration = get("/url-generation") {
+ new AsyncResult { val is = Future { layoutTemplate("/urlGeneration.jade") } }
+ }
+
+ val urlGenerationWithParams = get("/url-generation-with-params/:a/vs/:b") {
+
+ new AsyncResult { val is = Future {
+ println("Rendering reverse routing template")
+ layoutTemplate("/urlGenerationWithParams.jade", ("a" -> params("a")), ("b" -> params("b")))
+ } }
+ }
+
+ get("/legacy-view-path") {
+ new AsyncResult { val is = Future { jade("legacy") } }
+ }
+
+ get("/directory") {
+ new AsyncResult { val is = Future { jade("directory/index") } }
+ }
+
+ get("/bindings/*") {
+ new AsyncResult { val is =
+ Future {
+ flash.now("message") = "flash works"
+ session("message") = "session works"
+ jade(requestPath)
+ }
+ }
+ }
+
+ get("/bindings/params/:foo") {
+ new AsyncResult { val is = Future { jade("/bindings/params") } }
+ }
+
+ get("/bindings/multiParams/*/*") {
+ new AsyncResult { val is = Future { jade("/bindings/multiParams") } }
+ }
+
+ get("/template-attributes") {
+ new AsyncResult { val is =
+ Future {
+ templateAttributes("foo") = "from attributes"
+ scaml("params")
+ }
+ }
+ }
+
+ get("/render-to-string") {
+ new AsyncResult { val is = Future { response.setHeader("X-Template-Output", layoutTemplate("simple")) } }
+ }
+}
+
+class ScalateFuturesSupportSpec extends MutableScalatraSpec {
+ sequential
+ "ScalateSupport with Futures" should {
+ "render uncaught errors with 500.scaml" in e1
+ "not throw a NullPointerException for trivial requests" in e2
+ "render a simple template" in e3
+ "render a simple template with params" in e4
+ "looks for layouts in /WEB-INF/layouts" in e5
+ "generate a url from a template" in e6
+ "generate a url with params from a template" in e7
+ "render a simple template via jade method" in e8
+ "render a simple template with params via jade method" in e9
+ "render a simple template via scaml method" in e10
+ "render a simple template with params via scaml method" in e11
+ "render a simple template via ssp method" in e12
+ "render a simple template with params via ssp method" in e13
+ "render a simple template via mustache method" in e14
+ "render a simple template with params via mustache method" in e15
+ "looks for templates in legacy /WEB-INF/scalate/templates" in e16
+ "looks for index page if no template found" in e17
+ "implicitly bind flash" in e18
+ "implicitly bind session" in e19
+ "implicitly bind params" in e20
+ "implicitly bind multiParams" in e21
+ "set templateAttributes when creating a render context" in e22
+ "render to a string instead of response" in e23
+ }
+
+ val pool = DaemonThreadFactory.newPool()
+ addServlet(new ScalateFuturesSupportServlet(pool), "/*")
+
+
+ override def map(fs: => Fragments): Fragments = super.map(fs) ^ Step(pool.shutdown())
+
+ def e1 = get("/barf") {
+ status must_== 500
+ body must contain ("id=\"scalate-error\"")
+ }
+
+ def e2 = get("/happy-happy") {
+ body must_== "puppy dogs"
+ }
+
+ def e3 = get("/simple-template") {
+ body must_== "<div>Jade template</div>\n"
+ }
+
+ def e4 = get("/params") {
+ body must_== "<div>Configurable template</div>\n"
+ }
+
+ // Testing the default layouts is going to be hard, but we can at least
+ // verify that it's looking in the right place.
+ def e5 = get("/layout-strategy") {
+ body must_== (List(
+ "/WEB-INF/layouts/default.jade",
+ "/WEB-INF/layouts/default.mustache",
+ "/WEB-INF/layouts/default.scaml",
+ "/WEB-INF/layouts/default.ssp",
+ "/WEB-INF/scalate/layouts/default.jade",
+ "/WEB-INF/scalate/layouts/default.mustache",
+ "/WEB-INF/scalate/layouts/default.scaml",
+ "/WEB-INF/scalate/layouts/default.ssp"
+ ) mkString ";")
+ }
+
+ def e6 = get("/url-generation") {
+ body must_== "/url-generation\n"
+ }
+
+ def e7 = {
+ println("reverse route params")
+ get("/url-generation-with-params/jedi/vs/sith") {
+ body must_== "/url-generation-with-params/jedi/vs/sith\n"
+ }
+ }
+
+ def e8 = get("/jade-template") {
+ body must_== "<div>Jade template</div>\n"
+ }
+
+ def e9 = get("/jade-params") {
+ body must_== "<div>Configurable template</div>\n"
+ }
+
+ def e10 = get("/scaml-template") {
+ body must_== "<div>Scaml template</div>\n"
+ }
+
+ def e11 = get("/scaml-params") {
+ body must_== "<div>Configurable template</div>\n"
+ }
+
+ def e12 = get("/ssp-template") {
+ body must_== "<div>SSP template</div>\n"
+ }
+
+ def e13 = get("/ssp-params") {
+ body must_== "<div>Configurable template</div>\n"
+ }
+
+ def e14 = get("/mustache-template") {
+ body must_== "<div>Mustache template</div>\n"
+ }
+
+ def e15 = get("/mustache-params") {
+ body must_== "<div>Configurable template</div>\n"
+ }
+
+ def e16 = get("/legacy-view-path") {
+ body must_== "<p>legacy</p>\n"
+ }
+
+ def e17 = get("/directory") {
+ body must_== "<p>index</p>\n"
+ }
+
+ def e18 = get("/bindings/flash") {
+ body must_== "<div>flash works</div>\n"
+ }
+
+ def e19 = get("/bindings/session") {
+ body must_== "<div>session works</div>\n"
+ }
+
+ def e20 = get("/bindings/params/bar") {
+ body must_== "<div>bar</div>\n"
+ }
+
+ def e21 = get("/bindings/multiParams/bar/baz") {
+ body must_== "<div>bar;baz</div>\n"
+ }
+
+ def e22 = get("/template-attributes") {
+ body must_== "<div>from attributes template</div>\n"
+ }
+
+ def e23 = get("/render-to-string") {
+ header("X-Template-Output") must_== "<div>SSP template</div>"
+ }
+}
View
2 slf4j/src/main/scala/org/scalatra/slf4j/ScalatraSlf4jRequestLogging.scala
@@ -101,7 +101,7 @@ trait ScalatraSlf4jRequestLogging extends ScalatraBase with Handler {
try { logRequest() } catch { case _: Throwable => }
action
}
- val route = Route(transformers, newAction, () => routeBasePath)
+ val route = Route(transformers, newAction, (req: HttpServletRequest) => routeBasePath(req))
routes.prependRoute(method, route)
route
}
View
4 swagger/src/main/scala/org/scalatra/swagger/Swagger.scala
@@ -77,7 +77,9 @@ object Swagger {
else {
val docObj = ApiPropertiesReader.read(klass)
val name = docObj.getName
- val fields = for (field <- docObj.getFields.asScala.filter(d => d.paramType != null))
+ val flds = docObj.getFields.asScala.filter(d => d.paramType != null)
+
+ val fields = for (field <- flds)
yield (field.name -> ModelField(field.name, field.notes, DataType(field.paramType)))
Some(Model(name, name, fields.toMap))

0 comments on commit 4f7f91b

Please sign in to comment.
Something went wrong with that request. Please try again.