--- 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")