On Monday, September 30, 2013 5:36:53 AM UTC-4, Mathias wrote:Thanks, Kaspar!
Yes, thank you. That's helpful to see, and gosh I feel selfish now. Here's the current state of what I've come up with: I have a base class, then a class for each type of resource that needs to be (un)marshalled. There's a JSON marshaller, and an XML marshaller, and then an HTML marshaller that runs the output of the XML marshaller through an XSLT translation. Each subclass defines (1) the method toXml, invoked by the superclass XML marshaller, and which uses XML literals to define the XML marshalling; (2) a value member whose value is the name of the XSLT sheet to use; and (3) a member value, prepareHtmlTransformer, that is a function can be overridden to set parameters on the XSLT translation for things that are in the HTML but not the XML representation.
First the base class, Representation:
abstract class Representation[T] extends MyJsonProtocol with SprayJsonSupport {
val log = org.slf4j.LoggerFactory.getLogger(this.getClass)
val acceptedContentTypes = List[ContentType] (
`application/xhtml+xml`,
`application/xml`,
`text/xml`,
`application/json`
)
val xmlMarshaller = Marshaller.delegate[T, String](
`text/xml`,`application/xml`
) { (obj: T) => toXml(obj).toString }
protected def toXml(obj: T): scala.xml.Elem = <error/>
protected val xsltFilename: String = ""
protected val prepareHtmlTransformer: Function[XsltTransformer,Unit] = {
(x:XsltTransformer) =>
}
/** Is XHTML not HTML */
val htmlMarshaller: Marshaller[T] = Marshaller.delegate[T, String](
`application/xhtml+xml`
){ (obj,contentType) =>
val xmlReader = new PipedReader
val xmlWriter = new PipedWriter(xmlReader)
new Thread { override def run {
try {
XML.write(xmlWriter, toXml(obj), "UTF-8", false, null)
} catch {
case e: Exception => log.error(e.getMessage)
} finally {
xmlWriter.close()
}
}}.start
val xsltTransformer = xsltTransformerDef(xsltFilename)
prepareHtmlTransformer(xsltTransformer)
xsltTransformer.setSource(new javax.xml.transform.stream.StreamSource(xmlReader))
val stringWriter = new java.io.StringWriter()
xsltTransformer.setDestination(new Serializer(stringWriter))
xsltTransformer.transform()
stringWriter.toString
}
// Translation sheet will be reloaded on every request.
// Uncomment next line and delete following line for production use.
//private lazy val xsltTransformer: net.sf.saxon.s9api.XsltTransformer = {
def xsltTransformerDef(filename: String) = {
val processor = new net.sf.saxon.s9api.Processor(false)
val compiler = processor.newXsltCompiler
compiler.compile {
new javax.xml.transform.sax.SAXSource (
scala.xml.Source fromFile filename
)
} load()
}
val jsonMarshaller: Marshaller[T]
implicit val marshaller = Marshaller[T] {
(obj: T, ctx: MarshallingContext) =>
(ctx.tryAccept(acceptedContentTypes): @unchecked) match {
case Some(ContentType(`application/xhtml+xml`,_)) =>
htmlMarshaller(obj, ctx)
case Some(ContentType(`application/json`,_)) => jsonMarshaller(obj, ctx)
case Some(ContentType(`application/xml`,_)) | Some(ContentType(`text/xml`,_)) =>
xmlMarshaller(obj, ctx)
case _ => ctx.rejectMarshalling(acceptedContentTypes)
}
}
}
Then, for example, people requesting the root path of my site get a representation of the Index class.
import spray.json.BasicFormats
import collection.JavaConversions._
case class IndexEntry( name: String, foo: Int, etc... )
class Index { entries: Set[IndexEntry] = getEntries }
object Index extends Representation[Index] {
import spray.json._
def apply = new Index()
override def toXml(index: Index): scala.xml.Elem =
<index>{ index.entries.map(e =>
foo={e.foo}
/> )
}</index>
override val xsltFilename: String = "src/main/resources/index.xsl"
override val prepareHtmlTransformer = { (xsltTransformer: XsltTransformer) =>
xsltTransformer.setParameter (new QName("myParam"), paramValue)
}
val jsonMarshaller = sprayJsonMarshaller(
new spray.json.RootJsonWriter[Index] {
implicit val entryFormat = jsonFormat7(IndexEntry)
def write(index: Index) = index.entries.toList.toJson
}
)
}
I still have some implementing to do, but the general outline of a type that unmarshals so far is looking something like this:
case class Foo (
name : String,
date : DateTime
)
object Foo extends Representation[Foo] {
import spray.json._
import spray.httpx.unmarshalling._
implicit val fooJsonFormat = jsonFormat5(Foo.apply)
val jsonMarshaller = sprayJsonMarshaller(fooJsonFormat)
implicit val unmarshaller = Unmarshaller[Foo] (
`application/json`,`application/xml`,`application/x-www-form-urlencoded`
) {
case entity @ NonEmpty(ContentType(mediaType,charset), bytes) => mediaType match {
case `application/x-www-form-urlencoded` =>
case Right(formData) => formData.fields
case Left(ContentExpected) => throw new Exception("Missing content")
case Left(MalformedContent(message,cause)) => throw new Exception(message)
case Left(UnsupportedContentType(errorMessage)) => throw new Exception(errorMessage)
}
val name = formFields("name")
val date = formFields("date")
Foo(name, date)
case `application/json` => throw new Exception("json not implemented")
case `application/xml` => throw new Exception("xml not implemented")
case o => throw new Exception(s"Unrecognized media type $o")
}
}
}
--
Adam Mackler