had to merge but removed all my changes
authorTim Berners-Lee <timbl+hg@w3.org>
Thu, 27 Oct 2011 22:05:25 -0400
changeset 106 0e01bf07ab68
parent 105 81d2fa82b753 (current diff)
parent 73 5a8fc47be78b (diff)
child 112 2afbc8fdc6f8
had to merge but removed all my changes
src/main/scala/Main.scala
src/main/scala/Resource.scala
src/main/scala/util.scala
src/test/scala/Test.scala
src/test/scala/utiltest.scala
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/resources/log4j.properties	Thu Oct 27 22:05:25 2011 -0400
@@ -0,0 +1,9 @@
+// Appenders
+log4j.appender.Console=org.apache.log4j.ConsoleAppender
+log4j.appender.Console.layout=org.apache.log4j.PatternLayout
+// Pattern not for use in production ( %C
+log4j.appender.Console.layout.ConversionPattern=%-5p (%F:%L) : %m%n
+
+// Loggers
+log4j.rootLogger=info, Console
+log4j.logger.org.w3.readwriteweb=debug
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/Authoritative.scala	Thu Oct 27 22:05:25 2011 -0400
@@ -0,0 +1,20 @@
+package org.w3.readwriteweb
+
+import unfiltered.request._
+import java.net.URL
+
+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 _ if uri endsWith "/" => Some("/")
+      case _ => None
+    }
+    Some((new URL(uri), Representation(suffixOpt, Accept(req))))
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/Filesystem.scala	Thu Oct 27 22:05:25 2011 -0400
@@ -0,0 +1,91 @@
+package org.w3.readwriteweb
+
+import org.w3.readwriteweb.util._
+
+import java.io._
+import java.net.URL
+import org.slf4j.{Logger, LoggerFactory}
+import com.hp.hpl.jena.rdf.model._
+import com.hp.hpl.jena.shared.JenaException
+
+import scalaz.{sys => _, _}
+import Scalaz._
+
+class Filesystem(
+  baseDirectory: File,
+  val basePath: String,
+  val lang: Lang)(mode: RWWMode) extends ResourceManager {
+  
+  val logger: Logger = LoggerFactory.getLogger(this.getClass)
+  
+  def sanityCheck(): Boolean =
+    baseDirectory.exists && baseDirectory.isDirectory
+  
+  def resource(url: URL): Resource = new Resource {
+    val relativePath: String = url.getPath.replaceAll("^"+basePath.toString+"/?", "")
+    val fileOnDisk = new File(baseDirectory, relativePath)
+    
+    private def parentMustExist(): Unit = {
+      val parent = fileOnDisk.getParentFile
+      if (! parent.exists) sys.error("Parent directory %s does not exist" format parent.getAbsolutePath)
+      if (! parent.isDirectory) sys.error("Parent %s is not a directory" format parent.getAbsolutePath)
+    }
+    
+    private def createDirectoryOnDisk(): Unit = {
+      parentMustExist()
+      val r = fileOnDisk.mkdir()
+      if (!r) sys.error("Could not create %s" format fileOnDisk.getAbsolutePath)
+      logger.debug("%s successfully created: %s" format (fileOnDisk.getAbsolutePath, r.toString))
+    }
+    
+    private def createFileOnDisk(): Unit = {
+      parentMustExist()
+      val r = fileOnDisk.createNewFile()
+      logger.debug("%s successfully created: %s" format (fileOnDisk.getAbsolutePath, r.toString))
+    }
+    
+    def get(): Validation[Throwable, Model] = {
+      val model = ModelFactory.createDefaultModel()
+      if (fileOnDisk.exists()) {
+        val fis = new FileInputStream(fileOnDisk)
+        try {
+          val reader = model.getReader(lang.jenaLang)
+          reader.read(model, fis, url.toString)
+        } catch {
+          case je: JenaException => throw je
+        }
+        fis.close()
+        model.success
+      } else {
+        mode match {
+          case AllResourcesAlreadyExist => model.success
+          case ResourcesDontExistByDefault => new FileNotFoundException().fail
+        }
+      }
+    }
+    
+    def save(model: Model): Validation[Throwable, Unit] =
+      try {
+        createFileOnDisk()
+        val fos = new FileOutputStream(fileOnDisk)
+        val writer = model.getWriter(lang.jenaLang)
+        writer.write(model, fos, url.toString)
+        fos.close().success
+      } catch {
+        case t => t.fail
+      }
+
+    def createDirectory(model: Model): Validation[Throwable, Unit] =
+      try {
+        createDirectoryOnDisk().success
+//        val fos = new FileOutputStream(fileOnDisk)
+//        val writer = model.getWriter(lang.contentType)
+//        writer.write(model, fos, url.toString)
+//        fos.close().success
+      } catch {
+        case t => t.fail
+      }
+
+  }
+  
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/Lang.scala	Thu Oct 27 22:05:25 2011 -0400
@@ -0,0 +1,46 @@
+package org.w3.readwriteweb
+
+import unfiltered.request._
+
+sealed trait Lang {
+  
+  def contentType = this match {
+    case RDFXML => "application/rdf+xml"
+    case TURTLE => "text/turtle"
+    case N3 => "text/n3"
+  }
+  
+  def jenaLang = this match {
+    case RDFXML => "RDF/XML-ABBREV"
+    case TURTLE => "TURTLE"
+    case N3 => "N3"
+  }
+  
+}
+
+object Lang {
+  
+  val supportedLanguages = Set(RDFXML, TURTLE, N3)
+  val supportContentTypes = supportedLanguages map (_.contentType)
+  val supportedAsString = supportContentTypes mkString ", "
+  
+  val default = RDFXML
+  
+  def apply(contentType: String): Option[Lang] =
+    contentType match {
+      case "text/n3" => Some(N3)
+      case "text/turtle" => Some(TURTLE)
+      case "application/rdf+xml" => Some(RDFXML)
+      case _ => None
+  }
+  
+  def apply(cts: Iterable[String]): Option[Lang] =
+    cts map Lang.apply collectFirst { case Some(lang) => lang }
+    
+}
+
+case object RDFXML extends Lang
+
+case object TURTLE extends Lang
+
+case object N3 extends Lang
--- a/src/main/scala/Main.scala	Thu Oct 27 21:53:32 2011 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,222 +0,0 @@
-package org.w3.readwriteweb
-
-import javax.servlet._
-import javax.servlet.http._
-import unfiltered.request._
-import unfiltered.response._
-import unfiltered.jetty._
-
-import java.io._
-import scala.io.Source
-import java.net.URL
-
-import org.slf4j.{Logger, LoggerFactory}
-
-import com.hp.hpl.jena.rdf.model._
-import com.hp.hpl.jena.query._
-import com.hp.hpl.jena.update._
-import com.hp.hpl.jena.shared.JenaException
-import Query.{QueryTypeSelect => SELECT, QueryTypeAsk => ASK,
-              QueryTypeConstruct => CONSTRUCT, QueryTypeDescribe => DESCRIBE}
-
-import scalaz._
-import Scalaz._
-
-import org.w3.readwriteweb.util._
-
-class ReadWriteWeb(rm:ResourceManager) {
-  
-  val logger:Logger = LoggerFactory.getLogger(this.getClass)
-
-  def isHTML(accepts:List[String]):Boolean = {
-    val accept = accepts.headOption
-    accept == Some("text/html") || accept == Some("application/xhtml+xml")
-  }
-  
-  /** I believe some documentation is needed here, as many different tricks
-   *  are used to make this code easy to read and still type-safe
-   *  
-   *  Planify.apply takes an Intent, which is defined in Cycle by
-   *  type Intent [-A, -B] = PartialFunction[HttpRequest[A], ResponseFunction[B]]
-   *  the corresponding syntax is: case ... => ...
-   *  
-   *  this code makes use of the Validation monad. For example of how to use it, see
-   *  http://scalaz.googlecode.com/svn/continuous/latest/browse.sxr/scalaz/example/ExampleValidation.scala.html
-   *  
-   *  the Resource abstraction returns Validation[Throwable, ?something]
-   *  we use the for monadic constructs.
-   *  Everything construct are mapped to Validation[ResponseFunction, ResponseFuntion],
-   *  the left value always denoting the failure. Hence, the rest of the for-construct
-   *  is not evaluated, but let the reader of the code understand clearly what's happening.
-   *  
-   *  This mapping is made possible with the failMap method. I couldn't find an equivalent
-   *  in the ScalaZ API so I made my own through an implicit.
-   *  
-   *  At last, Validation[ResponseFunction, ResponseFuntion] is exposed as a ResponseFunction
-   *  through another implicit conversion. It saves us the call to the Validation.lift() method
-   */
-  val read = unfiltered.filter.Planify {
-    case req @ Path(path) if path startsWith rm.basePath => {
-      val baseURI = req.underlying.getRequestURL.toString
-      val r:Resource = rm.resource(new URL(baseURI))
-      req match {
-        case GET(_) & Accept(accepts) if isHTML(accepts) => {
-          val source = Source.fromFile("src/main/resources/skin.html")("UTF-8")
-          val body = source.getLines.mkString("\n")
-          Ok ~> ViaSPARQL ~> ContentType("text/html") ~> ResponseString(body)
-        }
-        case GET(_) | HEAD(_) =>
-          for {
-            model <- r.get() failMap { x => NotFound }
-            encoding = RDFEncoding(req)
-          } yield {
-            req match {
-              case GET(_) => Ok ~> ViaSPARQL ~> ContentType(encoding.toContentType) ~> ResponseModel(model, baseURI, encoding)
-              case HEAD(_) => Ok ~> ViaSPARQL ~> ContentType(encoding.toContentType)
-            }
-          }
-        case PUT(_) =>
-          for {
-            bodyModel <- modelFromInputStream(Body.stream(req), baseURI) failMap { t => BadRequest ~> ResponseString(t.getStackTraceString) }
-            _ <- r.save(bodyModel) failMap { t => InternalServerError ~> ResponseString(t.getStackTraceString) }
-          } yield Created
-        case POST(_) => {
-          Post.parse(Body.stream(req), baseURI) match {
-            case PostUnknown => {
-              logger.info("Couldn't parse the request")
-              BadRequest ~> ResponseString("You MUST provide valid content for either: SPARQL UPDATE, SPARQL Query, RDF/XML, TURTLE")
-            }
-            case PostUpdate(update) => {
-              logger.info("SPARQL UPDATE:\n" + update.toString())
-              for {
-                model <- r.get() failMap { t => NotFound }
-                // TODO: we should handle an error here
-                _ = UpdateAction.execute(update, model)
-                _ <- r.save(model) failMap { t =>  InternalServerError ~> ResponseString(t.getStackTraceString)}
-              } yield Ok
-            }
-            case PostRDF(diffModel) => {
-              logger.info("RDF content:\n" + diffModel.toString())
-              for {
-                model <- r.get() failMap { t => NotFound }
-                // TODO: we should handle an error here
-                _ = model.add(diffModel)
-                _ <- r.save(model) failMap { t =>  InternalServerError ~> ResponseString(t.getStackTraceString)}
-              } yield Ok
-            }
-            case PostQuery(query) => {
-              logger.info("SPARQL Query:\n" + query.toString())
-              lazy val encoding = RDFEncoding(req)
-              for {
-                model <- r.get() failMap { t => NotFound }
-              } yield {
-                val qe:QueryExecution = QueryExecutionFactory.create(query, model)
-                query.getQueryType match {
-                  case SELECT =>
-                    Ok ~> ContentType("application/sparql-results+xml") ~> ResponseResultSet(qe.execSelect())
-                  case ASK =>
-                    Ok ~> ContentType("application/sparql-results+xml") ~> ResponseResultSet(qe.execAsk())
-                  case CONSTRUCT => {
-                    val result:Model = qe.execConstruct()
-                    Ok ~> ContentType(encoding.toContentType) ~> ResponseModel(model, baseURI, encoding)
-                  }
-                  case DESCRIBE => {
-                    val result:Model = qe.execDescribe()
-                    Ok ~> ContentType(encoding.toContentType) ~> ResponseModel(model, baseURI, encoding)
-                  }
-                }
-              }
-            }
-          }
-        }
-        case _ => MethodNotAllowed ~> Allow("GET", "PUT", "POST")
-      }
-    }
-
-  }
-
-}
-
-
-import org.clapper.argot._
-import ArgotConverters._
-
-object ReadWriteWebMain {
-
-  val logger:Logger = LoggerFactory.getLogger(this.getClass)
-
-  val parser = new ArgotParser("read-write-web")
-
-  val mode = parser.option[RWWMode](List("mode"), "m", "wiki mode") {
-    (sValue, opt) =>
-      sValue match {
-        case "wiki" => AllResourcesAlreadyExist
-        case "strict" => ResourcesDontExistByDefault
-        case _ => throw new ArgotConversionException("Option %s: must be either wiki or strict" format (opt.name, sValue))
-      }
-    }
-
-  val rdfLanguage = parser.option[String](List("language"), "l", "RDF language") {
-    (sValue, opt) =>
-      sValue match {
-        case "n3" => "N3"
-        case "turtle" => "N3"
-        case "rdfxml" => "RDF/XML-ABBREV"
-        case _ => throw new ArgotConversionException("Option %s: must be either n3, turtle or rdfxml" format (opt.name, sValue))
-      }
-  }
-
-  val port = parser.parameter[Int]("port", "Port to use", false)
-
-  val rootDirectory = parser.parameter[File]("rootDirectory", "root directory", false) {
-    (sValue, opt) => {
-      val file = new File(sValue)
-      if (! file.exists)
-        throw new ArgotConversionException("Option %s: %s must be a valid path" format (opt.name, sValue))
-      else
-        file
-    }
-  }
-
-  val baseURL = parser.parameter[String]("baseURL", "base URL", false)
-
-  // regular Java main
-  def main(args: Array[String]) {
-
-    try {
-      parser.parse(args)
-    } catch {
-      case e: ArgotUsageException => println(e.message); System.exit(1)
-    }
-
-    val filesystem =
-      new Filesystem(
-        rootDirectory.value.get,
-        baseURL.value.get,
-        lang=rdfLanguage.value getOrElse "N3")(mode.value getOrElse ResourcesDontExistByDefault)
-    
-    val app = new ReadWriteWeb(filesystem)
-
-    // configures and launches a Jetty server
-    unfiltered.jetty.Http(port.value.get).filter {
-      // a jee Servlet filter that logs HTTP requests
-      new Filter {
-        def destroy():Unit = ()
-        def doFilter(request:ServletRequest, response:ServletResponse, chain:FilterChain):Unit = {
-          val r:HttpServletRequest = request.asInstanceOf[HttpServletRequest]
-          val method = r.getMethod
-          val uri = r.getRequestURI 
-          logger.info("%s %s" format (method, uri))
-          chain.doFilter(request, response)
-        }
-        def init(filterConfig:FilterConfig):Unit = ()
-      }
-    // Unfiltered filters
-    }.context("/public"){ ctx:ContextBuilder =>
-      ctx.resources(MyResourceManager.fromClasspath("public/").toURI.toURL)
-    }.filter(app.read).run()
-    
-  }
-
-}
-
--- a/src/main/scala/Post.scala	Thu Oct 27 21:53:32 2011 -0400
+++ b/src/main/scala/Post.scala	Thu Oct 27 22:05:25 2011 -0400
@@ -1,21 +1,20 @@
 package org.w3.readwriteweb
 
-import java.io._
+import org.w3.readwriteweb.util.modelFromString
+
+import java.io.{InputStream, StringReader}
+import java.net.URL
 import scala.io.Source
-
 import org.slf4j.{Logger, LoggerFactory}
-
 import com.hp.hpl.jena.rdf.model._
 import com.hp.hpl.jena.query._
 import com.hp.hpl.jena.update._
 import com.hp.hpl.jena.shared.JenaException
 
-import org.w3.readwriteweb.util._
-
 sealed trait Post
-case class PostUpdate(update:UpdateRequest) extends Post
-case class PostRDF(model:Model) extends Post
-case class PostQuery(query:Query) extends Post
+case class PostUpdate(update: UpdateRequest) extends Post
+case class PostRDF(model: Model) extends Post
+case class PostQuery(query: Query) extends Post
 case object PostUnknown extends Post
 
 import scalaz._
@@ -23,33 +22,55 @@
 
 object Post {
   
-  val logger:Logger = LoggerFactory.getLogger(this.getClass)
+  val SPARQL = "application/sparql-query"
+  val supportContentTypes = Lang.supportContentTypes + SPARQL
+  val supportedAsString = supportContentTypes mkString ", "
 
-  def parse(is:InputStream, baseURI:String):Post = {
+  
+  val logger: Logger = LoggerFactory.getLogger(this.getClass)
+
+  def parse(
+      is: InputStream,
+      base: URL,
+      contentType: String): Post = {
+    assert(supportContentTypes contains contentType)
     val source = Source.fromInputStream(is, "UTF-8")
     val s = source.getLines.mkString("\n")
-    parse(s, baseURI)
+    parse(s, base, contentType)
   }
   
-  def parse(s:String, baseURI:String):Post = {
+  def parse(
+      s: String,
+      base: URL,
+      contentType: String): Post = {
+    assert(supportContentTypes contains contentType)
+    
     val reader = new StringReader(s)
+    
     def postUpdate =
       try {
-        val update:UpdateRequest = UpdateFactory.create(s, baseURI)
+        val update: UpdateRequest = UpdateFactory.create(s, base.toString)
         PostUpdate(update).success
       } catch {
-        case qpe:QueryParseException => qpe.fail
+        case qpe: QueryParseException => qpe.fail
       }
-    def postRDF =
-      modelFromString(s, baseURI) flatMap { model => PostRDF(model).success }
+      
+    def postRDF(lang: Lang) =
+      modelFromString(s, base, lang) flatMap { model => PostRDF(model).success }
+    
     def postQuery =
       try {
         val query = QueryFactory.create(s)
         PostQuery(query).success
       } catch {
-        case qe:QueryException => qe.fail
+        case qe: QueryException => qe.fail
       }
-    postUpdate | (postRDF | (postQuery | PostUnknown))
+    
+    contentType match {
+      case SPARQL => postUpdate | (postQuery | PostUnknown)
+      case RequestLang(lang) => postRDF(lang) | PostUnknown
+    }
+
   }
   
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/ReadWriteWebMain.scala	Thu Oct 27 22:05:25 2011 -0400
@@ -0,0 +1,82 @@
+package org.w3.readwriteweb
+
+import org.w3.readwriteweb.util._
+
+import javax.servlet._
+import javax.servlet.http._
+import unfiltered.jetty._
+import java.io.File
+import Console.err
+import org.slf4j.{Logger, LoggerFactory}
+
+import org.clapper.argot._
+import ArgotConverters._
+
+object ReadWriteWebMain {
+
+  val logger: Logger = LoggerFactory.getLogger(this.getClass)
+
+  val parser = new ArgotParser("read-write-web")
+
+  val mode = parser.option[RWWMode](List("mode"), "m", "wiki mode") {
+    (sValue, opt) =>
+      sValue match {
+        case "wiki" => AllResourcesAlreadyExist
+        case "strict" => ResourcesDontExistByDefault
+        case _ => throw new ArgotConversionException("Option %s: must be either wiki or strict" format (opt.name, sValue))
+      }
+    }
+
+  val rdfLanguage = parser.option[Lang](List("language"), "l", "RDF language") {
+    (sValue, opt) =>
+      sValue match {
+        case "n3" => N3
+        case "turtle" => TURTLE
+        case "rdfxml" => RDFXML
+        case _ => throw new ArgotConversionException("Option %s: must be either n3, turtle or rdfxml" format (opt.name, sValue))
+      }
+  }
+
+  val port = parser.parameter[Int]("port", "Port to use", false)
+
+  val rootDirectory = parser.parameter[File]("rootDirectory", "root directory", false) {
+    (sValue, opt) => {
+      val file = new File(sValue)
+      if (! file.exists)
+        throw new ArgotConversionException("Option %s: %s must be a valid path" format (opt.name, sValue))
+      else
+        file
+    }
+  }
+
+  val baseURL = parser.parameter[String]("baseURL", "base URL", false)
+
+  // regular Java main
+  def main(args: Array[String]) {
+
+    try {
+      parser.parse(args)
+    } catch {
+      case e: ArgotUsageException => err.println(e.message); sys.exit(1)
+    }
+
+    val filesystem =
+      new Filesystem(
+        rootDirectory.value.get,
+        baseURL.value.get,
+        lang=rdfLanguage.value getOrElse RDFXML)(mode.value getOrElse ResourcesDontExistByDefault)
+    
+    val app = new ReadWriteWeb(filesystem)
+
+    // configures and launches a Jetty server
+    unfiltered.jetty.Http(port.value.get)
+    .filter(new FilterLogger(logger))
+    .context("/public") {
+      ctx: ContextBuilder =>
+        ctx.resources(ClasspathUtils.fromClasspath("public/").toURI.toURL)
+    }.filter(app.plan).run()
+    
+  }
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/Representation.scala	Thu Oct 27 22:05:25 2011 -0400
@@ -0,0 +1,55 @@
+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 "/" => DirectoryRepr
+      case _ => UnknownRepr
+    }
+  }
+  
+  val htmlContentTypes = Set("text/html", "application/xhtml+xml")
+  
+  def acceptsHTML(ct: Iterable[String]) =
+    ! (htmlContentTypes & 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 DirectoryRepr extends Representation
+case object UnknownRepr extends Representation
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/RequestLang.scala	Thu Oct 27 22:05:25 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/Resource.scala	Thu Oct 27 21:53:32 2011 -0400
+++ b/src/main/scala/Resource.scala	Thu Oct 27 22:05:25 2011 -0400
@@ -1,19 +1,11 @@
 package org.w3.readwriteweb
 
-import java.io._
-import java.net.URL
-
-import org.slf4j.{Logger, LoggerFactory}
-
-import com.hp.hpl.jena.rdf.model._
-import com.hp.hpl.jena.shared.JenaException
-
 import org.w3.readwriteweb.util._
 
+import java.net.URL
+import com.hp.hpl.jena.rdf.model._
 import scalaz._
 import Scalaz._
-import java.io.StringWriter   // tbl
-import _root_.scala.sys.error
 
 trait ResourceManager {
   def basePath:String
@@ -24,72 +16,6 @@
 trait Resource {
   def get():Validation[Throwable, Model]
   def save(model:Model):Validation[Throwable, Unit]
+  def createDirectory(model: Model): Validation[Throwable, Unit]
 }
 
-class Filesystem(
-  baseDirectory: File,
-  val basePath: String,
-  val lang: String = "RDF/XML-ABBREV")(mode: RWWMode) extends ResourceManager {
-  
-  val logger:Logger = LoggerFactory.getLogger(this.getClass)
-  
-  def sanityCheck():Boolean = baseDirectory.exists
-  
-  def resource(url:URL):Resource = new Resource {
-    val relativePath:String = url.getPath.replaceAll("^"+basePath.toString+"/?", "")
-    val fileOnDisk = new File(baseDirectory, relativePath)
-    
-    private def createFileOnDisk():Unit = {
-      // create parent directory if needed
-      val parent = fileOnDisk.getParentFile
-      if (! parent.exists) println(parent.mkdirs)
-      val r = fileOnDisk.createNewFile()
-      logger.debug("Create file %s with success: %s" format (fileOnDisk.getAbsolutePath, r.toString))
-    }
-    
-    def get(): Validation[Throwable, Model] = {
-      val model = ModelFactory.createDefaultModel()
-      if (fileOnDisk.exists()) {
-        val fis = new FileInputStream(fileOnDisk)
-        try {
-          val reader = model.getReader(lang)
-          reader.read(model, fis, url.toString)
-        } catch {
-          case je:JenaException => error("Fail to parse <"+ url.toString +"> : "+ je)
-        }
-        fis.close()
-        model.success
-      } else {
-        mode match {
-          case AllResourcesAlreadyExist => model.success
-          case ResourcesDontExistByDefault => new FileNotFoundException().fail
-        }
-      }
-    }
-    
-    def save(model:Model):Validation[Throwable, Unit] =
-      try {
-        createFileOnDisk()
-        val fos = new FileOutputStream(fileOnDisk)
-        val writer = model.getWriter(lang)
-        
-        val temp = new StringWriter();  // tbl  kludge until Jena fixed @@@
-        writer.write(model, temp, url.toString); //tbl 
-        def baseBit(u:String) = u.slice(0, u.lastIndexOf('/')+1);  //tbl
-        // We remove any base URIs on same server different port as well, for proxying.
-        def generalize(u:String) = u.replaceAll("//localhost[:0-9]*/", "//localhost[:0-9]*/");
-        // fos.write(("# Resource.scala 80: "+generalize(baseBit(url.toString))+"\n").getBytes) 
-        logger.debug("@@ Munged output string with regexp: %s " format (generalize(baseBit(url.toString))));
-
-        fos.write(temp.toString().replaceAll(generalize(baseBit(url.toString)), "").getBytes); // tbl
-        
-//        writer.write(model, fos, url.toString)
-
-        fos.close().success
-      } catch {
-        case t => t.fail
-      }
-
-  }
-  
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/plan.scala	Thu Oct 27 22:05:25 2011 -0400
@@ -0,0 +1,151 @@
+package org.w3.readwriteweb
+
+import org.w3.readwriteweb.util._
+
+import unfiltered.request._
+import unfiltered.response._
+
+import scala.io.Source
+import java.net.URL
+
+import org.slf4j.{Logger, LoggerFactory}
+
+import com.hp.hpl.jena.rdf.model.Model
+import com.hp.hpl.jena.query.{Query, QueryExecution, QueryExecutionFactory}
+import com.hp.hpl.jena.update.UpdateAction
+import Query.{QueryTypeSelect => SELECT,
+              QueryTypeAsk => ASK,
+              QueryTypeConstruct => CONSTRUCT,
+              QueryTypeDescribe => DESCRIBE}
+
+import scalaz._
+import Scalaz._
+
+//object ReadWriteWeb {
+//  
+//  val defaultHandler: PartialFunction[Throwable, HttpResponse[_]] = {
+//    case t => InternalServerError ~> ResponseString(t.getStackTraceString)
+//  }
+//  
+//}
+
+class ReadWriteWeb(rm: ResourceManager) {
+  
+  val logger: Logger = LoggerFactory.getLogger(this.getClass)
+
+  /** I believe some documentation is needed here, as many different tricks
+   *  are used to make this code easy to read and still type-safe
+   *  
+   *  Planify.apply takes an Intent, which is defined in Cycle by
+   *  type Intent [-A, -B] = PartialFunction[HttpRequest[A], ResponseFunction[B]]
+   *  the corresponding syntax is: case ... => ...
+   *  
+   *  this code makes use of ScalaZ Validation. For example of how to use it, see
+   *  http://scalaz.googlecode.com/svn/continuous/latest/browse.sxr/scalaz/example/ExampleValidation.scala.html
+   *  
+   *  the Resource abstraction returns Validation[Throwable, ?something]
+   *  we use the for monadic constructs (although it's *not* a monad).
+   *  Everything construct are mapped to Validation[ResponseFunction, ResponseFuntion],
+   *  the left value always denoting the failure. Hence, the rest of the for-construct
+   *  is not evaluated, but let the reader of the code understand clearly what's happening.
+   *  
+   *  This mapping is made possible with the failMap method. I couldn't find an equivalent
+   *  in the ScalaZ API so I made my own through an implicit.
+   *  
+   *  At last, Validation[ResponseFunction, ResponseFuntion] is exposed as a ResponseFunction
+   *  through another implicit conversion. It saves us the call to the Validation.lift() method
+   */
+  val plan = unfiltered.filter.Planify {
+    case req @ Path(path) if path startsWith rm.basePath => {
+      val Authoritative(uri, representation) = req
+      val r: Resource = rm.resource(uri)
+      req match {
+        case GET(_) if representation == HTMLRepr => {
+          val source = Source.fromFile("src/main/resources/skin.html")("UTF-8")
+          val body = source.getLines.mkString("\n")
+          Ok ~> ViaSPARQL ~> ContentType("text/html") ~> ResponseString(body)
+        }
+        case GET(_) | HEAD(_) =>
+          for {
+            model <- r.get() failMap { x => NotFound }
+            lang = representation match {
+              case RDFRepr(l) => l
+              case _ => Lang.default
+            }
+          } yield {
+            req match {
+              case GET(_) => Ok ~> ViaSPARQL ~> ContentType(lang.contentType) ~> ResponseModel(model, uri, lang)
+              case HEAD(_) => Ok ~> ViaSPARQL ~> ContentType(lang.contentType)
+            }
+          }
+        case PUT(_) & RequestLang(lang) if representation == DirectoryRepr => {
+          for {
+            bodyModel <- modelFromInputStream(Body.stream(req), uri, lang) failMap { t => BadRequest ~> ResponseString(t.getStackTraceString) }
+            _ <- r.createDirectory(bodyModel) failMap { t => InternalServerError ~> ResponseString(t.getStackTraceString) }
+          } yield Created
+        }
+        case PUT(_) & RequestLang(lang) =>
+          for {
+            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), uri, ct) match {
+            case PostUnknown => {
+              logger.info("Couldn't parse the request")
+              BadRequest ~> ResponseString("You MUST provide valid content for given Content-Type: " + ct)
+            }
+            case PostUpdate(update) => {
+              logger.info("SPARQL UPDATE:\n" + update.toString())
+              for {
+                model <- r.get() failMap { t => NotFound }
+                // TODO: we should handle an error here
+                _ = UpdateAction.execute(update, model)
+                _ <- r.save(model) failMap { t =>  InternalServerError ~> ResponseString(t.getStackTraceString)}
+              } yield Ok
+            }
+            case PostRDF(diffModel) => {
+              logger.info("RDF content:\n" + diffModel.toString())
+              for {
+                model <- r.get() failMap { t => NotFound }
+                // TODO: we should handle an error here
+                _ = model.add(diffModel)
+                _ <- r.save(model) failMap { t =>  InternalServerError ~> ResponseString(t.getStackTraceString)}
+              } yield Ok
+            }
+            case PostQuery(query) => {
+              logger.info("SPARQL Query:\n" + query.toString())
+              lazy val lang = RequestLang(req) getOrElse Lang.default
+              for {
+                model <- r.get() failMap { t => NotFound }
+              } yield {
+                val qe: QueryExecution = QueryExecutionFactory.create(query, model)
+                query.getQueryType match {
+                  case SELECT =>
+                    Ok ~> ContentType("application/sparql-results+xml") ~> ResponseResultSet(qe.execSelect())
+                  case ASK =>
+                    Ok ~> ContentType("application/sparql-results+xml") ~> ResponseResultSet(qe.execAsk())
+                  case CONSTRUCT => {
+                    val result: Model = qe.execConstruct()
+                    Ok ~> ContentType(lang.contentType) ~> ResponseModel(model, uri, lang)
+                  }
+                  case DESCRIBE => {
+                    val result: Model = qe.execDescribe()
+                    Ok ~> ContentType(lang.contentType) ~> ResponseModel(model, uri, lang)
+                  }
+                }
+              }
+            }
+          }
+        }
+        case POST(_) =>
+          BadRequest ~> ResponseString("Content-Type MUST be one of: " + Post.supportedAsString)
+        case _ => MethodNotAllowed ~> Allow("GET", "PUT", "POST")
+      }
+    }
+
+  }
+
+}
\ No newline at end of file
--- a/src/main/scala/util.scala	Thu Oct 27 21:53:32 2011 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,189 +0,0 @@
-package org.w3.readwriteweb
-
-import javax.servlet._
-import javax.servlet.http._
-import unfiltered.request._
-import unfiltered.response._
-import unfiltered.jetty._
-
-import java.io._
-import scala.io.Source
-
-import scalaz._
-import Scalaz._
-
-import _root_.scala.sys.error
-
-import org.slf4j.{Logger, LoggerFactory}
-
-import com.hp.hpl.jena.rdf.model._
-import com.hp.hpl.jena.query._
-import com.hp.hpl.jena.update._
-
-import unfiltered.request._
-import unfiltered.response._
-import unfiltered.jetty._
-
-sealed trait RWWMode
-case object AllResourcesAlreadyExist extends RWWMode
-case object ResourcesDontExistByDefault extends RWWMode
-
-sealed trait RDFEncoding {
-  def toContentType:String
-}
-case object RDFXML extends RDFEncoding {
-  def toContentType = "application/rdf+xml"
-}
-case object TURTLE extends RDFEncoding {
-  def toContentType = "text/turtle"
-}
-
-object RDFEncoding {
-  
-  def apply(contentType:String):RDFEncoding =
-    contentType match {
-      case "text/turtle" => TURTLE
-      case "application/rdf+xml" => RDFXML
-      case _ => RDFXML
-    }
-    
-  def apply(req:HttpRequest[_]):RDFEncoding = {
-    val contentType = Accept(req).headOption
-    contentType map { RDFEncoding(_) } getOrElse RDFXML
-  }
-  
-}
-
-trait ValidationW[E, S] {
-  val validation:Validation[E, S]
-  def failMap[EE](f:E => EE):Validation[EE, S] = validation.fail map f validation
-}
-
-package object util {
-  
-  val defaultLang = "RDF/XML-ABBREV"
-
-  class MSAuthorVia(value:String) extends ResponseHeader("MS-Author-Via", List(value))
-  object ViaSPARQL extends MSAuthorVia("SPARQL")
-  
-  object ResponseModel {
-    def apply(model:Model, base:String, encoding:RDFEncoding):ResponseStreamer =
-      new ResponseStreamer {
-        def stream(os:OutputStream):Unit =
-          encoding match {
-            case RDFXML => model.getWriter("RDF/XML-ABBREV").write(model, os, base)
-            case TURTLE => model.getWriter("TURTLE").write(model, os, base)
-          }
-      }
-  }
-
-  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) 
-      }
-  }
-
-  def modelFromInputStream(
-      is:InputStream,
-      base:String,
-      lang:String = "RDF/XML-ABBREV"):Validation[Throwable, Model] =
-    try {
-      val m = ModelFactory.createDefaultModel()
-      m.read(is, base, lang)
-      m.success
-    } catch {
-      case t => t.fail
-    }
-  
-  def modelFromString(s:String,
-      base:String,
-      lang:String = "RDF/XML-ABBREV"):Validation[Throwable, Model] =
-    try {
-      val reader = new StringReader(s)
-      val m = ModelFactory.createDefaultModel()
-      m.read(reader, base, lang)
-      m.success
-    } catch {
-      case t => t.fail
-    }
-
-  implicit def wrapValidation[E, S](v:Validation[E,S]):ValidationW[E, S] =
-    new ValidationW[E, S] { val validation = v }
-  
-  implicit def unwrap[E, F <: E, S <: E](v:Validation[F,S]):E = v.fold(e => e, s => s)
-  
-}
-
-
-import java.io.{File, FileWriter}
-import java.util.jar._
-import scala.collection.JavaConversions._
-import scala.io.Source
-import java.net.{URL, URLDecoder}
-import org.slf4j.{Logger, LoggerFactory}
-
-/** useful stuff to read resources from the classpath */
-object MyResourceManager {
-  
-  val logger:Logger = LoggerFactory.getLogger(this.getClass)
-
-  val clazz:Class[_] = this.getClass
-  val classloader = this.getClass.getClassLoader
-  
-  /** http://www.uofr.net/~greg/java/get-resource-listing.html
-   */
-  def getResourceListing(path:String):List[String] = {
-    var dirURL:URL = classloader.getResource(path)
-    if (dirURL != null && dirURL.getProtocol == "file") {
-      /* A file path: easy enough */
-      new File(dirURL.toURI).list.toList
-    } else {
-      if (dirURL == null) {
-        val me = clazz.getName().replace(".", "/")+".class"
-        dirURL = classloader.getResource(me)
-      }
-      if (dirURL.getProtocol == "jar") {
-        val jarPath = dirURL.getPath.substring(5, dirURL.getPath().indexOf("!"))
-        val jar:JarFile = new JarFile(URLDecoder.decode(jarPath, "UTF-8"))
-        val entries = jar.entries filter { _.getName startsWith path } map { e => {
-          var entry = e.getName substring path.length
-          val checkSubdir = entry indexOf "/"
-          if (checkSubdir >= 0) entry = entry.substring(0, checkSubdir)
-          entry
-        } }
-        entries filterNot { _.isEmpty } toList
-      } else
-        error("Cannot list files for URL "+dirURL);
-    }
-  }
-  
-  /** extract a path found in the classpath
-   * 
-   *  @return the file on disk
-   */
-  def fromClasspath(path:String, base:File = new File("src/main/resources")):File = {
-    val workingPath = new File(base, path)
-    if (workingPath isDirectory) {
-      workingPath
-    } else {
-      val dir = new File(System.getProperty("java.io.tmpdir"),
-                         "virtual-trainer-" + scala.util.Random.nextInt(10000).toString)
-      if (! dir.mkdir()) logger.error("Couldn't extract %s from jar to %s" format (path, dir.getAbsolutePath))
-      val entries = getResourceListing(path) foreach { entry => {
-        val url = classloader.getResource(path + entry)
-        val content = Source.fromURL(url, "UTF-8").getLines.mkString("\n")
-        val writer = new FileWriter(new File(dir, entry))
-        writer.write(content)
-        writer.close()
-      }
-    }
-    dir
-    }
-  }
-  
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/util/ClasspathUtils.scala	Thu Oct 27 22:05:25 2011 -0400
@@ -0,0 +1,69 @@
+package org.w3.readwriteweb.util
+
+import java.io.{File, FileWriter}
+import java.util.jar._
+import scala.collection.JavaConversions._
+import scala.io.Source
+import java.net.{URL, URLDecoder}
+import org.slf4j.{Logger, LoggerFactory}
+
+/** useful stuff to read resources from the classpath */
+object ClasspathUtils {
+  
+  val logger: Logger = LoggerFactory.getLogger(this.getClass)
+
+  val clazz: Class[_] = this.getClass
+  val classloader = this.getClass.getClassLoader
+  
+  /** http://www.uofr.net/~greg/java/get-resource-listing.html
+   */
+  def getResourceListing(path: String): List[String] = {
+    var dirURL: URL = classloader.getResource(path)
+    if (dirURL != null && dirURL.getProtocol == "file") {
+      /* A file path: easy enough */
+      new File(dirURL.toURI).list.toList
+    } else {
+      if (dirURL == null) {
+        val me = clazz.getName().replace(".", "/")+".class"
+        dirURL = classloader.getResource(me)
+      }
+      if (dirURL.getProtocol == "jar") {
+        val jarPath = dirURL.getPath.substring(5, dirURL.getPath().indexOf("!"))
+        val jar: JarFile = new JarFile(URLDecoder.decode(jarPath, "UTF-8"))
+        val entries = jar.entries filter { _.getName startsWith path } map { e => {
+          var entry = e.getName substring path.length
+          val checkSubdir = entry indexOf "/"
+          if (checkSubdir >= 0) entry = entry.substring(0, checkSubdir)
+          entry
+        } }
+        entries filterNot { _.isEmpty } toList
+      } else
+        sys.error("Cannot list files for URL "+dirURL);
+    }
+  }
+  
+  /** extract a path found in the classpath
+   * 
+   *  @return the file on disk
+   */
+  def fromClasspath(path: String, base: File = new File("src/main/resources")): File = {
+    val workingPath = new File(base, path)
+    if (workingPath isDirectory) {
+      workingPath
+    } else {
+      val dir = new File(System.getProperty("java.io.tmpdir"),
+                         "virtual-trainer-" + scala.util.Random.nextInt(10000).toString)
+      if (! dir.mkdir()) logger.error("Couldn't extract %s from jar to %s" format (path, dir.getAbsolutePath))
+      val entries = getResourceListing(path) foreach { entry => {
+        val url = classloader.getResource(path + entry)
+        val content = Source.fromURL(url, "UTF-8").getLines.mkString("\n")
+        val writer = new FileWriter(new File(dir, entry))
+        writer.write(content)
+        writer.close()
+      }
+    }
+    dir
+    }
+  }
+  
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/util/FilterLogger.scala	Thu Oct 27 22:05:25 2011 -0400
@@ -0,0 +1,28 @@
+package org.w3.readwriteweb
+
+import javax.servlet._
+import javax.servlet.http._
+import unfiltered.jetty._
+import java.io.File
+import org.slf4j.{Logger, LoggerFactory}
+
+/** a simple JEE Servlet filter that logs HTTP requests
+ */
+class FilterLogger(logger: Logger) extends Filter {
+
+  def destroy(): Unit = ()
+
+  def doFilter(
+      request: ServletRequest,
+      response: ServletResponse,
+      chain: FilterChain): Unit = {
+    val r: HttpServletRequest = request.asInstanceOf[HttpServletRequest]
+    val method = r.getMethod
+    val uri = r.getRequestURI 
+    logger.info("%s %s" format (method, uri))
+    chain.doFilter(request, response)
+  }
+
+  def init(filterConfig: FilterConfig): Unit = ()
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/util/ResponseModel.scala	Thu Oct 27 22:05:25 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	Thu Oct 27 22:05:25 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/ValidationW.scala	Thu Oct 27 22:05:25 2011 -0400
@@ -0,0 +1,13 @@
+package org.w3.readwriteweb.util
+
+import scalaz._
+import Scalaz._
+
+trait ValidationW[E, S] {
+
+  val validation: Validation[E, S]
+ 
+  def failMap[EE](f: E => EE): Validation[EE, S] =
+    validation.fail map f validation
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/util/ViaSPARQL.scala	Thu Oct 27 22:05:25 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")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/util/mode.scala	Thu Oct 27 22:05:25 2011 -0400
@@ -0,0 +1,7 @@
+package org.w3.readwriteweb
+
+sealed trait RWWMode
+
+case object AllResourcesAlreadyExist extends RWWMode
+
+case object ResourcesDontExistByDefault extends RWWMode
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/util/package.scala	Thu Oct 27 22:05:25 2011 -0400
@@ -0,0 +1,42 @@
+package org.w3.readwriteweb
+
+import java.io._
+import java.net.URL
+import com.hp.hpl.jena.rdf.model._
+
+import scalaz._
+import Scalaz._
+
+package object util {
+  
+  def modelFromInputStream(
+      is: InputStream,
+      base: URL,
+      lang: Lang): Validation[Throwable, Model] =
+    try {
+      val m = ModelFactory.createDefaultModel()
+      m.getReader(lang.jenaLang).read(m, is, base.toString)
+      m.success
+    } catch {
+      case t => t.fail
+    }
+  
+  def modelFromString(
+      s: String,
+      base: URL,
+      lang: Lang): Validation[Throwable, Model] =
+    try {
+      val reader = new StringReader(s)
+      val m = ModelFactory.createDefaultModel()
+      m.getReader(lang.jenaLang).read(m, reader, base.toString)
+      m.success
+    } catch {
+      case t => t.fail
+    }
+
+  implicit def wrapValidation[E, S](v: Validation[E,S]): ValidationW[E, S] =
+    new ValidationW[E, S] { val validation = v }
+  
+  implicit def unwrap[E, F <: E, S <: E](v: Validation[F,S]): E = v.fold(e => e, s => s)
+  
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/scala/AccessContentSpecs.scala	Thu Oct 27 22:05:25 2011 -0400
@@ -0,0 +1,46 @@
+package org.w3.readwriteweb
+
+import org.w3.readwriteweb.util._
+import org.w3.readwriteweb.utiltest._
+
+import dispatch._
+
+object GetStrictModeSpec extends FilesystemBased with SomeRDF with SomeURI {
+  
+  "a GET on a URL that does not exist" should {
+    "return a 404" in {
+      val httpCode:Int = Http.when( _ => true)(uri get_statusCode)
+      httpCode must_== 404
+    }
+  }
+
+}
+
+object GetWikiModeSpec extends FilesystemBased with SomeRDF with SomeURI {
+  
+  override lazy val mode = AllResourcesAlreadyExist
+  
+  "a GET on a URL that does not exist" should {
+    "return a 200 and an empty model" in {
+      val (statusCode, model) = Http(uri >+ {
+        req => (req.get_statusCode,
+                req as_model(uriBase, RDFXML))
+      } )
+      statusCode must_== 200
+      model must beIsomorphicWith (emptyModel)
+    }
+  }
+  
+}
+
+object ContentNegociationSpec extends SomeDataInStore {
+
+  "a GET on Joe's URI" should {
+    "deliver TURTLE and RDF/XML graphs that are isomorphic to each other" in {
+      val rdfxml = Http(uri as_model(uriBase, RDFXML))
+      val turtle = Http(uri <:< Map("Accept" -> "text/turtle") as_model(uriBase, TURTLE))
+      rdfxml must beIsomorphicWith(turtle)
+    }
+  }
+  
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/scala/CreateContentSpecs.scala	Thu Oct 27 22:05:25 2011 -0400
@@ -0,0 +1,96 @@
+package org.w3.readwriteweb
+
+import org.w3.readwriteweb.util._
+import org.w3.readwriteweb.utiltest._
+
+import dispatch._
+
+object PutRDFXMLSpec extends SomePeopleDirectory {
+
+  "PUTing an RDF document on Joe's URI (which does not exist yet)" should {
+    "return a 201" in {
+      val httpCode = Http(uri.put(RDFXML, rdfxml) get_statusCode)
+      httpCode must_== 201
+    }
+    "create a document on disk" in {
+      resourceOnDisk must exist
+    }
+  }
+  
+  "Joe's URI" should {
+    "now exist and be isomorphic with the original document" in {
+      val (statusCode, via, model) = Http(uri >++ { req => (req.get_statusCode,
+                                                            req.get_header("MS-Author-Via"),
+                                                            req as_model(uriBase, RDFXML))
+                                                  } )
+      statusCode must_== 200
+      via must_== "SPARQL"
+      model must beIsomorphicWith (referenceModel)
+    }
+  }
+  
+}
+
+
+object PostRDFSpec extends SomeDataInStore {
+  
+    val diffRDF =
+"""
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> 
+  <foaf:Person rdf:about="#JL" xmlns:foaf="http://xmlns.com/foaf/0.1/">
+    <foaf:openid rdf:resource="/2007/wiki/people/JoeLambda" />
+    <foaf:img rdf:resource="/2007/wiki/people/JoeLambda/images/me.jpg" />
+  </foaf:Person>
+</rdf:RDF>
+"""
+
+  val finalRDF =
+"""
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> 
+  <foaf:Person rdf:about="#JL" xmlns:foaf="http://xmlns.com/foaf/0.1/">
+    <foaf:name>Joe Lambda</foaf:name>
+    <foaf:homepage rdf:resource="/2007/wiki/people/JoeLambda" />
+    <foaf:openid rdf:resource="/2007/wiki/people/JoeLambda" />
+    <foaf:img rdf:resource="/2007/wiki/people/JoeLambda/images/me.jpg" />
+  </foaf:Person>
+</rdf:RDF>
+"""  
+    
+  val expectedFinalModel = modelFromString(finalRDF, uriBase, RDFXML).toOption.get
+
+  "POSTing an RDF document to Joe's URI" should {
+    "succeed" in {
+      val httpCode:Int = Http(uri.post(diffRDF, RDFXML) get_statusCode)
+      httpCode must_== 200
+    }
+    "append the diff graph to the initial graph" in {
+      val model = Http(uri as_model(uriBase, RDFXML))
+      model must beIsomorphicWith (expectedFinalModel)
+    }
+  }
+  
+}
+
+
+object PutInvalidRDFXMLSpec extends SomeDataInStore {
+
+  """PUTting not-valid RDF to Joe's URI""" should {
+    "return a 400 Bad Request" in {
+      val statusCode = Http.when(_ == 400)(uri.put(RDFXML, "that's bouleshit") get_statusCode)
+      statusCode must_== 400
+    }
+  }
+  
+}
+
+object PostOnNonExistingResourceSpec extends FilesystemBased {
+
+  "POSTing an RDF document to a resource that does not exist" should {
+    val doesnotexist = host / "2007/wiki/doesnotexist"
+    "return a 404" in {
+      val httpCode:Int = Http.when( _ => true)(doesnotexist get_statusCode)
+      httpCode must_== 404
+    }
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/scala/CreateDirectorySpec.scala	Thu Oct 27 22:05:25 2011 -0400
@@ -0,0 +1,20 @@
+package org.w3.readwriteweb
+
+import org.w3.readwriteweb.util._
+import org.w3.readwriteweb.utiltest._
+
+import dispatch._
+
+object CreateDirSpec extends FilesystemBased with SomeRDF with SomeURI {
+
+  "PUTing an RDF document on /people/" should {
+    "return a 201" in {
+      val httpCode = Http(dirUri.put(RDFXML, rdfxml) get_statusCode)
+      httpCode must_== 201
+    }
+    "create a directory on disk" in {
+      directory must be directory
+    }
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/scala/OtherSpecs.scala	Thu Oct 27 22:05:25 2011 -0400
@@ -0,0 +1,29 @@
+package org.w3.readwriteweb
+
+import org.w3.readwriteweb.util._
+import org.w3.readwriteweb.utiltest._
+
+import dispatch._
+
+object PostBouleshitSpec extends SomeDataInStore {
+
+  """POSTing something that does not make sense to Joe's URI""" should {
+    "return a 400 Bad Request" in {
+      val statusCode = Http.when(_ == 400)(uri.post("that's bouleshit", RDFXML) get_statusCode)
+      statusCode must_== 400
+    }
+  }
+  
+}
+
+
+object DeleteResourceSpec extends SomeDataInStore {
+
+  """a DELETE request""" should {
+    "not be supported yet" in {
+      val statusCode = Http.when(_ == 405)(uri.copy(method="DELETE") get_statusCode)
+      statusCode must_== 405
+    }
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/scala/ReadWriteWebSpecs.scala	Thu Oct 27 22:05:25 2011 -0400
@@ -0,0 +1,25 @@
+package org.w3.readwriteweb
+
+import org.specs._
+
+object ReadWriteWebSpec extends Specification {
+  "The Read Write Web".isSpecifiedBy(
+      // access content
+      GetStrictModeSpec, GetWikiModeSpec,
+      ContentNegociationSpec,
+      // create directory
+      CreateDirSpec,
+      // create content
+      PutRDFXMLSpec, PostRDFSpec,
+      PutInvalidRDFXMLSpec, PostOnNonExistingResourceSpec,
+      // sparql query
+      PostSelectSpec, PostConstructSpec, PostAskSpec, 
+      // sparql update
+      PostInsertSpec,
+      // delete content
+      DeleteResourceSpec,
+      // common errors
+      PostBouleshitSpec
+  )
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/scala/SparqlQuerySpecs.scala	Thu Oct 27 22:05:25 2011 -0400
@@ -0,0 +1,65 @@
+package org.w3.readwriteweb
+
+import org.w3.readwriteweb.util._
+import org.w3.readwriteweb.utiltest._
+
+import dispatch._
+
+import com.hp.hpl.jena.rdf.model._
+import com.hp.hpl.jena.query._
+
+import com.codecommit.antixml._
+
+object PostSelectSpec extends SomeDataInStore {
+
+  val selectFoafName =
+"""
+PREFIX foaf: <http://xmlns.com/foaf/0.1/>
+SELECT ?name WHERE { [] foaf:name ?name }
+"""
+  
+  """POSTing "SELECT ?name WHERE { [] foaf:name ?name }" to Joe's URI""" should {
+    "return Joe's name" in {
+      val resultSet = Http(uri.postSPARQL(selectFoafName) >- { body => ResultSetFactory.fromXML(body) } )
+      resultSet.next().getLiteral("name").getString must_== "Joe Lambda"
+    }
+  }
+  
+}
+
+
+object PostAskSpec extends SomeDataInStore {
+
+  val askFoafName =
+"""
+PREFIX foaf: <http://xmlns.com/foaf/0.1/>
+ASK { [] foaf:name ?name }
+"""
+  
+  """POSTing "ASK ?name WHERE { [] foaf:name ?name }" to Joe's URI""" should {
+    "return true" in {
+      val result: Boolean =
+        Http(uri.postSPARQL(askFoafName) >~ { s => 
+          (XML.fromSource(s) \ "boolean" \ text).head.toBoolean
+          } )
+      result must_== true
+    }
+  }
+  
+}
+
+object PostConstructSpec extends SomeDataInStore {
+
+  val constructIdentity =
+"""
+CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }
+"""
+  
+  """POSTing "CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }" to Joe's URI""" should {
+    "return an isomorphic RDF graph" in {
+      val model = Http(uri as_model(uriBase, RDFXML))
+      model must beIsomorphicWith (referenceModel)
+    }
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/scala/SparqlUpdateSpecs.scala	Thu Oct 27 22:05:25 2011 -0400
@@ -0,0 +1,27 @@
+package org.w3.readwriteweb
+
+import org.w3.readwriteweb.util._
+import org.w3.readwriteweb.utiltest._
+
+import dispatch._
+
+object PostInsertSpec extends SomeDataInStore {
+
+  val insertQuery =
+"""
+PREFIX foaf: <http://xmlns.com/foaf/0.1/>
+INSERT DATA { </2007/wiki/people/JoeLambda#JL> foaf:openid </2007/wiki/people/JoeLambda> }
+"""
+  
+  "POSTing an INSERT query on Joe's URI (which does not exist yet)" should {
+    "succeed" in {
+      val httpCode = Http(uri.postSPARQL(insertQuery) get_statusCode)
+      httpCode must_== 200
+    }
+    "produce a graph with one more triple than the original one" in {
+      val model = Http(uri as_model(uriBase, RDFXML))
+      model.size must_== (referenceModel.size + 1)
+    }
+  }
+  
+}
--- a/src/test/scala/Test.scala	Thu Oct 27 21:53:32 2011 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,212 +0,0 @@
-package org.w3.readwriteweb
-
-import org.specs._
-import java.net.URL
-import unfiltered.response._
-import unfiltered.request._
-import dispatch._
-import java.io._
-
-import com.codecommit.antixml._
-import grizzled.file.GrizzledFile._
-
-import com.hp.hpl.jena.rdf.model._
-import com.hp.hpl.jena.query._
-import com.hp.hpl.jena.update._
-
-import org.w3.readwriteweb.util._
-import org.w3.readwriteweb.utiltest._
-
-object ReadWriteWebSpec extends Specification with unfiltered.spec.jetty.Served {
-
-  val base = new File(new File(System.getProperty("java.io.tmpdir")), "readwriteweb")
-  val joe = host / "2007/wiki/people/JoeLambda"
-  val joeBaseURI = baseURI(joe)
-  val joeOnDisk = new File(base, "people/JoeLambda")
-  
-  //base.deleteRecursively()
-
-  doBeforeSpec {
-    if (base.exists)
-      base.deleteRecursively()
-    base.mkdir()
-  }
-  
-  doAfterSpec {
-//    if (joeOnDisk.exists) joeOnDisk.delete()
-  }
-  
-  val filesystem = new Filesystem(base, "/2007/wiki", "N3")(ResourcesDontExistByDefault)
-
-  def setup = { _.filter(new ReadWriteWeb(filesystem).read) }
-    
-  val joeRDF =
-"""
-<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> 
-  <foaf:Person rdf:about="#JL" xmlns:foaf="http://xmlns.com/foaf/0.1/">
-    <foaf:name>Joe Lambda</foaf:name>
-    <foaf:homepage rdf:resource="/2007/wiki/people/JoeLambda" />
-  </foaf:Person>
-</rdf:RDF>
-"""
-  
-  val initialModel = modelFromString(joeRDF, joeBaseURI).toOption.get
-
-  "a GET on a URL that does not exist" should {
-    "return a 404" in {
-      val httpCode:Int = Http.when( _ => true)(joe get_statusCode)
-      httpCode must_== 404
-    }
-  }
-  
-  "PUTing an RDF document on Joe's URI (which does not exist yet)" should {
-    "return a 201" in {
-      val httpCode:Int = Http(joe.put(joeRDF) get_statusCode)
-      httpCode must_== 201
-    }
-    "create a document on disk" in {
-      joeOnDisk must exist
-    }
-  }
-  
-  "Joe's URI" should {
-    "now exist and be isomorphic with the original document" in {
-      val (statusCode, via, model) = Http(joe >++ { req => (req.get_statusCode,
-                                                            req.get_header("MS-Author-Via"),
-                                                            req as_model(joeBaseURI))
-                                                  } )
-      statusCode must_== 200
-      via must_== "SPARQL"
-      model must beIsomorphicWith (initialModel)
-    }
-  }
-  
-  val insertQuery =
-"""
-PREFIX foaf: <http://xmlns.com/foaf/0.1/>
-INSERT DATA { </2007/wiki/people/JoeLambda#JL> foaf:openid </2007/wiki/people/JoeLambda> }
-"""
-  
-  "POSTing an INSERT query on Joe's URI (which does not exist yet)" should {
-    "succeed" in {
-      val httpCode:Int = Http(joe.post(insertQuery) get_statusCode)
-      httpCode must_== 200
-    }
-    "produce a graph with one more triple than the original one" in {
-      val model = Http(joe as_model(joeBaseURI))
-      model.size must_== (initialModel.size + 1)
-    }
-  }
-
-  "a GET on Joe's URI" should {
-    "deliver TURTLE and RDF/XML graphs that are isomorphic to each other" in {
-      val rdfxml = Http(joe as_model(joeBaseURI))
-      val turtle = Http(joe <:< Map("Accept" -> "text/turtle") as_model(joeBaseURI, lang="TURTLE"))
-      rdfxml must beIsomorphicWith(turtle)
-    }
-  }
-  
-  val diffRDF =
-"""
-<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> 
-  <foaf:Person rdf:about="#JL" xmlns:foaf="http://xmlns.com/foaf/0.1/">
-    <foaf:img rdf:resource="/2007/wiki/people/JoeLambda/images/me.jpg" />
-  </foaf:Person>
-</rdf:RDF>
-"""
-
-  val finalRDF =
-"""
-<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> 
-  <foaf:Person rdf:about="#JL" xmlns:foaf="http://xmlns.com/foaf/0.1/">
-    <foaf:name>Joe Lambda</foaf:name>
-    <foaf:homepage rdf:resource="/2007/wiki/people/JoeLambda" />
-    <foaf:openid rdf:resource="/2007/wiki/people/JoeLambda" />
-    <foaf:img rdf:resource="/2007/wiki/people/JoeLambda/images/me.jpg" />
-  </foaf:Person>
-</rdf:RDF>
-"""  
-    
-  val expectedFinalModel = modelFromString(finalRDF, joeBaseURI).toOption.get
-
-  "POSTing an RDF document to Joe's URI" should {
-    "succeed" in {
-      val httpCode:Int = Http(joe.post(diffRDF) get_statusCode)
-      httpCode must_== 200
-    }
-    "append the diff graph to the initial graph" in {
-      val model = Http(joe as_model(joeBaseURI))
-      model must beIsomorphicWith (expectedFinalModel)
-    }
-  }
-
-  "POSTing an RDF document to a resource that does not exist" should {
-    val doesnotexist = host / "2007/wiki/doesnotexist"
-    "return a 404" in {
-      val httpCode:Int = Http.when( _ => true)(doesnotexist get_statusCode)
-      httpCode must_== 404
-    }
-  }
-  
-  val selectFoafName =
-"""
-PREFIX foaf: <http://xmlns.com/foaf/0.1/>
-SELECT ?name WHERE { [] foaf:name ?name }
-"""
-  
-  """POSTing "SELECT ?name WHERE { [] foaf:name ?name }" to Joe's URI""" should {
-    "return Joe's name" in {
-      val resultSet = Http(joe.post(selectFoafName) >- { body => ResultSetFactory.fromXML(body) } )
-      resultSet.next().getLiteral("name").getString must_== "Joe Lambda"
-    }
-  }
-  
-  val askFoafName =
-"""
-PREFIX foaf: <http://xmlns.com/foaf/0.1/>
-ASK { [] foaf:name ?name }
-"""
-  
-  """POSTing "ASK ?name WHERE { [] foaf:name ?name }" to Joe's URI""" should {
-    "return true" in {
-      val result:Boolean =
-        Http(joe.post(askFoafName) >~ { s => 
-          (XML.fromSource(s) \ "boolean" \ text).head.toBoolean
-          } )
-      result must_== true
-    }
-  }
-  
-  val constructIdentity =
-"""
-CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }
-"""
-  
-  """POSTing "CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }" to Joe's URI""" should {
-    "return an isomorphic RDF graph" in {
-      val model = Http(joe as_model(joeBaseURI))
-      model must beIsomorphicWith (expectedFinalModel)
-    }
-  }
-  
-  """POSTing something that does not make sense to Joe's URI""" should {
-    "return a 400 Bad Request" in {
-      val statusCode = Http.when(_ == 400)(joe.post("that's bouleshit") get_statusCode)
-      statusCode must_== 400
-    }
-  }
-  
-  """PUTting not-valid RDF to Joe's URI""" should {
-    "return a 400 Bad Request" in {
-      val statusCode = Http.when(_ == 400)(joe.put("that's bouleshit") get_statusCode)
-      statusCode must_== 400
-    }
-  }
-
-  """a DELETE request""" should {
-    "not be supported yet" in {
-      val statusCode = Http.when(_ == 405)(joe.copy(method="DELETE") get_statusCode)
-      statusCode must_== 405
-    }
-  }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/scala/util/specs.scala	Thu Oct 27 22:05:25 2011 -0400
@@ -0,0 +1,101 @@
+package org.w3.readwriteweb.util
+
+import org.w3.readwriteweb._
+
+import org.specs._
+import java.net.URL
+import unfiltered.response._
+import unfiltered.request._
+import dispatch._
+import java.io._
+
+import com.codecommit.antixml._
+import grizzled.file.GrizzledFile._
+
+import com.hp.hpl.jena.rdf.model._
+import com.hp.hpl.jena.query._
+import com.hp.hpl.jena.update._
+
+import org.w3.readwriteweb.util._
+import org.w3.readwriteweb.utiltest._
+
+trait ResourceManaged extends Specification with unfiltered.spec.jetty.Served {
+  
+  def resourceManager: ResourceManager
+  
+  def setup = { _.filter(new ReadWriteWeb(resourceManager).plan) }
+ 
+}
+
+trait FilesystemBased extends ResourceManaged {
+  
+  lazy val mode: RWWMode = ResourcesDontExistByDefault
+  
+  lazy val lang = RDFXML
+    
+  lazy val baseURL = "/wiki"
+  
+  lazy val root = new File(new File(System.getProperty("java.io.tmpdir")), "readwriteweb")
+
+  lazy val resourceManager = new Filesystem(root, baseURL, lang)(mode)
+  
+  doBeforeSpec {
+    if (root.exists) root.deleteRecursively()
+    root.mkdir()
+  }
+  
+}
+
+trait SomeRDF extends SomeURI {
+  
+  val rdfxml =
+"""
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> 
+  <foaf:Person rdf:about="#JL" xmlns:foaf="http://xmlns.com/foaf/0.1/">
+    <foaf:name>Joe Lambda</foaf:name>
+    <foaf:homepage rdf:resource="/2007/wiki/people/JoeLambda" />
+  </foaf:Person>
+</rdf:RDF>
+"""
+      
+  val turtle =
+"""
+"""
+    
+  val referenceModel = modelFromString(rdfxml, uriBase, RDFXML).toOption.get
+
+}
+
+trait SomeURI extends FilesystemBased {
+  
+  val emptyModel = com.hp.hpl.jena.rdf.model.ModelFactory.createDefaultModel()
+  
+  lazy val dirUri = host / "wiki/people/"
+  
+  lazy val uri = host / "wiki/people/JoeLambda"
+  
+  lazy val uriBase = baseURI(uri)
+  
+  lazy val directory = new File(root, "people")
+  
+  lazy val resourceOnDisk = new File(root, "people/JoeLambda")
+  
+}
+
+trait SomePeopleDirectory extends SomeRDF with SomeURI {
+  
+  doBeforeSpec {
+    val httpCode = Http(dirUri.put(RDFXML, rdfxml) get_statusCode)
+    httpCode must_== 201
+  }
+  
+}
+
+trait SomeDataInStore extends SomePeopleDirectory {
+  
+  doBeforeSpec {
+    val httpCode = Http(uri.put(RDFXML, rdfxml) get_statusCode)
+    httpCode must_== 201
+  }
+  
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/scala/util/utiltest.scala	Thu Oct 27 22:05:25 2011 -0400
@@ -0,0 +1,87 @@
+package org.w3.readwriteweb
+
+import java.net.URL
+import javax.servlet._
+import javax.servlet.http._
+import unfiltered.request._
+import unfiltered.response._
+import unfiltered.jetty._
+
+import java.io._
+import scala.io.Source
+
+import org.slf4j.{Logger, LoggerFactory}
+
+import com.hp.hpl.jena.rdf.model._
+import com.hp.hpl.jena.query._
+import com.hp.hpl.jena.update._
+
+import unfiltered.request._
+import unfiltered.response._
+import unfiltered.jetty._
+
+import dispatch._
+
+import org.specs.matcher.Matcher
+
+import org.w3.readwriteweb.util._
+
+package object utiltest {
+  
+  def baseURI(req: Request): URL = new URL("%s%s" format (req.host, req.path))
+  
+  def beIsomorphicWith(that: Model): Matcher[Model] =
+    new Matcher[Model] {
+      def apply(otherModel:  => Model) =
+        (that isIsomorphicWith otherModel,
+         "Model A is isomorphic to model B",
+         "%s not isomorphic with %s" format (otherModel.toString, that.toString))
+  }
+  
+  class RequestW(req: Request) {
+
+    def as_model(base: URL, lang: Lang): Handler[Model] =
+      req >> { is => modelFromInputStream(is, base, lang).toOption.get }
+
+    def post(body: String, lang: Lang): Request =
+      post(body, lang.contentType)
+    
+    def postSPARQL(body: String): Request =
+      post(body, Post.SPARQL)
+      
+    private def post(body: String, contentType: String): Request =
+      (req <:< Map("Content-Type" -> contentType) <<< body).copy(method="POST")
+
+      
+    def put(lang: Lang, body: String): Request =
+      req <:< Map("Content-Type" -> lang.contentType) <<< body
+      
+    def get_statusCode: Handler[Int] = new Handler(req, (c, r, e) => c, { case t => () })
+    
+    def get_header(header: String): Handler[String] = req >:> { _(header).head }
+    
+    def get: Request = req.copy(method="GET")
+    
+    def >++ [A, B, C] (block:  Request => (Handler[A], Handler[B], Handler[C])) = {
+      Handler(req, { (code, res, opt_ent) =>
+        val (a, b, c) = block( /\ )
+          (a.block(code, res, opt_ent), b.block(code,res,opt_ent), c.block(code,res,opt_ent))
+      } )
+    }
+    
+    def >+ [A, B] (block:  Request => (Handler[A], Handler[B])) = {
+      Handler(req, { (code, res, opt_ent) =>
+        val (a, b) = block( /\ )
+        (a.block(code, res, opt_ent), b.block(code,res,opt_ent))
+      } )
+    }
+    
+  }
+  
+  implicit def wrapRequest(req: Request): RequestW = new RequestW(req)
+  
+
+
+
+
+}
\ No newline at end of file
--- a/src/test/scala/utiltest.scala	Thu Oct 27 21:53:32 2011 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,70 +0,0 @@
-package org.w3.readwriteweb
-
-import javax.servlet._
-import javax.servlet.http._
-import unfiltered.request._
-import unfiltered.response._
-import unfiltered.jetty._
-
-import java.io._
-import scala.io.Source
-
-import org.slf4j.{Logger, LoggerFactory}
-
-import com.hp.hpl.jena.rdf.model._
-import com.hp.hpl.jena.query._
-import com.hp.hpl.jena.update._
-
-import unfiltered.request._
-import unfiltered.response._
-import unfiltered.jetty._
-
-import dispatch._
-
-import org.specs.matcher.Matcher
-
-import org.w3.readwriteweb.util._
-
-package object utiltest {
-  
-  def baseURI(req:Request):String = "%s%s" format (req.host, req.path)
-  
-  def beIsomorphicWith(that:Model):Matcher[Model] =
-    new Matcher[Model] {
-      def apply(otherModel: => Model) =
-        (that isIsomorphicWith otherModel,
-         "Model A is isomorphic to model B",
-         "%s not isomorphic with %s" format (otherModel.toString, that.toString))
-  }
-  
-  class RequestW(req:Request) {
-
-    def as_model(base:String, lang:String = "RDF/XML-ABBREV"):Handler[Model] =
-      req >> { is => modelFromInputStream(is, base, lang).toOption.get }
-
-    def post(body:String):Request =
-      (req <<< body).copy(method="POST")
-      
-    def put(body:String):Request = req <<< body
-      
-    def get_statusCode:Handler[Int] = new Handler(req, (c, r, e) => c, { case t => () })
-    
-    def get_header(header:String):Handler[String] = req >:> { _(header).head }
-    
-    def get:Request = req.copy(method="GET")
-    
-    def >++ [A, B, C] (block: Request => (Handler[A], Handler[B], Handler[C])) = {
-      Handler(req, { (code, res, opt_ent) =>
-        val (a, b, c) = block( /\ )
-          (a.block(code, res, opt_ent), b.block(code,res,opt_ent), c.block(code,res,opt_ent))
-      } )
-    }
-  }
-  
-  implicit def wrapRequest(req:Request):RequestW = new RequestW(req)
-  
-
-
-
-
-}
\ No newline at end of file