summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristoph Helma <helma@in-silico.ch>2012-07-18 23:11:17 +0200
committerChristoph Helma <helma@in-silico.ch>2012-07-18 23:11:17 +0200
commit6005b625ce4daba861fa2785b5bbe281dd4c52b3 (patch)
tree30c0022dee0f5c8a42c0f589f681ba7623c3a10e
parent07477de2c221c7cee4ad0afe203dba3c74f8749f (diff)
parent01aeccce13a0f46ac23eeb680039d03189329922 (diff)
Merge branch 'feature/opentox-client' into development
Conflicts: application.rb
-rw-r--r--.gitignore10
-rw-r--r--Gemfile4
-rw-r--r--Rakefile1
-rw-r--r--VERSION1
-rw-r--r--application.rb786
-rw-r--r--config.ru11
-rw-r--r--data/.gitignore3
-rw-r--r--dataset.gemspec25
-rw-r--r--public/.gitignore2
-rw-r--r--public/robots.txt2
10 files changed, 453 insertions, 392 deletions
diff --git a/.gitignore b/.gitignore
index d21a58b..4040c6c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,4 @@
-api_key.rb
-*.sqlite3
-tmp/*
-log/*
-public/*
-data/*
+*.gem
+.bundle
+Gemfile.lock
+pkg/*
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..36f1a90
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,4 @@
+source :gemcutter
+gemspec
+gem "opentox-server", :path => "~/opentox-server"
+gem "opentox-client", :path => "~/opentox-client"
diff --git a/Rakefile b/Rakefile
new file mode 100644
index 0000000..2995527
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1 @@
+require "bundler/gem_tasks"
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..3eefcb9
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+1.0.0
diff --git a/application.rb b/application.rb
index d4bd78a..494f050 100644
--- a/application.rb
+++ b/application.rb
@@ -1,412 +1,452 @@
-require 'rubygems'
-gem "opentox-ruby", "~> 4"
-require 'opentox-ruby'
-require 'profiler'
-require 'rjb'
-
-set :lock, true
-
-@@datadir = "data"
-
-@@idfile_path = @@datadir+"/id"
-unless File.exist?(@@idfile_path)
- id = Dir["./#{@@datadir}/*json"].collect{|f| File.basename(f.sub(/.json/,'')).to_i}.sort.last
- id = 0 if id.nil?
- open(@@idfile_path,"w") do |f|
- f.puts(id)
- end
-end
-
-helpers do
- def next_id
- open(@@idfile_path, "r+") do |f|
- f.flock(File::LOCK_EX)
- @id = f.gets.to_i + 1
- f.rewind
- f.print @id
- end
- return @id
- end
+require 'roo'
+#require 'profiler'
- def uri(id)
- url_for "/#{id}", :full
- end
+module OpenTox
+ class Application < Service
- # subjectid ist stored as memeber variable, not in params
- def load_dataset(id, params,content_type,input_data)
+ @warnings = []
- @uri = uri id
- raise "store subject-id in dataset-object, not in params" if params.has_key?(:subjectid) and @subjectid==nil
+ helpers do
- content_type = "application/rdf+xml" if content_type.nil?
- dataset = OpenTox::Dataset.new(@uri, @subjectid)
-
- case content_type
-
- when /yaml/
- dataset.load_yaml(input_data)
-
- when /json/
- dataset.load_json(input_data)
+ def from_csv(csv)
+ from_table CSV.parse(csv)
+ end
- when "text/csv"
- dataset.load_csv(input_data, @subjectid)
+ def from_spreadsheet spreadsheet
+ extensions = { Excel => ".xls", Excelx => ".xlsx", Openoffice => ".ods" }
+ input = params[:file][:tempfile].path + ".xls"
+ csv_file = params[:file][:tempfile].path + ".csv"
+ File.rename params[:file][:tempfile].path, input # roo needs "correct" extensions
+ spreadsheet.new(input).to_csv csv_file # roo cannot write to strings
+ @body = from_csv File.read(csv_file)
+ @content_type = "text/plain"
+ end
- when /application\/rdf\+xml/
- dataset.load_rdfxml(input_data, @subjectid)
+=begin
+ def from_sdf(sdf)
+
+ #obconversion = OpenBabel::OBConversion.new
+ #obmol = OpenBabel::OBMol.new
+ #obconversion.set_in_and_out_formats "sdf", "inchi"
+
+ table = []
+
+ properties = []
+ sdf.each_line { |l| properties << l.to_s if l.match(/</) }
+ properties.sort!
+ properties.uniq!
+ properties.collect!{ |p| p.gsub(/<|>/,'').strip.chomp }
+ properties.insert 0, "InChI"
+ table[0] = properties
+
+ rec = 0
+ sdf.split(/\$\$\$\$\r*\n/).each do |s|
+ rec += 1
+ table << []
+ begin
+ # TODO: use compound service
+ compound = OpenTox::Compound.from_sdf sdf
+ #obconversion.read_string obmol, s
+ table.last << obconversion.write_string(obmol).gsub(/\s/,'').chomp
+ rescue
+ # TODO: Fix, will lead to follow up errors
+ table.last << "Could not convert structure at record #{rec}) have been ignored! \n#{s}"
+ end
+ obmol.get_data.each { |d| table.last[table.first.index(d.get_attribute)] = d.get_value }
+ end
+ from_table table
+ end
+=end
+
+ def from_table table
+
+=begin
+ dataset = OpenTox::Dataset.new @uri
+ puts dataset.uri
+ feature_names = table.shift.collect{|f| f.strip}
+ puts feature_names.inspect
+ dataset.append RDF::OT.Warnings, "Duplicated features in table header." unless feature_names.size == feature_names.uniq.size
+ compound_format = feature_names.shift.strip
+ bad_request_error "#{compound_format} is not a supported compound format. Accepted formats: URI, SMILES, InChI." unless compound_format =~ /URI|URL|SMILES|InChI/i
+ features = []
+ feature_names.each_with_index do |f,i|
+ feature = OpenTox::Feature.new File.join($feature[:uri], SecureRandom.uuid)
+ feature[RDF::DC.title] = f
+ features << feature
+ values = table.collect{|row| row[i+1].strip unless row[i+1].nil?}.uniq.compact # skip compound column
+ if values.size <= 3 # max classes
+ feature.append RDF.type, RDF::OT.NominalFeature
+ feature.append RDF.type, RDF::OT.StringFeature
+ feature[RDF::OT.acceptValue] = values
+ else
+ types = values.collect{|v| feature_type(v)}
+ if types.include?(RDF::OT.NominalFeature)
+ dataset.append RDF::OT.Warnings, "Feature #{f} contains nominal and numeric values."
+ else
+ feature.append RDF.type, RDF::OT.NumericFeature
+ end
+ end
+ feature.put
+ end
+ dataset.features = features
+ compounds = []
+ table.each_with_index do |values,j|
+ c = values.shift
+ puts c
+ puts compound_format
+ values.collect!{|v| v.nil? ? nil : v.strip }
+ #begin
+ case compound_format
+ when /URI|URL/i
+ compound = OpenTox::Compound.new c
+ when /SMILES/i
+ compound = OpenTox::Compound.from_smiles($compound[:uri], c)
+ when /InChI/i
+ compound = OpenTox::Compound.from_inchi($compound[:uri], URI.decode_www_form_component(c))
+ end
+ #rescue
+ #dataset.append RDF::OT.Warnings, "Cannot parse compound \"#{c}\" at position #{j+2}, all entries are ignored."
+ #next
+ #end
+ unless compound_uri.match(/InChI=/)
+ dataset.append RDF::OT.Warnings, "Cannot parse compound \"#{c}\" at position #{j+2}, all entries are ignored."
+ next
+ end
+ compounds << compound
+ unless values.size == features.size
+ dataset.append RDF::OT.Warnings, "Number of values at position #{j+2} (#{values.size}) is different than header size (#{features.size}), all entries are ignored."
+ next
+ end
+
+ dataset << values
- when "chemical/x-mdl-sdfile"
- dataset.load_sdf(input_data, @subjectid)
+ end
+ dataset.compounds = compounds
+ compounds.duplicates.each do |compound|
+ positions = []
+ compounds.each_with_index{|c,i| positions << i+1 if c.uri == compound.uri}
+ dataset.append RDF::OT.Warnings, "Duplicated compound #{compound.uri} at rows #{positions.join(', ')}. Entries are accepted, assuming that measurements come from independent experiments."
+ end
+ puts dataset.to_ntriples
+ dataset.to_ntriples
+=end
+
+ @warnings = []
+ ntriples = ["<#{@uri}> <#{RDF.type}> <#{RDF::OT.Dataset}>."]
+ ntriples << ["<#{@uri}> <#{RDF.type}> <#{RDF::OT.OrderedDataset}>."]
+
+ # features
+ feature_names = table.shift.collect{|f| f.strip}
+ @warnings << "Duplicated features in table header." unless feature_names.size == feature_names.uniq.size
+ compound_format = feature_names.shift.strip
+ bad_request_error "#{compound_format} is not a supported compound format. Accepted formats: URI, SMILES, InChI." unless compound_format =~ /URI|URL|SMILES|InChI/i
+ features = []
+ ignored_feature_indices = []
+ feature_names.each_with_index do |f,i|
+ feature = OpenTox::Feature.new File.join($feature[:uri], SecureRandom.uuid)
+ feature[RDF::DC.title] = f
+ features << feature
+ values = table.collect{|row| row[i+1].strip unless row[i+1].nil?}.uniq.compact # skip compound column
+ if values.size <= 3 # max classes
+ feature.append RDF.type, RDF::OT.NominalFeature
+ feature.append RDF.type, RDF::OT.StringFeature
+ feature[RDF::OT.acceptValue] = values
+ else
+ types = values.collect{|v| feature_type(v)}
+ if types.include?(RDF::OT.NominalFeature)
+ @warnings << "Feature #{f} contains nominal and numeric values."
+ else
+ feature.append RDF.type, RDF::OT.NumericFeature
+ end
+ end
+ feature.put
+ ntriples << "<#{feature.uri}> <#{RDF.type}> <#{RDF::OT.Feature}>."
+ ntriples << "<#{feature.uri}> <#{RDF::OLO.index}> #{i} ."
+ end
- when /multipart\/form-data/ , "application/x-www-form-urlencoded" # file uploads
+ # compounds and values
+ compound_uris = []
+ table.each_with_index do |values,j|
+ values.collect!{|v| v.nil? ? nil : v.strip }
+ compound = values.shift
+ begin
+ case compound_format
+ when /URI|URL/i
+ compound_uri = compound
+ when /SMILES/i
+ compound_uri = OpenTox::Compound.from_smiles($compound[:uri], compound).uri
+ when /InChI/i
+ compound_uri = OpenTox::Compound.from_inchi($compound[:uri], URI.decode_www_form_component(compound)).uri
+ end
+ rescue
+ @warnings << "Cannot parse compound \"#{compound}\" at position #{j+2}, all entries are ignored."
+ next
+ end
+ unless compound_uri.match(/InChI=/)
+ @warnings << "Cannot parse compound \"#{compound}\" at position #{j+2}, all entries are ignored."
+ next
+ end
+ compound_uris << compound_uri
+ unless values.size == features.size
+ @warnings << "Number of values at position #{j+2} (#{values.size}) is different than header size (#{features.size}), all entries are ignored."
+ next
+ end
+ ntriples << "<#{compound_uri}> <#{RDF.type}> <#{RDF::OT.Compound}>."
+ ntriples << "<#{compound_uri}> <#{RDF::OLO.index}> #{j} ."
+
+ values.each_with_index do |v,i|
+ #@warnings << "Empty value for compound #{compound} (row #{j+2}) and feature \"#{feature_names[i]}\" (column #{i+2})." if v.blank?
+ #@warnings << "Empty value in row #{j+2}, column #{i+2} (feature \"#{feature_names[i]}\")." if v.blank?
+
+ data_entry_node = "_:dataentry"+ j.to_s
+ value_node = data_entry_node+ "_value"+ i.to_s
+ ntriples << "<#{@uri}> <#{RDF::OT.dataEntry}> #{data_entry_node} ."
+ ntriples << "#{data_entry_node} <#{RDF.type}> <#{RDF::OT.DataEntry}> ."
+ ntriples << "#{data_entry_node} <#{RDF::OLO.index}> #{j} ."
+ ntriples << "#{data_entry_node} <#{RDF::OT.compound}> <#{compound_uri}> ."
+ ntriples << "#{data_entry_node} <#{RDF::OT.values}> #{value_node} ."
+ ntriples << "#{value_node} <#{RDF::OT.feature}> <#{features[i].uri}> ."
+ ntriples << "#{value_node} <#{RDF::OT.value}> \"#{v}\" ."
+
+ end
- case params[:file][:type]
+ end
+ compound_uris.duplicates.each do |uri|
+ positions = []
+ compound_uris.each_with_index{|c,i| positions << i+1 if c == uri}
+ @warnings << "Duplicated compound #{uri} at rows #{positions.join(', ')}. Entries are accepted, assuming that measurements come from independent experiments."
+ end
- when "chemical/x-mdl-sdfile"
- dataset.load_sdf(input_data, @subjectid)
+ ntriples << "<#{@uri}> <#{RDF::OT.Warnings}> \"#{@warnings.join('\n')}\" ."
+ ntriples.join("\n")
+=begin
+=end
+ end
- when /json/
- dataset.load_json(params[:file][:tempfile].read)
+=begin
+ def to_xlsx
- when /yaml/
- dataset.load_yaml(params[:file][:tempfile].read)
+ # both simple_xlsx and axlsx create empty documents with OLE2 errors
+ xlsx = @uri.split("/").last+".xlsx"
+ p = Axlsx::Package.new
+ wb = p.workbook
+ wb.add_worksheet(:name => "test") do |sheet|
+ to_table.each { |row| sheet.add_row row; puts row }
+ end
+ p.serialize("test.xlsx")
+
+ p.to_stream
+#```
+ #Tempfile.open(@uri.split("/").last+".xlsx") do |xlsx|
+ SimpleXlsx::Serializer.new(xlsx) do |doc|
+ doc.add_sheet("People") do |sheet|
+ to_table.each { |row| sheet.add_row row }
+ end
+ end
+ send_file xlsx
+ #end
+ end
+=end
- when "application/rdf+xml"
- dataset.load_rdfxml_file(params[:file][:tempfile], @subjectid)
+ def to_csv
+ csv_string = CSV.generate do |csv|
+ to_table.each { |row| csv << row }
+ end
+ end
- when "text/csv"
- dataset.load_csv(params[:file][:tempfile].read, @subjectid)
- dataset.add_metadata({
- DC.title => File.basename(params[:file][:filename],".csv"),
- OT.hasSource => File.basename(params[:file][:filename])
- })
+ def to_table
+=begin
+ table = []
+ dataset = OpenTox::Dataset.new @uri
+ dataset.get
+ table << ["SMILES"] + dataset.features.collect{|f| f.get; f.title}
+ dataset.data_entries.each_with_index do |data_entry,i|
+ table << [dataset.compounds[i]] + data_entry
+ end
+ table
+=end
+ accept = "text/uri-list"
+ table = []
+ if ordered?
+ sparql = "SELECT DISTINCT ?s FROM <#{@uri}> WHERE {?s <#{RDF.type}> <#{RDF::OT.Feature}> . ?s <#{RDF::OLO.index}> ?i} ORDER BY ?i"
+ features = FourStore.query(sparql, accept).split("\n").collect{|uri| OpenTox::Feature.new uri}
+ table << ["SMILES"] + features.collect{ |f| f.get; f[RDF::DC.title] }
+ sparql = "SELECT DISTINCT ?i FROM <#{@uri}> WHERE {?s <#{RDF.type}> <#{RDF::OT.DataEntry}> . ?s <#{RDF::OLO.index}> ?i} ORDER BY ?i"
+ FourStore.query(sparql, accept).split("\n").each do |data_entry_idx|
+ sparql = "SELECT DISTINCT ?compound FROM <#{@uri}> WHERE {
+ ?data_entry <#{RDF::OLO.index}> #{data_entry_idx} ;
+ <#{RDF::OT.compound}> ?compound. }"
+ compound = OpenTox::Compound.new FourStore.query(sparql, accept).strip
+ sparql = "SELECT ?value FROM <#{@uri}> WHERE {
+ ?data_entry <#{RDF::OLO.index}> #{data_entry_idx} ;
+ <#{RDF::OT.values}> ?v .
+ ?v <#{RDF::OT.feature}> ?f;
+ <#{RDF::OT.value}> ?value .
+ ?f <#{RDF::OLO.index}> ?i.
+
+ } ORDER BY ?i"
+ values = FourStore.query(sparql,accept).split("\n")
+ # Fill up trailing empty cells
+ table << [compound.to_smiles] + values.fill("",values.size,features.size-values.size)
+ end
+ else
+ sparql = "SELECT DISTINCT ?s FROM <#{@uri}> WHERE {?s <#{RDF.type}> <#{RDF::OT.Feature}>}"
+ features = FourStore.query(sparql, accept).split("\n").collect{|uri| OpenTox::Feature.new uri}
+ table << ["SMILES"] + features.collect{ |f| f.get; f[RDF::DC.title] }
+ sparql = "SELECT ?s FROM <#{@uri}> WHERE {?s <#{RDF.type}> <#{RDF::OT.Compound}>. }"
+ compounds = FourStore.query(sparql, accept).split("\n").collect{|uri| OpenTox::Compound.new uri}
+ compounds.each do |compound|
+ data_entries = []
+ features.each do |feature|
+ sparql = "SELECT ?value FROM <#{@uri}> WHERE {
+ ?data_entry <#{RDF::OT.compound}> <#{compound.uri}>;
+ <#{RDF::OT.values}> ?v .
+ ?v <#{RDF::OT.feature}> <#{feature.uri}>;
+ <#{RDF::OT.value}> ?value.
+ } ORDER BY ?data_entry"
+ FourStore.query(sparql, accept).split("\n").each_with_index do |value,i|
+ data_entries[i] = Array.new(features.size) unless data_entries[i]
+ data_entries[i] << value
+ end
+ end
+ data_entries.each{|data_entry| table << [compound.to_smiles] + data_entry}
+ end
+ end
+ table
+=begin
+=end
+ end
- when /ms-excel/
- extension = File.extname(params[:file][:filename])
- case extension
- when ".xls"
- xls = params[:file][:tempfile].path + ".xls"
- File.rename params[:file][:tempfile].path, xls # roo needs these endings
- book = Excel.new xls
- when ".xlsx"
- xlsx = params[:file][:tempfile].path + ".xlsx"
- File.rename params[:file][:tempfile].path, xlsx # roo needs these endings
- book = Excel.new xlsx
+ def feature_type(value)
+ if value.blank?
+ nil
+ elsif value.numeric?
+ RDF::OT.NumericFeature
else
- raise "#{params[:file][:filename]} is not a valid Excel input file."
+ RDF::OT.NominalFeature
end
- dataset.load_spreadsheet(book, @subjectid)
- dataset.add_metadata({
- DC.title => File.basename(params[:file][:filename],extension),
- OT.hasSource => File.basename(params[:file][:filename])
- })
+ end
- else
- raise "MIME type \"#{params[:file][:type]}\" not supported."
+ def ordered?
+ sparql = "SELECT DISTINCT ?s FROM <#{@uri}> WHERE {?s <#{RDF.type}> <#{RDF::OT.OrderedDataset}>}"
+ FourStore.query(sparql, "text/uri-list").split("\n").empty? ? false : true
end
- else
- raise "MIME type \"#{content_type}\" not supported."
+ def parse_put
+ task = OpenTox::Task.create $task[:uri], nil, RDF::DC.description => "Dataset upload: #{@uri}" do
+ #Profiler__::start_profile
+ case @content_type
+ when "text/plain", "text/turtle", "application/rdf+xml" # no conversion needed
+ when "text/csv"
+ @body = from_csv @body
+ @content_type = "text/plain"
+ when "application/vnd.ms-excel"
+ from_spreadsheet Excel
+ when "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
+ from_spreadsheet Excelx
+ when "application/vnd.oasis.opendocument.spreadsheet"
+ from_spreadsheet Openoffice
+ # when "chemical/x-mdl-sdfile"
+ # @body = parse_sdf @body
+ # @content_type = "text/plain"
+ else
+ bad_request_error "#{@content_type} is not a supported content type."
+ end
+ FourStore.put @uri, @body, @content_type
+ if params[:file]
+ nt = "<#{@uri}> <#{RDF::DC.title}> \"#{params[:file][:filename]}\".\n<#{uri}> <#{RDF::OT.hasSource}> \"#{params[:file][:filename]}\"."
+ FourStore.post(@uri, nt, "text/plain")
+ end
+ #Profiler__::stop_profile
+ #Profiler__::print_profile($stdout)
+ @uri
+ end
+ response['Content-Type'] = "text/uri-list"
+ task.uri
+ end
end
- dataset.uri = @uri # update uri (also in metdata)
- dataset.features.keys.each { |f| dataset.features[f][OT.hasSource] = dataset.metadata[OT.hasSource] unless dataset.features[f][OT.hasSource]}
- File.open("#{@@datadir}/#{@id}.json","w+"){|f| f.puts dataset.to_json}
- end
-end
-
-before do
-
- @accept = request.env['HTTP_ACCEPT']
- @accept = 'application/rdf+xml' if @accept == '*/*' or @accept == '' or @accept.nil?
- @id = request.path_info.match(/^\/\d+/)
- unless @id.nil?
- @id = @id.to_s.sub(/\//,'').to_i
-
- @uri = uri @id
- @json_file = "#{@@datadir}/#{@id}.json"
- raise OpenTox::NotFoundError.new "Dataset #{@id} not found." unless File.exists? @json_file
-
- extension = File.extname(request.path_info)
- unless extension.empty?
- case extension
- when ".html"
- @accept = 'text/html'
- when ".json"
- @accept = 'application/json'
- when ".yaml"
- @accept = 'application/x-yaml'
- when ".csv"
- @accept = 'text/csv'
- when ".rdfxml"
- @accept = 'application/rdf+xml'
- when ".xls"
- @accept = 'application/ms-excel'
- when ".sdf"
- @accept = 'chemical/x-mdl-sdfile'
- else
- raise OpenTox::NotFoundError.new "File format #{extension} not supported."
- end
+ before "/#{SERVICE}/:id/:property" do
+ @uri = uri("/#{SERVICE}/#{params[:id]}")
end
- end
-
- # make sure subjectid is not included in params, subjectid is set as member variable
- params.delete(:subjectid)
-end
-
-## REST API
-
-# Get a list of available datasets
-# @return [text/uri-list] List of available datasets
-get '/?' do
- uri_list = Dir["./#{@@datadir}/*json"].collect{|f| File.basename(f.sub(/.json/,'')).to_i}.sort.collect{|n| uri n}.join("\n") + "\n"
- case @accept
- when /html/
- response['Content-Type'] = 'text/html'
- OpenTox.text_to_html uri_list
- else
- response['Content-Type'] = 'text/uri-list'
- uri_list
- end
-end
-# Get a dataset representation
-# @param [Header] Accept one of `application/rdf+xml, application-x-yaml, text/csv, application/ms-excel` (default application/rdf+xml)
-# @return [application/rdf+xml, application-x-yaml, text/csv, application/ms-excel] Dataset representation
-get '/:id' do
- case @accept
-
- when /rdf/ # redland sends text/rdf instead of application/rdf+xml
- file = "#{@@datadir}/#{params[:id]}.rdfxml"
- unless File.exists? file # lazy rdfxml generation
- dataset = OpenTox::Dataset.from_json File.read(@json_file)
- File.open(file,"w+") { |f| f.puts dataset.to_rdfxml }
+ # Create a new resource
+ post "/dataset/?" do
+ @uri = uri("/#{SERVICE}/#{SecureRandom.uuid}")
+ parse_put
end
- send_file file, :type => 'application/rdf+xml'
- when /json/
- send_file @json_file, :type => 'application/json'
-
- when /yaml/
- file = "#{@@datadir}/#{params[:id]}.yaml"
- unless File.exists? file # lazy yaml generation
- dataset = OpenTox::Dataset.from_json File.read(@json_file)
- File.open(file,"w+") { |f| f.puts dataset.to_yaml }
+ get "/dataset/:id/?" do
+ #Profiler__::start_profile
+ @accept = "text/html" if @accept == '*/*'
+ case @accept
+ when "application/rdf+xml", "text/turtle", "text/plain", /html/
+ r = FourStore.get(@uri, @accept)
+ else
+ case @accept
+ when "text/csv"
+ r = to_csv
+ #when "application/vnd.ms-excel"
+ #to_spreadsheet Excel
+ #when "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
+ #to_xlsx
+ #when "application/vnd.oasis.opendocument.spreadsheet"
+ #to_spreadsheet Openoffice
+ #when "chemical/x-mdl-sdfile"
+ else
+ bad_request_error "'#{@accept}' is not a supported content type."
+ end
+ end
+ #Profiler__::stop_profile
+ #Profiler__::print_profile($stdout)
+ r
end
- send_file file, :type => 'application/x-yaml'
-
- when /html/
- response['Content-Type'] = 'text/html'
- OpenTox.text_to_html JSON.pretty_generate(JSON.parse(File.read(@json_file)))
-
- when "text/csv"
- response['Content-Type'] = 'text/csv'
- OpenTox::Dataset.from_json(File.read(@json_file)).to_csv
-
- when /ms-excel/
- file = "#{@@datadir}/#{params[:id]}.xls"
- OpenTox::Dataset.from_json(File.read(@json_file)).to_xls.write(file) unless File.exists? file # lazy xls generation
- send_file file, :type => 'application/ms-excel'
-
- when /sdfile/
- response['Content-Type'] = 'chemical/x-mdl-sdfile'
- OpenTox::Dataset.from_json(File.read(@json_file)).to_sdf
-
-# when /uri-list/
-# response['Content-Type'] = 'text/uri-list'
-# Yajl::Parser.parse(File.read(@json_file)).to_urilist
-
- else
- raise OpenTox::NotFoundError.new "Content-type #{@accept} not supported."
- end
-end
-
-# Get metadata of the dataset
-# @return [application/rdf+xml] Metadata OWL-DL
-get '/:id/metadata' do
- metadata = OpenTox::Dataset.from_json(File.read(@json_file)).metadata
-
- case @accept
- when /rdf/ # redland sends text/rdf instead of application/rdf+xml
- response['Content-Type'] = 'application/rdf+xml'
- serializer = OpenTox::Serializer::Owl.new
- serializer.add_metadata url_for("/#{params[:id]}",:full), metadata
- serializer.to_rdfxml
- when /yaml/
- response['Content-Type'] = 'application/x-yaml'
- metadata.to_yaml
- end
-
-end
-
-# Get a dataset feature
-# @param [Header] Accept one of `application/rdf+xml or application-x-yaml` (default application/rdf+xml)
-# @return [application/rdf+xml,application/x-yaml] Feature metadata
-get %r{/(\d+)/feature/(.*)$} do |id,feature|
-
- @id = id
- @uri = uri @id
- @json_file = "#{@@datadir}/#{@id}.json"
- feature_uri = url_for("/#{@id}/feature/#{URI.encode(feature)}",:full) # work around racks internal uri decoding
- metadata = OpenTox::Dataset.from_json(File.read(@json_file)).features[feature_uri]
-
- case @accept
- when /rdf/ # redland sends text/rdf instead of application/rdf+xml
- response['Content-Type'] = 'application/rdf+xml'
- serializer = OpenTox::Serializer::Owl.new
- serializer.add_feature feature_uri, metadata
- serializer.to_rdfxml
- when /yaml/
- response['Content-Type'] = 'application/x-yaml'
- metadata.to_yaml
- when /json/
- response['Content-Type'] = 'application/json'
- Yajl::Encoder.encode(metadata)
- end
-
-end
-
-# Get a list of all features
-# @param [Header] Accept one of `application/rdf+xml, application-x-yaml, text/uri-list` (default application/rdf+xml)
-# @return [application/rdf+xml, application-x-yaml, text/uri-list] Feature list
-get '/:id/features' do
-
- features = OpenTox::Dataset.from_json(File.read(@json_file)).features
-
- case @accept
- when /rdf/ # redland sends text/rdf instead of application/rdf+xml
- response['Content-Type'] = 'application/rdf+xml'
- serializer = OpenTox::Serializer::Owl.new
- features.each { |feature,metadata| serializer.add_feature feature, metadata }
- serializer.to_rdfxml
- when /yaml/
- response['Content-Type'] = 'application/x-yaml'
- features.to_yaml
- when /json/
- response['Content-Type'] = 'application/json'
- Yajl::Encoder.encode(features)
- when "text/uri-list"
- response['Content-Type'] = 'text/uri-list'
- features.keys.join("\n") + "\n"
- end
-end
-
-# Get a list of all compounds
-# @return [text/uri-list] Feature list
-get '/:id/compounds' do
- response['Content-Type'] = 'text/uri-list'
- OpenTox::Dataset.from_json(File.read(@json_file)).compounds.join("\n") + "\n"
-end
-# Create a new dataset.
-#
-# Posting without parameters creates and saves an empty dataset (with assigned URI).
-# Posting with parameters creates and saves a new dataset.
-# Data can be submitted either
-# - in the message body with the appropriate Content-type header or
-# - as file uploads with Content-type:multipart/form-data and a specified file type
-# @example
-# curl -X POST -F "file=@training.csv;type=text/csv" http://webservices.in-silico.ch/dataset
-# @param [Header] Content-type one of `application/x-yaml, application/rdf+xml, multipart/form-data/`
-# @param [BODY] - string with data in selected Content-type
-# @param [optional] file, for file uploads, Content-type should be multipart/form-data, please specify the file type `application/rdf+xml, application-x-yaml, text/csv, application/ms-excel`
-# @return [text/uri-list] Task URI or Dataset URI (empty datasets)
-post '/?' do
-
- response['Content-Type'] = 'text/uri-list'
-
- # it could be that the read function works only once!, store in varible
- input_data = request.env["rack.input"].read
- @id = next_id
- @uri = uri @id
- @json_file = "#{@@datadir}/#{@id}.json"
- if params.size == 0 and input_data.size==0
- File.open(@json_file,"w+"){|f| f.puts OpenTox::Dataset.new(@uri).to_json}
- OpenTox::Authorization.check_policy(@uri, @subjectid) if File.exists? @json_file
- @uri
- else
- task = OpenTox::Task.create("Converting and saving dataset ", @uri) do
- load_dataset @id, params, request.content_type, input_data
- OpenTox::Authorization.check_policy(@uri, @subjectid) if File.exists? @json_file
- @uri
+ # Create or updata a resource
+ put "/dataset/:id/?" do
+ parse_put
end
- raise OpenTox::ServiceUnavailableError.newtask.uri+"\n" if task.status == "Cancelled"
- halt 202,task.uri+"\n"
- end
-end
-
-# Save a dataset, will overwrite all existing data
-#
-# Data can be submitted either
-# - in the message body with the appropriate Content-type header or
-# - as file uploads with Content-type:multipart/form-data and a specified file type
-# @example
-# curl -X POST -F "file=@training.csv;type=text/csv" http://webservices.in-silico.ch/dataset/1
-# @param [Header] Content-type one of `application/x-yaml, application/rdf+xml, multipart/form-data/`
-# @param [BODY] - string with data in selected Content-type
-# @param [optional] file, for file uploads, Content-type should be multipart/form-data, please specify the file type `application/rdf+xml, application-x-yaml, text/csv, application/ms-excel`
-# @return [text/uri-list] Task ID
-post '/:id' do
- response['Content-Type'] = 'text/uri-list'
- task = OpenTox::Task.create("Converting and saving dataset ", @uri) do
- FileUtils.rm Dir["#{@@datadir}/#{@id}.*"]
- load_dataset @id, params, request.content_type, request.env["rack.input"].read
- @uri
- end
- raise OpenTox::ServiceUnavailableError.newtask.uri+"\n" if task.status == "Cancelled"
- halt 202,task.uri.to_s+"\n"
-end
+ # Get metadata of the dataset
+ # @return [application/rdf+xml] Metadata OWL-DL
+ get '/dataset/:id/metadata' do
+ case @accept
+ when "application/rdf+xml", "text/turtle", "text/plain"
+ sparql = "CONSTRUCT {?s ?p ?o.} FROM <#{@uri}> WHERE {<#{@uri}> ?p ?o. }"
+ FourStore.query sparql, @accept
+ else
+ bad_request_error "'#{@accept}' is not a supported content type."
+ end
+ end
-# Deletes datasets that have been created by a crossvalidatoin that does not exist anymore
-# (This can happen if a crossvalidation fails unexpectedly)
-delete '/cleanup' do
- Dir["./#{@@datadir}/*json"].each do |file|
- dataset = OpenTox::Dataset.from_json File.read(file)
- if dataset.metadata[DC.creator] && dataset.metadata[DC.creator] =~ /crossvalidation\/[0-9]/
- begin
- cv = OpenTox::Crossvalidation.find(dataset.metadata[DC.creator],@subjectid)
- raise unless cv
- rescue
- LOGGER.debug "deleting #{dataset.uri}, crossvalidation missing: #{dataset.metadata[DC.creator]}"
- begin
- dataset.delete @subjectid
- rescue
- end
+ # Get a list of all features
+ # @param [Header] Accept one of `application/rdf+xml, text/turtle, text/plain, text/uri-list` (default application/rdf+xml)
+ # @return [application/rdf+xml, text/turtle, text/plain, text/uri-list] Feature list
+ get '/dataset/:id/features' do
+ case @accept
+ when "application/rdf+xml", "text/turtle", "text/plain"
+ sparql = "CONSTRUCT {?s ?p ?o.} FROM <#{@uri}> WHERE {?s <#{RDF.type}> <#{RDF::OT.Feature}>; ?p ?o. }"
+ when "text/uri-list"
+ sparql = "SELECT DISTINCT ?s FROM <#{@uri}> WHERE {?s <#{RDF.type}> <#{RDF::OT.Feature}>. }"
+ else
+ bad_request_error "'#{@accept}' is not a supported content type."
end
+ FourStore.query sparql, @accept
end
- end
- "cleanup done"
-end
-# Delete a dataset
-# @return [text/plain] Status message
-delete '/:id' do
- LOGGER.debug "deleting dataset with id #{@id}"
- begin
- FileUtils.rm Dir["#{@@datadir}/#{@id}.*"]
- if @subjectid and !File.exists? @json_file and @uri
- begin
- res = OpenTox::Authorization.delete_policies_from_uri(@uri, @subjectid)
- LOGGER.debug "Policy deleted for Dataset URI: #{@uri} with result: #{res}"
- rescue
- LOGGER.warn "Policy delete error for Dataset URI: #{@uri}"
+ # Get a list of all compounds
+ # @return [text/uri-list] Feature list
+ get '/dataset/:id/compounds' do
+ case @accept
+ when "application/rdf+xml", "text/turtle", "text/plain"
+ sparql = "CONSTRUCT {?s ?p ?o.} FROM <#{@uri}> WHERE {?s <#{RDF.type}> <#{RDF::OT.Compound}>; ?p ?o. }"
+ when "text/uri-list"
+ sparql = "SELECT DISTINCT ?s FROM <#{@uri}> WHERE {?s <#{RDF.type}> <#{RDF::OT.Compound}>. }"
+ else
+ bad_request_error "'#{@accept}' is not a supported content type."
end
+ FourStore.query sparql, @accept
end
- response['Content-Type'] = 'text/plain'
- "Dataset #{@id} deleted."
- rescue
- raise OpenTox::NotFoundError.new "Dataset #{@id} does not exist."
end
end
-# Delete all datasets
-# @return [text/plain] Status message
-delete '/?' do
- FileUtils.rm Dir["#{@@datadir}/*.rdfxml"]
- FileUtils.rm Dir["#{@@datadir}/*.xls"]
- FileUtils.rm Dir["#{@@datadir}/*.yaml"]
- FileUtils.rm Dir["#{@@datadir}/*.json"]
- response['Content-Type'] = 'text/plain'
- "All datasets deleted."
-end
diff --git a/config.ru b/config.ru
index a1aab0d..a8d4293 100644
--- a/config.ru
+++ b/config.ru
@@ -1,6 +1,5 @@
-require 'rubygems'
-require 'opentox-ruby'
-require 'config/config_ru'
-run Sinatra::Application
-set :raise_errors, false
-set :show_exceptions, false \ No newline at end of file
+SERVICE = "dataset"
+require 'bundler'
+Bundler.require
+require './application.rb'
+run OpenTox::Application
diff --git a/data/.gitignore b/data/.gitignore
deleted file mode 100644
index 59e3edd..0000000
--- a/data/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-*.rdfxml
-*.xls
-*.yaml
diff --git a/dataset.gemspec b/dataset.gemspec
new file mode 100644
index 0000000..58b427a
--- /dev/null
+++ b/dataset.gemspec
@@ -0,0 +1,25 @@
+# -*- encoding: utf-8 -*-
+$:.push File.expand_path("../lib", __FILE__)
+
+Gem::Specification.new do |s|
+ s.name = "feature"
+ s.version = File.read("./VERSION")
+ s.authors = ["Christoph Helma"]
+ s.email = ["helma@in-silico.ch"]
+ s.homepage = ""
+ s.summary = %q{OpenTox Dataset Service}
+ s.description = %q{OpenTox Dataset Service}
+ s.license = 'GPL-3'
+
+ s.rubyforge_project = "dataset"
+
+ s.files = `git ls-files`.split("\n")
+ s.required_ruby_version = '>= 1.9.2'
+
+ # specify any dependencies here; for example:
+ s.add_runtime_dependency "opentox-server"
+ s.add_runtime_dependency 'roo'
+ #s.add_runtime_dependency 'axlsx'
+ #s.add_runtime_dependency 'simple_xlsx_writer'
+ s.post_install_message = "Please configure your service in ~/.opentox/config/dataset.rb"
+end
diff --git a/public/.gitignore b/public/.gitignore
deleted file mode 100644
index debc7d4..0000000
--- a/public/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-*.rdfxml
-*.xls
diff --git a/public/robots.txt b/public/robots.txt
deleted file mode 100644
index 1f53798..0000000
--- a/public/robots.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-User-agent: *
-Disallow: /