merged with Alexandre Bertails branch. Apart from adding initial WebID code, a few small changes such as a method for translating mime type objects into their Jena equivalent. webid
authorHenry Story <henry.story@bblfish.net>
Wed, 12 Oct 2011 15:26:31 +0200
branchwebid
changeset 57 f647f0335a35
parent 56 1ef59b9dab9b (current diff)
parent 51 e34ee305ffe3 (diff)
child 58 6f860f662ed4
child 59 34ec240b4fd2
merged with Alexandre Bertails branch. Apart from adding initial WebID code, a few small changes such as a method for translating mime type objects into their Jena equivalent.
src/main/scala/Filesystem.scala
src/main/scala/ReadWriteWebMain.scala
src/main/scala/Resource.scala
src/main/scala/plan.scala
src/main/scala/rdfLanguage.scala
src/main/scala/util/package.scala
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/Filesystem.scala	Wed Oct 12 15:26:31 2011 +0200
@@ -0,0 +1,68 @@
+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 sys.error
+import scalaz._
+import Scalaz._
+
+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("@@@")
+        }
+        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)
+        writer.write(model, fos, url.toString)
+        fos.close().success
+      } catch {
+        case t => t.fail
+      }
+
+  }
+  
+}
--- a/src/main/scala/Post.scala	Wed Oct 12 14:19:47 2011 +0200
+++ b/src/main/scala/Post.scala	Wed Oct 12 15:26:31 2011 +0200
@@ -1,54 +1,61 @@
 package org.w3.readwriteweb
 
-import java.io._
+import org.w3.readwriteweb.util.modelFromString
+
+import java.io.{InputStream, StringReader}
 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._
 import Scalaz._
 
 object Post {
   
-  val logger:Logger = LoggerFactory.getLogger(this.getClass)
+  val logger: Logger = LoggerFactory.getLogger(this.getClass)
 
-  def parse(is:InputStream, baseURI:String):Post = {
+  def parse(is: InputStream, baseURI:String): Post = {
     val source = Source.fromInputStream(is, "UTF-8")
     val s = source.getLines.mkString("\n")
     parse(s, baseURI)
   }
   
-  def parse(s:String, baseURI:String):Post = {
+  def parse(s: String, baseURI: String): Post = {
     val reader = new StringReader(s)
+    
     def postUpdate =
       try {
-        val update:UpdateRequest = UpdateFactory.create(s, baseURI)
+        val update: UpdateRequest = UpdateFactory.create(s, baseURI)
         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 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))
   }
   
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/ReadWriteWebMain.scala	Wed Oct 12 15:26:31 2011 +0200
@@ -0,0 +1,106 @@
+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 postUsageMsg= Some("""
+  |PROPERTIES
+  |
+  | * Keystore properties that need to be set if https is started
+  |  -Djetty.ssl.keyStoreType=type : the type of the keystore, JKS by default usually
+  |  -Djetty.ssl.keyStore=path : specify path to key store (for https server certificate)
+  |  -Djetty.ssl.keyStorePassword=password : specify password for keystore store (optional)
+  |
+  |NOTES
+  |
+  |  - Trust stores are not needed because we use the WebID protocol, and client certs are nearly never signed by CAs
+  |  - one of --http or --https must be selected
+     """.stripMargin);
+
+  val parser = new ArgotParser("read-write-web",postUsage=postUsageMsg)
+
+  val mode = parser.option[RWWMode](List("mode"), "m", "wiki mode: wiki or strict") {
+      (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: n3, turtle, or rdfxml") {
+      (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 httpPort = parser.option[Int]("http", "Port","start the http server on port")
+    val httpsPort = parser.option[Int]("https","port","start the https server on port")
+
+    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
+    }
+    }
+
+   implicit val webCache = new WebCache()
+
+
+   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 "N3")(mode.value getOrElse ResourcesDontExistByDefault)
+
+    val app = new ReadWriteWeb(filesystem)
+
+    //this is incomplete: we should be able to start both ports.... not sure how to do this yet.
+    val service = httpsPort.value match {
+      case Some(port) => HttpsTrustAll(port,"0.0.0.0")
+      case None => Http(httpPort.value.get)
+    }
+
+    // configures and launches a Jetty server
+    service.filter(new FilterLogger(logger)).
+      filter(new webid.AuthFilter).
+      context("/public"){ ctx:ContextBuilder =>
+      ctx.resources(ClasspathUtils.fromClasspath("public/").toURI.toURL)
+    }.filter(app.plan).run()
+
+  }
+
+}
+
+
--- a/src/main/scala/Resource.scala	Wed Oct 12 14:19:47 2011 +0200
+++ b/src/main/scala/Resource.scala	Wed Oct 12 15:26:31 2011 +0200
@@ -1,84 +1,20 @@
 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 _root_.scala.sys.error
-
 trait ResourceManager {
-  def basePath:String
-  def sanityCheck():Boolean
-  def resource(url:URL):Resource
+  def basePath: String
+  def sanityCheck(): Boolean
+  def resource(url: URL): Resource
 }
 
 trait Resource {
-  def get():Validation[Throwable, Model]
-  def save(model:Model):Validation[Throwable, Unit]
+  def get(): Validation[Throwable, Model]
+  def save(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("@@@")
-        }
-        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)
-        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	Wed Oct 12 15:26:31 2011 +0200
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2011 Henry Story (bblfish.net)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by Henry Story.  The name of bblfish.net may not be used to endorse
+ * or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+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._
+
+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 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 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")
+      }
+    }
+
+  }
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/rdfLanguage.scala	Wed Oct 12 15:26:31 2011 +0200
@@ -0,0 +1,37 @@
+package org.w3.readwriteweb
+
+import unfiltered.request._
+
+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 jena(encoding: RDFEncoding) = encoding match {
+     case RDFXML => "RDF/XML-ABBREV"
+     case TURTLE => "TURTLE"
+     case _      => "RDF/XML-ABBREV" //don't like this default
+   }
+
+  def apply(req:HttpRequest[_]):RDFEncoding = {
+    val contentType = Accept(req).headOption
+    contentType map { RDFEncoding(_) } getOrElse RDFXML
+  }
+  
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/util/ClasspathUtils.scala	Wed Oct 12 15:26:31 2011 +0200
@@ -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	Wed Oct 12 15:26:31 2011 +0200
@@ -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/ValidationW.scala	Wed Oct 12 15:26:31 2011 +0200
@@ -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/mode.scala	Wed Oct 12 15:26:31 2011 +0200
@@ -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	Wed Oct 12 15:26:31 2011 +0200
@@ -0,0 +1,75 @@
+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._
+
+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) 
+      }
+  }
+
+  //Passing strings into mathod arguments, especially as these differ completely between rdf stacks is not so good
+  //passing objects is better
+  def modelFromInputStream(is:InputStream, base: String,  lang: RDFEncoding): Validation[Throwable, Model]=
+      modelFromInputStream(is, base, RDFEncoding.jena(lang))
+
+  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)
+  
+}
--- a/src/test/scala/util/specs.scala	Wed Oct 12 14:19:47 2011 +0200
+++ b/src/test/scala/util/specs.scala	Wed Oct 12 15:26:31 2011 +0200
@@ -23,7 +23,7 @@
   
   def resourceManager: ResourceManager
   
-  def setup = { _.filter(new ReadWriteWeb(resourceManager).read) }
+  def setup = { _.filter(new ReadWriteWeb(resourceManager).plan) }
  
 }