~ merge
authorAlexandre Bertails <bertails@w3.org>
Mon, 03 Oct 2011 13:00:03 -0400
changeset 42 de67196c30ad
parent 41 7d4fdaad3a2a (current diff)
parent 40 8562e8b7d113 (diff)
child 43 691915e25ea1
~ merge
README.markdown
--- a/.hgignore	Mon Oct 03 12:56:21 2011 -0400
+++ b/.hgignore	Mon Oct 03 13:00:03 2011 -0400
@@ -13,5 +13,6 @@
 *\#
 src/main/scala.egp
 src/test/scala.egp
-sbt-launch.jar
-.scala_dependencies
\ No newline at end of file
+sbt-launch*.jar
+.scala_dependencies
+*.orig
\ No newline at end of file
--- a/README.markdown	Mon Oct 03 12:56:21 2011 -0400
+++ b/README.markdown	Mon Oct 03 13:00:03 2011 -0400
@@ -1,7 +1,17 @@
 The ReadWriteWeb app
 --------------------
 
-It depends on:
+To get this:
+
+    hg clone http://dvcs.w3.org/read-write-web
+    cd read-write-web
+    less README.txt
+    ./sbt
+    
+See http://mercurial.selenic.com/ for hg
+See https://github.com/harrah/xsbt/wiki for sbt
+
+This projectdepends on:
 
 * Java 6
 * that's all :-)
@@ -55,4 +65,10 @@
 Using the stand-alone jar
 -------------------------
 
-java -jar target/read-write-web.jar 8080 ~/WWW/2011/09 /2011/09
+java -jar target/read-write-web.jar 8080 ~/WWW/2011/09 /2011/09  [options]
+
+Options:
+    --relax   All documents exist as empty RDF files (like a wiki).
+    --strict  Documents must be created using PUT else they return 404
+    
+    
--- a/project/build.scala	Mon Oct 03 12:56:21 2011 -0400
+++ b/project/build.scala	Mon Oct 03 13:00:03 2011 -0400
@@ -4,17 +4,17 @@
 // some usefull libraries
 // they are pulled only if used
 object Dependencies {
-  val specs = "org.scala-tools.testing" % "specs_2.9.0-1" % "1.6.8" % "test"
-  val scalatest = "org.scalatest" % "scalatest_2.9.0" % "1.6.1" % "test"
-  val dispatch = "net.databinder" %% "dispatch-http" % "0.8.4" % "test"
+  val specs = "org.scala-tools.testing" %% "specs" % "1.6.9" % "test"
+  val dispatch = "net.databinder" %% "dispatch-http" % "0.8.5" % "test"
   val unfiltered_filter = "net.databinder" %% "unfiltered-filter" % "0.4.1"
   val unfiltered_jetty = "net.databinder" %% "unfiltered-jetty" % "0.4.1"
   val unfiltered_spec = "net.databinder" %% "unfiltered-spec" % "0.4.1" % "test"
   val slf4jSimple = "org.slf4j" % "slf4j-simple" % "1.5.8"
-  val antiXML = "com.codecommit" %% "anti-xml" % "0.3-SNAPSHOT" % "test"
+  val antiXML = "com.codecommit" %% "anti-xml" % "0.4-SNAPSHOT" % "test"
   val jena = "com.hp.hpl.jena" % "jena" % "2.6.4"
   val arq = "com.hp.hpl.jena" % "arq" % "2.8.8"
-  val grizzled = "org.clapper" %% "grizzled-scala" % "1.0.7" % "test"
+  val grizzled = "org.clapper" %% "grizzled-scala" % "1.0.8" % "test"
+  val scalaz = "org.scalaz" %% "scalaz-core" % "6.0.2"
 }
 
 // some usefull repositories
@@ -27,7 +27,7 @@
 
   val buildOrganization = "org.w3"
   val buildVersion      = "0.1-SNAPSHOT"
-  val buildScalaVersion = "2.9.0-1"
+  val buildScalaVersion = "2.9.1"
 
   val buildSettings = Defaults.defaultSettings ++ Seq (
     organization := buildOrganization,
@@ -46,6 +46,8 @@
   import BuildSettings._
   import ProguardPlugin._
   import sbtassembly.Plugin._
+  import sbtassembly.Plugin.AssemblyKeys._
+  
 
   def keepUnder(pakage:String):String = "-keep class %s.**" format pakage
   
@@ -74,7 +76,8 @@
       libraryDependencies += arq,
       libraryDependencies += antiXML,
       libraryDependencies += grizzled,
-      jarName in Assembly := "read-write-web.jar"
+      libraryDependencies += scalaz,
+      jarName in assembly := "read-write-web.jar"
     )
 
   lazy val project = Project(
--- a/project/plugins/build.sbt	Mon Oct 03 12:56:21 2011 -0400
+++ b/project/plugins/build.sbt	Mon Oct 03 13:00:03 2011 -0400
@@ -1,16 +1,10 @@
-libraryDependencies <+= (sbtVersion) { sv => "com.eed3si9n" %% "sbt-assembly" % ("sbt" + sv + "_0.4") }
+addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.7.0")
 
-resolvers += {
-  val typesafeRepoUrl = new java.net.URL("http://repo.typesafe.com/typesafe/releases")
-  val pattern = Patterns(false, "[organisation]/[module]/[sbtversion]/[revision]/[type]s/[module](-[classifier])-[revision].[ext]")
-  Resolver.url("Typesafe Repository", typesafeRepoUrl)(pattern)
-}
+resolvers += Classpaths.typesafeResolver
 
-libraryDependencies <<= (libraryDependencies, sbtVersion) { (deps, version) => 
-  deps :+ ("com.typesafe.sbteclipse" %% "sbteclipse" % "1.3-RC3" extra("sbtversion" -> version))
-}
+addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse" % "1.4.0")
 
 resolvers += "Proguard plugin repo" at "http://siasia.github.com/maven2"
 
-libraryDependencies <+= sbtVersion("com.github.siasia" %% "xsbt-proguard-plugin" % _)
+addSbtPlugin("com.github.siasia" % "xsbt-proguard-plugin" % "0.1")
 
--- a/sbt	Mon Oct 03 12:56:21 2011 -0400
+++ b/sbt	Mon Oct 03 13:00:03 2011 -0400
@@ -3,9 +3,40 @@
 dir=$(dirname $0)
 cd "$dir"
 
-if [ ! -f sbt-launch.jar ]; then
-    wget http://typesafe.artifactoryonline.com/typesafe/ivy-releases/org.scala-tools.sbt/sbt-launch/0.10.1/sbt-launch.jar
+url="http://repo.typesafe.com/typesafe/ivy-releases/org.scala-tools.sbt/sbt-launch/0.11.0/sbt-launch.jar"
+
+sbt="sbt-launch-0.11.0.jar"
+
+# set the right tool to download sbt
+if [ -n "$tool" ]; then
+    echo -n
+elif [ -n "$(which wget)" ]; then
+    tool="wget"
+elif [ -n "$(which curl)" ]; then
+    tool="curl"
+else
+    echo "Couldn't find a tool to download sbt. Please do the following"
+    echo "* download $url"
+    echo "* set the name of the file to $sbt"
+    echo "* relaunch ./sbt"
+    exit 1
 fi
 
-java -Xmx512M -jar -Dfile.encoding=UTF8 -Xmx1536M -Xss1M -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m "$dir/sbt-launch.jar" "$@"
+# download the sbt launcher if it's not already here
+if [ ! -f "$sbt" ]; then
+    case "$tool" in
+        "wget"*)
+            wget "$url" -O "./$sbt"
+            ;;
+        "curl"*)
+            curl "$url" -o "./$sbt"
+            ;;
+        *)
+            echo "don't know this tool: $tool"
+            exit 2
+    esac
+fi
 
+# tweak this line according to your needs
+java -Xmx512M -jar -Dfile.encoding=UTF8 -Xmx1536M -Xss1M -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m "$dir/$sbt" "$@"
+
--- a/src/main/resources/public/mashlib.js	Mon Oct 03 12:56:21 2011 -0400
+++ b/src/main/resources/public/mashlib.js	Mon Oct 03 13:00:03 2011 -0400
@@ -1150,7 +1150,7 @@
  *  and industry standards where appropriate (DOM, ECMAScript, &c.)
  *
  *  Author: David Sheets <dsheets@mit.edu>
- *  SVN ID: $Id: mashlib.js,v 1.4 2011/08/30 18:40:18 timbl Exp $
+ *  SVN ID: $Id$
  *
  * W3C® SOFTWARE NOTICE AND LICENSE
  * http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231
@@ -2620,7 +2620,7 @@
 
 /*
 
-$Id: mashlib.js,v 1.4 2011/08/30 18:40:18 timbl Exp $
+$Id: n3parser.js 14561 2008-02-23 06:37:26Z kennyluck $
 
 HAND EDITED FOR CONVERSION TO JAVASCRIPT
 
@@ -4664,7 +4664,7 @@
 //
 // Here we introduce for the first time a subclass of term: variable.
 //
-// SVN ID: $Id: mashlib.js,v 1.4 2011/08/30 18:40:18 timbl Exp $
+// SVN ID: $Id: query.js 25116 2008-11-15 16:13:48Z timbl $
 
 //  Variable
 //
@@ -7659,7 +7659,7 @@
         kb.the(req, ns.link('status')).append(kb.literal(status))
     }
 
-    // Record errors in the system omn failure
+    // Record errors in the system on failure
     // Returns xhr so can just do return this.failfetch(...)
     this.failFetch = function(xhr, status) {
         this.addStatus(xhr.req, status)
@@ -7974,7 +7974,10 @@
                     xhr.handle(function() {
                         sf.doneFetch(xhr, args)
                     })
-                }
+                } else {
+                    sf.failFetch(xhr, "HTTP failed unusually. (Skipped readyState3?) (cross-site violation?) for <"+
+                        docuri+">");
+                }    
                 break
             }
         }
@@ -8003,7 +8006,11 @@
         }
 
         // Setup the request
-        xhr.open('GET', uri2, this.async)
+        try {
+            xhr.open('GET', uri2, this.async)
+        } catch (er) {
+            return this.failFetch(xhr, "XHR open for GET failed for <"+uri2+">:\n\t" + er);
+        }
         
         // Set redirect callback and request headers -- alas Firefox Only
         
@@ -8120,8 +8127,7 @@
         try {
             xhr.send(null)
         } catch (er) {
-            this.failFetch(xhr, "sendFailed:" + er)
-            return xhr
+            return this.failFetch(xhr, "XHR send failed:" + er);
         }
         this.addStatus(xhr.req, "HTTP Request sent.");
 
@@ -20771,88 +20777,41 @@
      
      {
      
-        display = window.open(" ",'NewWin','menubar=0,location=no,status=no,directories=no,toolbar=no,scrollbars=yes,height=200,width=200')
+        display = window.open(" ",'NewWin',
+            'menubar=0,location=no,status=no,directories=no,toolbar=no,scrollbars=yes,height=200,width=200')
      
         display.tabulator = tabulator;
+        tabulator.options.names = [ 'BY-NC-ND', 'BY-NC-SA', 'BY-NC', 'BY-ND', 'BY-SA', 'BY'];
                                   
         var message="<font face='arial' size='2'><form name ='checkboxes'>";
-                 
-        if(tabulator.options.checkedLicenses[0]){    
-            message+="<input type='checkbox' name = 'one' onClick = 'tabulator.options.submit()' CHECKED />CC: BY-NC-ND<br />";        
-        }
-        
-        else{
-            message+="<input type='checkbox' name = 'one' onClick = 'tabulator.options.submit()' />CC: BY-NC-ND<br />";
-        }
-        
-        if(tabulator.options.checkedLicenses[1]){    
-            message+="<input type='checkbox' name = 'two' onClick = 'tabulator.options.submit()' CHECKED />CC: BY-NC-SA<br />";        
-        }
-                
-        else{
-            message+="<input type='checkbox' name = 'two' onClick = 'tabulator.options.submit()' />CC: BY-NC-SA<br />";
-        }
-        if(tabulator.options.checkedLicenses[2]){    
-            message+="<input type='checkbox' name = 'three' onClick = 'tabulator.options.submit()' CHECKED />CC: BY-NC<br />";        
-        }
-                
-                else{
-                    message+="<input type='checkbox' name = 'three' onClick = 'tabulator.options.submit()' />CC: BY-NC<br />";
-        }
-         if(tabulator.options.checkedLicenses[3]){    
-                    message+="<input type='checkbox' name = 'four' onClick = 'tabulator.options.submit()' CHECKED />CC: BY-ND<br />";        
-                }
-                
-                else{
-                    message+="<input type='checkbox' name = 'four' onClick = 'tabulator.options.submit()' />CC: BY-ND<br />";
-        }
-         if(tabulator.options.checkedLicenses[4]){    
-                    message+="<input type='checkbox' name = 'five' onClick = 'tabulator.options.submit()' CHECKED />CC: BY-SA<br />";        
-                }
-                
-                else{
-                    message+="<input type='checkbox' name = 'five' onClick = 'tabulator.options.submit()' />CC: BY-SA<br />";
-        }
-         if(tabulator.options.checkedLicenses[5]){    
-                    message+="<input type='checkbox' name = 'six' onClick = 'tabulator.options.submit()' CHECKED />CC: BY<br />";        
-                }
-                
-         else{
-             message+="<input type='checkbox' name = 'six' onClick = 'tabulator.options.submit()' />CC: BY<br />";
-        }
+        var lics = tabulator.options.checkedLicenses;
+        for (var kk =0; kk< lics.length; kk++)
+            message += "<input type='checkbox' name = 'n"+kk+
+                "' onClick = 'tabulator.options.submit()'"
+                + (lics[kk] ? "CHECKED" : "") + " />CC: "+tabulator.options.names[kk]+"<br />";
                  
         message+="<br /> <a onclick='tabulator.options.selectAll()'>[Select All] </a>";
-                 
         message+="<a onclick='tabulator.options.deselectAll()'> [Deselect All]</a>";
-     
         message+="</form></font>";
                  
         display.document.write(message);
                  
         display.document.close(); 
         
-        tabulator.options.references[0] = display.document.checkboxes.one;
-        tabulator.options.references[1] = display.document.checkboxes.two;
-        tabulator.options.references[2] = display.document.checkboxes.three;
-        tabulator.options.references[3] = display.document.checkboxes.four;
-        tabulator.options.references[4] = display.document.checkboxes.five;
-        tabulator.options.references[5] = display.document.checkboxes.six;
-     
-            }
+        var i;
+        for(i=0; i<6; i++){
+            tabulator.options.references[i] = display.document.checkboxes.elements[i];
+        }     
+    }
     
     
     tabulator.options.checkedLicenses = [];
    
     tabulator.options.selectAll = function()
     {
-        display.document.checkboxes.one.checked = true;
-        display.document.checkboxes.two.checked = true;
-        display.document.checkboxes.three.checked = true;
-        display.document.checkboxes.four.checked = true;
-        display.document.checkboxes.five.checked = true;
-        display.document.checkboxes.six.checked = true;
-        
+        var i;
         for(i=0; i<6; i++){
+            display.document.checkboxes.elements[i].checked = true;
             tabulator.options.references[i].checked = true;
             tabulator.options.checkedLicenses[i] = true;
         }
@@ -20861,16 +20820,11 @@
     
     tabulator.options.deselectAll = function()
     {
-        display.document.checkboxes.one.checked = false;
-        display.document.checkboxes.two.checked = false;
-        display.document.checkboxes.three.checked = false;
-        display.document.checkboxes.four.checked = false;
-        display.document.checkboxes.five.checked = false;
-        display.document.checkboxes.six.checked = false;
-        
+        var i;
         for(i=0; i<6; i++){
-                    tabulator.options.references[i].checked = false;
-                    tabulator.options.checkedLicenses[i] = false;
+            display.document.checkboxes.elements[i].checked = false;
+            tabulator.options.references[i].checked = false;
+            tabulator.options.checkedLicenses[i] = false;
         }
     
     }
@@ -20878,19 +20832,10 @@
     
     tabulator.options.submit = function()
     {   
-    
         alert('tabulator.options.submit: checked='+tabulator.options.references[0].checked);
-        
         for(i=0; i<6; i++)
-        {
-            if(tabulator.options.references[i].checked)
-            {
-                tabulator.options.checkedLicenses[i] = true;
-            }
-            else
-            {
-                tabulator.options.checkedLicenses[i] = false;
-            }
+        {   tabulator.options.checkedLicenses[i] = !!
+                    tabulator.options.references[i].checked;
         }
     }
         
@@ -20931,7 +20876,7 @@
         // tabulator.log.info('class on '+td)
         var check = td.getAttribute('class')
         // tabulator.log.info('td has class:' + check)
-        tabulator.log.info("selection has " +selection.map(function(item){return item.textContent;}).join(", "));             
+        // tabulator.log.info("selection has " +selection.map(function(item){return item.textContent;}).join(", "));             
          
         if (kb.whether(obj, tabulator.ns.rdf('type'), tabulator.ns.link('Request')))
             td.className='undetermined'; //@@? why-timbl
@@ -21594,8 +21539,8 @@
     this.showSource = function showSource(){
         //deselect all before going on, this is necessary because you would switch tab,
         //close tab or so on...
-        for (var sourceRow in sourceWidget.sources)
-            sourceRow.setAttribute('class', ''); //.class doesn't work. Be careful!
+        for (var uri in sourceWidget.sources)
+            sourceWidget.sources[uri].setAttribute('class', ''); //.class doesn't work. Be careful!
         for (var i=0;i<selection.length;i++){
             if (!selection[i].parentNode) {
                 dump("showSource: EH? no parentNode? "+selection[i]+"\n");
@@ -25530,8 +25475,8 @@
 
 
     tabulator.requestUUIDs = {};
-
-    // This has an empty id attribute instead of uuid string, beware.
+/*
+    // This has an empty id attribute instead of uuid string, beware. Not used
     tabulator.outlineTemplate = //    ###    This needs its link URIs adjusting! @@@
             // "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"+
             "<html id='docHTML'>\n"+
@@ -25546,7 +25491,7 @@
             "        </div>\n"+
             "    </body>\n"+
             "</html>\n";
-
+*/
     // complain("@@ init.js test 118 )");
 
 
--- a/src/main/scala/Main.scala	Mon Oct 03 12:56:21 2011 -0400
+++ b/src/main/scala/Main.scala	Mon Oct 03 13:00:03 2011 -0400
@@ -19,6 +19,9 @@
 import Query.{QueryTypeSelect => SELECT, QueryTypeAsk => ASK,
               QueryTypeConstruct => CONSTRUCT, QueryTypeDescribe => DESCRIBE}
 
+import scalaz._
+import Scalaz._
+
 import org.w3.readwriteweb.util._
 
 class ReadWriteWeb(rm:ResourceManager) {
@@ -30,8 +33,30 @@
     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) => {
+    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 {
@@ -41,55 +66,50 @@
           Ok ~> ViaSPARQL ~> ContentType("text/html") ~> ResponseString(body)
         }
         case GET(_) | HEAD(_) =>
-          try {
-            val model:Model = r.get()
-            val encoding = RDFEncoding(req)
+          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)
             }
-          } catch {
-            case fnfe:FileNotFoundException => NotFound
-            case t:Throwable => {
-              req match {
-                case GET(_) => InternalServerError ~> ViaSPARQL
-                case HEAD(_) => InternalServerError ~> ViaSPARQL ~> ResponseString(t.getStackTraceString)
-              }
-            }
           }
         case PUT(_) =>
-          try {
-            val bodyModel = modelFromInputStream(Body.stream(req), baseURI)
-            r.save(bodyModel)
-            Created
-          } catch {
-            case t:Throwable => InternalServerError ~> ResponseString(t.getStackTraceString)
-          }
-        case POST(_) =>
-          try {
-            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())
-                val model = r.get()
-                UpdateAction.execute(update, model)
-                r.save(model)
-                Ok
-              }
-              case PostRDF(diffModel) => {
-                logger.info("RDF content:\n" + diffModel.toString())
-                val model = r.get()
-                model.add(diffModel)
-                r.save(model)
-                Ok
-              }
-              case PostQuery(query) => {
-                logger.info("SPARQL Query:\n" + query.toString())
-                lazy val encoding = RDFEncoding(req)
-                val model:Model = r.get()
+          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 =>
@@ -107,10 +127,8 @@
                 }
               }
             }
-          } catch {
-            case fnfe:FileNotFoundException => NotFound
-            case t:Throwable => InternalServerError ~> ResponseString(t.getStackTraceString)
           }
+        }
         case _ => MethodNotAllowed ~> Allow("GET", "PUT", "POST")
       }
     }
--- a/src/main/scala/Post.scala	Mon Oct 03 12:56:21 2011 -0400
+++ b/src/main/scala/Post.scala	Mon Oct 03 13:00:03 2011 -0400
@@ -18,8 +18,13 @@
 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)
+
   def parse(is:InputStream, baseURI:String):Post = {
     val source = Source.fromInputStream(is, "UTF-8")
     val s = source.getLines.mkString("\n")
@@ -28,25 +33,23 @@
   
   def parse(s:String, baseURI:String):Post = {
     val reader = new StringReader(s)
-    try {
+    def postUpdate =
       try {
         val update:UpdateRequest = UpdateFactory.create(s, baseURI)
-        PostUpdate(update)      
+        PostUpdate(update).success
       } catch {
-        case qpe:QueryParseException =>
-          try {
-            val model = modelFromString(s, baseURI)
-            PostRDF(model)
-          } catch {
-            case je:JenaException => {
-              val query = QueryFactory.create(s)
-              PostQuery(query)
-            }
-          }
+        case qpe:QueryParseException => qpe.fail
       }
-    } catch {
-      case _ => PostUnknown
-    }
+    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
+      }
+    postUpdate | (postRDF | (postQuery | PostUnknown))
   }
   
 }
--- a/src/main/scala/Resource.scala	Mon Oct 03 12:56:21 2011 -0400
+++ b/src/main/scala/Resource.scala	Mon Oct 03 13:00:03 2011 -0400
@@ -10,14 +10,19 @@
 
 import org.w3.readwriteweb.util._
 
+import scalaz._
+import Scalaz._
+
+import _root_.scala.sys.error
+
 trait ResourceManager {
   def basePath:String
   def sanityCheck():Boolean
   def resource(url:URL):Resource
 }
 trait Resource {
-  def get():Model
-  def save(model:Model):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")(implicit mode:RWWMode) extends ResourceManager {
@@ -38,33 +43,36 @@
       logger.debug("Create file %s with success: %s" format (fileOnDisk.getAbsolutePath, r.toString))
     }
     
-    def get():Model = {
+    def get():Validation[Throwable, Model] = {
       val m = ModelFactory.createDefaultModel()
       if (fileOnDisk.exists()) {
         val fis = new FileInputStream(fileOnDisk)
         try {
           m.read(fis, url.toString)
         } catch {
-          case je:JenaException => sys.error("File %s was either empty or corrupted: considered as empty graph" format fileOnDisk.getAbsolutePath)
+          case je:JenaException => error("File %s was either empty or corrupted: considered as empty graph" format fileOnDisk.getAbsolutePath)
         }
         fis.close()
-        m
+        m.success
       } else {
         mode match {
-          case AllResourcesAlreadyExist => m
-          case ResourcesDontExistByDefault => throw new FileNotFoundException
+          case AllResourcesAlreadyExist => m.success
+          case ResourcesDontExistByDefault => new FileNotFoundException().fail
       }
       }
     }
     
-    def save(model:Model):Unit = {
-      createFileOnDisk()
-      val fos = new FileOutputStream(fileOnDisk)
-      val writer = model.getWriter("RDF/XML-ABBREV")
-      writer.write(model, fos, url.toString)
-      fos.close()
-    }
-    
+    def save(model:Model):Validation[Throwable, Unit] =
+      try {
+        createFileOnDisk()
+        val fos = new FileOutputStream(fileOnDisk)
+        val writer = model.getWriter("RDF/XML-ABBREV")
+        writer.write(model, fos, url.toString)
+        fos.close().success
+      } catch {
+        case t => t.fail
+      }
+
   }
   
 }
--- a/src/main/scala/util.scala	Mon Oct 03 12:56:21 2011 -0400
+++ b/src/main/scala/util.scala	Mon Oct 03 13:00:03 2011 -0400
@@ -9,6 +9,11 @@
 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._
@@ -49,6 +54,11 @@
   
 }
 
+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"
@@ -78,21 +88,38 @@
       }
   }
 
-  def modelFromInputStream(is:InputStream, base:String, lang:String = "RDF/XML-ABBREV"):Model = {
-    val m = ModelFactory.createDefaultModel()
-    m.read(is, base, lang)
-    m
-  }
+  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"):Model = {
-    val reader = new StringReader(s)
-    val m = ModelFactory.createDefaultModel()
-    m.read(reader, base, lang)
-    m
-  }
+  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._
@@ -100,7 +127,6 @@
 import java.net.{URL, URLDecoder}
 import org.slf4j.{Logger, LoggerFactory}
 
-
 /** useful stuff to read resources from the classpath */
 object MyResourceManager {
   
@@ -132,7 +158,7 @@
         } }
         entries filterNot { _.isEmpty } toList
       } else
-        sys.error("Cannot list files for URL "+dirURL);
+        error("Cannot list files for URL "+dirURL);
     }
   }
   
--- a/src/test/scala/Test.scala	Mon Oct 03 12:56:21 2011 -0400
+++ b/src/test/scala/Test.scala	Mon Oct 03 13:00:03 2011 -0400
@@ -50,7 +50,7 @@
 </rdf:RDF>
 """
   
-  val initialModel = modelFromString(joeRDF, joeBaseURI)
+  val initialModel = modelFromString(joeRDF, joeBaseURI).toOption.get
 
   "a GET on a URL that does not exist" should {
     "return a 404" in {
@@ -127,7 +127,7 @@
 </rdf:RDF>
 """  
     
-  val expectedFinalModel = modelFromString(finalRDF, joeBaseURI)
+  val expectedFinalModel = modelFromString(finalRDF, joeBaseURI).toOption.get
 
   "POSTing an RDF document to Joe's URI" should {
     "succeed" in {
@@ -195,6 +195,13 @@
       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 {
--- a/src/test/scala/utiltest.scala	Mon Oct 03 12:56:21 2011 -0400
+++ b/src/test/scala/utiltest.scala	Mon Oct 03 13:00:03 2011 -0400
@@ -40,7 +40,7 @@
   class RequestW(req:Request) {
 
     def as_model(base:String, lang:String = "RDF/XML-ABBREV"):Handler[Model] =
-      req >> { is => modelFromInputStream(is, base, lang) }
+      req >> { is => modelFromInputStream(is, base, lang).toOption.get }
 
     def post(body:String):Request =
       (req <<< body).copy(method="POST")