scalaxb-appengine

scalaxb-appengine is a RESTful API to run scalaxb over the web. It's implemented using n8han/Unfiltered and the full source is available on eed3si9n/scalaxb.

compile

URL:
http://scalaxb.appspot.com/compile/{output}.{format}
Formats:
scala, zip
HTTP method:
POST
HTTP encoding:
multipart/form-data
Parameters:
- arg: Optional, multiple allowed. URL of the schema file.
- argf: Optional, multiple allowed. Uploaded schema file.
- defaultPackageName: Optional. Name of the target package.
- namespaceURI: Optional, multiple allowed. URI of namespace to be packaged.
- packageName: Optional, multiple allowed. Name of the target package for the namespaceURI.
- classPrefix: Optional. Prefixes generated class names.
- paramPrefix: Optional. Prefixes generated parameter names.
- wrapContents: Optional, multiple allowed. Wraps inner contents into a seperate case class.
Notes:
- At least one arg or argf must be present.
- The n-th packageName specifies the target package for the n-th namespaceURI.

development notes

scalaxb-appengine came into being because I didn't know how to deploy a command line application written in Scala. Greg kindly suggested that I turn scalaxb into a web service, so I did. I guess I always thought about the possibility of making it into a web app, but this gave me a perfect opportunity to seize the moment and write a web app in Scala.

First I wrapped scalaxb using Play! framework with Scala module and Google App Engine module. Unfortunately, Scala module only works with Play 1.1-unstable and GAE module only works with 1.0-stable. I kind of learnt it the hard way after spending all day learning the framework and coding the thing, and then it blowing up when I deployed the app on App Engine. But, it was a fun experience using Play. No-deployment development makes the coding-checking loop tight, which I imagine Rails or JRebel would do. I wasn't keen on Groovy-based template language, which could be swapped with Scalate. Also, I'd be concerned with spin-up time if I were to put it on App Engine. I tried pinky next, but it didn't compile using Scala 2.8.0.

It dawned on me that I could write this service without mvc frameworks. Servlets won't bite you if all you need is REST. I implemented the servlet using NetBeans 6.9, but eventually I went back to TextMate and sbt using @yasushia's sbt-appengine-plugin.

import javax.servlet.http.{HttpServlet,
  HttpServletRequest => HSReqeust,
  HttpServletResponse => HSResponse}
 
class CompilerServlet extends HttpServlet {
  override def doPost(request : HSReqeust, response : HSResponse) {
    // grab the input from the params
    val args = request.getParameterValues("arg")
    ...    
 
    // call scalaxb
    val module = org.scalaxb.compiler.xsd.Driver
    module.processReaders(inputToOutput, packageNames)
 
    // return the output
    response.setContentType("application/octet-stream")
    response.setHeader("Content-disposition", "attachment; filename=\"" +
                       outFileName + "\"")
    response.getWriter.print(scalaFiles(0).content)
  }
}

The above is the basic structure of the servlet handing /compile/a.scala. I didn't bother implementing the web frontend part using jsp, since I could do that using just html and ajax somewhere else. There are several issues. One of them is that the validation logic tends to be intertwined with the output logic, which ideally should be abstracted out. Another is that the Servlet API is that of verbose Java style and does not feel Scala.

Then @n8han tweeted me

@eed3si9n You should try this thing, which is like that thing but better: http://github.com/n8han/unfiltered

It's dope. I didn't even know myself, but this was exactly what I needed. In short, it's the controller portion of mvc, expressed in Scala's pattern matching. Pattern matching makes it easy to write type-safe validations, and the built-in DSL expresses the responses concisely.

For example, here's how I was checking if "arg" is passed in:

val args = request.getParameterValues("arg")
if (args == null) {
  response.sendError(HSResponse.SC_BAD_REQUEST)
    return
}

You really don't want to see null or return when you're righting Scala. Here's the unfiltered version:

{
  case req @ POST(UFPath(Seg("compile" :: what :: Nil)) & Params(params)) =>
  params("arg") match {
    case Nil => BadRequest ~> ResponseString("missing arg.")
    case args => ...
  }
}

As you can see it's actually part of the routing logic to map "/compile/*". The complete source for CompilerFilter is:

package org.scalaxb.servlet
 
import org.scalaxb.servlet.model.{Driver, ScalaFile}
import java.net.{URL, MalformedURLException}
import java.io.{File}
import unfiltered.request._
import unfiltered.response._
import unfiltered.request.{Path => UFPath}
 
class CompilerFilter extends unfiltered.Planify ({
  case req @ POST(UFPath(Seg("compile" :: what :: Nil)) & Params(params)) => 
      params("arg") filter { "" != } match {
    case Nil => BadRequest ~> ResponseString("missing arg.")
    case args =>
      try {
        val urls = args map { new URL(_) }
        val defaultPackageName = params("defaultPackageName").headOption
        val scalaFiles = Driver.process(urls, defaultPackageName)
 
        ContentType("application/octet-stream") ~> 
        ResponseHeader("Content-disposition",
          Seq("attachment; filename=\"" + what + "\"")) ~>
        ("""([.]\w+)$""".r.findFirstIn(what) match {
          case Some(".scala") => ResponseString(scalaFiles.head.content)
          case Some(".zip") => ResponseBytes(ScalaFile.zip(scalaFiles))
          case _ => BadRequest ~> ResponseString("unsupported file extension.")
        })
      }
      catch {
        case e: MalformedURLException =>
          BadRequest ~> ResponseString(e.getMessage)
      }
  }
})

The interesting thing is that the above contains everything. web.xml will remain static once you set it up in the beginning and there are no other configurations. No template language processing either. For RESTful API implementations, Unfiltered is something to consider. I haven't tried it but there's unfiltered-template for starter. Now, it's softprops / unfiltered.g8.

Here are some of the links I consulted: