~ refactoring + abstraction for asked resources, based on URI extension and Accept headers
authorAlexandre Bertails <bertails@gmail.com>
Sat, 15 Oct 2011 18:28:35 -0400
changeset 70 1a34982ea440
parent 69 3e84e72b6e82
child 71 753ae107492c
child 82 42e553469cef
~ refactoring + abstraction for asked resources, based on URI extension and Accept headers
src/main/scala/AcceptLang.scala
src/main/scala/Authoritative.scala
src/main/scala/Lang.scala
src/main/scala/Post.scala
src/main/scala/RequestLang.scala
src/main/scala/plan.scala
src/main/scala/util/FilterLogger.scala
src/main/scala/util/ResponseModel.scala
src/main/scala/util/ResponseResultSet.scala
src/main/scala/util/ViaSPARQL.scala
--- a/src/main/scala/AcceptLang.scala	Sat Oct 15 16:47:37 2011 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-package org.w3.readwriteweb
-
-import unfiltered.request._
-
-object AcceptLang {
-  
-  def unapply(req: HttpRequest[_]): Option[Lang] =
-    Accept(req) map Lang.apply collectFirst { case Some(lang) => lang }
-  
-  def apply(req: HttpRequest[_]): Option[Lang] =
-    unapply(req)
-
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/Authoritative.scala	Sat Oct 15 18:28:35 2011 -0400
@@ -0,0 +1,68 @@
+package org.w3.readwriteweb
+
+import unfiltered.request._
+import java.net.URL
+
+sealed trait Representation
+
+object Representation {
+  
+  def fromSuffix(suffix: String): Representation = {
+    suffix match {
+      case "n3" => RDFRepr(N3)
+      case "turtle" | "ttl" => RDFRepr(TURTLE)
+      case "rdf" => RDFRepr(RDFXML)
+      case "htm" | "html" | "xhtml" => HTMLRepr
+      case _ => UnknownRepr
+    }
+  }
+  
+  val htmlCharsets = Set("text/html", "application/xhtml+xml")
+  
+  def acceptsHTML(ct: Iterable[String]) =
+    ! (htmlCharsets & ct.toSet).isEmpty
+  
+  def fromAcceptedContentTypes(ct: Iterable[String]): Representation = {
+    Lang(ct) map RDFRepr.apply getOrElse {
+      if (acceptsHTML(ct))
+        HTMLRepr
+      else
+        UnknownRepr
+    }
+  }
+  
+  /** implements http://www.w3.org/2001/tag/doc/metaDataInURI-31 and http://www.w3.org/2001/tag/doc/mime-respect
+    * 
+    * if there is no known suffix (eg. the URI was already the authoritative one),
+    * inspects the given accepted content types
+    * 
+    * This knows only about the RDF and HTML charsets
+    */
+  def apply(
+      suffixOpt: Option[String],
+      ct: Iterable[String]): Representation = {
+    suffixOpt map fromSuffix match {
+      case None | Some(UnknownRepr) => fromAcceptedContentTypes(ct)
+      case Some(repr) => repr
+    }
+  }
+}
+
+case class RDFRepr(lang: Lang) extends Representation
+case object HTMLRepr extends Representation
+case object UnknownRepr extends Representation
+case object NoRepr extends Representation
+
+object Authoritative {
+  
+  val r = """^(.*)\.(\w{0,4})$""".r
+  
+  def unapply(req: HttpRequest[javax.servlet.http.HttpServletRequest]): Option[(URL, Representation)] = {
+    val uri = req.underlying.getRequestURL.toString
+    val suffixOpt = uri match {
+      case r(_, suffix) => Some(suffix)
+      case _ => None
+    }
+    Some((new URL(uri), Representation(suffixOpt, Accept(req))))
+  }
+}
--- a/src/main/scala/Lang.scala	Sat Oct 15 16:47:37 2011 -0400
+++ b/src/main/scala/Lang.scala	Sat Oct 15 18:28:35 2011 -0400
@@ -34,14 +34,8 @@
       case _ => None
   }
   
-  def unapply(contentType: String): Option[Lang] =
-    apply(contentType)
-
-  def apply(req: HttpRequest[_]): Option[Lang] =
-    RequestContentType(req) flatMap Lang.apply
-    
-  def unapply(req: HttpRequest[_]): Option[Lang] =
-    apply(req)
+  def apply(cts: Iterable[String]): Option[Lang] =
+    cts map Lang.apply collectFirst { case Some(lang) => lang }
     
 }
 
--- a/src/main/scala/Post.scala	Sat Oct 15 16:47:37 2011 -0400
+++ b/src/main/scala/Post.scala	Sat Oct 15 18:28:35 2011 -0400
@@ -68,7 +68,7 @@
     
     contentType match {
       case SPARQL => postUpdate | (postQuery | PostUnknown)
-      case Lang(lang) => postRDF(lang) | PostUnknown
+      case RequestLang(lang) => postRDF(lang) | PostUnknown
     }
 
   }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/RequestLang.scala	Sat Oct 15 18:28:35 2011 -0400
@@ -0,0 +1,16 @@
+package org.w3.readwriteweb
+
+import unfiltered.request._
+
+object RequestLang {
+  
+  def apply(req: HttpRequest[_]): Option[Lang] =
+    Lang(RequestContentType(req))
+
+  def unapply(req: HttpRequest[_]): Option[Lang] =
+    apply(req)
+
+  def unapply(ct: String): Option[Lang] =
+    Lang(ct)
+    
+}
--- a/src/main/scala/plan.scala	Sat Oct 15 16:47:37 2011 -0400
+++ b/src/main/scala/plan.scala	Sat Oct 15 18:28:35 2011 -0400
@@ -54,8 +54,8 @@
    */
   val plan = unfiltered.filter.Planify {
     case req @ Path(path) if path startsWith rm.basePath => {
-      val Authoritative(baseURI, _) = req
-      val r: Resource = rm.resource(baseURI)
+      val Authoritative(uri, representation) = req
+      val r: Resource = rm.resource(uri)
       req match {
         case GET(_) & Accept(accepts) if isHTML(accepts) => {
           val source = Source.fromFile("src/main/resources/skin.html")("UTF-8")
@@ -65,22 +65,25 @@
         case GET(_) | HEAD(_) =>
           for {
             model <- r.get() failMap { x => NotFound }
-            lang = AcceptLang(req) getOrElse Lang.default
+            lang = representation match {
+              case RDFRepr(l) => l
+              case _ => Lang.default
+            }
           } yield {
             req match {
-              case GET(_) => Ok ~> ViaSPARQL ~> ContentType(lang.contentType) ~> ResponseModel(model, baseURI, lang)
+              case GET(_) => Ok ~> ViaSPARQL ~> ContentType(lang.contentType) ~> ResponseModel(model, uri, lang)
               case HEAD(_) => Ok ~> ViaSPARQL ~> ContentType(lang.contentType)
             }
           }
-        case PUT(_) & Lang(lang) =>
+        case PUT(_) & RequestLang(lang) =>
           for {
-            bodyModel <- modelFromInputStream(Body.stream(req), baseURI, lang) failMap { t => BadRequest ~> ResponseString(t.getStackTraceString) }
+            bodyModel <- modelFromInputStream(Body.stream(req), uri, lang) failMap { t => BadRequest ~> ResponseString(t.getStackTraceString) }
             _ <- r.save(bodyModel) failMap { t => InternalServerError ~> ResponseString(t.getStackTraceString) }
           } yield Created
         case PUT(_) =>
           BadRequest ~> ResponseString("Content-Type MUST be one of: " + Lang.supportedAsString)
         case POST(_) & RequestContentType(ct) if Post.supportContentTypes contains ct => {
-          Post.parse(Body.stream(req), baseURI, ct) match {
+          Post.parse(Body.stream(req), uri, ct) match {
             case PostUnknown => {
               logger.info("Couldn't parse the request")
               BadRequest ~> ResponseString("You MUST provide valid content for given Content-Type: " + ct)
@@ -105,7 +108,7 @@
             }
             case PostQuery(query) => {
               logger.info("SPARQL Query:\n" + query.toString())
-              lazy val lang = Lang(req) getOrElse Lang.default
+              lazy val lang = RequestLang(req) getOrElse Lang.default
               for {
                 model <- r.get() failMap { t => NotFound }
               } yield {
@@ -117,11 +120,11 @@
                     Ok ~> ContentType("application/sparql-results+xml") ~> ResponseResultSet(qe.execAsk())
                   case CONSTRUCT => {
                     val result: Model = qe.execConstruct()
-                    Ok ~> ContentType(lang.contentType) ~> ResponseModel(model, baseURI, lang)
+                    Ok ~> ContentType(lang.contentType) ~> ResponseModel(model, uri, lang)
                   }
                   case DESCRIBE => {
                     val result: Model = qe.execDescribe()
-                    Ok ~> ContentType(lang.contentType) ~> ResponseModel(model, baseURI, lang)
+                    Ok ~> ContentType(lang.contentType) ~> ResponseModel(model, uri, lang)
                   }
                 }
               }
--- a/src/main/scala/util/FilterLogger.scala	Sat Oct 15 16:47:37 2011 -0400
+++ b/src/main/scala/util/FilterLogger.scala	Sat Oct 15 18:28:35 2011 -0400
@@ -13,9 +13,9 @@
   def destroy(): Unit = ()
 
   def doFilter(
-    request: ServletRequest,
-    response: ServletResponse,
-    chain: FilterChain): Unit = {
+      request: ServletRequest,
+      response: ServletResponse,
+      chain: FilterChain): Unit = {
     val r: HttpServletRequest = request.asInstanceOf[HttpServletRequest]
     val method = r.getMethod
     val uri = r.getRequestURI 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/util/ResponseModel.scala	Sat Oct 15 18:28:35 2011 -0400
@@ -0,0 +1,15 @@
+package org.w3.readwriteweb
+
+import java.io._
+import java.net.URL
+
+import com.hp.hpl.jena.rdf.model._
+import unfiltered.response._
+
+object ResponseModel {
+  def apply(model: Model, base: URL, lang: Lang): ResponseStreamer =
+    new ResponseStreamer {
+      def stream(os: OutputStream): Unit =
+        model.getWriter(lang.jenaLang).write(model, os, base.toString)
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/util/ResponseResultSet.scala	Sat Oct 15 18:28:35 2011 -0400
@@ -0,0 +1,23 @@
+package org.w3.readwriteweb
+
+import java.io._
+import com.hp.hpl.jena.rdf.model._
+import com.hp.hpl.jena.query._
+import unfiltered.response._
+import scalaz._
+import Scalaz._
+
+object ResponseResultSet {
+
+  def apply(rs: ResultSet): ResponseStreamer =
+    new ResponseStreamer {
+      def stream(os: OutputStream): Unit = ResultSetFormatter.outputAsXML(os, rs) 
+    }
+  
+  def apply(result: Boolean): ResponseStreamer =
+    new ResponseStreamer {
+      def stream(os: OutputStream): Unit =
+        ResultSetFormatter.outputAsXML(os, result) 
+    }
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/util/ViaSPARQL.scala	Sat Oct 15 18:28:35 2011 -0400
@@ -0,0 +1,7 @@
+package org.w3.readwriteweb
+
+import unfiltered.response._
+
+class MSAuthorVia(value: String) extends ResponseHeader("MS-Author-Via", List(value))
+
+object ViaSPARQL extends MSAuthorVia("SPARQL")