summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorrautenberg <rautenberg@in-silico.ch>2012-05-07 18:27:47 +0200
committerrautenberg <rautenberg@in-silico.ch>2012-05-07 18:27:47 +0200
commit4749e5d4ec616d52a2cdaaef06c67b2641a3a4a5 (patch)
tree1043bb03b92a35e5ac34cb4fb677266c2dd8fdf1
parent6af3bd6ebf4b9a787fd23b4fe2ed64de5aea88d0 (diff)
parent05a640c539455e36355a2ca3b69dcfc08deab1b1 (diff)
Merge branch 'release/v0.0.3'v0.0.3
-rw-r--r--.gitignore3
-rw-r--r--ChangeLog2
-rw-r--r--VERSION1
-rw-r--r--lib/4store.rb189
-rw-r--r--lib/environment.rb41
-rw-r--r--lib/opentox-server.rb8
-rw-r--r--lib/opentox.rb74
-rw-r--r--opentox-server.gemspec4
8 files changed, 248 insertions, 74 deletions
diff --git a/.gitignore b/.gitignore
index 4040c6c..5a5fa8c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
*.gem
.bundle
-Gemfile.lock
pkg/*
+Gemfile.lock
+*~
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..08d2402
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,2 @@
+v0.0.3 2012-05-07
+* switch from v0.0.2pre to v0.0.3
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..bcab45a
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+0.0.3
diff --git a/lib/4store.rb b/lib/4store.rb
new file mode 100644
index 0000000..b20af00
--- /dev/null
+++ b/lib/4store.rb
@@ -0,0 +1,189 @@
+module OpenTox
+ module Backend
+ class FourStore
+
+ #TODO: catch 4store errors
+
+ @@mime_format = {
+ "application/rdf+xml" => :rdfxml,
+ "text/turtle" => :turtle,
+ "text/plain" => :ntriples,
+ "text/uri-list" => :uri_list,
+ #"application/json" => :json,
+ #"application/x-yaml" => :yaml,
+ #"text/x-yaml" => :yaml,
+ #"text/yaml" => :yaml,
+ "text/html" => :html,
+ # TODO: compression, forms
+ #/sparql/ => :sparql #removed to prevent sparql injections
+ }
+
+ @@format_mime = {
+ :rdfxml => "application/rdf+xml",
+ :turtle => "text/turtle",
+ :ntriples => "text/plain",
+ :uri_list => "text/uri-list",
+ #:json => "application/json",
+ #:yaml => "text/yaml",
+ :html => "text/html",
+ }
+
+ @@accept_formats = [:rdfxml, :turtle, :ntriples, :uri_list, :html] #, :json, :yaml]
+ @@content_type_formats = [:rdfxml, :turtle, :ntriples]#, :json, :yaml]
+ @@rdf_formats = [:rdfxml, :turtle, :ntriples]
+
+ def self.list mime_type
+ mime_type = "text/html" if mime_type.match(%r{\*/\*})
+ bad_request_error "'#{mime_type}' is not a supported mime type. Please specify one of #{@@accept_formats.collect{|f| @@format_mime[f]}.join(", ")} in the Accept Header." unless @@accept_formats.include? @@mime_format[mime_type]
+ if mime_type =~ /json|yaml|uri-list/
+ sparql = "SELECT ?s WHERE {?s <#{RDF.type}> <#{@@class}>. }"
+ elsif mime_type =~ /turtle|html|rdf|plain/
+ sparql = "CONSTRUCT {?s ?p ?o.} WHERE {?s <#{RDF.type}> <#{@@class}>; ?p ?o. }"
+ end
+ query sparql, mime_type
+ end
+
+ def self.get uri, mime_type
+ mime_type = "text/html" if mime_type.match(%r{\*/\*})
+ bad_request_error "'#{mime_type}' is not a supported mime type. Please specify one of #{@@accept_formats.collect{|f| @@format_mime[f]}.join(", ")} in the Accept Header." unless @@accept_formats.include? @@mime_format[mime_type]
+ not_found_error "#{uri} not found." unless list("text/uri-list").split("\n").include?(uri)
+ sparql = "CONSTRUCT {?s ?p ?o.} FROM <#{uri}> WHERE { ?s ?p ?o. }"
+ query sparql, mime_type
+ end
+
+ def self.post uri, rdf, mime_type
+ bad_request_error "'#{mime_type}' is not a supported content type. Please use one of #{@@content_type_formats.collect{|f| @@format_mime[f]}.join(", ")}." unless @@content_type_formats.include? @@mime_format[mime_type]
+ rdf = convert rdf, @@mime_format[mime_type], :ntriples#, uri unless mime_type == 'text/plain'
+ RestClient.post File.join(four_store_uri,"data")+"/", :data => rdf, :graph => uri, "mime-type" => "application/x-turtle" # not very consistent in 4store
+ end
+
+ def self.put uri, rdf, mime_type, skip_rewrite=false
+ bad_request_error "'#{mime_type}' is not a supported content type. Please use one of #{@@content_type_formats.collect{|f| @@format_mime[f]}.join(", ")}." unless @@content_type_formats.include? @@mime_format[mime_type]
+ uuid = uri.sub(/\/$/,'').split('/').last
+ bad_request_error "'#{uri}' is not a valid URI." unless uuid =~ /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/
+ if !skip_rewrite
+ rdf = convert rdf, @@mime_format[mime_type], :ntriples, uri
+ elsif mime_type != "text/plain" # ntriples are not converted
+ rdf = convert rdf, @@mime_format[mime_type], :ntriples
+ end
+ rdf = "<#{uri}> <#{RDF.type}> <#{@@class}>." unless rdf # create empty resource
+ RestClient.put File.join(four_store_uri,"data",uri), rdf, :content_type => "application/x-turtle" # content-type not very consistent in 4store
+ end
+
+ def self.delete uri
+ RestClientWrapper.delete data_uri uri
+ end
+
+ def self.query sparql, mime_type
+ if sparql =~ /SELECT/i
+ xml = RestClient.post File.join(four_store_uri,"sparql")+"/", :query => sparql
+ #TODO request tab delimited format to speed up parsing
+ list = parse_sparql_xml_results(xml).collect{|hash| hash["s"]}
+ case mime_type
+ when /json/
+ return list.to_json
+ when /yaml/
+ return list.to_yaml
+ when /uri-list/
+ return list.join "\n"
+ else
+ bad_request_error "#{mime_type} is not a supported mime type for SELECT statements. Please use one of text/uri-list, application/json, text/yaml, text/html."
+ end
+ elsif sparql =~ /CONSTRUCT/i
+ nt = RestClient.get(sparql_uri, :params => { :query => sparql }, :accept => "text/plain").body
+ return nt if mime_type == 'text/plain'
+ case mime_type
+ when /turtle/
+ return convert(nt,:ntriples, :turtle)
+ when /html/
+ # TODO: fix and improve
+ html = "<html><body>"
+ html += convert(nt,:ntriples, :turtle).gsub(%r{<(.*)>},'&lt;<a href="\1">\1</a>&gt;').gsub(/\n/,'<br/>')#.gsub(/ /,'&nbsp;')
+ html += "</body></html>"
+ return html
+ when "application/rdf+xml"
+ return convert(nt,:ntriples, :rdfxml)
+ end
+ else
+ # TODO: check if this prevents SPARQL injections
+ bad_request_error "Only SELECT and CONSTRUCT are accepted SPARQL statements."
+ end
+ end
+
+ private
+
+ def self.convert rdf_string, input_format, output_format, rewrite_uri=nil
+ rewrite_uri ? serialize(parse_and_rewrite_uri(rdf_string,input_format, rewrite_uri), output_format) : serialize(parse(rdf_string,input_format), output_format)
+ end
+
+ def self.parse_and_rewrite_uri string, format, rewrite_uri
+ rdf = RDF::Graph.new
+ subject = nil
+ statements = [] # use array instead of graph for performance reasons
+ RDF::Reader.for(format).new(string) do |reader|
+ reader.each_statement do |statement|
+ subject = statement.subject if statement.predicate == RDF.type and statement.object == @@class
+ statements << statement
+ end
+ end
+ bad_request_error "No class specified with <#{RDF.type}> statement." unless subject
+ statements.each do |statement|
+ statement.subject = RDF::URI.new rewrite_uri if rewrite_uri and statement.subject == subject
+ rdf << statement
+ end
+ rdf
+ end
+
+ def self.parse string, format
+ rdf = RDF::Graph.new
+ RDF::Reader.for(format).new(string) do |reader|
+ reader.each_statement { |statement| rdf << statement }
+ end
+ rdf
+ end
+
+ def self.serialize rdf, format
+ if format == :turtle # prefixes seen to need RDF::N3
+ # TODO add prefixes
+ string = RDF::N3::Writer.for(format).buffer(:prefixes => {:ot => "http://www.opentox.org/api/1.2#"}) do |writer|
+ rdf.each{|statement| writer << statement}
+ end
+ else
+ string = RDF::Writer.for(format).buffer do |writer|
+ rdf.each{|statement| writer << statement}
+ end
+ end
+ string
+ end
+
+ def self.four_store_uri
+ $four_store[:uri].sub(%r{//},"//#{$four_store[:user]}:#{$four_store[:password]}@")
+ end
+
+ def self.sparql_uri
+ File.join(four_store_uri, "sparql") + '/'
+ end
+
+ def self.data_uri uri
+ File.join(four_store_uri, "data","?graph=#{uri}")
+ end
+
+ def self.parse_sparql_xml_results(xml)
+ results = []
+ doc = REXML::Document.new(REXML::Source.new(xml))
+ doc.elements.each("*/results/result") do |result|
+ result_hash = {}
+ result.elements.each do |binding|
+ key = binding.attributes["name"]
+ value = binding.elements[1].text
+ type = binding.elements[1].name
+ result_hash[key] = value
+ end
+ results.push result_hash
+ end
+ results
+ end
+
+ end
+ end
+end
diff --git a/lib/environment.rb b/lib/environment.rb
deleted file mode 100644
index 3889dfb..0000000
--- a/lib/environment.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-# set default environment
-ENV['RACK_ENV'] = 'production' unless ENV['RACK_ENV']
-
-# load/setup configuration
-basedir = File.join(ENV['HOME'], ".opentox")
-config_dir = File.join(basedir, "config")
-config_file = File.join(config_dir, "#{ENV['RACK_ENV']}.yaml")
-
-TMP_DIR = File.join(basedir, "tmp")
-LOG_DIR = File.join(basedir, "log")
-
-=begin
-if File.exist?(config_file)
- CONFIG = YAML.load_file(config_file)
- not_found_error "Could not load configuration from \"#{config_file.to_s}\"" unless CONFIG
-else
- FileUtils.mkdir_p TMP_DIR
- FileUtils.mkdir_p LOG_DIR
- FileUtils.mkdir_p config_dir
- puts "Please edit #{config_file} and restart your application."
- exit
-end
-=end
-
-logfile = "#{LOG_DIR}/#{ENV["RACK_ENV"]}.log"
-$logger = OTLogger.new(logfile)
-
-=begin
-if CONFIG[:logger] and CONFIG[:logger] == "debug"
- $logger.level = Logger::DEBUG
-else
- $logger.level = Logger::WARN
-end
-
-# TODO: move to opentox-client???
-AA_SERVER = CONFIG[:authorization] ? (CONFIG[:authorization][:server] ? CONFIG[:authorization][:server] : nil) : nil
-CONFIG[:authorization][:authenticate_request] = [""] unless CONFIG[:authorization][:authenticate_request]
-CONFIG[:authorization][:authorize_request] = [""] unless CONFIG[:authorization][:authorize_request]
-CONFIG[:authorization][:free_request] = [""] unless CONFIG[:authorization][:free_request]
-=end
-
diff --git a/lib/opentox-server.rb b/lib/opentox-server.rb
index 358788b..15cb0cd 100644
--- a/lib/opentox-server.rb
+++ b/lib/opentox-server.rb
@@ -1,10 +1,10 @@
-require "opentox-client"
+require 'opentox-client'
require 'rack'
require 'rack/contrib'
require 'sinatra'
-require 'sinatra/url_for'
require 'roo'
-require File.join(File.dirname(__FILE__),"environment.rb")
+require 'rdf/n3'
+#require File.join(File.dirname(__FILE__),"environment.rb")
+require File.join(File.dirname(__FILE__),"4store.rb")
require File.join(File.dirname(__FILE__),"opentox.rb")
-require File.join(File.dirname(__FILE__),"file-store.rb")
require File.join(File.dirname(__FILE__),"authorization-helper.rb")
diff --git a/lib/opentox.rb b/lib/opentox.rb
index e3c1f3b..7a73d66 100644
--- a/lib/opentox.rb
+++ b/lib/opentox.rb
@@ -1,12 +1,17 @@
require 'sinatra/base'
require "sinatra/reloader"
+ENV["RACK_ENV"] ||= "production"
+
+logfile = File.join(ENV['HOME'], ".opentox","log","#{ENV["RACK_ENV"]}.log")
+$logger = OTLogger.new(logfile)
+
module OpenTox
# Base class for OpenTox services
class Service < Sinatra::Base
+ include Backend
- helpers Sinatra::UrlForHelper
# use OpenTox error handling
set :raise_errors, false
set :show_exceptions, false
@@ -16,36 +21,15 @@ module OpenTox
register Sinatra::Reloader
end
- helpers do
- def uri
- params[:id] ? url_for("/#{params[:id]}", :full) : "#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}"
- end
- end
-
before do
- @accept = request.env['HTTP_ACCEPT']
- response['Content-Type'] = @accept
- # TODO: A+A
+ request.content_type ? response['Content-Type'] = request.content_type : response['Content-Type'] = request.env['HTTP_ACCEPT']
end
+ # Attention: Error within tasks are catched by Task.create
error do
- # TODO: convert to OpenTox::Error and set URI
error = request.env['sinatra.error']
- #error.uri = uri
if error.respond_to? :report
- # Errors are formated according to acccept-header
- case @accept
- when 'application/rdf+xml'
- body = error.report.to_rdfxml
- when /html/
- # TODO
- # body = error.report.to_html
- body = error.report.to_turtle
- when "text/n3"
- body = error.report.to_ntriples
- else
- body = error.report.to_turtle
- end
+ body = error.report.to_turtle
else
response['Content-Type'] = "text/plain"
body = error.message
@@ -54,6 +38,44 @@ module OpenTox
error.respond_to?(:http_code) ? code = error.http_code : code = 500
halt code, body
end
+
+ # Default methods, may be overwritten by derived services
+ # see http://jcalcote.wordpress.com/2008/10/16/put-or-post-the-rest-of-the-story/
+
+ # Get a list of objects at the server
+ get '/?' do
+ FourStore.list request.env['HTTP_ACCEPT']
+ end
+
+ # Create a new resource
+ # TODO: handle multipart uploads
+ post '/?' do
+ rdf = request.body.read
+ uri = uri(SecureRandom.uuid)
+ FourStore.put(uri, rdf, request.content_type) unless rdf == ''
+ response['Content-Type'] = "text/uri-list"
+ uri
+ end
+
+ # Get resource representation
+ get '/:id/?' do
+ FourStore.get(uri("/#{params[:id]}"), request.env['HTTP_ACCEPT'])
+ end
+
+ # Modify (i.e. add rdf statments to) a resource
+ post '/:id/?' do
+ FourStore.post uri("/#{params[:id]}"), request.body.read, request.content_type
+ end
+
+ # Create or updata a resource
+ put '/:id/?' do
+ FourStore.put uri("/#{params[:id]}"), request.body.read, request.content_type
+ end
+
+ # Delete a resource
+ delete '/:id/?' do
+ FourStore.delete uri("/#{params[:id]}")
+ end
+
end
end
-
diff --git a/opentox-server.gemspec b/opentox-server.gemspec
index 08438f0..1074fbf 100644
--- a/opentox-server.gemspec
+++ b/opentox-server.gemspec
@@ -3,7 +3,7 @@ $:.push File.expand_path("../lib", __FILE__)
Gem::Specification.new do |s|
s.name = "opentox-server"
- s.version = "0.0.2pre"
+ s.version = "0.0.3"
s.authors = ["Christoph Helma, Martin Guetlein, Andreas Maunz, Micha Rautenberg, David Vorgrimmler"]
s.email = ["helma@in-silico.ch"]
s.homepage = "http://github.com/opentox/opentox-server"
@@ -24,7 +24,7 @@ Gem::Specification.new do |s|
s.add_runtime_dependency 'rack-contrib'
s.add_runtime_dependency 'sinatra'
s.add_runtime_dependency 'sinatra-contrib'
- s.add_runtime_dependency 'emk-sinatra-url-for'
s.add_runtime_dependency 'roo'
s.add_runtime_dependency 'unicorn'
+ s.add_runtime_dependency 'rdf-n3'
end