From 48d044219674a96ba0d387311612c9fb95949e05 Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Wed, 25 Aug 2010 14:49:25 +0200 Subject: Version bump to 1.6.6 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 9f05f9f..ec70f75 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.6.5 +1.6.6 -- cgit v1.2.3 From 6a560290805ff49d7d43842dd8119a2e2bb06b4a Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Thu, 2 Sep 2010 13:09:19 +0200 Subject: old user authentification removed --- lib/algorithm.rb | 4 ++-- lib/authorization.rb | 24 ------------------------ lib/environment.rb | 14 -------------- lib/model.rb | 4 ++-- lib/opentox-ruby-api-wrapper.rb | 2 +- lib/rest_client_wrapper.rb | 2 +- lib/templates/users.yaml | 5 ----- opentox-ruby-api-wrapper.gemspec | 2 +- 8 files changed, 7 insertions(+), 50 deletions(-) delete mode 100644 lib/authorization.rb delete mode 100644 lib/templates/users.yaml diff --git a/lib/algorithm.rb b/lib/algorithm.rb index d7b57af..4d9156a 100644 --- a/lib/algorithm.rb +++ b/lib/algorithm.rb @@ -25,7 +25,7 @@ module OpenTox def self.create_feature_dataset(params) LOGGER.debug File.basename(__FILE__) + ": creating feature dataset" - resource = RestClient::Resource.new(params[:feature_generation_uri], :user => @@users[:users].keys[0], :password => @@users[:users].values[0]) + resource = RestClient::Resource.new(params[:feature_generation_uri]) resource.post :dataset_uri => params[:dataset_uri], :feature_uri => params[:feature_uri] end @@ -40,7 +40,7 @@ module OpenTox LOGGER.debug params LOGGER.debug File.basename(__FILE__) + ": creating model" LOGGER.debug File.join(@@config[:services]["opentox-algorithm"], "lazar") - resource = RestClient::Resource.new(File.join(@@config[:services]["opentox-algorithm"], "lazar"), :user => @@users[:users].keys[0], :password => @@users[:users].values[0], :content_type => "application/x-yaml") + resource = RestClient::Resource.new(File.join(@@config[:services]["opentox-algorithm"], "lazar"), :content_type => "application/x-yaml") @uri = resource.post(:dataset_uri => params[:dataset_uri], :prediction_feature => params[:prediction_feature], :feature_generation_uri => File.join(@@config[:services]["opentox-algorithm"], "fminer")).body.chomp end diff --git a/lib/authorization.rb b/lib/authorization.rb deleted file mode 100644 index 9a1760a..0000000 --- a/lib/authorization.rb +++ /dev/null @@ -1,24 +0,0 @@ -helpers do - - def protected! - response['WWW-Authenticate'] = %(Basic realm="Opentox Webservice Authentication") and \ - throw(:halt, [401, "Not authorized\n"]) and \ - return unless authorized? - end - - def authorized? - @auth ||= Rack::Auth::Basic::Request.new(request.env) - @auth.provided? && @auth.basic? && @auth.credentials && valid_user? - end - - def valid_user? - users = @@users[:users] - return @auth.credentials == [@auth.username, users.fetch(@auth.username)] if users.has_key?(@auth.username) - return false - end - -end - -before do - #protected! unless env['REQUEST_METHOD'] == "GET" -end diff --git a/lib/environment.rb b/lib/environment.rb index cfc875d..c662e8b 100644 --- a/lib/environment.rb +++ b/lib/environment.rb @@ -54,20 +54,6 @@ else LOGGER.level = Logger::WARN end -if File.exist?(user_file) - @@users = YAML.load_file(user_file) -else - FileUtils.cp(File.join(File.dirname(__FILE__), 'templates/users.yaml'), user_file) - puts "Please edit #{user_file} and restart your application." - exit -end - -begin - 0 < @@users[:users].keys.length -rescue - raise "Please edit #{user_file} and restart your application. Create at least one user with password." -end - # Regular expressions for parsing classification data TRUE_REGEXP = /^(true|active|1|1.0)$/i FALSE_REGEXP = /^(false|inactive|0|0.0)$/i diff --git a/lib/model.rb b/lib/model.rb index e36b538..3ecd61c 100644 --- a/lib/model.rb +++ b/lib/model.rb @@ -95,7 +95,7 @@ module OpenTox def save @features.uniq! - resource = RestClient::Resource.new(@@config[:services]["opentox-model"], :user => @@users[:users].keys[0], :password => @@users[:users].values[0]) + resource = RestClient::Resource.new(@@config[:services]["opentox-model"]) resource.post(self.to_yaml, :content_type => "application/x-yaml").chomp.to_s end @@ -126,7 +126,7 @@ module OpenTox def save @features.uniq! - resource = RestClient::Resource.new(@@config[:services]["opentox-model"], :user => @@users[:users].keys[0], :password => @@users[:users].values[0]) + resource = RestClient::Resource.new(@@config[:services]["opentox-model"]) resource.post(self.to_yaml, :content_type => "application/x-yaml").chomp.to_s end diff --git a/lib/opentox-ruby-api-wrapper.rb b/lib/opentox-ruby-api-wrapper.rb index 45a3428..bae1155 100644 --- a/lib/opentox-ruby-api-wrapper.rb +++ b/lib/opentox-ruby-api-wrapper.rb @@ -8,6 +8,6 @@ rescue LoadError puts "Please install Openbabel with 'rake openbabel:install' in the compound component" end -['owl', 'compound','dataset','algorithm','model','task','validation','utils','authorization','features', 'ot-logger', 'overwrite', 'rest_client_wrapper'].each do |lib| +['owl', 'compound','dataset','algorithm','model','task','validation','utils','features', 'ot-logger', 'overwrite', 'rest_client_wrapper'].each do |lib| require lib end diff --git a/lib/rest_client_wrapper.rb b/lib/rest_client_wrapper.rb index 1282bee..82836d9 100644 --- a/lib/rest_client_wrapper.rb +++ b/lib/rest_client_wrapper.rb @@ -67,7 +67,7 @@ module OpenTox begin #LOGGER.debug "RestCall: "+rest_call.to_s+" "+uri.to_s+" "+headers.inspect - resource = RestClient::Resource.new(uri,{:timeout => 60}) #, :user => @@users[:users].keys[0], :password => @@users[:users].values[0]}) + resource = RestClient::Resource.new(uri,{:timeout => 60}) if payload result = resource.send(rest_call, payload, headers) elsif headers diff --git a/lib/templates/users.yaml b/lib/templates/users.yaml deleted file mode 100644 index 483fd7b..0000000 --- a/lib/templates/users.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# please insert users and passwords here. -# one user and password each line. uncomment the line. -:users: -# username: "secretpassword" -# exampleuser: "ih9aiTog" \ No newline at end of file diff --git a/opentox-ruby-api-wrapper.gemspec b/opentox-ruby-api-wrapper.gemspec index f0a0816..8217240 100644 --- a/opentox-ruby-api-wrapper.gemspec +++ b/opentox-ruby-api-wrapper.gemspec @@ -5,7 +5,7 @@ Gem::Specification.new do |s| s.name = %q{opentox-ruby-api-wrapper} - s.version = "1.6.5" + s.version = "1.6.6" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Christoph Helma, Martin Guetlein"] -- cgit v1.2.3 From b032c1cef9afd1720235b7d6d121f292e8c77d1e Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Tue, 7 Sep 2010 11:08:14 +0200 Subject: tabs expanded --- lib/owl.rb | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/owl.rb b/lib/owl.rb index dcf26a5..48a115f 100644 --- a/lib/owl.rb +++ b/lib/owl.rb @@ -93,7 +93,7 @@ end module OpenTox - class Owl + class Owl # to get correct owl-dl, properties and objects have to be typed # i.e. the following triple is insufficient: @@ -188,26 +188,26 @@ module OpenTox # ot_class is the class of the object as string, e.g. "Model","Dataset", ... # root_node is the root-object node in the rdf # uri the uri of the object - attr_accessor :ot_class, :root_node, :uri, :model + attr_accessor :ot_class, :root_node, :uri, :model private - def initialize - @model = Redland::Model.new Redland::MemoryStore.new - end + def initialize + @model = Redland::Model.new Redland::MemoryStore.new + end # build new owl object # ot_class is the class of this object, should be a string like "Model", "Task", ... # uri is name and identifier of this object public - def self.create( ot_class, uri ) + def self.create( ot_class, uri ) owl = OpenTox::Owl.new owl.ot_class = ot_class owl.root_node = Redland::Resource.new(uri.to_s.strip) owl.set("type",owl.ot_class) owl.uri = uri - owl - end + owl + end # loads owl from data def self.from_data(data, base_uri, ot_class) @@ -250,13 +250,13 @@ module OpenTox end end - def self.from_uri(uri, ot_class) + def self.from_uri(uri, ot_class) return from_data(RestClientWrapper.get(uri,:accept => "application/rdf+xml").to_s, uri, ot_class) - end + end - def rdf - @model.to_string - end + def rdf + @model.to_string + end # returns the first object for subject:root_node and property # (sufficient for accessing simple, root-node properties) @@ -430,14 +430,14 @@ module OpenTox # for "backwards-compatiblity" # better use directly: # set_data( { "parameters" => [ { "title" => , "paramScope" => , "paramValue" => } ] ) - def parameters=(params) + def parameters=(params) converted_params = [] params.each do |name, settings| converted_params << { :title => name, :paramScope => settings[:scope], :paramValue => settings[:value] } end set_data( :parameters => converted_params ) - end + end # PENDING move to dataset.rb # this is for dataset.to_owl -- cgit v1.2.3 From 3c3b13f024a7d5e5b94c772be9ca4d9b99988743 Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Tue, 7 Sep 2010 13:06:32 +0200 Subject: initial ntriples serialisation --- lib/owl.rb | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/lib/owl.rb b/lib/owl.rb index 48a115f..1a0d27c 100644 --- a/lib/owl.rb +++ b/lib/owl.rb @@ -188,11 +188,12 @@ module OpenTox # ot_class is the class of the object as string, e.g. "Model","Dataset", ... # root_node is the root-object node in the rdf # uri the uri of the object - attr_accessor :ot_class, :root_node, :uri, :model + attr_accessor :ot_class, :root_node, :uri, :model, :triples private def initialize @model = Redland::Model.new Redland::MemoryStore.new + @triples = "" end # build new owl object @@ -255,7 +256,8 @@ module OpenTox end def rdf - @model.to_string + #@model.to_string + @triples end # returns the first object for subject:root_node and property @@ -335,16 +337,23 @@ module OpenTox # model_xy,rdf:type,ot:Model # ot:Model,rdf:type,owl:Class def set_type(ot_class, current_node=@root_node) - @model.add current_node, RDF_TYPE, node(ot_class) - @model.add node(ot_class), RDF_TYPE, OWL_TYPE_CLASS + #@triples += "#{ot_class.to_s} #{RDF_TYPE.to_s} #{current_node.to_s}" + @triples += "#{current_node} #{RDF_TYPE} #{node(ot_class).to_s}.\n".gsub(/\[/,'<').gsub(/\]/,'>') + @triples += "#{node(ot_class).to_s} #{RDF_TYPE} #{OWL_TYPE_CLASS}.\n".gsub(/\[/,'<').gsub(/\]/,'>') + #@model.add current_node, RDF_TYPE, node(ot_class) + #@model.add node(ot_class), RDF_TYPE, OWL_TYPE_CLASS end # example-triples for setting description of a model: # model_xy,ot:description,bla..bla^^xml:string # ot:description,rdf:type,owl:Literal def set_literal(literal_name, literal_value, literal_datatype, current_node=@root_node) - @model.add current_node, node(literal_name), Redland::Literal.create(literal_value, literal_datatype) - @model.add node(literal_name), RDF_TYPE, OWL_TYPE_LITERAL + #@triples += "#{current_node} #{node(literal_name)} #{Redland::Literal.create(literal_value, literal_datatype)}.\n".gsub(/\[/,'<').gsub(/\]/,'>') + #TODO: add datatype + @triples += "#{current_node} #{node(literal_name)} \"#{literal_value}\".\n".gsub(/\[/,'<').gsub(/\]/,'>') + @triples += "#{node(literal_name)} #{RDF_TYPE} #{OWL_TYPE_LITERAL}.\n".gsub(/\[/,'<').gsub(/\]/,'>') + #@model.add current_node, node(literal_name), Redland::Literal.create(literal_value, literal_datatype) + #@model.add node(literal_name), RDF_TYPE, OWL_TYPE_LITERAL end # example-triples for setting algorithm property of a model: @@ -353,11 +362,15 @@ module OpenTox # algorihtm_xy,rdf:type,ot:Algorithm # ot:Algorithm,rdf:type,owl:Class def set_object_property(property, object, object_class, current_node=@root_node) - object_node = Redland::Resource.new(object) - @model.add current_node, node(property), object_node - @model.add node(property), RDF_TYPE, OWL_TYPE_OBJECT_PROPERTY - @model.add object_node, RDF_TYPE, node(object_class) - @model.add node(object_class), RDF_TYPE, OWL_TYPE_CLASS +# object_node = Redland::Resource.new(object) +# @triples += "#{current_node} #{node(property)} #{object_node}.\n".gsub(/\[/,'<').gsub(/\]/,'>') +# @triples += "#{node(property)} #{RDF_TYPE} #{OWL_TYPE_OBJECT_PROPERTY}.\n".gsub(/\[/,'<').gsub(/\]/,'>') +# @triples += "#{object_node} #{RDF_TYPE} #{node(object_class)}.\n".gsub(/\[/,'<').gsub(/\]/,'>') +# @triples += "#{node(object_class)} #{RDF_TYPE} #{OWL_TYPE_CLASS}.\n".gsub(/\[/,'<').gsub(/\]/,'>') + #@model.add current_node, node(property), object_node + #@model.add node(property), RDF_TYPE, OWL_TYPE_OBJECT_PROPERTY + #@model.add object_node, RDF_TYPE, node(object_class) + #@model.add node(object_class), RDF_TYPE, OWL_TYPE_CLASS end # this is (a recursiv method) to set nested-data via hashes (not only simple properties) -- cgit v1.2.3 From da9582a7d176358cf4240972a423a011cbaac132 Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Wed, 8 Sep 2010 14:03:32 +0200 Subject: string concatination with << instead of +=, speeds things up for order of magnitudes --- lib/owl.rb | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/lib/owl.rb b/lib/owl.rb index 1a0d27c..c505f5c 100644 --- a/lib/owl.rb +++ b/lib/owl.rb @@ -1,3 +1,4 @@ +require 'open3' # RDF namespaces RDF = Redland::Namespace.new 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' OWL = Redland::Namespace.new 'http://www.w3.org/2002/07/owl#' @@ -257,7 +258,11 @@ module OpenTox def rdf #@model.to_string - @triples + #stdin, stdout, stderr = Open3.popen3('rapper -I test.org -i ntriples -o rdfxml -') + #stdin.puts @triples + #stdout + File.open("/tmp/d","w+") {|f| f.puts @triples} + `rapper -i ntriples -o rdfxml /tmp/d` end # returns the first object for subject:root_node and property @@ -338,8 +343,8 @@ module OpenTox # ot:Model,rdf:type,owl:Class def set_type(ot_class, current_node=@root_node) #@triples += "#{ot_class.to_s} #{RDF_TYPE.to_s} #{current_node.to_s}" - @triples += "#{current_node} #{RDF_TYPE} #{node(ot_class).to_s}.\n".gsub(/\[/,'<').gsub(/\]/,'>') - @triples += "#{node(ot_class).to_s} #{RDF_TYPE} #{OWL_TYPE_CLASS}.\n".gsub(/\[/,'<').gsub(/\]/,'>') + @triples << "#{current_node} #{RDF_TYPE} #{node(ot_class).to_s}.\n".gsub(/\[/,'<').gsub(/\]/,'>') + @triples << "#{node(ot_class).to_s} #{RDF_TYPE} #{OWL_TYPE_CLASS}.\n".gsub(/\[/,'<').gsub(/\]/,'>') #@model.add current_node, RDF_TYPE, node(ot_class) #@model.add node(ot_class), RDF_TYPE, OWL_TYPE_CLASS end @@ -350,8 +355,8 @@ module OpenTox def set_literal(literal_name, literal_value, literal_datatype, current_node=@root_node) #@triples += "#{current_node} #{node(literal_name)} #{Redland::Literal.create(literal_value, literal_datatype)}.\n".gsub(/\[/,'<').gsub(/\]/,'>') #TODO: add datatype - @triples += "#{current_node} #{node(literal_name)} \"#{literal_value}\".\n".gsub(/\[/,'<').gsub(/\]/,'>') - @triples += "#{node(literal_name)} #{RDF_TYPE} #{OWL_TYPE_LITERAL}.\n".gsub(/\[/,'<').gsub(/\]/,'>') + @triples << "#{current_node} #{node(literal_name)} \"#{literal_value}\".\n".gsub(/\[/,'<').gsub(/\]/,'>') + @triples << "#{node(literal_name)} #{RDF_TYPE} #{OWL_TYPE_LITERAL}.\n".gsub(/\[/,'<').gsub(/\]/,'>') #@model.add current_node, node(literal_name), Redland::Literal.create(literal_value, literal_datatype) #@model.add node(literal_name), RDF_TYPE, OWL_TYPE_LITERAL end @@ -362,11 +367,11 @@ module OpenTox # algorihtm_xy,rdf:type,ot:Algorithm # ot:Algorithm,rdf:type,owl:Class def set_object_property(property, object, object_class, current_node=@root_node) -# object_node = Redland::Resource.new(object) -# @triples += "#{current_node} #{node(property)} #{object_node}.\n".gsub(/\[/,'<').gsub(/\]/,'>') -# @triples += "#{node(property)} #{RDF_TYPE} #{OWL_TYPE_OBJECT_PROPERTY}.\n".gsub(/\[/,'<').gsub(/\]/,'>') -# @triples += "#{object_node} #{RDF_TYPE} #{node(object_class)}.\n".gsub(/\[/,'<').gsub(/\]/,'>') -# @triples += "#{node(object_class)} #{RDF_TYPE} #{OWL_TYPE_CLASS}.\n".gsub(/\[/,'<').gsub(/\]/,'>') + object_node = Redland::Resource.new(object) + @triples << "#{current_node} #{node(property)} #{object_node}.\n".gsub(/\[/,'<').gsub(/\]/,'>') + @triples << "#{node(property)} #{RDF_TYPE} #{OWL_TYPE_OBJECT_PROPERTY}.\n".gsub(/\[/,'<').gsub(/\]/,'>') + @triples << "#{object_node} #{RDF_TYPE} #{node(object_class)}.\n".gsub(/\[/,'<').gsub(/\]/,'>') + @triples << "#{node(object_class)} #{RDF_TYPE} #{OWL_TYPE_CLASS}.\n".gsub(/\[/,'<').gsub(/\]/,'>') #@model.add current_node, node(property), object_node #@model.add node(property), RDF_TYPE, OWL_TYPE_OBJECT_PROPERTY #@model.add object_node, RDF_TYPE, node(object_class) -- cgit v1.2.3 From ea33651026aeb6868e7c675304a08eccd7b5e695 Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Wed, 8 Sep 2010 15:49:07 +0200 Subject: initial attempts to switch to rdfxml --- lib/owl.rb | 55 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/lib/owl.rb b/lib/owl.rb index c505f5c..4f3a5da 100644 --- a/lib/owl.rb +++ b/lib/owl.rb @@ -1,4 +1,7 @@ require 'open3' +require 'rdf' +require 'rdf/raptor' +include RDF # RDF namespaces RDF = Redland::Namespace.new 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' OWL = Redland::Namespace.new 'http://www.w3.org/2002/07/owl#' @@ -193,8 +196,9 @@ module OpenTox private def initialize + @triples = [] @model = Redland::Model.new Redland::MemoryStore.new - @triples = "" + #@triples = "" end # build new owl object @@ -261,8 +265,15 @@ module OpenTox #stdin, stdout, stderr = Open3.popen3('rapper -I test.org -i ntriples -o rdfxml -') #stdin.puts @triples #stdout - File.open("/tmp/d","w+") {|f| f.puts @triples} - `rapper -i ntriples -o rdfxml /tmp/d` + #File.open("/tmp/d","w+") {|f| f.puts @triples} + #`rapper -i ntriples -o rdfxml /tmp/d` + #@triples + output = RDF::Writer.for(:rdfxml).buffer do |writer| + #@triples.each do |statement| + #writer << statement + #end + end + #output end # returns the first object for subject:root_node and property @@ -343,10 +354,10 @@ module OpenTox # ot:Model,rdf:type,owl:Class def set_type(ot_class, current_node=@root_node) #@triples += "#{ot_class.to_s} #{RDF_TYPE.to_s} #{current_node.to_s}" - @triples << "#{current_node} #{RDF_TYPE} #{node(ot_class).to_s}.\n".gsub(/\[/,'<').gsub(/\]/,'>') - @triples << "#{node(ot_class).to_s} #{RDF_TYPE} #{OWL_TYPE_CLASS}.\n".gsub(/\[/,'<').gsub(/\]/,'>') - #@model.add current_node, RDF_TYPE, node(ot_class) - #@model.add node(ot_class), RDF_TYPE, OWL_TYPE_CLASS + #@triples << "#{current_node} #{RDF_TYPE} #{node(ot_class).to_s}.\n".gsub(/\[/,'<').gsub(/\]/,'>') + #@triples << "#{node(ot_class).to_s} #{RDF_TYPE} #{OWL_TYPE_CLASS}.\n".gsub(/\[/,'<').gsub(/\]/,'>') + add current_node, RDF_TYPE, node(ot_class) + add node(ot_class), RDF_TYPE, OWL_TYPE_CLASS end # example-triples for setting description of a model: @@ -355,10 +366,10 @@ module OpenTox def set_literal(literal_name, literal_value, literal_datatype, current_node=@root_node) #@triples += "#{current_node} #{node(literal_name)} #{Redland::Literal.create(literal_value, literal_datatype)}.\n".gsub(/\[/,'<').gsub(/\]/,'>') #TODO: add datatype - @triples << "#{current_node} #{node(literal_name)} \"#{literal_value}\".\n".gsub(/\[/,'<').gsub(/\]/,'>') - @triples << "#{node(literal_name)} #{RDF_TYPE} #{OWL_TYPE_LITERAL}.\n".gsub(/\[/,'<').gsub(/\]/,'>') - #@model.add current_node, node(literal_name), Redland::Literal.create(literal_value, literal_datatype) - #@model.add node(literal_name), RDF_TYPE, OWL_TYPE_LITERAL + #@triples << "#{current_node} #{node(literal_name)} \"#{literal_value}\".\n".gsub(/\[/,'<').gsub(/\]/,'>') + #@triples << "#{node(literal_name)} #{RDF_TYPE} #{OWL_TYPE_LITERAL}.\n".gsub(/\[/,'<').gsub(/\]/,'>') + add current_node, node(literal_name), Redland::Literal.create(literal_value, literal_datatype) + add node(literal_name), RDF_TYPE, OWL_TYPE_LITERAL end # example-triples for setting algorithm property of a model: @@ -368,14 +379,20 @@ module OpenTox # ot:Algorithm,rdf:type,owl:Class def set_object_property(property, object, object_class, current_node=@root_node) object_node = Redland::Resource.new(object) - @triples << "#{current_node} #{node(property)} #{object_node}.\n".gsub(/\[/,'<').gsub(/\]/,'>') - @triples << "#{node(property)} #{RDF_TYPE} #{OWL_TYPE_OBJECT_PROPERTY}.\n".gsub(/\[/,'<').gsub(/\]/,'>') - @triples << "#{object_node} #{RDF_TYPE} #{node(object_class)}.\n".gsub(/\[/,'<').gsub(/\]/,'>') - @triples << "#{node(object_class)} #{RDF_TYPE} #{OWL_TYPE_CLASS}.\n".gsub(/\[/,'<').gsub(/\]/,'>') - #@model.add current_node, node(property), object_node - #@model.add node(property), RDF_TYPE, OWL_TYPE_OBJECT_PROPERTY - #@model.add object_node, RDF_TYPE, node(object_class) - #@model.add node(object_class), RDF_TYPE, OWL_TYPE_CLASS + #@triples << "#{current_node} #{node(property)} #{object_node}.\n".gsub(/\[/,'<').gsub(/\]/,'>') + #@triples << "#{node(property)} #{RDF_TYPE} #{OWL_TYPE_OBJECT_PROPERTY}.\n".gsub(/\[/,'<').gsub(/\]/,'>') + #@triples << "#{object_node} #{RDF_TYPE} #{node(object_class)}.\n".gsub(/\[/,'<').gsub(/\]/,'>') + #@triples << "#{node(object_class)} #{RDF_TYPE} #{OWL_TYPE_CLASS}.\n".gsub(/\[/,'<').gsub(/\]/,'>') + add current_node, node(property), object_node + add node(property), RDF_TYPE, OWL_TYPE_OBJECT_PROPERTY + add object_node, RDF_TYPE, node(object_class) + add node(object_class), RDF_TYPE, OWL_TYPE_CLASS + end + + def add(s,p,o) + #@triples << "#{s} #{p} #{o}.\n".gsub(/\[/,'<').gsub(/\]/,'>') + @triples << [s,p,o] + #@model.add s,p,o end # this is (a recursiv method) to set nested-data via hashes (not only simple properties) -- cgit v1.2.3 From 5bb39ea69d1875306c640ad17f515f21edc1cda4 Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Wed, 8 Sep 2010 15:52:15 +0200 Subject: initial attempts to switch to rdfxml --- lib/owl.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/owl.rb b/lib/owl.rb index 4f3a5da..6fb457a 100644 --- a/lib/owl.rb +++ b/lib/owl.rb @@ -1,4 +1,3 @@ -require 'open3' require 'rdf' require 'rdf/raptor' include RDF @@ -268,10 +267,11 @@ module OpenTox #File.open("/tmp/d","w+") {|f| f.puts @triples} #`rapper -i ntriples -o rdfxml /tmp/d` #@triples - output = RDF::Writer.for(:rdfxml).buffer do |writer| - #@triples.each do |statement| - #writer << statement - #end + #output = RDF::Writer.for(:rdfxml).buffer do |writer| + RDF::Writer.for(:ntriples).buffer do |writer| + @triples.each do |statement| + writer << statement + end end #output end -- cgit v1.2.3 From 0e69de27fca7c1a13e3f38dd52d625e6b6e31758 Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Wed, 8 Sep 2010 16:21:19 +0200 Subject: initial attempts to switch to rdfxml --- lib/owl.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/owl.rb b/lib/owl.rb index 6fb457a..08041b6 100644 --- a/lib/owl.rb +++ b/lib/owl.rb @@ -1,8 +1,9 @@ require 'rdf' +require 'rdf/ntriples' require 'rdf/raptor' include RDF # RDF namespaces -RDF = Redland::Namespace.new 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' +#RDF = Redland::Namespace.new 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' OWL = Redland::Namespace.new 'http://www.w3.org/2002/07/owl#' DC = Redland::Namespace.new 'http://purl.org/dc/elements/1.1/' OT = Redland::Namespace.new 'http://www.opentox.org/api/1.1#' @@ -268,7 +269,7 @@ module OpenTox #`rapper -i ntriples -o rdfxml /tmp/d` #@triples #output = RDF::Writer.for(:rdfxml).buffer do |writer| - RDF::Writer.for(:ntriples).buffer do |writer| + RDF::Writer.for(:rdfxml).buffer do |writer| @triples.each do |statement| writer << statement end @@ -391,7 +392,7 @@ module OpenTox def add(s,p,o) #@triples << "#{s} #{p} #{o}.\n".gsub(/\[/,'<').gsub(/\]/,'>') - @triples << [s,p,o] + @triples << [s.to_s.sub(/\[/,'').sub(/\]/,''),p.to_s.sub(/\[/,'').sub(/\]/,''),o.to_s.sub(/\[/,'').sub(/\]/,'')] #@model.add s,p,o end -- cgit v1.2.3 From a05eefb79f82b79d609448e44d0ab533d2593a0f Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Mon, 13 Sep 2010 17:09:37 +0200 Subject: intermediary commit for new owl serializer --- lib/opentox-ruby-api-wrapper.rb | 2 +- lib/owl-serializer.rb | 313 ++++++++++++++++++++++++++++++++++++++++ lib/owl.rb | 7 +- 3 files changed, 320 insertions(+), 2 deletions(-) create mode 100644 lib/owl-serializer.rb diff --git a/lib/opentox-ruby-api-wrapper.rb b/lib/opentox-ruby-api-wrapper.rb index bae1155..d9db4ac 100644 --- a/lib/opentox-ruby-api-wrapper.rb +++ b/lib/opentox-ruby-api-wrapper.rb @@ -8,6 +8,6 @@ rescue LoadError puts "Please install Openbabel with 'rake openbabel:install' in the compound component" end -['owl', 'compound','dataset','algorithm','model','task','validation','utils','features', 'ot-logger', 'overwrite', 'rest_client_wrapper'].each do |lib| +['owl-serializer', 'compound','dataset','algorithm','model','task','validation','utils','features', 'ot-logger', 'overwrite', 'rest_client_wrapper'].each do |lib| require lib end diff --git a/lib/owl-serializer.rb b/lib/owl-serializer.rb new file mode 100644 index 0000000..8257b78 --- /dev/null +++ b/lib/owl-serializer.rb @@ -0,0 +1,313 @@ +require 'rdf' +require 'rdf/raptor' +require 'rdf/ntriples' + +# RDF namespaces +include RDF +OT = RDF::Vocabulary.new 'http://www.opentox.org/api/1.1#' + +module OpenTox + + class OwlSerializer + + # to get correct owl-dl, properties and objects have to be typed + # i.e. the following triple is insufficient: + # ModelXY,ot:algorithm,AlgorithmXY + # further needed: + # ot:algorithm,rdf:type,owl:ObjectProperty + # AlgorithmXY,rdf:type,ot:Algorithm + # ot:Algorithm,rdf:type,owl:Class DONE + attr_accessor :model + + def initialize(klass,uri) + @model = RDF::Graph.new(uri) + @model << [ RDF::URI.new(uri), RDF.type, OT[klass] ] + @model << [ OT[klass], RDF.type, OWL.Class ] + # add class statements from OT +=begin + RDF::Reader.open('http://www.opentox.org/api/1.1#', :format => :rdfxml).each_statement do |statement| + @model << statement if statement.predicate == RDF.type #and statement.object == OWL.class + end +=end + end + + # build new owl object + # klass is the class of this object, should be a string like "Model", "Task", ... + # uri is name and identifier of this object + + def self.create( klass, uri ) + OpenTox::OwlSerializer.new(klass,uri) + end + + def rdf + RDF::Writer.for(:rdfxml).buffer do |writer| + writer << @model + #@model.each do |statement| + #writer << statement + #end + end + end + + # sets values of current_node (by default root_node) + # + # note: this does not delete existing triples + # * there can be several triples for the same subject and predicate + # ( e.g. after set("description","bla1") and set("description","bla2") + # both descriptions are in the model, + # but the get("description") will give you only one object (by chance) + # * this does not matter in pratice (only dataset uses this -> load_dataset-methods) + # * identical values appear only once in rdf + def annotate(predicate, object) + @model << [ @model.to_uri, DC[predicate], RDF::Literal.new(object, :datatype => XSD.String) ] + @model << [ DC[predicate], RDF.type, OWL.AnnotationProperty ] + end +=begin + def set(predicate, object, current_node=@root_node ) + + pred = predicate.to_s + raise "uri is no prop, cannot set uri" if pred=="uri" + raise "dc[identifier] deprecated, use owl.uri" if pred=="identifier" + if (object.is_a?(Redland::Node) and object.blank?) or nil==object or object.to_s.size==0 + # set only not-nil values + LOGGER.warn "skipping (not setting) empty value in rdf for property: '"+pred+"'" + return + end + + if pred=="type" + # predicate is type, set class of current node + set_type(object, current_node) + elsif LITERAL_TYPES.has_key?(pred) + # predicate is literal + set_literal(pred,object,LITERAL_TYPES[pred],current_node) + elsif OBJECT_PROPERTY_CLASS.has_key?(pred) + # predicte is objectProperty, object is another resource + set_object_property(pred,object,OBJECT_PROPERTY_CLASS[pred],current_node) + else + raise "unkonwn rdf-property, please add: '"+pred+"' to OpenTox::OWL.OBJECT_PROPERTY_CLASS or OpenTox::OWL.LITERAL_TYPES" + end + end + + # example-triples for setting rdf-type to model: + # model_xy,rdf:type,ot:Model + # ot:Model,rdf:type,owl:Class + def set_type(ot_class, current_node=@root_node) + add current_node, RDF.type, node(ot_class) + add node(ot_class), RDF_TYPE, OWL_TYPE_CLASS + end + + # example-triples for setting description of a model: + # model_xy,ot:description,bla..bla^^xml:string + # ot:description,rdf:type,owl:Literal + def set_literal(literal_name, literal_value, literal_datatype, current_node=@root_node) + add current_node, node(literal_name), literal_value# TODO add literal_datatype + add node(literal_name), RDF_TYPE, OWL_TYPE_LITERAL + end + + # example-triples for setting algorithm property of a model: + # model_xy,ot:algorithm,algorihtm_xy + # ot:algorithm,rdf:type,owl:ObjectProperty + # algorihtm_xy,rdf:type,ot:Algorithm + # ot:Algorithm,rdf:type,owl:Class + def set_object_property(property, object, object_class, current_node=@root_node) + object_node = Redland::Resource.new(object) + add current_node, node(property), object_node + add node(property), RDF_TYPE, OWL_TYPE_OBJECT_PROPERTY + add object_node, RDF_TYPE, node(object_class) + add node(object_class), RDF_TYPE, OWL_TYPE_CLASS + end + + def add(s,p,o) + @triples << "#{s} #{p} #{o}.\n".gsub(/\[/,'<').gsub(/\]/,'>') + end + + # this is (a recursiv method) to set nested-data via hashes (not only simple properties) + # example (for a dataset) + # { :description => "bla", + # :dataEntry => { :compound => "compound_uri", + # :values => [ { :class => "FeatureValue" + # :feature => "feat1", + # :value => 42 }, + # { :class => "FeatureValue" + # :feature => "feat2", + # :value => 123 } ] } } + def set_data(hash, current_node=@root_node) + + hash.each do |k,v| + if v.is_a?(Hash) + # value is again a hash + prop = k.to_s + + # :class is a special key to specify the class value, if not defined in OBJECT_PROPERTY_CLASS + object_class = v.has_key?(:class) ? v.delete(:class) : OBJECT_PROPERTY_CLASS[prop] + raise "hash key must be a object-property, please add '"+prop.to_s+ + "' to OpenTox::OWL.OBJECT_PROPERTY_CLASS or specify :class value" unless object_class + + # the new node is a class node, to specify the uri of the resource use key :uri + if v[:uri] + # identifier is either a specified uri + class_node = Redland::Resource.new(v.delete(:uri)) + else + # or a new uri, make up internal uri with increment + class_node = new_class_node(object_class,current_node) + end + set_object_property(prop,class_node,object_class,current_node) + # recursivly call set_data method with new node + set_data(v,class_node) + elsif v.is_a?(Array) + # value is an array, each array element is added with current key as predicate + v.each do |value| + set_data( { k => value }, current_node ) + end + else + # neither hash nor array, call simple set-method + set( k, v, current_node ) + end + end + end + + # create a new (internal class) node with unique, uri-like name + def new_class_node(name, current_node=@root_node) + # to avoid anonymous nodes, make up uris for sub-objects + # use counter to make sure each uri is unique + # for example we will get ../confusion_matrix_cell/1, ../confusion_matrix_cell/2, ... + count = 1 + while (true) + res = Redland::Resource.new( File.join(current_node.uri.to_s,name.to_s,count.to_s) ) + match = false + @model.find(nil, nil, res) do |s,p,o| + match = true + break + end + if match + count += 1 + else + break + end + end + return res + end + + # for "backwards-compatiblity" + # better use directly: + # set_data( { "parameters" => [ { "title" => , "paramScope" => , "paramValue" => } ] ) + def parameters=(params) + + converted_params = [] + params.each do |name, settings| + converted_params << { :title => name, :paramScope => settings[:scope], :paramValue => settings[:value] } + end + set_data( :parameters => converted_params ) + end +=end + + # PENDING move to dataset.rb + # this is for dataset.to_owl + # adds feautre value for a single compound + def add_data_entries(compound_uri,features) + + data_entry = { :compound => compound_uri } + if features + feature_values = [] + features.each do |f| + f.each do |feature_uri,value| + if value.is_a?(Hash) + complex_values = [] + value.each do |uri,v| + complex_values << { :feature => uri, :value => v } + end + feature_values << { :class => "Tuple", :feature => feature_uri, :complexValue => complex_values } + else + feature_values << { :class => "FeatureValue", :feature => feature_uri, :value => value } + end + end + end + data_entry[:values] = feature_values + end + set_data( :dataEntry => data_entry ) + end + + # PENDING move to dataset.rb + # feature values are not loaded for performance reasons + # loading compounds and features into arrays that are given as params + def load_dataset( compounds, features ) + + @model.subjects(RDF_TYPE, node('Compound')).each do |compound| + compounds << get_value(compound) + end + + @model.subjects(RDF_TYPE, node('Feature')).each do |feature| + feature_value_found=false + @model.find(nil, node("feature"), feature) do |potential_feature_value,p,o| + @model.find(nil, node("values"), potential_feature_value) do |s,p,o| + feature_value_found=true + break + end + break if feature_value_found + end + features << get_value(feature) if feature_value_found + end + LOGGER.debug "loaded "+compounds.size.to_s+" compounds and "+features.size.to_s+" features from dataset "+uri.to_s + end + + # PENDING move to dataset.rb + # loading feature values for the specified feature + # if feature is nil, all feature values are loaded + # + # general remark on the rdf loading (found out with some testing): + # the search methods (subjects/find) are fast, the time consuming parts is creating resources, + # which cannot be avoided in general + def load_dataset_feature_values( compounds, data, feature_uris ) + + raise "no feature-uri array" unless feature_uris.is_a?(Array) + + # values are stored in the data-hash, hash has a key for each compound + compounds.each{|c| data[c] = [] unless data[c]} + + count = 0 + + feature_uris.each do |feature_uri| + LOGGER.debug("load feature values for feature: "+feature_uri ) + feature_node = Redland::Resource.new(feature_uri) + + # search for all feature_value_node with property 'ot_feature' and the feature we are looking for + @model.find(nil, node('feature'), feature_node) do |feature_value_node,p,o| + + # get compound_uri by "backtracking" to values node (property is 'values'), then get compound_node via 'compound' + value_nodes = @model.subjects(node('values'),feature_value_node) + if value_nodes.size>0 + raise "more than one value node "+value_nodes.size.to_s if value_nodes.size>1 + value_node = value_nodes[0] + + compound_uri = get_value( @model.object(value_node, node('compound')) ) + unless compound_uri + LOGGER.warn "'compound' missing for data-entry of feature "+feature_uri.to_s+ + ", value: "+@model.object(feature_value_node,node("value")).to_s + next + end + + value_node_type = @model.object(feature_value_node, RDF_TYPE) + if (value_node_type == node('FeatureValue')) + value_literal = @model.object( feature_value_node, node('value')) + raise "plain feature value no literal: "+value_literal.to_s unless value_literal.is_a?(Redland::Literal) + data[compound_uri] << {feature_uri => value_literal.get_value } + elsif (value_node_type == node('Tuple')) + complex_values = {} + @model.find(feature_value_node,node('complexValue'),nil) do |p,s,complex_value| + complex_value_type = @model.object(complex_value, RDF_TYPE) + raise "complex feature value no feature value: "+complex_value.to_s unless complex_value_type==node('FeatureValue') + complex_feature_uri = get_value(@model.object( complex_value, node('feature'))) + complex_value = @model.object( complex_value, node('value')) + raise "complex value no literal: "+complex_value.to_s unless complex_value.is_a?(Redland::Literal) + complex_values[ complex_feature_uri ] = complex_value.get_value + end + data[compound_uri] << { feature_uri => complex_values } if complex_values.size>0 + end + count += 1 + LOGGER.debug "loading feature values ("+count.to_s+")" if (count%1000 == 0) + end + end + LOGGER.debug "loaded "+count.to_s+" feature values for feature "+feature_node.to_s + end + end + end +end diff --git a/lib/owl.rb b/lib/owl.rb index 08041b6..f4128ee 100644 --- a/lib/owl.rb +++ b/lib/owl.rb @@ -271,7 +271,12 @@ module OpenTox #output = RDF::Writer.for(:rdfxml).buffer do |writer| RDF::Writer.for(:rdfxml).buffer do |writer| @triples.each do |statement| + begin writer << statement + rescue => e + LOGGER.error e + LOGGER.info statement.inspect + end end end #output @@ -392,7 +397,7 @@ module OpenTox def add(s,p,o) #@triples << "#{s} #{p} #{o}.\n".gsub(/\[/,'<').gsub(/\]/,'>') - @triples << [s.to_s.sub(/\[/,'').sub(/\]/,''),p.to_s.sub(/\[/,'').sub(/\]/,''),o.to_s.sub(/\[/,'').sub(/\]/,'')] + @triples << [RDF::URI.new(s.to_s.sub(/\[/,'').sub(/\]/,'')),RDF::URI.new(p.to_s.sub(/\[/,'').sub(/\]/,'')),o.to_s.sub(/\[/,'').sub(/\]/,'')] #@model.add s,p,o end -- cgit v1.2.3 From 3dd19c461d0b205ff504a85785f0c6e55114cd4e Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Tue, 14 Sep 2010 20:52:03 +0200 Subject: simplified version of OwlSerializer, RDF/XML for algorithm/lazar, RDF/XML for annotations,compounds, features in dataset --- lib/environment.rb | 4 +- lib/owl-serializer.rb | 310 ++++----------------------------------- opentox-ruby-api-wrapper.gemspec | 9 +- 3 files changed, 39 insertions(+), 284 deletions(-) diff --git a/lib/environment.rb b/lib/environment.rb index c662e8b..0c62113 100644 --- a/lib/environment.rb +++ b/lib/environment.rb @@ -59,5 +59,5 @@ TRUE_REGEXP = /^(true|active|1|1.0)$/i FALSE_REGEXP = /^(false|inactive|0|0.0)$/i # Task durations -DEFAULT_TASK_MAX_DURATION = 3600 -EXTERNAL_TASK_MAX_DURATION = 3600 +DEFAULT_TASK_MAX_DURATION = 36000 +EXTERNAL_TASK_MAX_DURATION = 36000 diff --git a/lib/owl-serializer.rb b/lib/owl-serializer.rb index 8257b78..8965bf2 100644 --- a/lib/owl-serializer.rb +++ b/lib/owl-serializer.rb @@ -9,304 +9,56 @@ OT = RDF::Vocabulary.new 'http://www.opentox.org/api/1.1#' module OpenTox class OwlSerializer - - # to get correct owl-dl, properties and objects have to be typed - # i.e. the following triple is insufficient: - # ModelXY,ot:algorithm,AlgorithmXY - # further needed: - # ot:algorithm,rdf:type,owl:ObjectProperty - # AlgorithmXY,rdf:type,ot:Algorithm - # ot:Algorithm,rdf:type,owl:Class DONE - attr_accessor :model def initialize(klass,uri) + @model = RDF::Graph.new(uri) - @model << [ RDF::URI.new(uri), RDF.type, OT[klass] ] - @model << [ OT[klass], RDF.type, OWL.Class ] - # add class statements from OT -=begin - RDF::Reader.open('http://www.opentox.org/api/1.1#', :format => :rdfxml).each_statement do |statement| - @model << statement if statement.predicate == RDF.type #and statement.object == OWL.class - end -=end - end - # build new owl object - # klass is the class of this object, should be a string like "Model", "Task", ... - # uri is name and identifier of this object + @triples = [] + @triples << [ OT[klass], RDF.type, OWL.Class ] + @triples << [ RDF::URI.new(uri), RDF.type, OT[klass] ] + + @classes = [ OT[klass] ] + @object_properties = [] + @annotation_properties = [] + @objects = [ uri ] + + end - def self.create( klass, uri ) + def self.create(klass, uri) OpenTox::OwlSerializer.new(klass,uri) end def rdf + @triples.each { |statement| @model << statement } RDF::Writer.for(:rdfxml).buffer do |writer| writer << @model - #@model.each do |statement| - #writer << statement - #end - end - end - - # sets values of current_node (by default root_node) - # - # note: this does not delete existing triples - # * there can be several triples for the same subject and predicate - # ( e.g. after set("description","bla1") and set("description","bla2") - # both descriptions are in the model, - # but the get("description") will give you only one object (by chance) - # * this does not matter in pratice (only dataset uses this -> load_dataset-methods) - # * identical values appear only once in rdf - def annotate(predicate, object) - @model << [ @model.to_uri, DC[predicate], RDF::Literal.new(object, :datatype => XSD.String) ] - @model << [ DC[predicate], RDF.type, OWL.AnnotationProperty ] - end -=begin - def set(predicate, object, current_node=@root_node ) - - pred = predicate.to_s - raise "uri is no prop, cannot set uri" if pred=="uri" - raise "dc[identifier] deprecated, use owl.uri" if pred=="identifier" - if (object.is_a?(Redland::Node) and object.blank?) or nil==object or object.to_s.size==0 - # set only not-nil values - LOGGER.warn "skipping (not setting) empty value in rdf for property: '"+pred+"'" - return - end - - if pred=="type" - # predicate is type, set class of current node - set_type(object, current_node) - elsif LITERAL_TYPES.has_key?(pred) - # predicate is literal - set_literal(pred,object,LITERAL_TYPES[pred],current_node) - elsif OBJECT_PROPERTY_CLASS.has_key?(pred) - # predicte is objectProperty, object is another resource - set_object_property(pred,object,OBJECT_PROPERTY_CLASS[pred],current_node) - else - raise "unkonwn rdf-property, please add: '"+pred+"' to OpenTox::OWL.OBJECT_PROPERTY_CLASS or OpenTox::OWL.LITERAL_TYPES" - end - end - - # example-triples for setting rdf-type to model: - # model_xy,rdf:type,ot:Model - # ot:Model,rdf:type,owl:Class - def set_type(ot_class, current_node=@root_node) - add current_node, RDF.type, node(ot_class) - add node(ot_class), RDF_TYPE, OWL_TYPE_CLASS - end - - # example-triples for setting description of a model: - # model_xy,ot:description,bla..bla^^xml:string - # ot:description,rdf:type,owl:Literal - def set_literal(literal_name, literal_value, literal_datatype, current_node=@root_node) - add current_node, node(literal_name), literal_value# TODO add literal_datatype - add node(literal_name), RDF_TYPE, OWL_TYPE_LITERAL - end - - # example-triples for setting algorithm property of a model: - # model_xy,ot:algorithm,algorihtm_xy - # ot:algorithm,rdf:type,owl:ObjectProperty - # algorihtm_xy,rdf:type,ot:Algorithm - # ot:Algorithm,rdf:type,owl:Class - def set_object_property(property, object, object_class, current_node=@root_node) - object_node = Redland::Resource.new(object) - add current_node, node(property), object_node - add node(property), RDF_TYPE, OWL_TYPE_OBJECT_PROPERTY - add object_node, RDF_TYPE, node(object_class) - add node(object_class), RDF_TYPE, OWL_TYPE_CLASS - end - - def add(s,p,o) - @triples << "#{s} #{p} #{o}.\n".gsub(/\[/,'<').gsub(/\]/,'>') - end - - # this is (a recursiv method) to set nested-data via hashes (not only simple properties) - # example (for a dataset) - # { :description => "bla", - # :dataEntry => { :compound => "compound_uri", - # :values => [ { :class => "FeatureValue" - # :feature => "feat1", - # :value => 42 }, - # { :class => "FeatureValue" - # :feature => "feat2", - # :value => 123 } ] } } - def set_data(hash, current_node=@root_node) - - hash.each do |k,v| - if v.is_a?(Hash) - # value is again a hash - prop = k.to_s - - # :class is a special key to specify the class value, if not defined in OBJECT_PROPERTY_CLASS - object_class = v.has_key?(:class) ? v.delete(:class) : OBJECT_PROPERTY_CLASS[prop] - raise "hash key must be a object-property, please add '"+prop.to_s+ - "' to OpenTox::OWL.OBJECT_PROPERTY_CLASS or specify :class value" unless object_class - - # the new node is a class node, to specify the uri of the resource use key :uri - if v[:uri] - # identifier is either a specified uri - class_node = Redland::Resource.new(v.delete(:uri)) - else - # or a new uri, make up internal uri with increment - class_node = new_class_node(object_class,current_node) - end - set_object_property(prop,class_node,object_class,current_node) - # recursivly call set_data method with new node - set_data(v,class_node) - elsif v.is_a?(Array) - # value is an array, each array element is added with current key as predicate - v.each do |value| - set_data( { k => value }, current_node ) - end - else - # neither hash nor array, call simple set-method - set( k, v, current_node ) - end end end - - # create a new (internal class) node with unique, uri-like name - def new_class_node(name, current_node=@root_node) - # to avoid anonymous nodes, make up uris for sub-objects - # use counter to make sure each uri is unique - # for example we will get ../confusion_matrix_cell/1, ../confusion_matrix_cell/2, ... - count = 1 - while (true) - res = Redland::Resource.new( File.join(current_node.uri.to_s,name.to_s,count.to_s) ) - match = false - @model.find(nil, nil, res) do |s,p,o| - match = true - break - end - if match - count += 1 - else - break - end - end - return res - end - # for "backwards-compatiblity" - # better use directly: - # set_data( { "parameters" => [ { "title" => , "paramScope" => , "paramValue" => } ] ) - def parameters=(params) - - converted_params = [] - params.each do |name, settings| - converted_params << { :title => name, :paramScope => settings[:scope], :paramValue => settings[:value] } + def object_property(subject,predicate,object,object_class) + s = [ RDF::URI.new(subject), predicate, RDF::URI.new(object) ] # + @triples << s unless @triples.include? s + unless @object_properties.include? predicate + @triples << [ predicate, RDF.type, OWL.ObjectProperty ] + @object_properties << predicate end - set_data( :parameters => converted_params ) - end -=end - - # PENDING move to dataset.rb - # this is for dataset.to_owl - # adds feautre value for a single compound - def add_data_entries(compound_uri,features) - - data_entry = { :compound => compound_uri } - if features - feature_values = [] - features.each do |f| - f.each do |feature_uri,value| - if value.is_a?(Hash) - complex_values = [] - value.each do |uri,v| - complex_values << { :feature => uri, :value => v } - end - feature_values << { :class => "Tuple", :feature => feature_uri, :complexValue => complex_values } - else - feature_values << { :class => "FeatureValue", :feature => feature_uri, :value => value } - end - end - end - data_entry[:values] = feature_values - end - set_data( :dataEntry => data_entry ) - end - - # PENDING move to dataset.rb - # feature values are not loaded for performance reasons - # loading compounds and features into arrays that are given as params - def load_dataset( compounds, features ) - - @model.subjects(RDF_TYPE, node('Compound')).each do |compound| - compounds << get_value(compound) + unless @objects.include? object + @triples << [ RDF::URI.new(object), RDF.type, object_class ] + @objects << object end - - @model.subjects(RDF_TYPE, node('Feature')).each do |feature| - feature_value_found=false - @model.find(nil, node("feature"), feature) do |potential_feature_value,p,o| - @model.find(nil, node("values"), potential_feature_value) do |s,p,o| - feature_value_found=true - break - end - break if feature_value_found - end - features << get_value(feature) if feature_value_found + unless @classes.include? object_class + @triples << [ object_class, RDF.type, OWL.Class ] + @classes << object_class end - LOGGER.debug "loaded "+compounds.size.to_s+" compounds and "+features.size.to_s+" features from dataset "+uri.to_s end - - # PENDING move to dataset.rb - # loading feature values for the specified feature - # if feature is nil, all feature values are loaded - # - # general remark on the rdf loading (found out with some testing): - # the search methods (subjects/find) are fast, the time consuming parts is creating resources, - # which cannot be avoided in general - def load_dataset_feature_values( compounds, data, feature_uris ) - - raise "no feature-uri array" unless feature_uris.is_a?(Array) - - # values are stored in the data-hash, hash has a key for each compound - compounds.each{|c| data[c] = [] unless data[c]} - - count = 0 - feature_uris.each do |feature_uri| - LOGGER.debug("load feature values for feature: "+feature_uri ) - feature_node = Redland::Resource.new(feature_uri) - - # search for all feature_value_node with property 'ot_feature' and the feature we are looking for - @model.find(nil, node('feature'), feature_node) do |feature_value_node,p,o| - - # get compound_uri by "backtracking" to values node (property is 'values'), then get compound_node via 'compound' - value_nodes = @model.subjects(node('values'),feature_value_node) - if value_nodes.size>0 - raise "more than one value node "+value_nodes.size.to_s if value_nodes.size>1 - value_node = value_nodes[0] - - compound_uri = get_value( @model.object(value_node, node('compound')) ) - unless compound_uri - LOGGER.warn "'compound' missing for data-entry of feature "+feature_uri.to_s+ - ", value: "+@model.object(feature_value_node,node("value")).to_s - next - end - - value_node_type = @model.object(feature_value_node, RDF_TYPE) - if (value_node_type == node('FeatureValue')) - value_literal = @model.object( feature_value_node, node('value')) - raise "plain feature value no literal: "+value_literal.to_s unless value_literal.is_a?(Redland::Literal) - data[compound_uri] << {feature_uri => value_literal.get_value } - elsif (value_node_type == node('Tuple')) - complex_values = {} - @model.find(feature_value_node,node('complexValue'),nil) do |p,s,complex_value| - complex_value_type = @model.object(complex_value, RDF_TYPE) - raise "complex feature value no feature value: "+complex_value.to_s unless complex_value_type==node('FeatureValue') - complex_feature_uri = get_value(@model.object( complex_value, node('feature'))) - complex_value = @model.object( complex_value, node('value')) - raise "complex value no literal: "+complex_value.to_s unless complex_value.is_a?(Redland::Literal) - complex_values[ complex_feature_uri ] = complex_value.get_value - end - data[compound_uri] << { feature_uri => complex_values } if complex_values.size>0 - end - count += 1 - LOGGER.debug "loading feature values ("+count.to_s+")" if (count%1000 == 0) - end - end - LOGGER.debug "loaded "+count.to_s+" feature values for feature "+feature_node.to_s + def annotation_property(subject, predicate, value, datatype) + s = [ RDF::URI.new(subject), predicate, RDF::Literal.new(value, :datatype => datatype) ] + @triples << s unless @triples.include? s + unless @annotation_properties.include? predicate + @triples << [ predicate, RDF.type, OWL.AnnotationProperty ] + @annotation_properties << predicate end end end diff --git a/opentox-ruby-api-wrapper.gemspec b/opentox-ruby-api-wrapper.gemspec index 8217240..58f0b46 100644 --- a/opentox-ruby-api-wrapper.gemspec +++ b/opentox-ruby-api-wrapper.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Christoph Helma, Martin Guetlein"] - s.date = %q{2010-08-25} + s.date = %q{2010-09-14} s.description = %q{Ruby wrapper for the OpenTox REST API (http://www.opentox.org)} s.email = %q{helma@in-silico.ch} s.executables = ["opentox-install-ubuntu.sh", "yaml2owl.rb", "opentox-install-debian.sh"] @@ -26,7 +26,6 @@ Gem::Specification.new do |s| "bin/opentox-install-ubuntu.sh", "bin/yaml2owl.rb", "lib/algorithm.rb", - "lib/authorization.rb", "lib/compound.rb", "lib/config/config_ru.rb", "lib/dataset.rb", @@ -38,12 +37,16 @@ Gem::Specification.new do |s| "lib/opentox.owl", "lib/ot-logger.rb", "lib/overwrite.rb", + "lib/owl-serializer.rb", "lib/owl.rb", + "lib/owl.rb.RDF", + "lib/owl.rb.nt", + "lib/owl.rb.rdfxml.initial", + "lib/owl.rb.redland", "lib/rest_client_wrapper.rb", "lib/spork.rb", "lib/task.rb", "lib/templates/config.yaml", - "lib/templates/users.yaml", "lib/utils.rb", "lib/validation.rb" ] -- cgit v1.2.3 From d6811507c1c1339cc4fe7cdb429b9b34b97dc422 Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Fri, 22 Oct 2010 17:45:19 +0200 Subject: new API with support for external services (initial version) --- Rakefile | 2 + lib/algorithm.rb | 41 ++- lib/compound.rb | 184 ++++++++----- lib/dataset.rb | 273 ++++++++++++++++-- lib/environment.rb | 20 +- lib/feature.rb | 7 + lib/features.rb | 19 -- lib/model.rb | 16 +- lib/opentox-ruby-api-wrapper.rb | 4 +- lib/opentox.rb | 79 ++++++ lib/owl-serializer.rb | 65 ----- lib/owl.rb | 593 ---------------------------------------- lib/parser.rb | 191 +++++++++++++ lib/serializer.rb | 297 ++++++++++++++++++++ lib/task.rb | 8 +- lib/validation.rb | 4 +- 16 files changed, 981 insertions(+), 822 deletions(-) create mode 100644 lib/feature.rb delete mode 100644 lib/features.rb create mode 100644 lib/opentox.rb delete mode 100644 lib/owl-serializer.rb delete mode 100644 lib/owl.rb create mode 100644 lib/parser.rb create mode 100644 lib/serializer.rb diff --git a/Rakefile b/Rakefile index 3846bd1..8dd1088 100644 --- a/Rakefile +++ b/Rakefile @@ -27,6 +27,8 @@ begin "google-spreadsheet-ruby", "tmail", "rinruby", + "rdf", + "rdf-raptor", "rjb" ].each { |dep| gem.add_dependency dep } [ "dm-core", diff --git a/lib/algorithm.rb b/lib/algorithm.rb index 4d9156a..e1d369a 100644 --- a/lib/algorithm.rb +++ b/lib/algorithm.rb @@ -1,27 +1,22 @@ - module OpenTox + module Algorithm - - - class Generic - - attr_accessor :uri, :title, :date - - def self.find(uri) - owl = OpenTox::Owl.from_uri(uri, "Algorithm") - return self.new(owl) - end - + + include OtObject + + class Generic + include Algorithm + #include OtObject protected - def initialize(owl) - @title = owl.get("title") - @date = owl.get("date") - @uri = owl.uri - end +# def initialize(owl) +# @title = owl.get("title") +# @date = owl.get("date") +# @uri = owl.uri +# end end - class Fminer + class Fminer < Generic def self.create_feature_dataset(params) LOGGER.debug File.basename(__FILE__) + ": creating feature dataset" @@ -30,7 +25,7 @@ module OpenTox end def self.uri - File.join(@@config[:services]["opentox-algorithm"], "fminer") + File.join(CONFIG[:services]["opentox-algorithm"], "fminer") end end @@ -39,13 +34,13 @@ module OpenTox def self.create_model(params) LOGGER.debug params LOGGER.debug File.basename(__FILE__) + ": creating model" - LOGGER.debug File.join(@@config[:services]["opentox-algorithm"], "lazar") - resource = RestClient::Resource.new(File.join(@@config[:services]["opentox-algorithm"], "lazar"), :content_type => "application/x-yaml") - @uri = resource.post(:dataset_uri => params[:dataset_uri], :prediction_feature => params[:prediction_feature], :feature_generation_uri => File.join(@@config[:services]["opentox-algorithm"], "fminer")).body.chomp + LOGGER.debug File.join(CONFIG[:services]["opentox-algorithm"], "lazar") + resource = RestClient::Resource.new(File.join(CONFIG[:services]["opentox-algorithm"], "lazar"), :content_type => "application/x-yaml") + @uri = resource.post(:dataset_uri => params[:dataset_uri], :prediction_feature => params[:prediction_feature], :feature_generation_uri => File.join(CONFIG[:services]["opentox-algorithm"], "fminer")).body.chomp end def self.uri - File.join(@@config[:services]["opentox-algorithm"], "lazar") + File.join(CONFIG[:services]["opentox-algorithm"], "lazar") end end diff --git a/lib/compound.rb b/lib/compound.rb index 49c166f..699e4c1 100644 --- a/lib/compound.rb +++ b/lib/compound.rb @@ -3,82 +3,120 @@ module OpenTox - class Compound #< OpenTox - - attr_reader :inchi, :uri - - # Initialize with :uri => uri, :smiles => smiles or :name => name (name can be also an InChI/InChiKey, CAS number, etc) - def initialize(params) - if params[:smiles] - @inchi = smiles2inchi(params[:smiles]) - @uri = File.join(@@config[:services]["opentox-compound"],URI.escape(@inchi)) - elsif params[:inchi] - @inchi = params[:inchi] - @uri = File.join(@@config[:services]["opentox-compound"],URI.escape(@inchi)) - elsif params[:sdf] - @inchi = sdf2inchi(params[:sdf]) - @uri = File.join(@@config[:services]["opentox-compound"],URI.escape(@inchi)) - elsif params[:name] - # paranoid URI encoding to keep SMILES charges and brackets - @inchi = RestClient.get("#{@@cactus_uri}#{URI.encode(params[:name], Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))}/stdinchi").body.chomp - # this was too hard for me to debug and leads to additional errors (ch) - #@inchi = RestClientWrapper.get("#{@@cactus_uri}#{URI.encode(params[:name], Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))}/stdinchi").chomp - @uri = File.join(@@config[:services]["opentox-compound"],URI.escape(@inchi)) - elsif params[:uri] - @uri = params[:uri] - case params[:uri] - when /ambit/ # Ambit does not deliver InChIs reliably - smiles = RestClientWrapper.get @uri, :accept => 'chemical/x-daylight-smiles' - @inchi = obconversion(smiles,'smi','inchi') - when /InChI/ # shortcut for IST services - @inchi = params[:uri].sub(/^.*InChI/, 'InChI') - else - @inchi = RestClientWrapper.get @uri, :accept => 'chemical/x-inchi' - end - end - end + # Ruby wrapper for OpenTox Compound Webservices (http://opentox.org/dev/apis/api-1.2/structure). + # + # Examples: + # require "opentox-ruby-api-wrapper" + # + # # Creating compounds + # + # # from smiles string + # compound = OpenTox::Compound.from_smiles("c1ccccc1") + # # from name + # compound = OpenTox::Compound.from_name("Benzene") + # # from uri + # compound = OpenTox::Compound.new("http://webservices.in-silico.ch/compound/InChI=1S/C6H6/c1-2-4-6-5-3-1/h1-6H"") + # + # # Getting compound representations + # + # # get InChI + # inchi = compound.inchi + # # get all compound names + # names = compound.names + # # get png image + # image = compound.png + # # get uri + # uri = compound.uri + # + # # SMARTS matching + # + # # match a smarts string + # compound.match?("cN") # returns false + # # match an array of smarts strings + # compound.match(['cc','cN']) # returns ['cc'] + class Compound + + attr_accessor :inchi, :uri + + # Create compound with optional uri + def initialize(uri=nil) + @uri = uri + case @uri + when /InChI/ # shortcut for IST services + @inchi = @uri.sub(/^.*InChI/, 'InChI') + else + @inchi = RestClientWrapper.get(@uri, :accept => 'chemical/x-inchi').to_s.chomp if @uri + end + end - # Get the (canonical) smiles + # Create a compound from smiles string + def self.from_smiles(smiles) + c = Compound.new + c.inchi = Compound.smiles2inchi(smiles) + c.uri = File.join(CONFIG[:services]["opentox-compound"],URI.escape(c.inchi)) + c + end + + # Create a compound from inchi string + def self.from_inchi(inchi) + c = Compound.new + c.inchi = inchi + c.uri = File.join(CONFIG[:services]["opentox-compound"],URI.escape(c.inchi)) + c + end + + # Create a compound from sdf string + def self.from_sdf(sdf) + c = Compound.new + c.inchi = Compound.sdf2inchi(sdf) + c.uri = File.join(CONFIG[:services]["opentox-compound"],URI.escape(c.inchi)) + c + end + + # Create a compound from name (name can be also an InChI/InChiKey, CAS number, etc) + def self.from_name(name) + c = Compound.new + # paranoid URI encoding to keep SMILES charges and brackets + c.inchi = RestClientWrapper.get("#{@@cactus_uri}#{URI.encode(name, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))}/stdinchi").to_s.chomp + c.uri = File.join(CONFIG[:services]["opentox-compound"],URI.escape(c.inchi)) + c + end + + # Get (canonical) smiles def smiles - obconversion(@inchi,'inchi','can') + Compound.obconversion(@inchi,'inchi','can') end + # Get sdf def sdf - obconversion(@inchi,'inchi','sdf') + Compound.obconversion(@inchi,'inchi','sdf') end + # Get gif image def gif RestClientWrapper.get("#{@@cactus_uri}#{@inchi}/image") end + # Get png image def png RestClientWrapper.get(File.join @uri, "image") end + # Get URI of compound image + def image_uri + File.join @uri, "image" + end + + # Get all known compound names def names begin - RestClientWrapper.get("#{@@cactus_uri}#{@inchi}/names") + RestClientWrapper.get("#{@@cactus_uri}#{@inchi}/names").split("\n") rescue "not available" end end - def display_smarts_uri(activating, deactivating, highlight = nil) - LOGGER.debug activating.to_yaml unless activating.nil? - activating_smarts = URI.encode "\"#{activating.join("\"/\"")}\"" - deactivating_smarts = URI.encode "\"#{deactivating.join("\"/\"")}\"" - if highlight.nil? - File.join @@config[:services]["opentox-compound"], "smiles", URI.encode(smiles), "smarts/activating", URI.encode(activating_smarts),"deactivating", URI.encode(deactivating_smarts) - else - File.join @@config[:services]["opentox-compound"], "smiles", URI.encode(smiles), "smarts/activating", URI.encode(activating_smarts),"deactivating", URI.encode(deactivating_smarts), "highlight", URI.encode(highlight) - end - end - - def image_uri - File.join @uri, "image" - end - - # Matchs a smarts string + # Match a smarts string def match?(smarts) obconversion = OpenBabel::OBConversion.new obmol = OpenBabel::OBMol.new @@ -89,30 +127,42 @@ module OpenTox smarts_pattern.match(obmol) end - # Match an array of smarts features, returns matching features + # Match an array of smarts strings, returns array with matching smarts def match(smarts_array) smarts_array.collect{|s| s if match?(s)}.compact end - # AM - # Match an array of smarts features, returns (0)1 for (non)matching features at each pos - def match_all(smarts_array) - smarts_array.collect{|s| match?(s) ? 1 : 0 } - end + # Get URI of compound image with highlighted fragments + def matching_smarts_image_uri(activating, deactivating, highlight = nil) + activating_smarts = URI.encode "\"#{activating.join("\"/\"")}\"" + deactivating_smarts = URI.encode "\"#{deactivating.join("\"/\"")}\"" + if highlight.nil? + File.join CONFIG[:services]["opentox-compound"], "smiles", URI.encode(smiles), "smarts/activating", URI.encode(activating_smarts),"deactivating", URI.encode(deactivating_smarts) + else + File.join CONFIG[:services]["opentox-compound"], "smiles", URI.encode(smiles), "smarts/activating", URI.encode(activating_smarts),"deactivating", URI.encode(deactivating_smarts), "highlight", URI.encode(highlight) + end + end + + + private - def sdf2inchi(sdf) - obconversion(sdf,'sdf','inchi') + # Convert sdf to inchi + def self.sdf2inchi(sdf) + Compound.obconversion(sdf,'sdf','inchi') end - def smiles2inchi(smiles) - obconversion(smiles,'smi','inchi') + # Convert smiles to inchi + def self.smiles2inchi(smiles) + Compound.obconversion(smiles,'smi','inchi') end - def smiles2cansmi(smiles) - obconversion(smiles,'smi','can') + # Convert smiles to canonical smiles + def self.smiles2cansmi(smiles) + Compound.obconversion(smiles,'smi','can') end - def obconversion(identifier,input_format,output_format) + # Convert identifier from OpenBabel input_format to OpenBabel output_format + def self.obconversion(identifier,input_format,output_format) obconversion = OpenBabel::OBConversion.new obmol = OpenBabel::OBMol.new obconversion.set_in_and_out_formats input_format, output_format diff --git a/lib/dataset.rb b/lib/dataset.rb index 2eb2206..7c8ce24 100644 --- a/lib/dataset.rb +++ b/lib/dataset.rb @@ -1,35 +1,253 @@ module OpenTox + # Ruby wrapper for OpenTox Dataset Webservices (http://opentox.org/dev/apis/api-1.2/dataset). + # + # Examples: + # require "opentox-ruby-api-wrapper" + # + # # Creating datasets + # + # # create an empty dataset + # dataset = OpenTox::Dataset.new + # # create an empty dataset with URI + # # this does not load data from the dataset service - use one of the load_* methods + # dataset = OpenTox::Dataset.new("http:://webservices.in-silico/ch/dataset/1") + # # create new dataset and sav it to obtain a URI + # dataset = OpenTox::Dataset.create + # # create a new dataset from yaml representation + # dataset = OpenTox::Dataset.from_yaml + # # create a new dataset from CSV string + # csv_string = "SMILES, Toxicity\nc1ccccc1N, true" + # dataset = OpenTox::Dataset.from_csv(csv_string) + # + # # Loading data + # # Datasets created with OpenTox::Dataset.new(uri) are empty by default + # # Invoking one of the following functions will load data into the object + # + # # create an empty dataset with URI + # dataset = OpenTox::Dataset.new("http:://webservices.in-silico/ch/dataset/1") + # # loads (and returns) only metadata + # dataset.load_metadata + # # loads (and returns) only compounds + # dataset.load_compounds + # # loads (and returns) only features + # dataset.load_features + # # load all data from URI + # dataset.load_all + # + # # Getting dataset representations + # + # dataset = OpenTox::Dataset.new("http:://webservices.in-silico/ch/dataset/1") + # dataset.load_all + # # OWL-DL (RDF/XML) + # dataset.rdfxml + # # OWL-DL (Ntriples) + # dataset.ntriples + # # YAML + # dataset.yaml + # # CSV + # dataset.csv + # + # # Modifying datasets + # + # # insert a statement (compound_uri,feature_uri,value) + # dataset.add "http://webservices.in-silico.ch/compound/InChI=1S/C6Cl6/c7-1-2(8)4(10)6(12)5(11)3(1)9", "http://webservices.in-silico.ch/dataset/1/feature/hamster_carcinogenicity", true + # + # + # # Saving datasets + # # save dataset at dataset service + # dataset.save + # + # # Deleting datasets + # # delete dataset (also at dataset service) + # dataset.delete class Dataset - attr_accessor :uri, :title, :creator, :data, :features, :compounds + include OtObject - def initialize( owl=nil ) - @data = {} - @features = [] + attr_reader :features, :compounds, :data_entries, :metadata + attr_writer :metadata + + # Create dataset with optional URI + def initialize(uri=nil) + super uri + @features = {} @compounds = [] - + @data_entries = {} + end + + # Create and save an empty dataset (assigns URI to dataset) + def self.create(uri=CONFIG[:services]["opentox-dataset"]) + dataset = Dataset.new + dataset.uri = RestClientWrapper.post(uri,{}).to_s.chomp + dataset + end + + # Get all datasets from a service +# def self.all(uri=CONFIG[:services]["opentox-dataset"]) +# RestClientWrapper.get(uri,:accept => "text/uri-list").to_s.each_line.collect{|u| Dataset.new(u)} +# end + + # Create a dataset from YAML string + def self.from_yaml(yaml) + dataset = Dataset.create + dataset.copy YAML.load(yaml) + dataset + end + + # Create dataset from CSV string (format specification: http://toxcreate.org/help) + # - loads data_entries, compounds, features + # - sets metadata (warnings) for parser errors + # - you will have to set remaining metadata manually + def self.from_csv(csv) + dataset = Dataset.create + Parser::Spreadsheet.new(dataset).load_csv(csv) + dataset + end + + # Create dataset from Spreadsheet book (created with roo gem http://roo.rubyforge.org/, excel format specification: http://toxcreate.org/help)) + # - loads data_entries, compounds, features + # - sets metadata (warnings) for parser errors + # - you will have to set remaining metadata manually + def self.from_spreadsheet(book) + dataset = Dataset.create + Parser::Spreadsheet.new(dataset).load_excel(book) + dataset + end + + # Load and return metadata of a Dataset object + def load_metadata + #if (CONFIG[:yaml_hosts].include?(URI.parse(@uri).host)) + #add_metadata YAML.load(RestClientWrapper.get(File.join(@uri,"metadata"), :accept => "application/x-yaml")) + #else + add_metadata Parser::Owl::Dataset.new(@uri).metadata + #end + self.uri = @uri if @uri # keep uri + @metadata + end + + # Load all data (metadata, data_entries, compounds and features) from URI + def load_all + if (CONFIG[:yaml_hosts].include?(URI.parse(@uri).host)) + copy YAML.load(RestClientWrapper.get(@uri, :accept => "application/x-yaml")) + else + parser = Parser::Owl::Dataset.new(@uri) + copy parser.load_uri + end + end + + # Load and return all compound URIs + def load_compounds + RestClientWrapper.get(File.join(uri,"compounds"),:accept=> "text/uri-list").to_s.each_line do |compound_uri| + @compounds << compound_uri.chomp + end + @compounds.uniq! + end + + # Load all feature URIs + def load_features + RestClientWrapper.get(File.join(uri,"features"),:accept=> "text/uri-list").to_s.each_line do |feature_uri| + @features[feature_uri.chomp] = Feature.new(feature_uri.chomp).load_metadata + end + @features + end + + # Get YAML representation + def yaml + self.to_yaml + end + + # Get Excel representation, returns a Spreadsheet::Workbook which can be written with the 'spreadsheet' gem (data_entries only, metadata will ) + def excel + Serializer::Spreadsheets.new(self).excel + end + + # Get CSV string representation (data_entries only, metadata will be discarded) + def csv + Serializer::Spreadsheets.new(self).csv + end + + # Get OWL-DL in ntriples format + def ntriples + s = Serializer::Owl.new + s.add_dataset(self) + s.ntriples + end + + # Get OWL-DL in RDF/XML format + def rdfxml + s = Serializer::Owl.new + s.add_dataset(self) + s.rdfxml + end + + # Insert a statement (compound_uri,feature_uri,value) + def add (compound,feature,value) + @compounds << compound unless @compounds.include? compound + @features[feature] = {} unless @features[feature] + @data_entries[compound] = {} unless @data_entries[compound] + @data_entries[compound][feature] = [] unless @data_entries[compound][feature] + @data_entries[compound][feature] << value + end + + # Add metadata (hash with predicate_uri => value) + def add_metadata(metadata) + metadata.each { |k,v| @metadata[k] = v } + end + + # Copy a dataset (rewrites URI) + def copy(dataset) + @metadata = dataset.metadata + @data_entries = dataset.data_entries + @compounds = dataset.compounds + @features = dataset.features + if @uri + self.uri = @uri + else + @uri = dataset.metadata[XSD.anyUri] + end + end + + # save dataset (overwrites existing dataset) + def save + # TODO: rewrite feature URI's ?? + # create dataset if uri empty + @compounds.uniq! + RestClientWrapper.post(@uri,{:content_type => "application/x-yaml"},self.to_yaml) + end + + # Delete dataset at the dataset service + def delete + RestClientWrapper.delete @uri + end + end +end + + ######################################################### + # kept for backward compatibility, may have to be fixed # + ######################################################### + +=begin + def from_owl(owl) # creates dataset object from Opentox::Owl object # use Dataset.find( ) to load dataset from rdf-supporting datasetservice # note: does not load all feature values, as this is time consuming - if owl - raise "invalid param" unless owl.is_a?(OpenTox::Owl) - @title = owl.get("title") - @creator = owl.get("creator") - @uri = owl.uri - # when loading a dataset from owl, only compound- and feature-uris are loaded - owl.load_dataset(@compounds, @features) - # all features are marked as dirty - # as soon as a feature-value is requested all values for this feature are loaded from the rdf - @dirty_features = @features.dclone - @owl = owl - end + raise "invalid param" unless owl.is_a?(OpenTox::Owl) + @metadata[DC.title] = owl.get("title") + @metadata[DC.creator] = owl.get("creator") + @metadata[XSD.anyUri] = owl.uri + # when loading a dataset from owl, only compound- and feature-uris are loaded + owl.load_dataset(@compounds, @features) + # all features are marked as dirty + # as soon as a feature-value is requested all values for this feature are loaded from the rdf + @dirty_features = @features.dclone + @owl = owl end def self.find(uri, accept_header=nil) unless accept_header - if (@@config[:yaml_hosts].include?(URI.parse(uri).host)) + if (CONFIG[:yaml_hosts].include?(URI.parse(uri).host)) accept_header = 'application/x-yaml' else accept_header = "application/rdf+xml" @@ -38,8 +256,10 @@ module OpenTox case accept_header when "application/x-yaml" + LOGGER.debug "DATASET: "+ uri + LOGGER.debug RestClientWrapper.get(uri.to_s.strip, :accept => 'application/x-yaml').to_s d = YAML.load RestClientWrapper.get(uri.to_s.strip, :accept => 'application/x-yaml').to_s - d.uri = uri unless d.uri + #d.uri = @metadata[XSD.anyUri] unless d.uri when "application/rdf+xml" owl = OpenTox::Owl.from_uri(uri.to_s.strip, "Dataset") d = Dataset.new(owl) @@ -48,7 +268,7 @@ module OpenTox end d end - + # converts a dataset represented in owl to yaml # (uses a temporary dataset) # note: to_yaml is overwritten, loads complete owl dataset values @@ -108,7 +328,7 @@ module OpenTox raise "predicted class value is an array\n"+ "value "+v.to_s+"\n"+ "value-class "+v.class.to_s+"\n"+ - "dataset "+@uri.to_s+"\n"+ + "dataset "+self.uri.to_s+"\n"+ "compound "+compound.to_s+"\n"+ "feature "+feature.to_s+"\n" else @@ -130,7 +350,7 @@ module OpenTox raise "predicted regression value is an array\n"+ "value "+v.to_s+"\n"+ "value-class "+v.class.to_s+"\n"+ - "dataset "+@uri.to_s+"\n"+ + "dataset "+self.uri.to_s+"\n"+ "compound "+compound.to_s+"\n"+ "feature "+feature.to_s+"\n" else @@ -181,7 +401,7 @@ module OpenTox raise "value is not an array\n"+ "value "+v.to_s+"\n"+ "value-class "+v.class.to_s+"\n"+ - "dataset "+@uri.to_s+"\n"+ + "dataset "+self.uri.to_s+"\n"+ "compound "+compound.to_s+"\n"+ "feature "+feature.to_s+"\n" end @@ -216,11 +436,6 @@ module OpenTox super - ["@owl"] end - # saves (changes) as new dataset in dataset service - # returns uri - # uses to yaml method (which is overwritten) - def save - OpenTox::RestClientWrapper.post(@@config[:services]["opentox-dataset"],{:content_type => "application/x-yaml"},self.to_yaml).strip - end end end +=end diff --git a/lib/environment.rb b/lib/environment.rb index 0c62113..b16b62f 100644 --- a/lib/environment.rb +++ b/lib/environment.rb @@ -12,8 +12,8 @@ TMP_DIR = File.join(basedir, "tmp") LOG_DIR = File.join(basedir, "log") if File.exist?(config_file) - @@config = YAML.load_file(config_file) - raise "could not load config, config file: "+config_file.to_s unless @@config + CONFIG = YAML.load_file(config_file) + raise "could not load config, config file: "+config_file.to_s unless CONFIG else FileUtils.mkdir_p TMP_DIR FileUtils.mkdir_p LOG_DIR @@ -24,20 +24,20 @@ else end # database -if @@config[:database] +if CONFIG[:database] ['dm-core', 'dm-serializer', 'dm-timestamps', 'dm-types', 'dm-migrations', 'dm-validations' ].each{|lib| require lib } - case @@config[:database][:adapter] + case CONFIG[:database][:adapter] when /sqlite/i db_dir = File.join(basedir, "db") FileUtils.mkdir_p db_dir DataMapper::setup(:default, "sqlite3://#{db_dir}/opentox.sqlite3") else DataMapper.setup(:default, { - :adapter => @@config[:database][:adapter], - :database => @@config[:database][:database], - :username => @@config[:database][:username], - :password => @@config[:database][:password], - :host => @@config[:database][:host]}) + :adapter => CONFIG[:database][:adapter], + :database => CONFIG[:database][:database], + :username => CONFIG[:database][:username], + :password => CONFIG[:database][:password], + :host => CONFIG[:database][:host]}) end end @@ -48,7 +48,7 @@ logfile = "#{LOG_DIR}/#{ENV["RACK_ENV"]}.log" #LOGGER = MyLogger.new(logfile,'daily') # daily rotation LOGGER = MyLogger.new(logfile) # no rotation LOGGER.formatter = Logger::Formatter.new #this is neccessary to restore the formating in case active-record is loaded -if @@config[:logger] and @@config[:logger] == "debug" +if CONFIG[:logger] and CONFIG[:logger] == "debug" LOGGER.level = Logger::DEBUG else LOGGER.level = Logger::WARN diff --git a/lib/feature.rb b/lib/feature.rb new file mode 100644 index 0000000..9616135 --- /dev/null +++ b/lib/feature.rb @@ -0,0 +1,7 @@ +module OpenTox + + class Feature + include OtObject + end + +end diff --git a/lib/features.rb b/lib/features.rb deleted file mode 100644 index 0fa1cf0..0000000 --- a/lib/features.rb +++ /dev/null @@ -1,19 +0,0 @@ -# CH: should go into validation service -# - not a complete OT object -# - only used twice -# - what about ./validation/validation/validation_service.rb:241: value = OpenTox::Feature.new(:uri => a.uri).value(prediction_feature).to_s -module OpenTox - - module Feature - - def self.domain( feature_uri ) - #TODO - if feature_uri =~ /ambit/ - return nil - else - return ["true", "false"] - end - end - - end -end diff --git a/lib/model.rb b/lib/model.rb index 3ecd61c..d0d6703 100644 --- a/lib/model.rb +++ b/lib/model.rb @@ -84,8 +84,8 @@ module OpenTox def initialize @source = "http://github.com/helma/opentox-model" - @algorithm = File.join(@@config[:services]["opentox-algorithm"],"lazar") - #@independent_variables = File.join(@@config[:services]["opentox-algorithm"],"fminer#BBRC_representative") + @algorithm = File.join(CONFIG[:services]["opentox-algorithm"],"lazar") + #@independent_variables = File.join(CONFIG[:services]["opentox-algorithm"],"fminer#BBRC_representative") @features = [] @effects = {} @activities = {} @@ -95,12 +95,12 @@ module OpenTox def save @features.uniq! - resource = RestClient::Resource.new(@@config[:services]["opentox-model"]) + resource = RestClient::Resource.new(CONFIG[:services]["opentox-model"]) resource.post(self.to_yaml, :content_type => "application/x-yaml").chomp.to_s end def self.find_all - RestClientWrapper.get(@@config[:services]["opentox-model"]).chomp.split("\n") + RestClientWrapper.get(CONFIG[:services]["opentox-model"]).chomp.split("\n") end def self.predict(compound_uri,model_uri) @@ -115,8 +115,8 @@ module OpenTox def initialize @source = "http://github.com/helma/opentox-model" - @algorithm = File.join(@@config[:services]["opentox-algorithm"],"property_lazar") - #@independent_variables = File.join(@@config[:services]["opentox-algorithm"],"fminer#BBRC_representative") + @algorithm = File.join(CONFIG[:services]["opentox-algorithm"],"property_lazar") + #@independent_variables = File.join(CONFIG[:services]["opentox-algorithm"],"fminer#BBRC_representative") @features = [] #@effects = {} @activities = {} @@ -126,12 +126,12 @@ module OpenTox def save @features.uniq! - resource = RestClient::Resource.new(@@config[:services]["opentox-model"]) + resource = RestClient::Resource.new(CONFIG[:services]["opentox-model"]) resource.post(self.to_yaml, :content_type => "application/x-yaml").chomp.to_s end def self.find_all - RestClientWrapper.get(@@config[:services]["opentox-model"]).chomp.split("\n") + RestClientWrapper.get(CONFIG[:services]["opentox-model"]).chomp.split("\n") end def self.predict(compound_uri,model_uri) diff --git a/lib/opentox-ruby-api-wrapper.rb b/lib/opentox-ruby-api-wrapper.rb index d9db4ac..2749899 100644 --- a/lib/opentox-ruby-api-wrapper.rb +++ b/lib/opentox-ruby-api-wrapper.rb @@ -1,4 +1,4 @@ -['rubygems', 'sinatra', 'sinatra/url_for', 'rest_client', 'yaml', 'cgi', 'spork', 'redland', 'rdf/redland', 'rdf/redland/util', 'environment'].each do |lib| +['rubygems', 'sinatra', 'sinatra/url_for', 'rest_client', 'yaml', 'cgi', 'spork', 'environment'].each do |lib| require lib end @@ -8,6 +8,6 @@ rescue LoadError puts "Please install Openbabel with 'rake openbabel:install' in the compound component" end -['owl-serializer', 'compound','dataset','algorithm','model','task','validation','utils','features', 'ot-logger', 'overwrite', 'rest_client_wrapper'].each do |lib| +['opentox', 'compound','dataset', 'parser','serializer', 'algorithm','model','task','validation','utils','feature', 'ot-logger', 'overwrite', 'rest_client_wrapper'].each do |lib| require lib end diff --git a/lib/opentox.rb b/lib/opentox.rb new file mode 100644 index 0000000..453ca66 --- /dev/null +++ b/lib/opentox.rb @@ -0,0 +1,79 @@ +module OpenTox + + # Generic OpenTox class + module OtObject + + attr_reader :uri + attr_accessor :metadata + + # Initialize OpenTox object with optional uri + def initialize(uri=nil) + @metadata = {} + self.uri = uri if uri + end + + # Set URI + def uri=(uri) + @uri = uri + @metadata[XSD.anyUri] = uri + end + + # Get title + def title + load_metadata unless @metadata[DC.title] + @metadata[DC.title] + end + + # Set title + def title=(title) + @metadata[DC.title] = title + end + + # Get all objects from a service + def self.all(uri) + #def OtObject.all(uri) + RestClientWrapper.get(uri,:accept => "text/uri-list").to_s.split(/\n/) + end + + # Load metadata from URI + def load_metadata + #if (CONFIG[:yaml_hosts].include?(URI.parse(@uri).host)) + # TODO: fix metadata retrie + #@metadata = YAML.load(RestClientWrapper.get(@uri, :accept => "application/x-yaml")) + #else + @metadata = Parser::Owl::Generic.new(@uri).metadata + #end + @metadata + #Parser::Owl::Generic.new(@uri).metadata + end + + end + + module Owl + + class Namespace + + def initialize(uri) + @uri = uri + end + + def [](property) + @uri+property.to_s + end + + def method_missing(property) + @uri+property.to_s + end + + end + end + +end +# +# OWL Namespaces +RDF = OpenTox::Owl::Namespace.new 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' +OWL = OpenTox::Owl::Namespace.new 'http://www.w3.org/2002/07/owl#' +DC = OpenTox::Owl::Namespace.new 'http://purl.org/dc/elements/1.1/' +OT = OpenTox::Owl::Namespace.new 'http://www.opentox.org/api/1.1#' +XSD = OpenTox::Owl::Namespace.new 'http://www.w3.org/2001/XMLSchema#' + diff --git a/lib/owl-serializer.rb b/lib/owl-serializer.rb deleted file mode 100644 index 8965bf2..0000000 --- a/lib/owl-serializer.rb +++ /dev/null @@ -1,65 +0,0 @@ -require 'rdf' -require 'rdf/raptor' -require 'rdf/ntriples' - -# RDF namespaces -include RDF -OT = RDF::Vocabulary.new 'http://www.opentox.org/api/1.1#' - -module OpenTox - - class OwlSerializer - - def initialize(klass,uri) - - @model = RDF::Graph.new(uri) - - @triples = [] - @triples << [ OT[klass], RDF.type, OWL.Class ] - @triples << [ RDF::URI.new(uri), RDF.type, OT[klass] ] - - @classes = [ OT[klass] ] - @object_properties = [] - @annotation_properties = [] - @objects = [ uri ] - - end - - def self.create(klass, uri) - OpenTox::OwlSerializer.new(klass,uri) - end - - def rdf - @triples.each { |statement| @model << statement } - RDF::Writer.for(:rdfxml).buffer do |writer| - writer << @model - end - end - - def object_property(subject,predicate,object,object_class) - s = [ RDF::URI.new(subject), predicate, RDF::URI.new(object) ] # - @triples << s unless @triples.include? s - unless @object_properties.include? predicate - @triples << [ predicate, RDF.type, OWL.ObjectProperty ] - @object_properties << predicate - end - unless @objects.include? object - @triples << [ RDF::URI.new(object), RDF.type, object_class ] - @objects << object - end - unless @classes.include? object_class - @triples << [ object_class, RDF.type, OWL.Class ] - @classes << object_class - end - end - - def annotation_property(subject, predicate, value, datatype) - s = [ RDF::URI.new(subject), predicate, RDF::Literal.new(value, :datatype => datatype) ] - @triples << s unless @triples.include? s - unless @annotation_properties.include? predicate - @triples << [ predicate, RDF.type, OWL.AnnotationProperty ] - @annotation_properties << predicate - end - end - end -end diff --git a/lib/owl.rb b/lib/owl.rb deleted file mode 100644 index f4128ee..0000000 --- a/lib/owl.rb +++ /dev/null @@ -1,593 +0,0 @@ -require 'rdf' -require 'rdf/ntriples' -require 'rdf/raptor' -include RDF -# RDF namespaces -#RDF = Redland::Namespace.new 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' -OWL = Redland::Namespace.new 'http://www.w3.org/2002/07/owl#' -DC = Redland::Namespace.new 'http://purl.org/dc/elements/1.1/' -OT = Redland::Namespace.new 'http://www.opentox.org/api/1.1#' -#OT = Redland::Namespace.new 'http://ortona.informatik.uni-freiburg.de/opentox.owl#' -XML = Redland::Namespace.new 'http://www.w3.org/2001/XMLSchema#' - -# overriding literal to give nice access to datatype -# and to access the stored value as correct ruby type -class Redland::Literal - - def self.create(value, type) - raise "literal datatype may not be nil" unless type - type = parse_datatype_uri(value) if OpenTox::Owl::PARSE_LITERAL_TYPE==type - - if type.is_a?(Redland::Uri) - Redland::Literal.new(value.to_s,nil,type) - else - Redland::Literal.new(value.to_s,nil,Redland::Uri.new(type.to_s)) - end - end - - # the literal node of the ruby swig api provdides the 'value' of a literal but not the 'datatype' - # found solution in mailing list - def datatype - uri = Redland.librdf_node_get_literal_value_datatype_uri(self.node) - return Redland.librdf_uri_to_string(uri) if uri - end - - # gets value of literal, value class is se according to literal datatype - def get_value - Redland::Literal.parse_value( self.value, self.datatype ) - end - - private - # parses value according to datatype uri - def self.parse_value(string_value, datatype_uri) - - if (datatype_uri==nil || datatype_uri.size==0) - LOGGER.warn("empty datatype for literal with value: '"+string_value+"'") - return string_value - end - case datatype_uri - when OpenTox::Owl::LITERAL_DATATYPE_STRING.to_s - return string_value - when OpenTox::Owl::LITERAL_DATATYPE_URI.to_s - return string_value #PENDING uri as string? - when OpenTox::Owl::LITERAL_DATATYPE_FLOAT.to_s - return string_value.to_f - when OpenTox::Owl::LITERAL_DATATYPE_DOUBLE.to_s - return string_value.to_f - when OpenTox::Owl::LITERAL_DATATYPE_BOOLEAN.to_s - return string_value.upcase=="TRUE" - when OpenTox::Owl::LITERAL_DATATYPE_DATE.to_s - return Time.parse(string_value) - when OpenTox::Owl::LITERAL_DATATYPE_DATETIME.to_s - return Time.parse(string_value) - when OpenTox::Owl::LITERAL_DATATYPE_INTEGER.to_s - return string_value.to_i - else - raise "unknown literal datatype: '"+datatype_uri.to_s+"' (value is "+string_value+ - "), please specify new OpenTox::Owl::LITERAL_DATATYPE" - end - end - - # parse datatype uri accoring to value class - def self.parse_datatype_uri(value) - if value==nil - raise "illegal datatype: value is nil" - elsif value.is_a?(String) - # PENDING: uri check too slow? - if OpenTox::Utils.is_uri?(value) - return OpenTox::Owl::LITERAL_DATATYPE_URI - else - return OpenTox::Owl::LITERAL_DATATYPE_STRING - end - elsif value.is_a?(Float) - return OpenTox::Owl::LITERAL_DATATYPE_FLOAT - elsif value.is_a?(TrueClass) or value.is_a?(FalseClass) - return OpenTox::Owl::LITERAL_DATATYPE_BOOLEAN - elsif value.is_a?(Integer) - return OpenTox::Owl::LITERAL_DATATYPE_INTEGER - elsif value.is_a?(DateTime) - return OpenTox::Owl::LITERAL_DATATYPE_DATETIME - elsif value.is_a?(Time) - return OpenTox::Owl::LITERAL_DATATYPE_DATETIME - else - raise "illegal datatype: "+value.class.to_s+" "+value.to_s - end - end -end - -module OpenTox - - class Owl - - # to get correct owl-dl, properties and objects have to be typed - # i.e. the following triple is insufficient: - # ModelXY,ot:algorithm,AlgorithmXY - # further needed: - # ot:algorithm,rdf:type,owl:ObjectProperty - # AlgorithmXY,rdf:type,ot:Algorithm - # ot:Algorithm,rdf:type,owl:Class - # - # therefore OpentoxOwl needs info about the opentox-ontology - # the info is stored in OBJECT_PROPERTY_CLASS and LITERAL_TYPES - - # contains all owl:ObjectProperty as keys, and the respective classes as value - # some object properties link to objects from different classes (e.g. "values can be "Tuple", or "FeatureValue") - # in this case, use set_object_property() (instead of set()) and specify class manually - OBJECT_PROPERTY_CLASS = {} - [ "model" ].each{ |c| OBJECT_PROPERTY_CLASS[c] = "Model"} - [ "algorithm" ].each{ |c| OBJECT_PROPERTY_CLASS[c] = "Algorithm"} - [ "trainingDataset", "testTargetDataset", "predictionDataset", - "testDataset", "dataset" ].each{ |c| OBJECT_PROPERTY_CLASS[c] = "Dataset"} - [ "feature", "dependentVariables", "independentVariables", - "predictedVariables", "predictionFeature" ].each{ |c| OBJECT_PROPERTY_CLASS[c] = "Feature"} - [ "parameters" ].each{ |c| OBJECT_PROPERTY_CLASS[c] = "Parameter"} - [ "compound" ].each{ |c| OBJECT_PROPERTY_CLASS[c] = "Compound"} - [ "dataEntry" ].each{ |c| OBJECT_PROPERTY_CLASS[c] = "DataEntry"} - [ "complexValue" ].each{ |c| OBJECT_PROPERTY_CLASS[c] = "FeatureValue"} - [ "classificationStatistics" ].each{ |c| OBJECT_PROPERTY_CLASS[c] = "ClassificationStatistics"} - [ "classValueStatistics" ].each{ |c| OBJECT_PROPERTY_CLASS[c] = "ClassValueStatistics"} - [ "confusionMatrix" ].each{ |c| OBJECT_PROPERTY_CLASS[c] = "ConfusionMatrix"} - [ "confusionMatrixCell" ].each{ |c| OBJECT_PROPERTY_CLASS[c] = "ConfusionMatrixCell"} - [ "regressionStatistics" ].each{ |c| OBJECT_PROPERTY_CLASS[c] = "RegressionStatistics"} - [ "validation" ].each{ |c| OBJECT_PROPERTY_CLASS[c] = "Validation"} - [ "crossvalidationInfo" ].each{ |c| OBJECT_PROPERTY_CLASS[c] = "CrossvalidationInfo"} - [ "crossvalidation" ].each{ |c| OBJECT_PROPERTY_CLASS[c] = "Crossvalidation"} - - # literals point to primitive values (not to other resources) - # the literal datatype is encoded via uri: - LITERAL_DATATYPE_STRING = XML["string"].uri - LITERAL_DATATYPE_URI = XML["anyURI"].uri - LITERAL_DATATYPE_FLOAT = XML["float"].uri - LITERAL_DATATYPE_DOUBLE = XML["double"].uri - LITERAL_DATATYPE_DATE = XML["date"].uri - LITERAL_DATATYPE_BOOLEAN = XML["boolean"].uri - LITERAL_DATATYPE_DATETIME = XML["dateTime"].uri - LITERAL_DATATYPE_INTEGER = XML["integer"].uri - - # list all literals (to distinguish from objectProperties) as keys, datatype as values - # (do not add dc-identifier, deprecated, object are identified via name=uri) - LITERAL_TYPES = {} - [ "title", "creator", "format", "description", "hasStatus", "paramScope", "paramValue", - "classValue", "reportType", "confusionMatrixActual", - "confusionMatrixPredicted" ].each{ |l| LITERAL_TYPES[l] = LITERAL_DATATYPE_STRING } - [ "date", "due_to_time" ].each{ |l| LITERAL_TYPES[l] = LITERAL_DATATYPE_DATE } - [ "percentageCompleted", "truePositiveRate", "fMeasure", "falseNegativeRate", - "areaUnderRoc", "falsePositiveRate", "trueNegativeRate", "precision", "recall", - "percentCorrect", "percentIncorrect", "weightedAreaUnderRoc", "numCorrect", - "percentIncorrect", "percentUnpredicted", "realRuntime", - "percentWithoutClass", "rootMeanSquaredError", "meanAbsoluteError", "rSquare", - "targetVarianceActual", "targetVariancePredicted", "sumSquaredError", - "sampleCorrelationCoefficient" ].each{ |l| LITERAL_TYPES[l] = LITERAL_DATATYPE_DOUBLE } - [ "numTrueNegatives", "numWithoutClass", "numFalseNegatives", "numTruePositives", - "numFalsePositives", "numIncorrect", "numInstances", "numUnpredicted", - "randomSeed", "numFolds", "confusionMatrixValue", - "crossvalidationFold" ].each{ |l| LITERAL_TYPES[l] = LITERAL_DATATYPE_INTEGER } - [ "resultURI" ].each{ |l| LITERAL_TYPES[l] = LITERAL_DATATYPE_URI } - [ "stratified" ].each{ |l| LITERAL_TYPES[l] = LITERAL_DATATYPE_BOOLEAN } - # some literals can have different types, parse from ruby type - PARSE_LITERAL_TYPE = "PARSE_LITERAL_TYPE" - [ "value" ].each{ |l| LITERAL_TYPES[l] = PARSE_LITERAL_TYPE } - - # constants for often used redland-resources - OWL_TYPE_LITERAL = OWL["AnnotationProperty"] - OWL_TYPE_CLASS = OWL["Class"] - OWL_TYPE_OBJECT_PROPERTY = OWL["ObjectProperty"] - RDF_TYPE = RDF['type'] - - # store redland:resources (=nodes) to: - # * separate namespaces (OT from RDF and DC) - # * save time, as generating resources is timeconsuming in redland - @@nodes = {} - [ "type", "about"].each{ |l| @@nodes[l] = RDF[l] } - [ "title", "creator", "date", "format" ].each{ |l| @@nodes[l] = DC[l] } - - def node(property) - raise "can only create node for non-empty-string, but given "+property.class.to_s+" (value: "+ - property.to_s+")" unless property.is_a?(String) and property.size>0 - raise "dc[identifier] deprecated, use owl.uri" if property=="identifier" - @@nodes[property] = OT[property] unless @@nodes.has_key?(property) - return @@nodes[property] - end - - # ot_class is the class of the object as string, e.g. "Model","Dataset", ... - # root_node is the root-object node in the rdf - # uri the uri of the object - attr_accessor :ot_class, :root_node, :uri, :model, :triples - - private - def initialize - @triples = [] - @model = Redland::Model.new Redland::MemoryStore.new - #@triples = "" - end - - # build new owl object - # ot_class is the class of this object, should be a string like "Model", "Task", ... - # uri is name and identifier of this object - public - def self.create( ot_class, uri ) - - owl = OpenTox::Owl.new - owl.ot_class = ot_class - owl.root_node = Redland::Resource.new(uri.to_s.strip) - owl.set("type",owl.ot_class) - owl.uri = uri - owl - end - - # loads owl from data - def self.from_data(data, base_uri, ot_class) - - owl = OpenTox::Owl.new - parser = Redland::Parser.new - - begin - parser.parse_string_into_model(owl.model, data, base_uri) - - # now loading root_node and uri - owl.model.find(nil, RDF_TYPE, owl.node(ot_class)) do |s,p,o| - #LOGGER.debug "about statements "+s.to_s+" . "+p.to_s+" -> "+o.to_s - is_root = true - owl.model.find(nil, nil, s) do |ss,pp,oo| - is_root = false - break - end - if is_root - # handle error if root is already set - raise "cannot derieve root object from rdf, more than one object specified" if owl.uri - raise "illegal root node type, no uri specified\n"+data.to_s if s.blank? - #store root note and uri - owl.uri = s.uri.to_s - owl.root_node = s - end - end - - # handle error if no root node was found - unless owl.root_node - types = [] - owl.model.find(nil, RDF_TYPE, nil){ |s,p,o| types << o.to_s } - raise "root node for class '"+owl.node(ot_class).to_s+"' not found (available type nodes: "+types.inspect+")" - end - raise "no uri in rdf: '"+owl.uri+"'" unless owl.uri and Utils.is_uri?(owl.uri) - owl.ot_class = ot_class - owl - rescue => e - RestClientWrapper.raise_uri_error(e.message, base_uri) - end - end - - def self.from_uri(uri, ot_class) - return from_data(RestClientWrapper.get(uri,:accept => "application/rdf+xml").to_s, uri, ot_class) - end - - def rdf - #@model.to_string - #stdin, stdout, stderr = Open3.popen3('rapper -I test.org -i ntriples -o rdfxml -') - #stdin.puts @triples - #stdout - #File.open("/tmp/d","w+") {|f| f.puts @triples} - #`rapper -i ntriples -o rdfxml /tmp/d` - #@triples - #output = RDF::Writer.for(:rdfxml).buffer do |writer| - RDF::Writer.for(:rdfxml).buffer do |writer| - @triples.each do |statement| - begin - writer << statement - rescue => e - LOGGER.error e - LOGGER.info statement.inspect - end - end - end - #output - end - - # returns the first object for subject:root_node and property - # (sufficient for accessing simple, root-node properties) - def get( property ) - raise "uri is no prop, use owl.uri instead" if property=="uri" - return get_value( @model.object( @root_node, node(property.to_s)) ) - end - - # returns an array of objects (not only the first one) that fit for the property - # accepts array of properties to access not-root-node vaules - # i.e. validation_owl.get_nested( [ "confusionMatrix", "confusionMatrixCell", "confusionMatrixValue" ] - # returns an array of all confusionMatrixValues - def get_nested( property_array ) - n = [ @root_node ] - property_array.each do |p| - new_nodes = [] - n.each do |nn| - @model.find( nn, node(p), nil ) do |sub,pred,obj| - new_nodes << obj - end - end - n = new_nodes - end - return n.collect{|nn| get_value( nn )} - end - - private - # returns node-value - def get_value( node ) - return nil unless node - if node.is_a?(Redland::Literal) - return node.get_value - elsif node.blank? - return nil - else - return node.uri.to_s - end - end - - public - # sets values of current_node (by default root_node) - # - # note: this does not delete existing triples - # * there can be several triples for the same subject and predicate - # ( e.g. after set("description","bla1") and set("description","bla2") - # both descriptions are in the model, - # but the get("description") will give you only one object (by chance) - # * this does not matter in pratice (only dataset uses this -> load_dataset-methods) - # * identical values appear only once in rdf - def set(predicate, object, current_node=@root_node ) - - pred = predicate.to_s - raise "uri is no prop, cannot set uri" if pred=="uri" - raise "dc[identifier] deprecated, use owl.uri" if pred=="identifier" - if (object.is_a?(Redland::Node) and object.blank?) or nil==object or object.to_s.size==0 - # set only not-nil values - LOGGER.warn "skipping (not setting) empty value in rdf for property: '"+pred+"'" - return - end - - if pred=="type" - # predicate is type, set class of current node - set_type(object, current_node) - elsif LITERAL_TYPES.has_key?(pred) - # predicate is literal - set_literal(pred,object,LITERAL_TYPES[pred],current_node) - elsif OBJECT_PROPERTY_CLASS.has_key?(pred) - # predicte is objectProperty, object is another resource - set_object_property(pred,object,OBJECT_PROPERTY_CLASS[pred],current_node) - else - raise "unkonwn rdf-property, please add: '"+pred+"' to OpenTox::OWL.OBJECT_PROPERTY_CLASS or OpenTox::OWL.LITERAL_TYPES" - end - end - - # example-triples for setting rdf-type to model: - # model_xy,rdf:type,ot:Model - # ot:Model,rdf:type,owl:Class - def set_type(ot_class, current_node=@root_node) - #@triples += "#{ot_class.to_s} #{RDF_TYPE.to_s} #{current_node.to_s}" - #@triples << "#{current_node} #{RDF_TYPE} #{node(ot_class).to_s}.\n".gsub(/\[/,'<').gsub(/\]/,'>') - #@triples << "#{node(ot_class).to_s} #{RDF_TYPE} #{OWL_TYPE_CLASS}.\n".gsub(/\[/,'<').gsub(/\]/,'>') - add current_node, RDF_TYPE, node(ot_class) - add node(ot_class), RDF_TYPE, OWL_TYPE_CLASS - end - - # example-triples for setting description of a model: - # model_xy,ot:description,bla..bla^^xml:string - # ot:description,rdf:type,owl:Literal - def set_literal(literal_name, literal_value, literal_datatype, current_node=@root_node) - #@triples += "#{current_node} #{node(literal_name)} #{Redland::Literal.create(literal_value, literal_datatype)}.\n".gsub(/\[/,'<').gsub(/\]/,'>') - #TODO: add datatype - #@triples << "#{current_node} #{node(literal_name)} \"#{literal_value}\".\n".gsub(/\[/,'<').gsub(/\]/,'>') - #@triples << "#{node(literal_name)} #{RDF_TYPE} #{OWL_TYPE_LITERAL}.\n".gsub(/\[/,'<').gsub(/\]/,'>') - add current_node, node(literal_name), Redland::Literal.create(literal_value, literal_datatype) - add node(literal_name), RDF_TYPE, OWL_TYPE_LITERAL - end - - # example-triples for setting algorithm property of a model: - # model_xy,ot:algorithm,algorihtm_xy - # ot:algorithm,rdf:type,owl:ObjectProperty - # algorihtm_xy,rdf:type,ot:Algorithm - # ot:Algorithm,rdf:type,owl:Class - def set_object_property(property, object, object_class, current_node=@root_node) - object_node = Redland::Resource.new(object) - #@triples << "#{current_node} #{node(property)} #{object_node}.\n".gsub(/\[/,'<').gsub(/\]/,'>') - #@triples << "#{node(property)} #{RDF_TYPE} #{OWL_TYPE_OBJECT_PROPERTY}.\n".gsub(/\[/,'<').gsub(/\]/,'>') - #@triples << "#{object_node} #{RDF_TYPE} #{node(object_class)}.\n".gsub(/\[/,'<').gsub(/\]/,'>') - #@triples << "#{node(object_class)} #{RDF_TYPE} #{OWL_TYPE_CLASS}.\n".gsub(/\[/,'<').gsub(/\]/,'>') - add current_node, node(property), object_node - add node(property), RDF_TYPE, OWL_TYPE_OBJECT_PROPERTY - add object_node, RDF_TYPE, node(object_class) - add node(object_class), RDF_TYPE, OWL_TYPE_CLASS - end - - def add(s,p,o) - #@triples << "#{s} #{p} #{o}.\n".gsub(/\[/,'<').gsub(/\]/,'>') - @triples << [RDF::URI.new(s.to_s.sub(/\[/,'').sub(/\]/,'')),RDF::URI.new(p.to_s.sub(/\[/,'').sub(/\]/,'')),o.to_s.sub(/\[/,'').sub(/\]/,'')] - #@model.add s,p,o - end - - # this is (a recursiv method) to set nested-data via hashes (not only simple properties) - # example (for a dataset) - # { :description => "bla", - # :dataEntry => { :compound => "compound_uri", - # :values => [ { :class => "FeatureValue" - # :feature => "feat1", - # :value => 42 }, - # { :class => "FeatureValue" - # :feature => "feat2", - # :value => 123 } ] } } - def set_data(hash, current_node=@root_node) - - hash.each do |k,v| - if v.is_a?(Hash) - # value is again a hash - prop = k.to_s - - # :class is a special key to specify the class value, if not defined in OBJECT_PROPERTY_CLASS - object_class = v.has_key?(:class) ? v.delete(:class) : OBJECT_PROPERTY_CLASS[prop] - raise "hash key must be a object-property, please add '"+prop.to_s+ - "' to OpenTox::OWL.OBJECT_PROPERTY_CLASS or specify :class value" unless object_class - - # the new node is a class node, to specify the uri of the resource use key :uri - if v[:uri] - # identifier is either a specified uri - class_node = Redland::Resource.new(v.delete(:uri)) - else - # or a new uri, make up internal uri with increment - class_node = new_class_node(object_class,current_node) - end - set_object_property(prop,class_node,object_class,current_node) - # recursivly call set_data method with new node - set_data(v,class_node) - elsif v.is_a?(Array) - # value is an array, each array element is added with current key as predicate - v.each do |value| - set_data( { k => value }, current_node ) - end - else - # neither hash nor array, call simple set-method - set( k, v, current_node ) - end - end - end - - # create a new (internal class) node with unique, uri-like name - def new_class_node(name, current_node=@root_node) - # to avoid anonymous nodes, make up uris for sub-objects - # use counter to make sure each uri is unique - # for example we will get ../confusion_matrix_cell/1, ../confusion_matrix_cell/2, ... - count = 1 - while (true) - res = Redland::Resource.new( File.join(current_node.uri.to_s,name.to_s,count.to_s) ) - match = false - @model.find(nil, nil, res) do |s,p,o| - match = true - break - end - if match - count += 1 - else - break - end - end - return res - end - - # for "backwards-compatiblity" - # better use directly: - # set_data( { "parameters" => [ { "title" => , "paramScope" => , "paramValue" => } ] ) - def parameters=(params) - - converted_params = [] - params.each do |name, settings| - converted_params << { :title => name, :paramScope => settings[:scope], :paramValue => settings[:value] } - end - set_data( :parameters => converted_params ) - end - - # PENDING move to dataset.rb - # this is for dataset.to_owl - # adds feautre value for a single compound - def add_data_entries(compound_uri,features) - - data_entry = { :compound => compound_uri } - if features - feature_values = [] - features.each do |f| - f.each do |feature_uri,value| - if value.is_a?(Hash) - complex_values = [] - value.each do |uri,v| - complex_values << { :feature => uri, :value => v } - end - feature_values << { :class => "Tuple", :feature => feature_uri, :complexValue => complex_values } - else - feature_values << { :class => "FeatureValue", :feature => feature_uri, :value => value } - end - end - end - data_entry[:values] = feature_values - end - set_data( :dataEntry => data_entry ) - end - - # PENDING move to dataset.rb - # feature values are not loaded for performance reasons - # loading compounds and features into arrays that are given as params - def load_dataset( compounds, features ) - - @model.subjects(RDF_TYPE, node('Compound')).each do |compound| - compounds << get_value(compound) - end - - @model.subjects(RDF_TYPE, node('Feature')).each do |feature| - feature_value_found=false - @model.find(nil, node("feature"), feature) do |potential_feature_value,p,o| - @model.find(nil, node("values"), potential_feature_value) do |s,p,o| - feature_value_found=true - break - end - break if feature_value_found - end - features << get_value(feature) if feature_value_found - end - LOGGER.debug "loaded "+compounds.size.to_s+" compounds and "+features.size.to_s+" features from dataset "+uri.to_s - end - - # PENDING move to dataset.rb - # loading feature values for the specified feature - # if feature is nil, all feature values are loaded - # - # general remark on the rdf loading (found out with some testing): - # the search methods (subjects/find) are fast, the time consuming parts is creating resources, - # which cannot be avoided in general - def load_dataset_feature_values( compounds, data, feature_uris ) - - raise "no feature-uri array" unless feature_uris.is_a?(Array) - - # values are stored in the data-hash, hash has a key for each compound - compounds.each{|c| data[c] = [] unless data[c]} - - count = 0 - - feature_uris.each do |feature_uri| - LOGGER.debug("load feature values for feature: "+feature_uri ) - feature_node = Redland::Resource.new(feature_uri) - - # search for all feature_value_node with property 'ot_feature' and the feature we are looking for - @model.find(nil, node('feature'), feature_node) do |feature_value_node,p,o| - - # get compound_uri by "backtracking" to values node (property is 'values'), then get compound_node via 'compound' - value_nodes = @model.subjects(node('values'),feature_value_node) - if value_nodes.size>0 - raise "more than one value node "+value_nodes.size.to_s if value_nodes.size>1 - value_node = value_nodes[0] - - compound_uri = get_value( @model.object(value_node, node('compound')) ) - unless compound_uri - LOGGER.warn "'compound' missing for data-entry of feature "+feature_uri.to_s+ - ", value: "+@model.object(feature_value_node,node("value")).to_s - next - end - - value_node_type = @model.object(feature_value_node, RDF_TYPE) - if (value_node_type == node('FeatureValue')) - value_literal = @model.object( feature_value_node, node('value')) - raise "plain feature value no literal: "+value_literal.to_s unless value_literal.is_a?(Redland::Literal) - data[compound_uri] << {feature_uri => value_literal.get_value } - elsif (value_node_type == node('Tuple')) - complex_values = {} - @model.find(feature_value_node,node('complexValue'),nil) do |p,s,complex_value| - complex_value_type = @model.object(complex_value, RDF_TYPE) - raise "complex feature value no feature value: "+complex_value.to_s unless complex_value_type==node('FeatureValue') - complex_feature_uri = get_value(@model.object( complex_value, node('feature'))) - complex_value = @model.object( complex_value, node('value')) - raise "complex value no literal: "+complex_value.to_s unless complex_value.is_a?(Redland::Literal) - complex_values[ complex_feature_uri ] = complex_value.get_value - end - data[compound_uri] << { feature_uri => complex_values } if complex_values.size>0 - end - count += 1 - LOGGER.debug "loading feature values ("+count.to_s+")" if (count%1000 == 0) - end - end - LOGGER.debug "loaded "+count.to_s+" feature values for feature "+feature_node.to_s - end - end - end -end diff --git a/lib/parser.rb b/lib/parser.rb new file mode 100644 index 0000000..e623bf5 --- /dev/null +++ b/lib/parser.rb @@ -0,0 +1,191 @@ +require 'spreadsheet' +require 'roo' +module OpenTox + + module Parser + + module Owl + + def initialize(uri) + @uri = uri + @metadata = {} + end + + def metadata + # TODO: load parameters + if @dataset + uri = File.join(@uri,"metadata") + else + uri = @uri + end + statements = [] + `rapper -i rdfxml -o ntriples #{uri}`.each_line do |line| + triple = line.chomp.split('> ') + statements << triple.collect{|i| i.sub(/\s+.$/,'').gsub(/[<>"]/,'')} + end + statements.each do |triple| + @metadata[triple[1]] = triple[2].split('^^').first if triple[0] == @uri and triple[1] != RDF['type'] + end + @metadata + end + + class Generic + include Owl + end + + class Dataset + + include Owl + + def initialize(uri) + super uri + @dataset = ::OpenTox::Dataset.new(@uri) + end + + def load_uri + data = {} + feature_values = {} + feature = {} + other_statements = {} + ntriples = `rapper -i rdfxml -o ntriples #{@uri}` + ntriples.each_line do |line| + triple = line.chomp.split(' ',3) + triple = triple[0..2].collect{|i| i.sub(/\s+.$/,'').gsub(/[<>"]/,'')} + case triple[1] # Ambit namespaces are case insensitive + when /#{OT.values}/i + data[triple[0]] = {:compound => "", :values => []} unless data[triple[0]] + data[triple[0]][:values] << triple[2] + when /#{OT.value}/i + feature_values[triple[0]] = triple[2] + when /#{OT.compound}/i + data[triple[0]] = {:compound => "", :values => []} unless data[triple[0]] + data[triple[0]][:compound] = triple[2] + when /#{OT.feature}/i + feature[triple[0]] = triple[2] + else + end + end + data.each do |id,entry| + entry[:values].each do |value_id| + value = feature_values[value_id].split(/\^\^/).first # remove XSD.type + @dataset.add entry[:compound],feature[value_id],value + end + end + load_features + @dataset.metadata = metadata + @dataset + end + + def load_features + @dataset.features.keys.each do |feature| + @dataset.features[feature] = Parser::Owl::Generic.new(feature).metadata + end + end + end + + end + + class Spreadsheet + + def initialize(dataset) + @dataset = dataset + @format_errors = "" + @smiles_errors = [] + @activity_errors = [] + @duplicates = {} + @nr_compounds = 0 + @data = [] + @activities = [] + @type = "classification" + end + + def load_excel(book) + book.default_sheet = 0 + 1.upto(book.last_row) do |row| + if row == 1 + @feature = File.join(@dataset.uri,"feature",book.cell(row,2)) + else + add( book.cell(row,1), book.cell(row,2), row ) # smiles, activity + end + end + parse + end + + def load_csv(csv) + row = 0 + csv.each_line do |line| + row += 1 + raise "Invalid CSV format at line #{row}: #{line.chomp}" unless line.chomp.match(/^.+[,;].*$/) # check CSV format + items = line.chomp.gsub(/["']/,'').split(/\s*[,;]\s*/) # remove quotes + if row == 1 + @feature = File.join(@dataset.uri,"feature",items[1]) + else + add(items[0], items[1], row) + end + end + parse + end + + def parse + + # create dataset + @data.each do |items| + case @type + when "classification" + case items[1].to_s + when TRUE_REGEXP + @dataset.add(items[0], @feature, true ) + when FALSE_REGEXP + @dataset.add(items[0], @feature, false) + end + when "regression" + if items[1].to_f == 0 + @activity_errors << "Row #{items[2]}: Zero values not allowed for regression datasets - entry ignored." + else + @dataset.add items[0], @feature, items[1].to_f + end + end + end + + warnings = '' + warnings += "

Incorrect Smiles structures (ignored):

" + @smiles_errors.join("
") unless @smiles_errors.empty? + warnings += "

Irregular activities (ignored):

" + @activity_errors.join("
") unless @activity_errors.empty? + duplicate_warnings = '' + @duplicates.each {|inchi,lines| duplicate_warnings << "

#{lines.join('
')}

" if lines.size > 1 } + warnings += "

Duplicated structures (all structures/activities used for model building, please make sure, that the results were obtained from independent experiments):

" + duplicate_warnings unless duplicate_warnings.empty? + + @dataset.metadata[OT.Warnings] = warnings + + @dataset + + end + + def add(smiles, act, row) + compound = Compound.from_smiles(smiles) + if compound.nil? or compound.inchi.nil? or compound.inchi == "" + @smiles_errors << "Row #{row}: " + [smiles,act].join(", ") + return false + end + unless numeric?(act) or classification?(act) + @activity_errors << "Row #{row}: " + [smiles,act].join(", ") + return false + end + @duplicates[compound.inchi] = [] unless @duplicates[compound.inchi] + @duplicates[compound.inchi] << "Row #{row}: " + [smiles, act].join(", ") + @type = "regression" unless classification?(act) + # TODO: set OT.NumericalFeature, ... + @nr_compounds += 1 + @data << [ compound.uri, act , row ] + end + + def numeric?(object) + true if Float(object) rescue false + end + + def classification?(object) + !object.to_s.strip.match(TRUE_REGEXP).nil? or !object.to_s.strip.match(FALSE_REGEXP).nil? + end + + end + end +end diff --git a/lib/serializer.rb b/lib/serializer.rb new file mode 100644 index 0000000..3def252 --- /dev/null +++ b/lib/serializer.rb @@ -0,0 +1,297 @@ +require 'spreadsheet' +require 'yajl' + +module OpenTox + + module Serializer + + # modelled according to to http://n2.talis.com/wiki/RDF_JSON_Specification + class Owl + + attr_accessor :object + + def initialize + + @object = { + # this should come from opntox.owl + OT.Compound => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , + OT.Feature => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , + OT.NominalFeature => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , + OT.NumericFeature => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , + OT.StringFeature => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , + OT.Dataset => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , + OT.DataEntry => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , + OT.FeatureValue => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , + OT.Algorithm => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , + OT.Parameter => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , + + OT.compound => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , + OT.feature => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , + OT.dataEntry => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , + OT.acceptValue => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , + OT.values => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , + #XSD.anyUri => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , + OT.algorithm => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , + OT.parameters => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , + + DC.title => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + DC.identifier => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + DC.contributor => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + DC.creator => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.isA => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + + OT.hasSource => { RDF["type"] => [{ "type" => "uri", "value" => OWL.DatatypeProperty }] } , + OT.value => { RDF["type"] => [{ "type" => "uri", "value" => OWL.DatatypeProperty }] } , + OT.paramScope => { RDF["type"] => [{ "type" => "uri", "value" => OWL.DatatypeProperty }] } , + OT.paramValue => { RDF["type"] => [{ "type" => "uri", "value" => OWL.DatatypeProperty }] } , + + #Untyped Individual: http://localhost/algorithm + } + + @data_entries = {} + @values_id = 0 + @parameter_id = 0 + + @classes = Set.new + @object_properties = Set.new + @annotation_properties = Set.new + @datatype_properties = Set.new + + @objects = Set.new + end + + def add_compound(uri) + #@classes << OT.Compound unless @classes.include? OT.Compound + @object[uri] = { RDF["type"] => [{ "type" => "uri", "value" => OT.Compound }] } + end + + def add_feature(uri,metadata) + #@classes << OT.Feature unless @classes.include? OT.Feature + #@classes << OT.NominalFeature unless @classes.include? OT.NominalFeature + #@classes << OT.NumericFeature unless @classes.include? OT.NumericFeature + #@classes << OT.StringFeature unless @classes.include? OT.StringFeature + @object[uri] = { RDF["type"] => [{ "type" => "uri", "value" => OT.Feature }] } + add_metadata uri, metadata + end + + def add_dataset(dataset) + + @dataset = dataset.uri + + @object[dataset.uri] = { RDF["type"] => [{ "type" => "uri", "value" => OT.Dataset }] } + + add_metadata dataset.uri, dataset.metadata + + dataset.compounds.each { |compound| add_compound compound } + + dataset.features.each { |feature,metadata| add_feature feature,metadata } + + dataset.data_entries.each do |compound,entry| + entry.each do |feature,values| + values.each { |value| add_data_entry compound,feature,value } + end + end + + end + + def add_algorithm(uri,metadata,parameters) + @object[uri] = { RDF["type"] => [{ "type" => "uri", "value" => OT.Algorithm }] } + add_metadata uri, metadata + add_parameters uri, parameters + #metadata.each { |u,v| @object[uri][u] = [{"type" => type(v), "value" => v }] } + end + + def add_model(uri,metadata) + end + + def add_metadata(uri,metadata) + #@object[uri] = { RDF["type"] => [{ "type" => "uri", "value" => OT[type] }] } + metadata.each do |u,v| + @object[uri][u] = [{"type" => type(v), "value" => v }] + end + end + + def add_parameters(uri,parameters) + #@object[uri] = { RDF["type"] => [{ "type" => "uri", "value" => OT[type] }] } + @object[uri][OT.parameters] = [] unless @object[uri][OT.parameters] + parameters.each do |p| + parameter = "_:parameter#{@parameter_id}" + @parameter_id += 1 + @object[uri][OT.parameters] << {"type" => "bnode", "value" => parameter} + @object[parameter] = { RDF["type"] => [{ "type" => "uri", "value" => OT.Parameter }] } + add_metadata parameter, p + end + end + + def add_data_entry(compound,feature,value) + add_compound(compound) unless @object[compound] + add_feature(feature,{}) unless @object[feature] + unless data_entry = @data_entries[compound] + data_entry = "_:dataentry#{@data_entries.size}" + @data_entries[compound] = data_entry + @object[@dataset][OT.dataEntry] = [] unless @object[@dataset][OT.dataEntry] + @object[@dataset][OT.dataEntry] << {"type" => "bnode", "value" => data_entry} + @object[data_entry] = { + RDF["type"] => [{ "type" => "uri", "value" => OT.DataEntry }], + OT.compound => [{ "type" => "uri", "value" => compound }], + OT.values => [], + } + end + values = "_:values#{@values_id}" + @values_id += 1 + @object[data_entry][OT.values] << {"type" => "bnode", "value" => values} + case type(value) + when "uri" + v = [{ "type" => "uri", "value" => value}] + when "literal" + v = [{ "type" => "literal", "value" => value, "datatype" => datatype(value) }] + else + raise "Illegal type #{type(value)} for #{value}." + end + @object[values] = { + RDF["type"] => [{ "type" => "uri", "value" => OT.FeatureValue }], + OT.feature => [{ "type" => "uri", "value" => feature }], + OT.value => v + } + @object[feature][RDF["type"]] << { "type" => "uri", "value" => featuretype(value) } + end + + # Serializers + + def ntriples + + #rdf_types + @triples = Set.new + @object.each do |s,entry| + s = url(s) if type(s) == "uri" + entry.each do |p,objects| + p = url(p) + objects.each do |o| + case o["type"] + when "uri" + o = url(o["value"]) + when "literal" + o = literal(o["value"],datatype(o["value"])) + when "bnode" + o = o["value"] + end + @triples << [s,p,o] + end + end + end + @triples.sort.collect{ |s| s.join(' ').concat(" .") }.join("\n")+"\n" + end + + def rdfxml + Tempfile.open("owl-serializer"){|f| f.write(ntriples); @path = f.path} + `rapper -i ntriples -o rdfxml #{@path}` + end + + def json + #rdf_types + Yajl::Encoder.encode(@object) + end + + # Helpers for type detection + private + + def datatype(value) + if value.is_a? TrueClass or value.is_a? FalseClass + XSD.boolean + elsif value.is_a? Float + XSD.float + else + XSD.string + end + end + + def featuretype(value) + if value.is_a? TrueClass or value.is_a? FalseClass + datatype = OT.NominalFeature + elsif value.is_a? Float + datatype = OT.NumericFeature + else + datatype = OT.StringFeature + end + end + + def type(value) + begin + uri = URI.parse(value) + if uri.class == URI::HTTP or uri.class == URI::HTTPS + "uri" + elsif value.match(/^_/) + "bnode" + else + "literal" + end + rescue + "literal" + end + end + + def literal(value,type) + # concat and << are faster string concatination operators than + + '"'.concat(value.to_s).concat('"^^<').concat(type).concat('>') + end + + def url(uri) + # concat and << are faster string concatination operators than + + '<'.concat(uri).concat('>') + end + + def rdf_types + @classes.each { |c| @object[c] = { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } } + @object_properties.each { |p| @object[p] = { RDF["type"] => [{ "type" => "uri", "value" => OWL['ObjectProperty'] }] } } + @annotation_properties.each { |a| @object[a] = { RDF["type"] => [{ "type" => "uri", "value" => OWL['AnnotationProperty'] }] } } + @datatype_properties.each { |d| @object[d] = { RDF["type"] => [{ "type" => "uri", "value" => OWL['DatatypeProperty'] }] } } + end + + end + + class Spreadsheets # to avoid nameclash with Spreadsheet gem + + def initialize(dataset) + @rows = [] + @rows << ["SMILES"] + features = dataset.features.keys + @rows.first << features + @rows.first.flatten! + dataset.data_entries.each do |compound,entries| + smiles = Compound.new(compound).smiles + row = Array.new(@rows.first.size) + row[0] = smiles + entries.each do |feature, values| + i = features.index(feature)+1 + values.each do |value| + row[i] = value #TODO overwrites duplicated values + end + end + @rows << row + end + end + + def csv + @rows.collect{|r| r.join(", ")}.join("\n") + end + + def excel + Spreadsheet.client_encoding = 'UTF-8' + book = Spreadsheet::Workbook.new + sheet = book.create_worksheet(:name => '') + sheet.column(0).width = 100 + i = 0 + @rows.each do |row| + row.each do |c| + sheet.row(i).push c + end + i+=1 + end + book + end + + end + + + end +end diff --git a/lib/task.rb b/lib/task.rb index 1ab3893..50f0347 100644 --- a/lib/task.rb +++ b/lib/task.rb @@ -16,7 +16,7 @@ module OpenTox # create is private now, use OpenTox::Task.as_task def self.create( params ) - task_uri = RestClientWrapper.post(@@config[:services]["opentox-task"], params, nil, false).to_s + task_uri = RestClientWrapper.post(CONFIG[:services]["opentox-task"], params, nil, false).to_s Task.find(task_uri.chomp) end @@ -36,7 +36,7 @@ module OpenTox def reload( accept_header=nil ) unless accept_header - if (@@config[:yaml_hosts].include?(URI.parse(uri).host)) + if (CONFIG[:yaml_hosts].include?(URI.parse(uri).host)) accept_header = "application/x-yaml" else accept_header = 'application/rdf+xml' @@ -99,7 +99,7 @@ module OpenTox # waits for a task, unless time exceeds or state is no longer running def wait_for_completion(dur=0.3) - if (@uri.match(@@config[:services]["opentox-task"])) + if (@uri.match(CONFIG[:services]["opentox-task"])) due_to_time = (@due_to_time.is_a?(Time) ? @due_to_time : Time.parse(@due_to_time)) running_time = due_to_time - (@date.is_a?(Time) ? @date : Time.parse(@date)) else @@ -144,7 +144,7 @@ module OpenTox #return yield nil params = {:title=>title, :creator=>creator, :max_duration=>max_duration, :description=>description } - task = OpenTox::Task.create(params) + task = ::OpenTox::Task.create(params) task_pid = Spork.spork(:logger => LOGGER) do LOGGER.debug "Task #{task.uri} started #{Time.now}" $self_task = task diff --git a/lib/validation.rb b/lib/validation.rb index 89a2a0c..340332a 100644 --- a/lib/validation.rb +++ b/lib/validation.rb @@ -4,11 +4,11 @@ module OpenTox attr_accessor :uri def initialize(params) - @uri = OpenTox::RestClientWrapper.post(File.join(@@config[:services]["opentox-validation"],"/crossvalidation"),params,nil,false) + @uri = OpenTox::RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/crossvalidation"),params,nil,false) end def self.crossvalidation(params) - params[:uri] = File.join(@@config[:services]['opentox-validation'], "crossvalidation") + params[:uri] = File.join(CONFIG[:services]['opentox-validation'], "crossvalidation") params[:num_folds] = 10 unless params[:num_folds] params[:random_seed] = 2 unless params[:random_seed] params[:stratified] = false unless params[:stratified] -- cgit v1.2.3 From b93002b4ea50ff7e357da08abd10577347ce2d5f Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Thu, 11 Nov 2010 09:31:27 +0100 Subject: first steps towards version 2.0, yard documentation started, passes compound, dataset, feature, algorithm, fminer tests --- Rakefile | 8 +- lib/algorithm.rb | 127 +++++++---- lib/compound.rb | 105 +++++---- lib/dataset.rb | 482 ++++++++++++--------------------------- lib/environment.rb | 29 +++ lib/feature.rb | 4 +- lib/model.rb | 485 +++++++++++++++++++++++++++++++--------- lib/opentox-ruby-api-wrapper.rb | 2 +- lib/opentox.rb | 106 ++++----- lib/overwrite.rb | 22 ++ lib/parser.rb | 208 +++++++++++------ lib/rest_client_wrapper.rb | 6 +- lib/serializer.rb | 69 +++--- lib/task.rb | 6 +- lib/utils.rb | 50 ----- 15 files changed, 944 insertions(+), 765 deletions(-) delete mode 100644 lib/utils.rb diff --git a/Rakefile b/Rakefile index 8dd1088..18f24bd 100644 --- a/Rakefile +++ b/Rakefile @@ -21,14 +21,14 @@ begin "rack-flash", "nokogiri", "rubyzip", - "builder", + #"builder", "roo", "spreadsheet", "google-spreadsheet-ruby", "tmail", "rinruby", - "rdf", - "rdf-raptor", + #"rdf", + #"rdf-raptor", "rjb" ].each { |dep| gem.add_dependency dep } [ "dm-core", @@ -42,7 +42,7 @@ begin gem.add_dependency "haml", ">=3" ['cucumber','jeweler'].each { |dep| gem.add_development_dependency dep } gem.files = FileList["[A-Z]*", "{bin,generators,lib,test}/**/*", 'lib/jeweler/templates/.gitignore'] - gem.files.include %w(lib/tasks/owl.rb, lib/environment.rb, lib/algorithm.rb, lib/compound.rb, lib/dataset.rb, lib/model.rb, lib/utils.rb, lib/validation.rb, lib/templates/*) + gem.files.include %w(lib/tasks/owl.rb, lib/environment.rb, lib/algorithm.rb, lib/compound.rb, lib/dataset.rb, lib/model.rb, lib/validation.rb, lib/templates/*) # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings end Jeweler::GemcutterTasks.new diff --git a/lib/algorithm.rb b/lib/algorithm.rb index e1d369a..711f63b 100644 --- a/lib/algorithm.rb +++ b/lib/algorithm.rb @@ -1,77 +1,122 @@ module OpenTox + # Wrapper for OpenTox Algorithms module Algorithm - include OtObject + include OpenTox + # Execute algorithm with parameters, please consult the OpenTox API and the webservice documentation for acceptable parameters + def run(params=nil) + RestClientWrapper.post(@uri, params) + end + + # Get OWL-DL representation in RDF/XML format + # @return [application/rdf+xml] RDF/XML representation + def to_rdfxml + s = Serializer::Owl.new + s.add_algorithm(@uri,@metadata) + s.to_rdfxml + end + + # Generic Algorithm class, should work with all OpenTox webservices class Generic include Algorithm - #include OtObject - protected -# def initialize(owl) -# @title = owl.get("title") -# @date = owl.get("date") -# @uri = owl.uri -# end - end - class Fminer < Generic + module Fminer + include Algorithm - def self.create_feature_dataset(params) - LOGGER.debug File.basename(__FILE__) + ": creating feature dataset" - resource = RestClient::Resource.new(params[:feature_generation_uri]) - resource.post :dataset_uri => params[:dataset_uri], :feature_uri => params[:feature_uri] + class BBRC + include Fminer + # Initialize bbrc algorithm + def initialize + super File.join(CONFIG[:services]["opentox-algorithm"], "fminer/bbrc") + load_metadata + end + end + + class LAST + include Fminer + # Initialize last algorithm + def initialize + super File.join(CONFIG[:services]["opentox-algorithm"], "fminer/last") + load_metadata + end end - def self.uri - File.join(CONFIG[:services]["opentox-algorithm"], "fminer") - end end - class Lazar - - def self.create_model(params) - LOGGER.debug params - LOGGER.debug File.basename(__FILE__) + ": creating model" - LOGGER.debug File.join(CONFIG[:services]["opentox-algorithm"], "lazar") - resource = RestClient::Resource.new(File.join(CONFIG[:services]["opentox-algorithm"], "lazar"), :content_type => "application/x-yaml") - @uri = resource.post(:dataset_uri => params[:dataset_uri], :prediction_feature => params[:prediction_feature], :feature_generation_uri => File.join(CONFIG[:services]["opentox-algorithm"], "fminer")).body.chomp - end + # Create lazar prediction model + class Lazar + include Algorithm + # Initialize lazar algorithm + def initialize + super File.join(CONFIG[:services]["opentox-algorithm"], "lazar") + load_metadata + end + end - def self.uri - File.join(CONFIG[:services]["opentox-algorithm"], "lazar") - end + # Utility methods without dedicated webservices - end + module Similarity + include Algorithm - class Similarity - def self.weighted_tanimoto(fp_a,fp_b,p) - common_features = fp_a & fp_b - all_features = (fp_a + fp_b).uniq + # Tanimoto similarity + # + # @param [Array] features_a Features of first compound + # @param [Array] features_b Features of second compound + # @param [optional, Hash] weights Weights for all features + # @return [Float] (Wighted) tanimoto similarity + def self.tanimoto(features_a,features_b,weights=nil) + common_features = features_a & features_b + all_features = (features_a + features_b).uniq common_p_sum = 0.0 if common_features.size > 0 - common_features.each{|f| common_p_sum += OpenTox::Utils.gauss(p[f])} - all_p_sum = 0.0 - all_features.each{|f| all_p_sum += OpenTox::Utils.gauss(p[f])} - common_p_sum/all_p_sum + if weights + common_features.each{|f| common_p_sum += Algorithm.gauss(weights[f])} + all_p_sum = 0.0 + all_features.each{|f| all_p_sum += Algorithm.gauss(weights[f])} + common_p_sum/all_p_sum + else + common_features.to_f/all_features + end else 0.0 end end - def self.euclidean(prop_a,prop_b) + + # Euclidean similarity + def self.euclidean(prop_a,prop_b,weights=nil) common_properties = prop_a.keys & prop_b.keys if common_properties.size > 1 dist_sum = 0 common_properties.each do |p| - dist_sum += (prop_a[p] - prop_b[p])**2 + if weights + dist_sum += ( (prop_a[p] - prop_b[p]) * Algorithm.gauss(weights[p]) )**2 + else + dist_sum += (prop_a[p] - prop_b[p])**2 + end end 1/(1+Math.sqrt(dist_sum)) else - nil + 0.0 end end end + + # Gauss kernel + def self.gauss(sim, sigma = 0.3) + x = 1.0 - sim + Math.exp(-(x*x)/(2*sigma*sigma)) + end + + # Median of an array + def self.median(array) + return nil if array.empty? + array.sort! + m_pos = array.size / 2 + return array.size % 2 == 1 ? array[m_pos] : (array[m_pos-1] + array[m_pos])/2 + end end end diff --git a/lib/compound.rb b/lib/compound.rb index 699e4c1..6834860 100644 --- a/lib/compound.rb +++ b/lib/compound.rb @@ -4,41 +4,15 @@ module OpenTox # Ruby wrapper for OpenTox Compound Webservices (http://opentox.org/dev/apis/api-1.2/structure). - # - # Examples: - # require "opentox-ruby-api-wrapper" - # - # # Creating compounds - # - # # from smiles string - # compound = OpenTox::Compound.from_smiles("c1ccccc1") - # # from name - # compound = OpenTox::Compound.from_name("Benzene") - # # from uri - # compound = OpenTox::Compound.new("http://webservices.in-silico.ch/compound/InChI=1S/C6H6/c1-2-4-6-5-3-1/h1-6H"") - # - # # Getting compound representations - # - # # get InChI - # inchi = compound.inchi - # # get all compound names - # names = compound.names - # # get png image - # image = compound.png - # # get uri - # uri = compound.uri - # - # # SMARTS matching - # - # # match a smarts string - # compound.match?("cN") # returns false - # # match an array of smarts strings - # compound.match(['cc','cN']) # returns ['cc'] class Compound attr_accessor :inchi, :uri # Create compound with optional uri + # @example + # compound = OpenTox::Compound.new("http://webservices.in-silico.ch/compound/InChI=1S/C6H6/c1-2-4-6-5-3-1/h1-6H"") + # @param [optional, String] uri Compound URI + # @return [OpenTox::Compound] Compound def initialize(uri=nil) @uri = uri case @uri @@ -50,6 +24,10 @@ module OpenTox end # Create a compound from smiles string + # @example + # compound = OpenTox::Compound.from_smiles("c1ccccc1") + # @param [String] smiles Smiles string + # @return [OpenTox::Compound] Compound def self.from_smiles(smiles) c = Compound.new c.inchi = Compound.smiles2inchi(smiles) @@ -58,6 +36,8 @@ module OpenTox end # Create a compound from inchi string + # @param [String] smiles InChI string + # @return [OpenTox::Compound] Compound def self.from_inchi(inchi) c = Compound.new c.inchi = inchi @@ -66,6 +46,8 @@ module OpenTox end # Create a compound from sdf string + # @param [String] smiles SDF string + # @return [OpenTox::Compound] Compound def self.from_sdf(sdf) c = Compound.new c.inchi = Compound.sdf2inchi(sdf) @@ -73,7 +55,11 @@ module OpenTox c end - # Create a compound from name (name can be also an InChI/InChiKey, CAS number, etc) + # Create a compound from name. Relies on an external service for name lookups. + # @example + # compound = OpenTox::Compound.from_name("Benzene") + # @param [String] name name can be also an InChI/InChiKey, CAS number, etc + # @return [OpenTox::Compound] Compound def self.from_name(name) c = Compound.new # paranoid URI encoding to keep SMILES charges and brackets @@ -83,32 +69,42 @@ module OpenTox end # Get (canonical) smiles - def smiles + # @return [String] Smiles string + def to_smiles Compound.obconversion(@inchi,'inchi','can') end # Get sdf - def sdf + # @return [String] SDF string + def to_sdf Compound.obconversion(@inchi,'inchi','sdf') end # Get gif image - def gif + # @return [image/gif] Image data + def to_gif RestClientWrapper.get("#{@@cactus_uri}#{@inchi}/image") end # Get png image - def png + # @example + # image = compound.to_png + # @return [image/png] Image data + def to_png RestClientWrapper.get(File.join @uri, "image") end # Get URI of compound image - def image_uri + # @return [String] Compound image URI + def to_image_uri File.join @uri, "image" end - # Get all known compound names - def names + # Get all known compound names. Relies on an external service for name lookups. + # @example + # names = compound.to_names + # @return [String] Compound names + def to_names begin RestClientWrapper.get("#{@@cactus_uri}#{@inchi}/names").split("\n") rescue @@ -117,6 +113,10 @@ module OpenTox end # Match a smarts string + # @example + # compound = OpenTox::Compound.from_name("Benzene") + # compound.match?("cN") # returns false + # @param [String] smarts Smarts string def match?(smarts) obconversion = OpenBabel::OBConversion.new obmol = OpenBabel::OBMol.new @@ -128,19 +128,34 @@ module OpenTox end # Match an array of smarts strings, returns array with matching smarts + # @example + # compound = OpenTox::Compound.from_name("Benzene") + # compound.match(['cc','cN']) # returns ['cc'] + # @param [Array] smarts_array Array with Smarts strings + # @return [Array] Array with matching Smarts strings def match(smarts_array) - smarts_array.collect{|s| s if match?(s)}.compact + # avoid recreation of OpenBabel objects + obconversion = OpenBabel::OBConversion.new + obmol = OpenBabel::OBMol.new + obconversion.set_in_format('inchi') + obconversion.read_string(obmol,@inchi) + smarts_pattern = OpenBabel::OBSmartsPattern.new + smarts_array.collect do |smarts| + smarts_pattern.init(smarts) + smarts if smarts_pattern.match(obmol) + end.compact + #smarts_array.collect { |s| s if match?(s)}.compact end # Get URI of compound image with highlighted fragments - def matching_smarts_image_uri(activating, deactivating, highlight = nil) + # + # @param [Array] activating Array with activating Smarts strings + # @param [Array] deactivating Array with deactivating Smarts strings + # @return [String] URI for compound image with highlighted fragments + def matching_smarts_image_uri(activating, deactivating) activating_smarts = URI.encode "\"#{activating.join("\"/\"")}\"" deactivating_smarts = URI.encode "\"#{deactivating.join("\"/\"")}\"" - if highlight.nil? - File.join CONFIG[:services]["opentox-compound"], "smiles", URI.encode(smiles), "smarts/activating", URI.encode(activating_smarts),"deactivating", URI.encode(deactivating_smarts) - else - File.join CONFIG[:services]["opentox-compound"], "smiles", URI.encode(smiles), "smarts/activating", URI.encode(activating_smarts),"deactivating", URI.encode(deactivating_smarts), "highlight", URI.encode(highlight) - end + File.join @uri, "smarts/activating", URI.encode(activating_smarts),"deactivating", URI.encode(deactivating_smarts) end diff --git a/lib/dataset.rb b/lib/dataset.rb index 7c8ce24..05b2ed3 100644 --- a/lib/dataset.rb +++ b/lib/dataset.rb @@ -1,74 +1,19 @@ module OpenTox # Ruby wrapper for OpenTox Dataset Webservices (http://opentox.org/dev/apis/api-1.2/dataset). - # - # Examples: - # require "opentox-ruby-api-wrapper" - # - # # Creating datasets - # - # # create an empty dataset - # dataset = OpenTox::Dataset.new - # # create an empty dataset with URI - # # this does not load data from the dataset service - use one of the load_* methods - # dataset = OpenTox::Dataset.new("http:://webservices.in-silico/ch/dataset/1") - # # create new dataset and sav it to obtain a URI - # dataset = OpenTox::Dataset.create - # # create a new dataset from yaml representation - # dataset = OpenTox::Dataset.from_yaml - # # create a new dataset from CSV string - # csv_string = "SMILES, Toxicity\nc1ccccc1N, true" - # dataset = OpenTox::Dataset.from_csv(csv_string) - # - # # Loading data - # # Datasets created with OpenTox::Dataset.new(uri) are empty by default - # # Invoking one of the following functions will load data into the object - # - # # create an empty dataset with URI - # dataset = OpenTox::Dataset.new("http:://webservices.in-silico/ch/dataset/1") - # # loads (and returns) only metadata - # dataset.load_metadata - # # loads (and returns) only compounds - # dataset.load_compounds - # # loads (and returns) only features - # dataset.load_features - # # load all data from URI - # dataset.load_all - # - # # Getting dataset representations - # - # dataset = OpenTox::Dataset.new("http:://webservices.in-silico/ch/dataset/1") - # dataset.load_all - # # OWL-DL (RDF/XML) - # dataset.rdfxml - # # OWL-DL (Ntriples) - # dataset.ntriples - # # YAML - # dataset.yaml - # # CSV - # dataset.csv - # - # # Modifying datasets - # - # # insert a statement (compound_uri,feature_uri,value) - # dataset.add "http://webservices.in-silico.ch/compound/InChI=1S/C6Cl6/c7-1-2(8)4(10)6(12)5(11)3(1)9", "http://webservices.in-silico.ch/dataset/1/feature/hamster_carcinogenicity", true - # - # - # # Saving datasets - # # save dataset at dataset service - # dataset.save - # - # # Deleting datasets - # # delete dataset (also at dataset service) - # dataset.delete class Dataset - include OtObject + include OpenTox attr_reader :features, :compounds, :data_entries, :metadata - attr_writer :metadata - # Create dataset with optional URI + # Create dataset with optional URI. Does not load data into the dataset - you will need to execute one of the load_* methods to pull data from a service or to insert it from other representations. + # @example Create an empty dataset + # dataset = OpenTox::Dataset.new + # @example Create an empty dataset with URI + # dataset = OpenTox::Dataset.new("http:://webservices.in-silico/ch/dataset/1") + # @param [optional, String] uri Dataset URI + # @return [OpenTox::Dataset] Dataset object def initialize(uri=nil) super uri @features = {} @@ -76,52 +21,79 @@ module OpenTox @data_entries = {} end - # Create and save an empty dataset (assigns URI to dataset) + # Create an empty dataset and save it at the dataset service (assigns URI to dataset) + # @example Create new dataset and save it to obtain a URI + # dataset = OpenTox::Dataset.create + # @param [optional, String] uri Dataset URI + # @return [OpenTox::Dataset] Dataset object def self.create(uri=CONFIG[:services]["opentox-dataset"]) dataset = Dataset.new - dataset.uri = RestClientWrapper.post(uri,{}).to_s.chomp + dataset.save + dataset + end + + # Find a dataset and load all data. This can be time consuming, use Dataset.new together with one of the load_* methods for a fine grained control over data loading. + # @param [String] uri Dataset URI + # @return [OpenTox::Dataset] Dataset object with all data + def self.find(uri) + dataset = Dataset.new(uri) + dataset.load_all dataset end # Get all datasets from a service -# def self.all(uri=CONFIG[:services]["opentox-dataset"]) -# RestClientWrapper.get(uri,:accept => "text/uri-list").to_s.each_line.collect{|u| Dataset.new(u)} -# end + # @param [optional,String] uri URI of the dataset service, defaults to service specified in configuration + # @return [Array] Array of dataset object with all data + def self.all(uri=CONFIG[:services]["opentox-dataset"]) + RestClientWrapper.get(uri,:accept => "text/uri-list").to_s.each_line.collect{|u| Dataset.new(u)} + end - # Create a dataset from YAML string - def self.from_yaml(yaml) - dataset = Dataset.create - dataset.copy YAML.load(yaml) - dataset + # Load YAML representation into the dataset + # @param [String] yaml YAML representation of the dataset + # @return [OpenTox::Dataset] Dataset object with YAML data + def load_yaml(yaml) + copy YAML.load(yaml) + end + + # Load RDF/XML representation from a file + # @param [String] file File with RDF/XML representation of the dataset + # @return [OpenTox::Dataset] Dataset object with RDF/XML data + def load_rdfxml_file(file) + parser = Parser::Owl::Dataset.new @uri + parser.uri = file.path + copy parser.load_uri end - # Create dataset from CSV string (format specification: http://toxcreate.org/help) + # Load CSV string (format specification: http://toxcreate.org/help) # - loads data_entries, compounds, features # - sets metadata (warnings) for parser errors # - you will have to set remaining metadata manually - def self.from_csv(csv) - dataset = Dataset.create - Parser::Spreadsheet.new(dataset).load_csv(csv) - dataset + # @param [String] csv CSV representation of the dataset + # @return [OpenTox::Dataset] Dataset object with CSV data + def load_csv(csv) + save unless @uri # get a uri for creating features + parser = Parser::Spreadsheets.new + parser.dataset = self + parser.load_csv(csv) end - # Create dataset from Spreadsheet book (created with roo gem http://roo.rubyforge.org/, excel format specification: http://toxcreate.org/help)) + # Load Spreadsheet book (created with roo gem http://roo.rubyforge.org/, excel format specification: http://toxcreate.org/help)) # - loads data_entries, compounds, features # - sets metadata (warnings) for parser errors # - you will have to set remaining metadata manually - def self.from_spreadsheet(book) - dataset = Dataset.create - Parser::Spreadsheet.new(dataset).load_excel(book) - dataset + # @param [Excel] book Excel workbook object (created with roo gem) + # @return [OpenTox::Dataset] Dataset object with Excel data + def load_spreadsheet(book) + save unless @uri # get a uri for creating features + parser = Parser::Spreadsheets.new + parser.dataset = self + parser.load_excel(book) end - # Load and return metadata of a Dataset object + # Load and return only metadata of a Dataset object + # @return [Hash] Metadata of the dataset def load_metadata - #if (CONFIG[:yaml_hosts].include?(URI.parse(@uri).host)) - #add_metadata YAML.load(RestClientWrapper.get(File.join(@uri,"metadata"), :accept => "application/x-yaml")) - #else - add_metadata Parser::Owl::Dataset.new(@uri).metadata - #end + add_metadata Parser::Owl::Dataset.new(@uri).metadata self.uri = @uri if @uri # keep uri @metadata end @@ -136,7 +108,8 @@ module OpenTox end end - # Load and return all compound URIs + # Load and return only compound URIs from the dataset service + # @return [Array] Compound URIs in the dataset def load_compounds RestClientWrapper.get(File.join(uri,"compounds"),:accept=> "text/uri-list").to_s.each_line do |compound_uri| @compounds << compound_uri.chomp @@ -144,44 +117,75 @@ module OpenTox @compounds.uniq! end - # Load all feature URIs + # Load and return only features from the dataset service + # @return [Hash] Features of the dataset def load_features - RestClientWrapper.get(File.join(uri,"features"),:accept=> "text/uri-list").to_s.each_line do |feature_uri| - @features[feature_uri.chomp] = Feature.new(feature_uri.chomp).load_metadata - end + parser = Parser::Owl::Dataset.new(@uri) + @features = parser.load_features @features end - # Get YAML representation - def yaml - self.to_yaml + # Detect feature type(s) in the dataset + # @return [String] `classification", "regression", "mixed" or unknown` + def feature_type + feature_types = @features.collect{|f,metadata| metadata[OT.isA]}.uniq + LOGGER.debug "FEATURES" + LOGGER.debug feature_types.inspect + if feature_types.size > 1 + "mixed" + else + case feature_types.first + when /NominalFeature/ + "classification" + when /NumericFeature/ + "regression" + else + "unknown" + end + end end - # Get Excel representation, returns a Spreadsheet::Workbook which can be written with the 'spreadsheet' gem (data_entries only, metadata will ) - def excel - Serializer::Spreadsheets.new(self).excel + # Get Excel representation + # @return [Spreadsheet::Workbook] Workbook which can be written with the spreadsheet gem (data_entries only, metadata will will be discarded)) + def to_xls + Serializer::Spreadsheets.new(self).to_xls end # Get CSV string representation (data_entries only, metadata will be discarded) - def csv - Serializer::Spreadsheets.new(self).csv + # @return [String] CSV representation + def to_csv + Serializer::Spreadsheets.new(self).to_csv end # Get OWL-DL in ntriples format - def ntriples + # @return [String] N-Triples representation + def to_ntriples s = Serializer::Owl.new s.add_dataset(self) - s.ntriples + s.to_ntriples end # Get OWL-DL in RDF/XML format - def rdfxml + # @return [String] RDF/XML representation + def to_rdfxml s = Serializer::Owl.new s.add_dataset(self) - s.rdfxml + s.to_rdfxml + end + + # Get name (DC.title) of a feature + # @param [String] feature Feature URI + # @return [String] Feture title + def feature_name(feature) + @features[feature][DC.title] end # Insert a statement (compound_uri,feature_uri,value) + # @example Insert a statement (compound_uri,feature_uri,value) + # dataset.add "http://webservices.in-silico.ch/compound/InChI=1S/C6Cl6/c7-1-2(8)4(10)6(12)5(11)3(1)9", "http://webservices.in-silico.ch/dataset/1/feature/hamster_carcinogenicity", true + # @param [String] compound Compound URI + # @param [String] feature Compound URI + # @param [Boolean,Float] value Feature value def add (compound,feature,value) @compounds << compound unless @compounds.include? compound @features[feature] = {} unless @features[feature] @@ -190,252 +194,62 @@ module OpenTox @data_entries[compound][feature] << value end - # Add metadata (hash with predicate_uri => value) + # Add/modify metadata, existing entries will be overwritten + # @example + # dataset.add_metadata({DC.title => "any_title", DC.creator => "my_email"}) + # @param [Hash] metadata Hash mapping predicate_uris to values def add_metadata(metadata) metadata.each { |k,v| @metadata[k] = v } end - # Copy a dataset (rewrites URI) - def copy(dataset) - @metadata = dataset.metadata - @data_entries = dataset.data_entries - @compounds = dataset.compounds - @features = dataset.features - if @uri - self.uri = @uri - else - @uri = dataset.metadata[XSD.anyUri] - end + # Add a feature + # @param [String] feature Feature URI + # @param [Hash] metadata Hash with feature metadata + def add_feature(feature,metadata={}) + @features[feature] = metadata end - # save dataset (overwrites existing dataset) + # Add/modify metadata for a feature + # @param [String] feature Feature URI + # @param [Hash] metadata Hash with feature metadata + def add_feature_metadata(feature,metadata) + metadata.each { |k,v| @features[feature][k] = v } + end + + # Save dataset at the dataset service + # - creates a new dataset if uri is not set + # - overwrites dataset if uri exists + # @return [String] Dataset URI def save # TODO: rewrite feature URI's ?? - # create dataset if uri empty @compounds.uniq! - RestClientWrapper.post(@uri,{:content_type => "application/x-yaml"},self.to_yaml) + if @uri + RestClientWrapper.post(@uri,{:content_type => "application/x-yaml"},self.to_yaml) + else + # create dataset if uri is empty + self.uri = RestClientWrapper.post(CONFIG[:services]["opentox-dataset"],{}).to_s.chomp + RestClientWrapper.post(@uri,{:content_type => "application/x-yaml"},self.to_yaml) + end + @uri end # Delete dataset at the dataset service def delete RestClientWrapper.delete @uri end - end -end - - ######################################################### - # kept for backward compatibility, may have to be fixed # - ######################################################### - -=begin - def from_owl(owl) - # creates dataset object from Opentox::Owl object - # use Dataset.find( ) to load dataset from rdf-supporting datasetservice - # note: does not load all feature values, as this is time consuming - raise "invalid param" unless owl.is_a?(OpenTox::Owl) - @metadata[DC.title] = owl.get("title") - @metadata[DC.creator] = owl.get("creator") - @metadata[XSD.anyUri] = owl.uri - # when loading a dataset from owl, only compound- and feature-uris are loaded - owl.load_dataset(@compounds, @features) - # all features are marked as dirty - # as soon as a feature-value is requested all values for this feature are loaded from the rdf - @dirty_features = @features.dclone - @owl = owl - end - - def self.find(uri, accept_header=nil) - - unless accept_header - if (CONFIG[:yaml_hosts].include?(URI.parse(uri).host)) - accept_header = 'application/x-yaml' - else - accept_header = "application/rdf+xml" - end - end - - case accept_header - when "application/x-yaml" - LOGGER.debug "DATASET: "+ uri - LOGGER.debug RestClientWrapper.get(uri.to_s.strip, :accept => 'application/x-yaml').to_s - d = YAML.load RestClientWrapper.get(uri.to_s.strip, :accept => 'application/x-yaml').to_s - #d.uri = @metadata[XSD.anyUri] unless d.uri - when "application/rdf+xml" - owl = OpenTox::Owl.from_uri(uri.to_s.strip, "Dataset") - d = Dataset.new(owl) - else - raise "cannot get datset with accept header: "+accept_header.to_s - end - d - end - # converts a dataset represented in owl to yaml - # (uses a temporary dataset) - # note: to_yaml is overwritten, loads complete owl dataset values - def self.owl_to_yaml( owl_data, uri) - owl = OpenTox::Owl.from_data(owl_data, uri, "Dataset") - d = Dataset.new(owl) - d.to_yaml - end - - # creates a new dataset, using only those compounsd specified in new_compounds - # returns uri of new dataset - def create_new_dataset( new_compounds, new_features, new_title, new_creator ) - - LOGGER.debug "create new dataset with "+new_compounds.size.to_s+"/"+compounds.size.to_s+" compounds" - raise "no new compounds selected" unless new_compounds and new_compounds.size>0 - - # load require features - if ((defined? @dirty_features) && (@dirty_features & new_features).size > 0) - (@dirty_features & new_features).each{|f| load_feature_values(f)} - end - - dataset = OpenTox::Dataset.new - dataset.title = new_title - dataset.creator = new_creator - dataset.features = new_features - dataset.compounds = new_compounds - - # Copy dataset data for compounds and features - # PENDING: why storing feature values in an array? - new_compounds.each do |c| - data_c = [] - raise "no data for compound '"+c.to_s+"'" if @data[c]==nil - @data[c].each do |d| - m = {} - new_features.each do |f| - m[f] = d[f] - end - data_c << m - end - dataset.data[c] = data_c - end - return dataset.save - end - - # returns classification value - def get_predicted_class(compound, feature) - v = get_value(compound, feature) - if v.is_a?(Hash) - k = v.keys.grep(/classification/).first - unless k.empty? - #if v.has_key?(:classification) - return v[k] - else - return "no classification key" - end - elsif v.is_a?(Array) - raise "predicted class value is an array\n"+ - "value "+v.to_s+"\n"+ - "value-class "+v.class.to_s+"\n"+ - "dataset "+self.uri.to_s+"\n"+ - "compound "+compound.to_s+"\n"+ - "feature "+feature.to_s+"\n" - else - return v - end - end - - # returns regression value - def get_predicted_regression(compound, feature) - v = get_value(compound, feature) - if v.is_a?(Hash) - k = v.keys.grep(/regression/).first - unless k.empty? - return v[k] - else - return "no regression key" - end - elsif v.is_a?(Array) - raise "predicted regression value is an array\n"+ - "value "+v.to_s+"\n"+ - "value-class "+v.class.to_s+"\n"+ - "dataset "+self.uri.to_s+"\n"+ - "compound "+compound.to_s+"\n"+ - "feature "+feature.to_s+"\n" - else - return v - end - end - - # returns prediction confidence if available - def get_prediction_confidence(compound, feature) - v = get_value(compound, feature) - if v.is_a?(Hash) - k = v.keys.grep(/confidence/).first - unless k.empty? - #if v.has_key?(:confidence) - return v[k].abs - #return v["http://ot-dev.in-silico.ch/model/lazar#confidence"].abs - else - # PENDING: return nil isntead of raising an exception - raise "no confidence key" - end - else - LOGGER.warn "no confidence for compound: "+compound.to_s+", feature: "+feature.to_s - return 1 - end - end - - # return compound-feature value - def get_value(compound, feature) - if (defined? @dirty_features) && @dirty_features.include?(feature) - load_feature_values(feature) - end - - v = @data[compound] - return nil if v == nil # missing values for all features - if v.is_a?(Array) - # PENDING: why using an array here? - v.each do |e| - if e.is_a?(Hash) - if e.has_key?(feature) - return e[feature] - end - else - raise "invalid internal value type" - end - end - return nil #missing value - else - raise "value is not an array\n"+ - "value "+v.to_s+"\n"+ - "value-class "+v.class.to_s+"\n"+ - "dataset "+self.uri.to_s+"\n"+ - "compound "+compound.to_s+"\n"+ - "feature "+feature.to_s+"\n" - end - end - - # loads specified feature and removes dirty-flag, loads all features if feature is nil - def load_feature_values(feature=nil) - if feature - raise "feature already loaded" unless @dirty_features.include?(feature) - @owl.load_dataset_feature_values(@compounds, @data, [feature]) - @dirty_features.delete(feature) + private + # Copy a dataset (rewrites URI) + def copy(dataset) + @metadata = dataset.metadata + @data_entries = dataset.data_entries + @compounds = dataset.compounds + @features = dataset.features + if @uri + self.uri = @uri else - @data = {} unless @data - @owl.load_dataset_feature_values(@compounds, @data, @dirty_features) - @dirty_features.clear + @uri = dataset.metadata[XSD.anyURI] end end - - # overwrite to yaml: - # in case dataset is loaded from owl: - # * load all values - def to_yaml - # loads all features - if ((defined? @dirty_features) && @dirty_features.size > 0) - load_feature_values - end - super - end - - # * remove @owl from yaml, not necessary - def to_yaml_properties - super - ["@owl"] - end - end end -=end diff --git a/lib/environment.rb b/lib/environment.rb index b16b62f..d66b062 100644 --- a/lib/environment.rb +++ b/lib/environment.rb @@ -61,3 +61,32 @@ FALSE_REGEXP = /^(false|inactive|0|0.0)$/i # Task durations DEFAULT_TASK_MAX_DURATION = 36000 EXTERNAL_TASK_MAX_DURATION = 36000 + +# OWL Namespaces +class OwlNamespace + + def initialize(uri) + @uri = uri + end + + def [](property) + @uri+property.to_s + end + + def type # for RDF.type + "#{@uri}type" + end + + def method_missing(property) + @uri+property.to_s + end + +end + +RDF = OwlNamespace.new 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' +OWL = OwlNamespace.new 'http://www.w3.org/2002/07/owl#' +DC = OwlNamespace.new 'http://purl.org/dc/elements/1.1/' +OT = OwlNamespace.new 'http://www.opentox.org/api/1.1#' +OTA = OwlNamespace.new 'http://www.opentox.org/algorithmTypes.owl#' +XSD = OwlNamespace.new 'http://www.w3.org/2001/XMLSchema#' + diff --git a/lib/feature.rb b/lib/feature.rb index 9616135..13d97a2 100644 --- a/lib/feature.rb +++ b/lib/feature.rb @@ -1,7 +1,5 @@ module OpenTox - class Feature - include OtObject + include OpenTox end - end diff --git a/lib/model.rb b/lib/model.rb index d0d6703..63013cb 100644 --- a/lib/model.rb +++ b/lib/model.rb @@ -1,143 +1,410 @@ module OpenTox + module Model + include OpenTox + + def run(params) + if CONFIG[:yaml_hosts].include?(URI.parse(@uri).host) + accept = 'application/x-yaml' + else + accept = 'application/rdf+xml' + end + begin + params[:acccept] = accept + #TODO fix: REstClientWrapper does not accept accept header + #RestClientWrapper.post(@uri,params)#,{:accept => accept}) + `curl -X POST -H "Accept:#{accept}" #{params.collect{|k,v| "-d #{k}=#{v}"}.join(" ")} #{@uri}`.to_s.chomp + rescue => e + LOGGER.error "Failed to run #{@uri} with #{params.inspect} (#{e.inspect})" + raise "Failed to run #{@uri} with #{params.inspect}" + end + end + +=begin + def classification? + #TODO replace with request to ontology server + if @metadata[DC.title] =~ /(?i)classification/ + return true + elsif @metadata[DC.title] =~ /(?i)regression/ + return false + elsif @uri =~/ntua/ and @metadata[DC.title] =~ /mlr/ + return false + elsif @uri =~/tu-muenchen/ and @metadata[DC.title] =~ /regression|M5P|GaussP/ + return false + elsif @uri =~/ambit2/ and @metadata[DC.title] =~ /pKa/ || @metadata[DC.title] =~ /Regression|Caco/ + return false + elsif @uri =~/majority/ + return (@uri =~ /class/) != nil + else + raise "unknown model, uri:'"+@uri+"' title:'"+@metadata[DC.title]+"'" + end + end +=end + class Generic + include Model + end + + class Lazar + + include Model + + #attr_accessor :prediction_type, :feature_type, :features, :effects, :activities, :p_values, :fingerprints, :parameters + attr_accessor :compound, :prediction_dataset, :features, :effects, :activities, :p_values, :fingerprints, :parameters, :feature_calculation_algorithm, :similarity_algorithm, :prediction_algorithm + + def initialize(uri=nil) + + if uri + super uri + else + super CONFIG[:services]["opentox-model"] + end + + # TODO: fix metadata, add parameters + @metadata[OT.algorithm] = File.join(CONFIG[:services]["opentox-algorithm"],"lazar") + + @features = [] + @effects = {} + @activities = {} + @p_values = {} + @fingerprints = {} + + @feature_calculation_algorithm = "substructure_match" + @similarity_algorithm = "weighted_tanimoto" + @prediction_algorithm = "weighted_majority_vote" - MODEL_ATTRIBS = [:uri, :title, :creator, :date, :format, :predictedVariables, :independentVariables, :dependentVariables, :trainingDataset, :algorithm] - MODEL_ATTRIBS.each{ |a| attr_accessor(a) } + @min_sim = 0.3 + + end def self.find(uri) - owl = OpenTox::Owl.from_uri(uri, "Model") - return self.new(owl) - end - - def self.to_rdf(model) - owl = OpenTox::Owl.create 'Model', model.uri - (MODEL_ATTRIBS - [:uri]).each do |a| - owl.set(a.to_s,model.send(a.to_s)) + YAML.load RestClientWrapper.get(uri,:content_type => 'application/x-yaml') + end + + def self.create_from_dataset(dataset_uri,feature_dataset_uri,prediction_feature=nil) + training_activities = OpenTox::Dataset.find(dataset_uri) + training_features = OpenTox::Dataset.find(feature_dataset_uri) + unless prediction_feature # try to read prediction_feature from dataset + raise "#{training_activities.features.size} features in dataset #{dataset_uri}. Please provide a prediction_feature parameter." unless training_activities.features.size == 1 + prediction_feature = training_activities.features.keys.first + params[:prediction_feature] = prediction_feature + end + lazar = Lazar.new + training_features = OpenTox::Dataset.new(feature_dataset_uri) + case training_features.feature_type + when "classification" + lazar.similarity_algorithm = "weighted_tanimoto" + when "regression" + lazar.similarity_algorithm = "weighted_euclid" end - owl.rdf end - - protected - def initialize(owl) - MODEL_ATTRIBS.each do |a| - self.send("#{a.to_s}=".to_sym, owl.get(a.to_s)) unless a==:uri + + def self.create(dataset_uri,prediction_feature=nil,feature_generation_uri=File.join(CONFIG[:services]["opentox-algorithm"],"fminer/bbrc"),params=nil) + + training_activities = OpenTox::Dataset.find(dataset_uri) + + unless prediction_feature # try to read prediction_feature from dataset + raise "#{training_activities.features.size} features in dataset #{dataset_uri}. Please provide a prediction_feature parameter." unless training_activities.features.size == 1 + prediction_feature = training_activities.features.keys.first + params[:prediction_feature] = prediction_feature end - @uri = owl.uri - if ENV['RACK_ENV'] =~ /test|debug/ - begin - raise "uri invalid" unless Utils.is_uri?(@uri) - raise "no predicted variables" unless @predictedVariables and @predictedVariables.size>0 - rescue => ex - RestClientWrapper.raise_uri_error "invalid model: '"+ex.message+"'\n"+self.to_yaml+"\n",@uri.to_s + + lazar = Lazar.new + params[:feature_generation_uri] = feature_generation_uri + feature_dataset_uri = OpenTox::Algorithm::Generic.new(feature_generation_uri).run(params).to_s + training_features = OpenTox::Dataset.find(feature_dataset_uri) + raise "Dataset #{feature_dataset_uri} not found or empty." if training_features.nil? + + # sorted features for index lookups + lazar.features = training_features.features.sort if training_features.feature_type == "regression" + + training_features.data_entries.each do |compound,entry| + lazar.fingerprints[compound] = [] unless lazar.fingerprints[compound] + entry.keys.each do |feature| + case training_features.feature_type + when "fminer" + # fingerprints are sets + smarts = training_features.features[feature][OT.smarts] + lazar.fingerprints[compound] << smarts + unless lazar.features.include? smarts + lazar.features << smarts + lazar.p_values[smarts] = training_features.features[feature][OT.p_value] + lazar.effects[smarts] = training_features.features[feature][OT.effect] + end + when "classification" + # fingerprints are sets + if entry[feature].flatten.size == 1 + lazar.fingerprints[compound] << feature if entry[feature].flatten.first.match(TRUE_REGEXP) + lazar.features << feature unless lazar.features.include? feature + else + LOGGER.warn "More than one entry (#{entry[feature].inspect}) for compound #{compound}, feature #{feature}" + end + when "regression" + # fingerprints are arrays + if entry[feature].flatten.size == 1 + lazar.fingerprints[compound][lazar.features.index(feature)] = entry[feature].flatten.first + else + LOGGER.warn "More than one entry (#{entry[feature].inspect}) for compound #{compound}, feature #{feature}" + end + end + end + + lazar.activities[compound] = [] unless lazar.activities[compound] + training_activities.data_entries[compound][params[:prediction_feature]].each do |value| + case value.to_s + when "true" + lazar.activities[compound] << true + when "false" + lazar.activities[compound] << false + else + lazar.activities[compound] << value.to_f + lazar.prediction_type = "regression" + end end - LOGGER.warn "model has no dependent variable" unless @dependentVariables and @dependentVariables.size>0 - LOGGER.warn "model has no algorithm" unless @algorithm and @algorithm.size>0 - LOGGER.warn "model has no indenpendent variables" unless @independentVariables end + + if feature_generation_uri.match(/fminer/) + lazar.feature_calculation_algorithm = "substructure_match" + else + halt 404, "External feature generation services not yet supported" + end + + lazar.metadata[OT.dependentVariables] = params[:prediction_feature] + lazar.metadata[OT.trainingDataset] = dataset_uri + lazar.metadata[OT.featureDataset] = feature_dataset_uri + + lazar.parameters = { + "dataset_uri" => dataset_uri, + "prediction_feature" => prediction_feature, + "feature_generation_uri" => feature_generation_uri + } + + model_uri = lazar.save + LOGGER.info model_uri + " created #{Time.now}" + model_uri end - end - - class PredictionModel < Generic - - def self.build( algorithm_uri, algorithm_params ) - - LOGGER.debug "Build model, algorithm_uri:"+algorithm_uri.to_s+", algorithm_parms: "+algorithm_params.inspect.to_s - uri = OpenTox::RestClientWrapper.post(algorithm_uri,algorithm_params).to_s - LOGGER.debug "Build model done: "+uri.to_s - RestClientWrapper.raise_uri_error("Invalid build model result: '"+uri.to_s+"'", algorithm_uri, algorithm_params ) unless Utils.model_uri?(uri) - return PredictionModel.find(uri) - end - - def predict_dataset( dataset_uri ) - - LOGGER.debug "Predict dataset: "+dataset_uri.to_s+" with model "+@uri.to_s - uri = RestClientWrapper.post(@uri, {:accept => "text/uri-list", :dataset_uri=>dataset_uri}) - RestClientWrapper.raise_uri_error("Prediciton result no dataset uri: "+uri.to_s, @uri, {:dataset_uri=>dataset_uri} ) unless Utils.dataset_uri?(uri) - uri - end - - def classification? - #HACK replace with request to ontology server - if @title =~ /(?i)classification/ - return true - elsif @title =~ /(?i)regression/ - return false - elsif @uri =~/ntua/ and @title =~ /mlr/ - return false - elsif @uri =~/tu-muenchen/ and @title =~ /regression|M5P|GaussP/ - return false - elsif @uri =~/ambit2/ and @title =~ /pKa/ || @title =~ /Regression|Caco/ - return false - elsif @uri =~/majority/ - return (@uri =~ /class/) != nil + + def predict_dataset(dataset_uri) + @prediction_dataset = Dataset.create + @prediction_dataset.add_metadata({ + OT.hasSource => @lazar.uri, + DC.creator => @lazar.uri, + DC.title => URI.decode(File.basename( @metadata[OT.dependentVariables] )) + }) + @prediction_dataset.add_parameters({"dataset_uri" => dataset_uri}) + Dataset.new(dataset_uri).load_compounds.each do |compound_uri| + predict(compound_uri,false) + end + @prediction_dataset.save + @prediction_dataset.uri + end + + def predict(compound_uri,verbose=false) + + @compound = Compound.new compound_uri + + unless @prediction_dataset + @prediction_dataset = Dataset.create + @prediction_dataset.add_metadata( { + OT.hasSource => @lazar.uri, + DC.creator => @lazar.uri, + DC.title => URI.decode(File.basename( @metadata[OT.dependentVariables] )) + } ) + @prediction_dataset.add_parameters( {"compound_uri" => compound_uri} ) + end + + neighbors + eval @prediction_algorithm + + if @prediction + + feature_uri = File.join( @prediction_dataset.uri, "feature", @prediction_dataset.compounds.size) + @prediction_dataset.add @compound.uri, feature_uri, @prediction + + feature_metadata = @prediction_dataset.metadata + feature_metadata[DC.title] = File.basename(@metadata[OT.dependentVariables]) + feature_metadata[OT.prediction] = @prediction + feature_metadata[OT.confidence] = @confidence + @prediction_dataset.add_feature(feature_uri, feature_metadata) + + if verbose + if @compound_features + @compound_features.each do |feature| + @prediction_dataset.add @compound.uri, feature, true + end + end + n = 0 + @neighbors.sort{|a,b| a[:similarity] <=> b[:similarity]}.each do |neighbor| + neighbor_uri = File.join( @prediction_dataset.uri, "feature/neighbor", n ) + @prediction_dataset.add @compound.uri, neighbor_uri, true + @prediction_dataset.add_feature(neighbor, { + OT.compound => neighbor[:compound], + OT.similarity => neighbor[:similarity], + OT.activity => neighbor[:activity] + }) + n+=1 + end + end + end + @prediction_dataset.save + @prediction_dataset.uri + end + + def weighted_majority_vote + conf = 0.0 + @neighbors.each do |neighbor| + case neighbor[:activity].to_s + when 'true' + conf += OpenTox::Algorithm.gauss(neighbor[:similarity]) + when 'false' + conf -= OpenTox::Algorithm.gauss(neighbor[:similarity]) + end + end + if conf > 0.0 + @prediction = true + elsif conf < 0.0 + @prediction = false else - raise "unknown model, uri:'"+@uri.to_s+"' title:'"+@title.to_s+"'" + @prediction = nil end + @confidence = conf/@neighbors.size if @neighbors.size > 0 end - end - - class Lazar < Generic - - attr_accessor :feature_dataset_uri, :effects, :activities, :p_values, :fingerprints, :features - - def initialize - @source = "http://github.com/helma/opentox-model" - @algorithm = File.join(CONFIG[:services]["opentox-algorithm"],"lazar") - #@independent_variables = File.join(CONFIG[:services]["opentox-algorithm"],"fminer#BBRC_representative") - @features = [] - @effects = {} - @activities = {} - @p_values = {} - @fingerprints = {} + + def local_svm_regression + sims = @neighbors.collect{ |n| n[:similarity] } # similarity values between query and neighbors + conf = sims.inject{|sum,x| sum + x } + acts = @neighbors.collect do |n| + act = n[:activity] + # TODO: check this in model creation + raise "0 values not allowed in training dataset. log10 is calculated internally." if act.to_f == 0 + Math.log10(act.to_f) + end # activities of neighbors for supervised learning + + neighbor_matches = @neighbors.collect{ |n| n[:features] } # as in classification: URIs of matches + gram_matrix = [] # square matrix of similarities between neighbors; implements weighted tanimoto kernel + if neighbor_matches.size == 0 + raise "No neighbors found" + else + # gram matrix + (0..(neighbor_matches.length-1)).each do |i| + gram_matrix[i] = [] + # lower triangle + (0..(i-1)).each do |j| + sim = OpenTox::Algorithm.weighted_tanimoto(neighbor_matches[i], neighbor_matches[j], @lazar.p_values) + gram_matrix[i] << OpenTox::Algorithm.gauss(sim) + end + # diagonal element + gram_matrix[i][i] = 1.0 + # upper triangle + ((i+1)..(neighbor_matches.length-1)).each do |j| + sim = OpenTox::Algorithm.weighted_tanimoto(neighbor_matches[i], neighbor_matches[j], @lazar.p_values) # double calculation? + gram_matrix[i] << OpenTox::Algorithm.gauss(sim) + end + end + + @r = RinRuby.new(false,false) # global R instance leads to Socket errors after a large number of requests + @r.eval "library('kernlab')" # this requires R package "kernlab" to be installed + LOGGER.debug "Setting R data ..." + # set data + @r.gram_matrix = gram_matrix.flatten + @r.n = neighbor_matches.size + @r.y = acts + @r.sims = sims + + LOGGER.debug "Preparing R data ..." + # prepare data + @r.eval "y<-as.vector(y)" + @r.eval "gram_matrix<-as.kernelMatrix(matrix(gram_matrix,n,n))" + @r.eval "sims<-as.vector(sims)" + + # model + support vectors + LOGGER.debug "Creating SVM model ..." + @r.eval "model<-ksvm(gram_matrix, y, kernel=matrix, type=\"nu-svr\", nu=0.8)" + @r.eval "sv<-as.vector(SVindex(model))" + @r.eval "sims<-sims[sv]" + @r.eval "sims<-as.kernelMatrix(matrix(sims,1))" + LOGGER.debug "Predicting ..." + @r.eval "p<-predict(model,sims)[1,1]" + @prediction = 10**(@r.p.to_f) + LOGGER.debug "Prediction is: '" + prediction.to_s + "'." + @r.quit # free R + end + @confidence = conf/@neighbors.size if @neighbors.size > 0 + end - def save - @features.uniq! - resource = RestClient::Resource.new(CONFIG[:services]["opentox-model"]) - resource.post(self.to_yaml, :content_type => "application/x-yaml").chomp.to_s + def neighbors + + @compound_features = eval(@feature_calculation_algorithm) if @feature_calculation_algorithm + + @neighbors = {} + @activities.each do |training_compound,activities| + @training_compound = training_compound + sim = eval(@similarity_algorithm) + if sim > @min_sim + activities.each do |act| + @neighbors << { + :compound => @training_compound, + :similarity => sim, + :features => @fingerprints[@training_compound], + :activity => act + } + end + end + end + end - def self.find_all - RestClientWrapper.get(CONFIG[:services]["opentox-model"]).chomp.split("\n") + def tanimoto + OpenTox::Algorithm.tanimoto(@compound_features,@fingerprints[@training_compound]) end - def self.predict(compound_uri,model_uri) - #RestClientWrapper.post(model_uri,{:compound_uri => compound_uri, :accept => 'application/x-yaml'}) - `curl -X POST -d 'compound_uri=#{compound_uri}' -H 'Accept:application/x-yaml' #{model_uri}` + def weighted_tanimoto + OpenTox::Algorithm.tanimoto(@compound_features,@fingerprints[@training_compound],@p_values) end - end - - class PropertyLazar < Generic - - attr_accessor :feature_dataset_uri, :properties, :features, :activities#, :effects, :p_values - - def initialize - @source = "http://github.com/helma/opentox-model" - @algorithm = File.join(CONFIG[:services]["opentox-algorithm"],"property_lazar") - #@independent_variables = File.join(CONFIG[:services]["opentox-algorithm"],"fminer#BBRC_representative") - @features = [] - #@effects = {} - @activities = {} - #@p_values = {} - @properties = {} + + def euclid + OpenTox::Algorithm.tanimoto(@compound_features,@fingerprints[@training_compound]) + end + + def weighted_euclid + OpenTox::Algorithm.tanimoto(@compound_features,@fingerprints[@training_compound],@p_values) + end + + def substructure_match + @compound.match(@features) + end + + def database_search + #TODO add features method to dataset + Dataset.new(@metadata[OT.featureDataset]).features(@compound.uri) + end + + def database_activity(compound_uri) + prediction = OpenTox::Dataset.new + # find database activities + if @activities[compound_uri] + @activities[compound_uri].each { |act| prediction.add compound_uri, @metadata[OT.dependentVariables], act } + prediction.add_metadata(OT.hasSource => @metadata[OT.trainingDataset]) + prediction + else + nil + end end def save - @features.uniq! - resource = RestClient::Resource.new(CONFIG[:services]["opentox-model"]) - resource.post(self.to_yaml, :content_type => "application/x-yaml").chomp.to_s + RestClientWrapper.post(@uri,{:content_type => "application/x-yaml"},self.to_yaml) end - def self.find_all - RestClientWrapper.get(CONFIG[:services]["opentox-model"]).chomp.split("\n") + def self.all + RestClientWrapper.get(CONFIG[:services]["opentox-model"]).to_s.split("\n") end - def self.predict(compound_uri,model_uri) - #RestClientWrapper.post(model_uri,{:compound_uri => compound_uri, :accept => 'application/x-yaml'}) - `curl -X POST -d 'compound_uri=#{compound_uri}' -H 'Accept:application/x-yaml' #{model_uri}` + def delete + RestClientWrapper.delete @uri unless @uri == CONFIG[:services]["opentox-model"] end + end end end diff --git a/lib/opentox-ruby-api-wrapper.rb b/lib/opentox-ruby-api-wrapper.rb index 2749899..9dc1372 100644 --- a/lib/opentox-ruby-api-wrapper.rb +++ b/lib/opentox-ruby-api-wrapper.rb @@ -8,6 +8,6 @@ rescue LoadError puts "Please install Openbabel with 'rake openbabel:install' in the compound component" end -['opentox', 'compound','dataset', 'parser','serializer', 'algorithm','model','task','validation','utils','feature', 'ot-logger', 'overwrite', 'rest_client_wrapper'].each do |lib| +['opentox', 'compound','dataset', 'parser','serializer', 'algorithm','model','task','validation','feature', 'ot-logger', 'overwrite', 'rest_client_wrapper'].each do |lib| require lib end diff --git a/lib/opentox.rb b/lib/opentox.rb index 453ca66..7e1deec 100644 --- a/lib/opentox.rb +++ b/lib/opentox.rb @@ -1,79 +1,49 @@ module OpenTox - # Generic OpenTox class - module OtObject - - attr_reader :uri - attr_accessor :metadata - - # Initialize OpenTox object with optional uri - def initialize(uri=nil) - @metadata = {} - self.uri = uri if uri - end - - # Set URI - def uri=(uri) - @uri = uri - @metadata[XSD.anyUri] = uri - end - - # Get title - def title - load_metadata unless @metadata[DC.title] - @metadata[DC.title] - end - - # Set title - def title=(title) - @metadata[DC.title] = title - end - - # Get all objects from a service - def self.all(uri) - #def OtObject.all(uri) - RestClientWrapper.get(uri,:accept => "text/uri-list").to_s.split(/\n/) - end - - # Load metadata from URI - def load_metadata - #if (CONFIG[:yaml_hosts].include?(URI.parse(@uri).host)) - # TODO: fix metadata retrie - #@metadata = YAML.load(RestClientWrapper.get(@uri, :accept => "application/x-yaml")) - #else - @metadata = Parser::Owl::Generic.new(@uri).metadata - #end - @metadata - #Parser::Owl::Generic.new(@uri).metadata - end - + attr_reader :uri + attr_accessor :metadata, :parameters + + # Initialize OpenTox object with optional uri + # @param [optional, String] URI + def initialize(uri=nil) + @metadata = {} + self.uri = uri if uri end - module Owl - - class Namespace - - def initialize(uri) - @uri = uri - end + # Set URI + # @param [String] URI + def uri=(uri) + @uri = uri + @metadata[XSD.anyURI] = uri + end - def [](property) - @uri+property.to_s - end + # Get all objects from a service + # @return [Array] List of available URIs + def self.all(uri) + RestClientWrapper.get(uri,:accept => "text/uri-list").to_s.split(/\n/) + end - def method_missing(property) - @uri+property.to_s - end + # Load (and return) metadata from object URI + # @return [Hash] Metadata + def load_metadata + @metadata = Parser::Owl::Generic.new(@uri).metadata + @metadata + end - end + # Load parameters from URI + #def load_parameters + #@parameters = Parser::Owl::Generic.new(@uri).parameters + #@parameters + #end + + # Get OWL-DL representation in RDF/XML format + # @return [application/rdf+xml] RDF/XML representation + def to_rdfxml + s = Serializer::Owl.new + s.add_metadata(@uri,@metadata) + #s.add_parameters(@uri,@parameters) if @parameters + s.to_rdfxml end end -# -# OWL Namespaces -RDF = OpenTox::Owl::Namespace.new 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' -OWL = OpenTox::Owl::Namespace.new 'http://www.w3.org/2002/07/owl#' -DC = OpenTox::Owl::Namespace.new 'http://purl.org/dc/elements/1.1/' -OT = OpenTox::Owl::Namespace.new 'http://www.opentox.org/api/1.1#' -XSD = OpenTox::Owl::Namespace.new 'http://www.w3.org/2001/XMLSchema#' diff --git a/lib/overwrite.rb b/lib/overwrite.rb index 1d0161b..2e4c396 100644 --- a/lib/overwrite.rb +++ b/lib/overwrite.rb @@ -12,3 +12,25 @@ class Sinatra::Base end end +class String + def task_uri? + self.uri? && !self.match(/task/).nil? + end + + def dataset_uri? + self.uri? && !self.match(/dataset/).nil? + end + + def self.model_uri? + self.uri? && !self.match(/model/).nil? + end + + def uri? + begin + u = URI::parse(self) + return (u.scheme!=nil and u.host!=nil) + rescue URI::InvalidURIError + return false + end + end +end diff --git a/lib/parser.rb b/lib/parser.rb index e623bf5..8c173f9 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -1,5 +1,14 @@ require 'spreadsheet' require 'roo' + +class String + + def to_triple + self.chomp.split(' ',3).collect{|i| i.sub(/\s+.$/,'').gsub(/[<>"]/,'')} + end + +end + module OpenTox module Parser @@ -12,19 +21,28 @@ module OpenTox end def metadata - # TODO: load parameters + if @dataset uri = File.join(@uri,"metadata") else uri = @uri end + statements = [] - `rapper -i rdfxml -o ntriples #{uri}`.each_line do |line| - triple = line.chomp.split('> ') - statements << triple.collect{|i| i.sub(/\s+.$/,'').gsub(/[<>"]/,'')} - end - statements.each do |triple| + parameter_ids = [] + `rapper -i rdfxml -o ntriples #{uri} 2>/dev/null`.each_line do |line| + triple = line.to_triple @metadata[triple[1]] = triple[2].split('^^').first if triple[0] == @uri and triple[1] != RDF['type'] + statements << triple + parameter_ids << triple[2] if triple[1] == OT.parameters + end + unless parameter_ids.empty? + @metadata[OT.parameters] = [] + parameter_ids.each do |p| + parameter = {} + statements.each{ |t| parameter[t[1]] = t[2] if t[0] == p and t[1] != RDF['type']} + @metadata[OT.parameters] << parameter + end end @metadata end @@ -37,6 +55,8 @@ module OpenTox include Owl + attr_writer :uri + def initialize(uri) super uri @dataset = ::OpenTox::Dataset.new(@uri) @@ -47,11 +67,10 @@ module OpenTox feature_values = {} feature = {} other_statements = {} - ntriples = `rapper -i rdfxml -o ntriples #{@uri}` - ntriples.each_line do |line| + `rapper -i rdfxml -o ntriples #{@uri} 2>/dev/null`.each_line do |line| triple = line.chomp.split(' ',3) triple = triple[0..2].collect{|i| i.sub(/\s+.$/,'').gsub(/[<>"]/,'')} - case triple[1] # Ambit namespaces are case insensitive + case triple[1] when /#{OT.values}/i data[triple[0]] = {:compound => "", :values => []} unless data[triple[0]] data[triple[0]][:values] << triple[2] @@ -77,76 +96,84 @@ module OpenTox end def load_features - @dataset.features.keys.each do |feature| - @dataset.features[feature] = Parser::Owl::Generic.new(feature).metadata + uri = File.join(@uri,"features") + statements = [] + features = Set.new + `rapper -i rdfxml -o ntriples #{uri} 2>/dev/null`.each_line do |line| + triple = line.chomp.split('> ').collect{|i| i.sub(/\s+.$/,'').gsub(/[<>"]/,'')}[0..2] + statements << triple + features << triple[0] if triple[1] == RDF['type'] and triple[2] == OT.Feature + end + statements.each do |triple| + if features.include? triple[0] + @dataset.features[triple[0]] = {} unless @dataset.features[triple[0]] + @dataset.features[triple[0]][triple[1]] = triple[2].split('^^').first + end end + @dataset.features end + end end - class Spreadsheet + class Spreadsheets + # TODO: expand for multiple columns + + attr_accessor :dataset + def initialize + + # TODO: fix 2 datasets created + #@dataset = Dataset.create + #@dataset.save # get uri + + @data = [] + @features = [] + @feature_types = {} - def initialize(dataset) - @dataset = dataset @format_errors = "" @smiles_errors = [] @activity_errors = [] @duplicates = {} - @nr_compounds = 0 - @data = [] - @activities = [] - @type = "classification" end def load_excel(book) book.default_sheet = 0 - 1.upto(book.last_row) do |row| - if row == 1 - @feature = File.join(@dataset.uri,"feature",book.cell(row,2)) - else - add( book.cell(row,1), book.cell(row,2), row ) # smiles, activity - end - end - parse + add_features book.row(1) + 2.upto(book.last_row) { |i| add_values book.row(i) } + warnings + @dataset end def load_csv(csv) row = 0 - csv.each_line do |line| - row += 1 - raise "Invalid CSV format at line #{row}: #{line.chomp}" unless line.chomp.match(/^.+[,;].*$/) # check CSV format - items = line.chomp.gsub(/["']/,'').split(/\s*[,;]\s*/) # remove quotes - if row == 1 - @feature = File.join(@dataset.uri,"feature",items[1]) - else - add(items[0], items[1], row) - end - end - parse + input = csv.split("\n") + add_features split_row(input.shift) + input.each { |row| add_values split_row(row) } + warnings + @dataset end - def parse + private - # create dataset - @data.each do |items| - case @type - when "classification" - case items[1].to_s - when TRUE_REGEXP - @dataset.add(items[0], @feature, true ) - when FALSE_REGEXP - @dataset.add(items[0], @feature, false) - end - when "regression" - if items[1].to_f == 0 - @activity_errors << "Row #{items[2]}: Zero values not allowed for regression datasets - entry ignored." - else - @dataset.add items[0], @feature, items[1].to_f - end + def warnings + + info = '' + @feature_types.each do |feature,types| + if types.uniq.size > 1 + type = OT.NumericFeature + else + type = types.first end + @dataset.add_feature_metadata(feature,{OT.isA => type}) + info += "\"#{@dataset.feature_name(feature)}\" detected as #{type.split('#').last}." + + # TODO: rewrite feature values + # TODO if value.to_f == 0 @activity_errors << "#{smiles} Zero values not allowed for regression datasets - entry ignored." end + @dataset.metadata[OT.Info] = info + warnings = '' warnings += "

Incorrect Smiles structures (ignored):

" + @smiles_errors.join("
") unless @smiles_errors.empty? warnings += "

Irregular activities (ignored):

" + @activity_errors.join("
") unless @activity_errors.empty? @@ -156,34 +183,75 @@ module OpenTox @dataset.metadata[OT.Warnings] = warnings - @dataset + end + def add_features(row) + row.shift # get rid of smiles entry + row.each do |feature_name| + feature_uri = File.join(@dataset.uri,"feature",URI.encode(feature_name)) + @feature_types[feature_uri] = [] + @features << feature_uri + @dataset.add_feature(feature_uri,{DC.title => feature_name}) + end end - def add(smiles, act, row) + def add_values(row) + + smiles = row.shift compound = Compound.from_smiles(smiles) if compound.nil? or compound.inchi.nil? or compound.inchi == "" - @smiles_errors << "Row #{row}: " + [smiles,act].join(", ") - return false - end - unless numeric?(act) or classification?(act) - @activity_errors << "Row #{row}: " + [smiles,act].join(", ") + @smiles_errors << smiles+", "+row.join(", ") return false end @duplicates[compound.inchi] = [] unless @duplicates[compound.inchi] - @duplicates[compound.inchi] << "Row #{row}: " + [smiles, act].join(", ") - @type = "regression" unless classification?(act) - # TODO: set OT.NumericalFeature, ... - @nr_compounds += 1 - @data << [ compound.uri, act , row ] + @duplicates[compound.inchi] << smiles+", "+row.join(", ") + + row.each_index do |i| + value = row[i] + feature = @features[i] + type = feature_type(value) + + @feature_types[feature] << type + + case type + when OT.NominalFeature + case value.to_s + when TRUE_REGEXP + @dataset.add(compound.uri, feature, true ) + when FALSE_REGEXP + @dataset.add(compound.uri, feature, false ) + end + when OT.NumericFeature + @dataset.add compound.uri, feature, value.to_f + when OT.StringFeature + # TODO: insert ?? + @dataset.add compound.uri, feature, value.to_s + @activity_errors << smiles+", "+row.join(", ") + #return false + end + end + end + + def numeric?(value) + true if Float(value) rescue false end - def numeric?(object) - true if Float(object) rescue false + def classification?(value) + !value.to_s.strip.match(TRUE_REGEXP).nil? or !value.to_s.strip.match(FALSE_REGEXP).nil? + end + + def feature_type(value) + if classification? value + return OT.NominalFeature + elsif numeric? value + return OT.NumericFeature + else + return OT.StringFeature + end end - def classification?(object) - !object.to_s.strip.match(TRUE_REGEXP).nil? or !object.to_s.strip.match(FALSE_REGEXP).nil? + def split_row(row) + row.chomp.gsub(/["']/,'').split(/\s*[,;]\s*/) # remove quotes end end diff --git a/lib/rest_client_wrapper.rb b/lib/rest_client_wrapper.rb index 82836d9..49549b5 100644 --- a/lib/rest_client_wrapper.rb +++ b/lib/rest_client_wrapper.rb @@ -1,5 +1,3 @@ - - module OpenTox #PENDING: implement ot error api, move to own file @@ -60,7 +58,7 @@ module OpenTox def self.execute( rest_call, uri, headers, payload=nil, wait=true ) do_halt 400,"uri is null",uri,headers,payload unless uri - do_halt 400,"not a uri",uri,headers,payload unless Utils.is_uri?(uri) + do_halt 400,"not a uri",uri,headers,payload unless uri.to_s.uri? do_halt 400,"headers are no hash",uri,headers,payload unless headers==nil or headers.is_a?(Hash) do_halt 400,"nil headers for post not allowed, use {}",uri,headers,payload if rest_call=="post" and headers==nil headers.each{ |k,v| headers.delete(k) if v==nil } if headers #remove keys with empty values, as this can cause problems @@ -115,7 +113,7 @@ module OpenTox when /text\// raise "uri list has more than one entry, should be a task" if res.content_type=~/text\/uri-list/ and res.split("\n").size > 1 #if uri list contains more then one uri, its not a task - task = OpenTox::Task.find(res.to_s) if Utils.task_uri?(res) + task = OpenTox::Task.find(res.to_s) if res.to_s.uri? else raise "unknown content-type for task: '"+res.content_type.to_s+"'" #+"' content: "+res[0..200].to_s end diff --git a/lib/serializer.rb b/lib/serializer.rb index 3def252..3a9cb60 100644 --- a/lib/serializer.rb +++ b/lib/serializer.rb @@ -30,7 +30,6 @@ module OpenTox OT.dataEntry => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , OT.acceptValue => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , OT.values => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , - #XSD.anyUri => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , OT.algorithm => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , OT.parameters => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , @@ -38,14 +37,15 @@ module OpenTox DC.identifier => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , DC.contributor => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , DC.creator => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + DC.description => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , OT.isA => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.Warnings => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + XSD.anyURI => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , OT.hasSource => { RDF["type"] => [{ "type" => "uri", "value" => OWL.DatatypeProperty }] } , OT.value => { RDF["type"] => [{ "type" => "uri", "value" => OWL.DatatypeProperty }] } , OT.paramScope => { RDF["type"] => [{ "type" => "uri", "value" => OWL.DatatypeProperty }] } , OT.paramValue => { RDF["type"] => [{ "type" => "uri", "value" => OWL.DatatypeProperty }] } , - - #Untyped Individual: http://localhost/algorithm } @data_entries = {} @@ -61,15 +61,10 @@ module OpenTox end def add_compound(uri) - #@classes << OT.Compound unless @classes.include? OT.Compound @object[uri] = { RDF["type"] => [{ "type" => "uri", "value" => OT.Compound }] } end def add_feature(uri,metadata) - #@classes << OT.Feature unless @classes.include? OT.Feature - #@classes << OT.NominalFeature unless @classes.include? OT.NominalFeature - #@classes << OT.NumericFeature unless @classes.include? OT.NumericFeature - #@classes << OT.StringFeature unless @classes.include? OT.StringFeature @object[uri] = { RDF["type"] => [{ "type" => "uri", "value" => OT.Feature }] } add_metadata uri, metadata end @@ -94,32 +89,37 @@ module OpenTox end - def add_algorithm(uri,metadata,parameters) + def add_algorithm(uri,metadata) @object[uri] = { RDF["type"] => [{ "type" => "uri", "value" => OT.Algorithm }] } + LOGGER.debug @object[uri] add_metadata uri, metadata - add_parameters uri, parameters - #metadata.each { |u,v| @object[uri][u] = [{"type" => type(v), "value" => v }] } + LOGGER.debug @object[uri] end - def add_model(uri,metadata) + def add_model(uri,metadata,parameters) + @object[uri] = { RDF["type"] => [{ "type" => "uri", "value" => OT.Model }] } + add_metadata uri, metadata + add_parameters uri, parameters end def add_metadata(uri,metadata) - #@object[uri] = { RDF["type"] => [{ "type" => "uri", "value" => OT[type] }] } + id = 0 metadata.each do |u,v| - @object[uri][u] = [{"type" => type(v), "value" => v }] - end - end - - def add_parameters(uri,parameters) - #@object[uri] = { RDF["type"] => [{ "type" => "uri", "value" => OT[type] }] } - @object[uri][OT.parameters] = [] unless @object[uri][OT.parameters] - parameters.each do |p| - parameter = "_:parameter#{@parameter_id}" - @parameter_id += 1 - @object[uri][OT.parameters] << {"type" => "bnode", "value" => parameter} - @object[parameter] = { RDF["type"] => [{ "type" => "uri", "value" => OT.Parameter }] } - add_metadata parameter, p + if v.is_a? String + @object[uri] = {} unless @object[uri] + @object[uri][u] = [{"type" => type(v), "value" => v }] + elsif v.is_a? Array and u == OT.parameters + @object[uri][u] = [] unless @object[uri][u] + v.each do |value| + id+=1 + genid = "_:genid#{id}" + @object[uri][u] << {"type" => "bnode", "value" => genid} + @object[genid] = { RDF["type"] => [{ "type" => "uri", "value" => OT.Parameter}] } + value.each do |name,entry| + @object[genid][name] = [{"type" => type(entry), "value" => entry }] + end + end + end end end @@ -158,10 +158,11 @@ module OpenTox # Serializers - def ntriples + def to_ntriples #rdf_types @triples = Set.new + #LOGGER.debug @object.to_yaml @object.each do |s,entry| s = url(s) if type(s) == "uri" entry.each do |p,objects| @@ -182,12 +183,12 @@ module OpenTox @triples.sort.collect{ |s| s.join(' ').concat(" .") }.join("\n")+"\n" end - def rdfxml - Tempfile.open("owl-serializer"){|f| f.write(ntriples); @path = f.path} - `rapper -i ntriples -o rdfxml #{@path}` + def to_rdfxml + Tempfile.open("owl-serializer"){|f| f.write(self.to_ntriples); @path = f.path} + `rapper -i ntriples -o rdfxml #{@path} 2>/dev/null` end - def json + def to_json #rdf_types Yajl::Encoder.encode(@object) end @@ -258,7 +259,7 @@ module OpenTox @rows.first << features @rows.first.flatten! dataset.data_entries.each do |compound,entries| - smiles = Compound.new(compound).smiles + smiles = Compound.new(compound).to_smiles row = Array.new(@rows.first.size) row[0] = smiles entries.each do |feature, values| @@ -271,11 +272,11 @@ module OpenTox end end - def csv + def to_csv @rows.collect{|r| r.join(", ")}.join("\n") end - def excel + def to_xls Spreadsheet.client_encoding = 'UTF-8' book = Spreadsheet::Workbook.new sheet = book.create_worksheet(:name => '') diff --git a/lib/task.rb b/lib/task.rb index 50f0347..96ee719 100644 --- a/lib/task.rb +++ b/lib/task.rb @@ -3,6 +3,7 @@ $self_task=nil module OpenTox class Task + attr_accessor :uri, :date, :title, :creator, :description, :hasStatus, :percentageCompleted, :resultURI, :due_to_time, :http_code # due_to_time is only set in local tasks TASK_ATTRIBS = [ :uri, :date, :title, :creator, :description, :hasStatus, :percentageCompleted, :resultURI, :due_to_time ] @@ -124,14 +125,14 @@ module OpenTox def check_state begin raise "illegal task state, task is completed, resultURI is no URI: '"+@resultURI.to_s+ - "'" unless @resultURI and Utils.is_uri?(@resultURI) if completed? + "'" unless @resultURI and @resultURI.to_s.uri? if completed? if @http_code == 202 raise "illegal task state, code is 202, but hasStatus is not Running: '"+@hasStatus+"'" unless running? elsif @http_code == 201 raise "illegal task state, code is 201, but hasStatus is not Completed: '"+@hasStatus+"'" unless completed? raise "illegal task state, code is 201, resultURI is no task-URI: '"+@resultURI.to_s+ - "'" unless @resultURI and Utils.task_uri?(@resultURI) + "'" unless @resultURI and @resultURI.to_s.uri? end rescue => ex RestClientWrapper.raise_uri_error(ex.message, @uri) @@ -171,6 +172,7 @@ module OpenTox LOGGER.debug "Started task: "+task.uri.to_s task.uri end + end end diff --git a/lib/utils.rb b/lib/utils.rb deleted file mode 100644 index a0e0cbe..0000000 --- a/lib/utils.rb +++ /dev/null @@ -1,50 +0,0 @@ -module OpenTox - module Utils - # gauss kernel - def self.gauss(sim, sigma = 0.3) - x = 1.0 - sim - Math.exp(-(x*x)/(2*sigma*sigma)) - end - - def self.task_uri?(uri) - is_uri?(uri) && uri.to_s =~ /task/ - end - - def self.dataset_uri?(uri) - is_uri?(uri) && uri.to_s =~ /dataset/ - end - - def self.model_uri?(uri) - is_uri?(uri) && uri.to_s =~ /model/ - end - - - def self.is_uri?(uri) - return false if uri==nil || uri.to_s.size==0 - begin - u = URI::parse(uri) - return (u.scheme!=nil and u.host!=nil) - rescue URI::InvalidURIError - return false - end - end - - def self.median(array) - return nil if array.empty? - array.sort! - m_pos = array.size / 2 - return array.size % 2 == 1 ? array[m_pos] : (array[m_pos-1] + array[m_pos])/2 - end - - end - -# ['rubygems', 'rest_client'].each do |r| -# require r -# end -# ["bla", "google.de", "http://google.de"].each do |u| -# puts u+"? "+Utils.is_uri?(u).to_s -# end - - -end - -- cgit v1.2.3 From 91c95f8dc8f60a8f0029b970ef881eecee28401b Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Thu, 11 Nov 2010 10:42:48 +0100 Subject: Documentation and API fixes for serializer and parser --- lib/dataset.rb | 10 +++++----- lib/opentox.rb | 2 +- lib/parser.rb | 45 +++++++++++++++++++++++++++++++++++++-------- lib/serializer.rb | 37 +++++++++++++++++++++++++++++++++---- 4 files changed, 76 insertions(+), 18 deletions(-) diff --git a/lib/dataset.rb b/lib/dataset.rb index 05b2ed3..6e270e9 100644 --- a/lib/dataset.rb +++ b/lib/dataset.rb @@ -77,7 +77,7 @@ module OpenTox parser.load_csv(csv) end - # Load Spreadsheet book (created with roo gem http://roo.rubyforge.org/, excel format specification: http://toxcreate.org/help)) + # Load Spreadsheet book (created with roo gem http://roo.rubyforge.org/, excel format specification: http://toxcreate.org/help) # - loads data_entries, compounds, features # - sets metadata (warnings) for parser errors # - you will have to set remaining metadata manually @@ -87,13 +87,13 @@ module OpenTox save unless @uri # get a uri for creating features parser = Parser::Spreadsheets.new parser.dataset = self - parser.load_excel(book) + parser.load_spreadsheet(book) end # Load and return only metadata of a Dataset object # @return [Hash] Metadata of the dataset def load_metadata - add_metadata Parser::Owl::Dataset.new(@uri).metadata + add_metadata Parser::Owl::Dataset.new(@uri).load_metadata self.uri = @uri if @uri # keep uri @metadata end @@ -147,8 +147,8 @@ module OpenTox # Get Excel representation # @return [Spreadsheet::Workbook] Workbook which can be written with the spreadsheet gem (data_entries only, metadata will will be discarded)) - def to_xls - Serializer::Spreadsheets.new(self).to_xls + def to_spreadsheet + Serializer::Spreadsheets.new(self).to_spreadsheet end # Get CSV string representation (data_entries only, metadata will be discarded) diff --git a/lib/opentox.rb b/lib/opentox.rb index 7e1deec..3b7fa65 100644 --- a/lib/opentox.rb +++ b/lib/opentox.rb @@ -26,7 +26,7 @@ module OpenTox # Load (and return) metadata from object URI # @return [Hash] Metadata def load_metadata - @metadata = Parser::Owl::Generic.new(@uri).metadata + @metadata = Parser::Owl::Generic.new(@uri).load_metadata @metadata end diff --git a/lib/parser.rb b/lib/parser.rb index 8c173f9..4d8e729 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -3,6 +3,8 @@ require 'roo' class String + # Split RDF statement into triples + # @return [Array] Array with [subject,predicate,object] def to_triple self.chomp.split(' ',3).collect{|i| i.sub(/\s+.$/,'').gsub(/[<>"]/,'')} end @@ -11,16 +13,23 @@ end module OpenTox + # Parser for various input formats module Parser + # OWL-DL parser module Owl + # Create a new OWL-DL parser + # @param uri URI of OpenTox object + # @return [OpenTox::Parser::Owl] OWL-DL parser def initialize(uri) @uri = uri @metadata = {} end - def metadata + # Read metadata from opentox service + # @return [Hash] Object metadata + def load_metadata if @dataset uri = File.join(@uri,"metadata") @@ -47,21 +56,37 @@ module OpenTox @metadata end + # Generic parser for all OpenTox classes class Generic include Owl end + # OWL-DL parser for datasets class Dataset include Owl attr_writer :uri + # Create a new OWL-DL dataset parser + # @param uri Dataset URI + # @return [OpenTox::Parser::Owl::Dataset] OWL-DL parser def initialize(uri) super uri @dataset = ::OpenTox::Dataset.new(@uri) end + # Read data from dataset service. Files can be parsed by setting #uri to a filename (after initialization with a real URI) + # @example Read data from an external service + # parser = OpenTox::Parser::Owl::Dataaset.new "http://wwbservices.in-silico.ch/dataset/1" + # dataset = parser.load_uri + # @example Create dataset from RDF/XML file + # dataset = OpenTox::Dataset.create + # parser = OpenTox::Parser::Owl::Dataaset.new dataset.uri + # parser.uri = "dataset.rdfxml" # insert your input file + # dataset = parser.load_uri + # dataset.save + # @return [Hash] Internal dataset representation def load_uri data = {} feature_values = {} @@ -95,6 +120,8 @@ module OpenTox @dataset end + # Read only features from a dataset service. + # @return [Hash] Internal features representation def load_features uri = File.join(@uri,"features") statements = [] @@ -117,16 +144,12 @@ module OpenTox end + # Parser for getting spreadsheet data into a dataset class Spreadsheets - # TODO: expand for multiple columns attr_accessor :dataset - def initialize - - # TODO: fix 2 datasets created - #@dataset = Dataset.create - #@dataset.save # get uri + def initialize @data = [] @features = [] @feature_types = {} @@ -137,7 +160,10 @@ module OpenTox @duplicates = {} end - def load_excel(book) + # Load Spreadsheet book (created with roo gem http://roo.rubyforge.org/, excel format specification: http://toxcreate.org/help) + # @param [Excel] book Excel workbook object (created with roo gem) + # @return [OpenTox::Dataset] Dataset object with Excel data + def load_spreadsheet(book) book.default_sheet = 0 add_features book.row(1) 2.upto(book.last_row) { |i| add_values book.row(i) } @@ -145,6 +171,9 @@ module OpenTox @dataset end + # Load CSV string (format specification: http://toxcreate.org/help) + # @param [String] csv CSV representation of the dataset + # @return [OpenTox::Dataset] Dataset object with CSV data def load_csv(csv) row = 0 input = csv.split("\n") diff --git a/lib/serializer.rb b/lib/serializer.rb index 3a9cb60..31aa0d1 100644 --- a/lib/serializer.rb +++ b/lib/serializer.rb @@ -3,9 +3,10 @@ require 'yajl' module OpenTox + # Serialzer for various oputput formats module Serializer - # modelled according to to http://n2.talis.com/wiki/RDF_JSON_Specification + # OWL-DL Serializer, modelled according to to http://n2.talis.com/wiki/RDF_JSON_Specification class Owl attr_accessor :object @@ -60,15 +61,21 @@ module OpenTox @objects = Set.new end + # Add a compound + # @param [String] uri Compound URI def add_compound(uri) @object[uri] = { RDF["type"] => [{ "type" => "uri", "value" => OT.Compound }] } end + # Add a feature + # @param [String] uri Feature URI def add_feature(uri,metadata) @object[uri] = { RDF["type"] => [{ "type" => "uri", "value" => OT.Feature }] } add_metadata uri, metadata end + # Add a dataset + # @param [String] uri Dataset URI def add_dataset(dataset) @dataset = dataset.uri @@ -89,6 +96,8 @@ module OpenTox end + # Add a algorithm + # @param [String] uri Algorithm URI def add_algorithm(uri,metadata) @object[uri] = { RDF["type"] => [{ "type" => "uri", "value" => OT.Algorithm }] } LOGGER.debug @object[uri] @@ -96,12 +105,16 @@ module OpenTox LOGGER.debug @object[uri] end + # Add a model + # @param [String] uri Model URI def add_model(uri,metadata,parameters) @object[uri] = { RDF["type"] => [{ "type" => "uri", "value" => OT.Model }] } add_metadata uri, metadata add_parameters uri, parameters end + # Add metadata + # @param [Hash] metadata def add_metadata(uri,metadata) id = 0 metadata.each do |u,v| @@ -123,6 +136,10 @@ module OpenTox end end + # Add a data entry + # @param [String] compound Compound URI + # @param [String] feature Feature URI + # @param [Boolead,Float] value Feature value def add_data_entry(compound,feature,value) add_compound(compound) unless @object[compound] add_feature(feature,{}) unless @object[feature] @@ -158,11 +175,11 @@ module OpenTox # Serializers + # Convert to N-Triples + # @return [text/plain] Object OWL-DL in N-Triples format def to_ntriples - #rdf_types @triples = Set.new - #LOGGER.debug @object.to_yaml @object.each do |s,entry| s = url(s) if type(s) == "uri" entry.each do |p,objects| @@ -183,11 +200,16 @@ module OpenTox @triples.sort.collect{ |s| s.join(' ').concat(" .") }.join("\n")+"\n" end + # Convert to RDF/XML + # @return [text/plain] Object OWL-DL in RDF/XML format def to_rdfxml Tempfile.open("owl-serializer"){|f| f.write(self.to_ntriples); @path = f.path} `rapper -i ntriples -o rdfxml #{@path} 2>/dev/null` end + # Convert to JSON as specified in http://n2.talis.com/wiki/RDF_JSON_Specification + # (Ambit services use a different JSON representation) + # @return [text/plain] Object OWL-DL in JSON format def to_json #rdf_types Yajl::Encoder.encode(@object) @@ -250,8 +272,11 @@ module OpenTox end + # Serializer for spreadsheet formats class Spreadsheets # to avoid nameclash with Spreadsheet gem + # Create a new spreadsheet serializer + # @param [OpenTox::Dataset] dataset Dataset object def initialize(dataset) @rows = [] @rows << ["SMILES"] @@ -272,11 +297,15 @@ module OpenTox end end + # Convert to CSV string + # @return [String] CSV string def to_csv @rows.collect{|r| r.join(", ")}.join("\n") end - def to_xls + # Convert to spreadsheet workbook + # @return [Spreadsheet::Workbook] Workbook object (use the spreadsheet gemc to write a file) + def to_spreadsheet Spreadsheet.client_encoding = 'UTF-8' book = Spreadsheet::Workbook.new sheet = book.create_worksheet(:name => '') -- cgit v1.2.3 From f8552611c2dbe25d76474f51e4e895bf9c2b5c5e Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Fri, 19 Nov 2010 16:53:21 +0100 Subject: lazar predictions for toxcreate working --- Rakefile | 3 - lib/algorithm.rb | 154 +++++++++++-- lib/dataset.rb | 72 ++++++- lib/environment.rb | 8 +- lib/feature.rb | 10 + lib/model.rb | 466 ++++++++++++++++------------------------ lib/opentox-ruby-api-wrapper.rb | 4 +- lib/opentox.rb | 10 +- lib/ot-logger.rb | 48 ----- lib/overwrite.rb | 50 +++++ lib/parser.rb | 4 +- lib/rest_client_wrapper.rb | 16 +- lib/serializer.rb | 23 +- lib/task.rb | 278 +++++++++++++++--------- lib/validation.rb | 64 +++++- 15 files changed, 720 insertions(+), 490 deletions(-) delete mode 100644 lib/ot-logger.rb diff --git a/Rakefile b/Rakefile index 18f24bd..6838e75 100644 --- a/Rakefile +++ b/Rakefile @@ -21,14 +21,11 @@ begin "rack-flash", "nokogiri", "rubyzip", - #"builder", "roo", "spreadsheet", "google-spreadsheet-ruby", "tmail", "rinruby", - #"rdf", - #"rdf-raptor", "rjb" ].each { |dep| gem.add_dependency dep } [ "dm-core", diff --git a/lib/algorithm.rb b/lib/algorithm.rb index 711f63b..a6fa4a7 100644 --- a/lib/algorithm.rb +++ b/lib/algorithm.rb @@ -1,3 +1,9 @@ +# R integration +# workaround to initialize R non-interactively (former rinruby versions did this by default) +# avoids compiling R with X +R = nil +require "rinruby" + module OpenTox # Wrapper for OpenTox Algorithms @@ -6,8 +12,10 @@ module OpenTox include OpenTox # Execute algorithm with parameters, please consult the OpenTox API and the webservice documentation for acceptable parameters + # @param [optional,Hash] params Algorithm parameters + # @return [String] URI of new resource (dataset, model, ...) def run(params=nil) - RestClientWrapper.post(@uri, params) + RestClientWrapper.post(@uri, params).to_s end # Get OWL-DL representation in RDF/XML format @@ -23,9 +31,11 @@ module OpenTox include Algorithm end + # Fminer algorithms (https://github.com/amaunz/fminer2) module Fminer include Algorithm + # Backbone Refinement Class mining (http://bbrc.maunz.de/) class BBRC include Fminer # Initialize bbrc algorithm @@ -35,6 +45,7 @@ module OpenTox end end + # LAtent STructure Pattern Mining (http://last-pm.maunz.de) class LAST include Fminer # Initialize last algorithm @@ -58,15 +69,15 @@ module OpenTox # Utility methods without dedicated webservices + # Similarity calculations module Similarity include Algorithm # Tanimoto similarity - # # @param [Array] features_a Features of first compound # @param [Array] features_b Features of second compound # @param [optional, Hash] weights Weights for all features - # @return [Float] (Wighted) tanimoto similarity + # @return [Float] (Weighted) tanimoto similarity def self.tanimoto(features_a,features_b,weights=nil) common_features = features_a & features_b all_features = (features_a + features_b).uniq @@ -86,15 +97,19 @@ module OpenTox end # Euclidean similarity - def self.euclidean(prop_a,prop_b,weights=nil) - common_properties = prop_a.keys & prop_b.keys + # @param [Hash] properties_a Properties of first compound + # @param [Hash] properties_b Properties of second compound + # @param [optional, Hash] weights Weights for all properties + # @return [Float] (Weighted) euclidean similarity + def self.euclidean(properties_a,properties_b,weights=nil) + common_properties = properties_a.keys & properties_b.keys if common_properties.size > 1 dist_sum = 0 common_properties.each do |p| if weights - dist_sum += ( (prop_a[p] - prop_b[p]) * Algorithm.gauss(weights[p]) )**2 + dist_sum += ( (properties_a[p] - properties_b[p]) * Algorithm.gauss(weights[p]) )**2 else - dist_sum += (prop_a[p] - prop_b[p])**2 + dist_sum += (properties_a[p] - properties_b[p])**2 end end 1/(1+Math.sqrt(dist_sum)) @@ -103,14 +118,129 @@ module OpenTox end end end + + module Neighbors + + # Classification with majority vote from neighbors weighted by similarity + # @param [Array] neighbors, each neighbor is a hash with keys `:similarity, :activity` + # @param [optional] params Ignored (only for compatibility with local_svm_regression) + # @return [Hash] Hash with keys `:prediction, :confidence` + def self.weighted_majority_vote(neighbors,params={}) + conf = 0.0 + confidence = 0.0 + neighbors.each do |neighbor| + case neighbor[:activity].to_s + when 'true' + conf += Algorithm.gauss(neighbor[:similarity]) + when 'false' + conf -= Algorithm.gauss(neighbor[:similarity]) + end + end + if conf > 0.0 + prediction = true + elsif conf < 0.0 + prediction = false + else + prediction = nil + end + confidence = conf/neighbors.size if neighbors.size > 0 + {:prediction => prediction, :confidence => confidence.abs} + end + + # Local support vector regression from neighbors + # @param [Array] neighbors, each neighbor is a hash with keys `:similarity, :activity, :features` + # @param [Hash] params Keys `:similarity_algorithm,:p_values` are required + # @return [Hash] Hash with keys `:prediction, :confidence` + def self.local_svm_regression(neighbors,params ) + sims = neighbors.collect{ |n| n[:similarity] } # similarity values between query and neighbors + conf = sims.inject{|sum,x| sum + x } + acts = neighbors.collect do |n| + act = n[:activity] + Math.log10(act.to_f) + end # activities of neighbors for supervised learning + + neighbor_matches = neighbors.collect{ |n| n[:features] } # as in classification: URIs of matches + gram_matrix = [] # square matrix of similarities between neighbors; implements weighted tanimoto kernel + if neighbor_matches.size == 0 + raise "No neighbors found" + else + # gram matrix + (0..(neighbor_matches.length-1)).each do |i| + gram_matrix[i] = [] unless gram_matrix[i] + # upper triangle + ((i+1)..(neighbor_matches.length-1)).each do |j| + sim = eval("#{params[:similarity_algorithm]}(neighbor_matches[i], neighbor_matches[j], params[:p_values])") + gram_matrix[i][j] = Algorithm.gauss(sim) + gram_matrix[j] = [] unless gram_matrix[j] + gram_matrix[j][i] = gram_matrix[i][j] # lower triangle + end + gram_matrix[i][i] = 1.0 + end + + LOGGER.debug gram_matrix.to_yaml + + @r = RinRuby.new(false,false) # global R instance leads to Socket errors after a large number of requests + @r.eval "library('kernlab')" # this requires R package "kernlab" to be installed + LOGGER.debug "Setting R data ..." + # set data + @r.gram_matrix = gram_matrix.flatten + @r.n = neighbor_matches.size + @r.y = acts + @r.sims = sims + + LOGGER.debug "Preparing R data ..." + # prepare data + @r.eval "y<-as.vector(y)" + @r.eval "gram_matrix<-as.kernelMatrix(matrix(gram_matrix,n,n))" + @r.eval "sims<-as.vector(sims)" + + # model + support vectors + LOGGER.debug "Creating SVM model ..." + @r.eval "model<-ksvm(gram_matrix, y, kernel=matrix, type=\"nu-svr\", nu=0.8)" + @r.eval "sv<-as.vector(SVindex(model))" + @r.eval "sims<-sims[sv]" + @r.eval "sims<-as.kernelMatrix(matrix(sims,1))" + LOGGER.debug "Predicting ..." + @r.eval "p<-predict(model,sims)[1,1]" + prediction = 10**(@r.p.to_f) + LOGGER.debug "Prediction is: '" + @prediction.to_s + "'." + @r.quit # free R + end + confidence = conf/neighbors.size if neighbors.size > 0 + {:prediction => prediction, :confidence => confidence} + + end + + end + + module Substructure + include Algorithm + # Substructure matching + # @param [OpenTox::Compound] compound Compound + # @param [Array] features Array with Smarts strings + # @return [Array] Array with matching Smarts + def self.match(compound,features) + compound.match(features) + end + end + + module Dataset + include Algorithm + # API should match Substructure.match + def features(dataset_uri,compound_uri) + end + end - # Gauss kernel - def self.gauss(sim, sigma = 0.3) - x = 1.0 - sim - Math.exp(-(x*x)/(2*sigma*sigma)) - end + # Gauss kernel + # @return [Float] + def self.gauss(x, sigma = 0.3) + d = 1.0 - x + Math.exp(-(d*d)/(2*sigma*sigma)) + end # Median of an array + # @param [Array] Array with values + # @return [Float] Median def self.median(array) return nil if array.empty? array.sort! diff --git a/lib/dataset.rb b/lib/dataset.rb index 6e270e9..4737ea1 100644 --- a/lib/dataset.rb +++ b/lib/dataset.rb @@ -43,7 +43,7 @@ module OpenTox # Get all datasets from a service # @param [optional,String] uri URI of the dataset service, defaults to service specified in configuration - # @return [Array] Array of dataset object with all data + # @return [Array] Array of dataset object without data (use one of the load_* methods to pull data from the server) def self.all(uri=CONFIG[:services]["opentox-dataset"]) RestClientWrapper.get(uri,:accept => "text/uri-list").to_s.each_line.collect{|u| Dataset.new(u)} end @@ -55,6 +55,10 @@ module OpenTox copy YAML.load(yaml) end + def load_rdfxml(rdfxml) + load_rdfxml_file Tempfile.open("ot-rdfxml"){|f| f.write(rdfxml)}.path + end + # Load RDF/XML representation from a file # @param [String] file File with RDF/XML representation of the dataset # @return [OpenTox::Dataset] Dataset object with RDF/XML data @@ -129,8 +133,6 @@ module OpenTox # @return [String] `classification", "regression", "mixed" or unknown` def feature_type feature_types = @features.collect{|f,metadata| metadata[OT.isA]}.uniq - LOGGER.debug "FEATURES" - LOGGER.debug feature_types.inspect if feature_types.size > 1 "mixed" else @@ -145,12 +147,18 @@ module OpenTox end end - # Get Excel representation + # Get Spreadsheet representation # @return [Spreadsheet::Workbook] Workbook which can be written with the spreadsheet gem (data_entries only, metadata will will be discarded)) def to_spreadsheet Serializer::Spreadsheets.new(self).to_spreadsheet end + # Get Excel representation (alias for to_spreadsheet) + # @return [Spreadsheet::Workbook] Workbook which can be written with the spreadsheet gem (data_entries only, metadata will will be discarded)) + def to_xls + to_spreadsheet + end + # Get CSV string representation (data_entries only, metadata will be discarded) # @return [String] CSV representation def to_csv @@ -180,6 +188,10 @@ module OpenTox @features[feature][DC.title] end + def title + @metadata[DC.title] + end + # Insert a statement (compound_uri,feature_uri,value) # @example Insert a statement (compound_uri,feature_uri,value) # dataset.add "http://webservices.in-silico.ch/compound/InChI=1S/C6Cl6/c7-1-2(8)4(10)6(12)5(11)3(1)9", "http://webservices.in-silico.ch/dataset/1/feature/hamster_carcinogenicity", true @@ -224,11 +236,18 @@ module OpenTox # TODO: rewrite feature URI's ?? @compounds.uniq! if @uri - RestClientWrapper.post(@uri,{:content_type => "application/x-yaml"},self.to_yaml) + if (CONFIG[:yaml_hosts].include?(URI.parse(@uri).host)) + RestClientWrapper.post(@uri,{:content_type => "application/x-yaml"},self.to_yaml) + else + File.open("ot-post-file.rdf","w+") { |f| f.write(self.to_rdfxml); @path = f.path } + task_uri = RestClient.post(@uri, {:file => File.new(@path)},{:accept => "text/uri-list"}).to_s.chomp + #task_uri = `curl -X POST -H "Accept:text/uri-list" -F "file=@#{@path};type=application/rdf+xml" http://apps.ideaconsult.net:8080/ambit2/dataset` + Task.find(task_uri).wait_for_completion + self.uri = RestClientWrapper.get(task_uri,:accept => 'text/uri-list') + end else # create dataset if uri is empty self.uri = RestClientWrapper.post(CONFIG[:services]["opentox-dataset"],{}).to_s.chomp - RestClientWrapper.post(@uri,{:content_type => "application/x-yaml"},self.to_yaml) end @uri end @@ -252,4 +271,45 @@ module OpenTox end end end + + # Class with special methods for lazar prediction datasets + class LazarPrediction < Dataset + + # Find a prediction dataset and load all data. + # @param [String] uri Prediction dataset URI + # @return [OpenTox::Dataset] Prediction dataset object with all data + def self.find(uri) + prediction = LazarPrediction.new(uri) + prediction.load_all + prediction + end + + def value(compound) + @data_entries[compound.uri].collect{|f,v| v.first if f.match(/prediction/)}.compact.first + end + + def confidence(compound) + feature_uri = @data_entries[compound.uri].collect{|f,v| f if f.match(/prediction/)}.compact.first + @features[feature_uri][OT.confidence] + end + + def descriptors(compound) + @data_entries[compound.uri].collect{|f,v| @features[f] if f.match(/descriptor/)}.compact if @data_entries[compound.uri] + end + + def measured_activities(compound) + source = @metadata[OT.hasSource] + @data_entries[compound.uri].collect{|f,v| v if f.match(/#{source}/)}.compact + end + + def neighbors(compound) + @data_entries[compound.uri].collect{|f,v| @features[f] if f.match(/neighbor/)}.compact + end + +# def errors(compound) +# features = @data_entries[compound.uri].keys +# features.collect{|f| @features[f][OT.error]}.join(" ") if features +# end + + end end diff --git a/lib/environment.rb b/lib/environment.rb index d66b062..4f1cc80 100644 --- a/lib/environment.rb +++ b/lib/environment.rb @@ -1,4 +1,3 @@ -require "ot-logger" # set default environment ENV['RACK_ENV'] = 'production' unless ENV['RACK_ENV'] @@ -45,8 +44,8 @@ end load File.join config_dir,"mail.rb" if File.exists?(File.join config_dir,"mail.rb") logfile = "#{LOG_DIR}/#{ENV["RACK_ENV"]}.log" -#LOGGER = MyLogger.new(logfile,'daily') # daily rotation -LOGGER = MyLogger.new(logfile) # no rotation +#LOGGER = OTLogger.new(logfile,'daily') # daily rotation +LOGGER = OTLogger.new(logfile) # no rotation LOGGER.formatter = Logger::Formatter.new #this is neccessary to restore the formating in case active-record is loaded if CONFIG[:logger] and CONFIG[:logger] == "debug" LOGGER.level = Logger::DEBUG @@ -60,11 +59,12 @@ FALSE_REGEXP = /^(false|inactive|0|0.0)$/i # Task durations DEFAULT_TASK_MAX_DURATION = 36000 -EXTERNAL_TASK_MAX_DURATION = 36000 +#EXTERNAL_TASK_MAX_DURATION = 36000 # OWL Namespaces class OwlNamespace + attr_accessor :uri def initialize(uri) @uri = uri end diff --git a/lib/feature.rb b/lib/feature.rb index 13d97a2..9e28077 100644 --- a/lib/feature.rb +++ b/lib/feature.rb @@ -1,5 +1,15 @@ module OpenTox class Feature include OpenTox + + def self.find(uri) + feature = Feature.new uri + if (CONFIG[:yaml_hosts].include?(URI.parse(uri).host)) + feature.add_metadata YAML.load(RestClientWrapper.get(uri,:accept => "application/x-yaml")) + else + feature.add_metadata Parser::Owl::Dataset.new(uri).load_metadata + end + feature + end end end diff --git a/lib/model.rb b/lib/model.rb index 63013cb..c6a2cf4 100644 --- a/lib/model.rb +++ b/lib/model.rb @@ -4,6 +4,9 @@ module OpenTox include OpenTox + # Run a model with parameters + # @param [Hash] params Parameters for OpenTox model + # @return [text/uri-list] Task or resource URI def run(params) if CONFIG[:yaml_hosts].include?(URI.parse(@uri).host) accept = 'application/x-yaml' @@ -11,47 +14,25 @@ module OpenTox accept = 'application/rdf+xml' end begin - params[:acccept] = accept - #TODO fix: REstClientWrapper does not accept accept header - #RestClientWrapper.post(@uri,params)#,{:accept => accept}) - `curl -X POST -H "Accept:#{accept}" #{params.collect{|k,v| "-d #{k}=#{v}"}.join(" ")} #{@uri}`.to_s.chomp + RestClientWrapper.post(@uri,{:accept => accept},params).to_s rescue => e LOGGER.error "Failed to run #{@uri} with #{params.inspect} (#{e.inspect})" raise "Failed to run #{@uri} with #{params.inspect}" end end - -=begin - def classification? - #TODO replace with request to ontology server - if @metadata[DC.title] =~ /(?i)classification/ - return true - elsif @metadata[DC.title] =~ /(?i)regression/ - return false - elsif @uri =~/ntua/ and @metadata[DC.title] =~ /mlr/ - return false - elsif @uri =~/tu-muenchen/ and @metadata[DC.title] =~ /regression|M5P|GaussP/ - return false - elsif @uri =~/ambit2/ and @metadata[DC.title] =~ /pKa/ || @metadata[DC.title] =~ /Regression|Caco/ - return false - elsif @uri =~/majority/ - return (@uri =~ /class/) != nil - else - raise "unknown model, uri:'"+@uri+"' title:'"+@metadata[DC.title]+"'" - end - end -=end + # Generic OpenTox model class for all API compliant services class Generic include Model end + # Lazy Structure Activity Relationship class class Lazar include Model + include Algorithm - #attr_accessor :prediction_type, :feature_type, :features, :effects, :activities, :p_values, :fingerprints, :parameters - attr_accessor :compound, :prediction_dataset, :features, :effects, :activities, :p_values, :fingerprints, :parameters, :feature_calculation_algorithm, :similarity_algorithm, :prediction_algorithm + attr_accessor :compound, :prediction_dataset, :features, :effects, :activities, :p_values, :fingerprints, :feature_calculation_algorithm, :similarity_algorithm, :prediction_algorithm, :min_sim def initialize(uri=nil) @@ -61,7 +42,6 @@ module OpenTox super CONFIG[:services]["opentox-model"] end - # TODO: fix metadata, add parameters @metadata[OT.algorithm] = File.join(CONFIG[:services]["opentox-algorithm"],"lazar") @features = [] @@ -70,284 +50,192 @@ module OpenTox @p_values = {} @fingerprints = {} - @feature_calculation_algorithm = "substructure_match" - @similarity_algorithm = "weighted_tanimoto" - @prediction_algorithm = "weighted_majority_vote" + @feature_calculation_algorithm = "Substructure.match" + @similarity_algorithm = "Similarity.tanimoto" + @prediction_algorithm = "Neighbors.weighted_majority_vote" @min_sim = 0.3 end - def self.find(uri) - YAML.load RestClientWrapper.get(uri,:content_type => 'application/x-yaml') + # Get URIs of all lazar models + # @return [Array] List of lazar model URIs + def self.all + RestClientWrapper.get(CONFIG[:services]["opentox-model"]).to_s.split("\n") end - def self.create_from_dataset(dataset_uri,feature_dataset_uri,prediction_feature=nil) - training_activities = OpenTox::Dataset.find(dataset_uri) - training_features = OpenTox::Dataset.find(feature_dataset_uri) - unless prediction_feature # try to read prediction_feature from dataset - raise "#{training_activities.features.size} features in dataset #{dataset_uri}. Please provide a prediction_feature parameter." unless training_activities.features.size == 1 - prediction_feature = training_activities.features.keys.first - params[:prediction_feature] = prediction_feature - end - lazar = Lazar.new - training_features = OpenTox::Dataset.new(feature_dataset_uri) - case training_features.feature_type - when "classification" - lazar.similarity_algorithm = "weighted_tanimoto" - when "regression" - lazar.similarity_algorithm = "weighted_euclid" - end + # Find a lazar model + # @param [String] uri Model URI + # @return [OpenTox::Model::Lazar] lazar model + def self.find(uri) + YAML.load RestClientWrapper.get(uri,:accept => 'application/x-yaml') end - def self.create(dataset_uri,prediction_feature=nil,feature_generation_uri=File.join(CONFIG[:services]["opentox-algorithm"],"fminer/bbrc"),params=nil) - - training_activities = OpenTox::Dataset.find(dataset_uri) - - unless prediction_feature # try to read prediction_feature from dataset - raise "#{training_activities.features.size} features in dataset #{dataset_uri}. Please provide a prediction_feature parameter." unless training_activities.features.size == 1 - prediction_feature = training_activities.features.keys.first - params[:prediction_feature] = prediction_feature - end - - lazar = Lazar.new - params[:feature_generation_uri] = feature_generation_uri - feature_dataset_uri = OpenTox::Algorithm::Generic.new(feature_generation_uri).run(params).to_s - training_features = OpenTox::Dataset.find(feature_dataset_uri) - raise "Dataset #{feature_dataset_uri} not found or empty." if training_features.nil? - - # sorted features for index lookups - lazar.features = training_features.features.sort if training_features.feature_type == "regression" - - training_features.data_entries.each do |compound,entry| - lazar.fingerprints[compound] = [] unless lazar.fingerprints[compound] - entry.keys.each do |feature| - case training_features.feature_type - when "fminer" - # fingerprints are sets - smarts = training_features.features[feature][OT.smarts] - lazar.fingerprints[compound] << smarts - unless lazar.features.include? smarts - lazar.features << smarts - lazar.p_values[smarts] = training_features.features[feature][OT.p_value] - lazar.effects[smarts] = training_features.features[feature][OT.effect] - end - when "classification" - # fingerprints are sets - if entry[feature].flatten.size == 1 - lazar.fingerprints[compound] << feature if entry[feature].flatten.first.match(TRUE_REGEXP) - lazar.features << feature unless lazar.features.include? feature - else - LOGGER.warn "More than one entry (#{entry[feature].inspect}) for compound #{compound}, feature #{feature}" - end - when "regression" - # fingerprints are arrays - if entry[feature].flatten.size == 1 - lazar.fingerprints[compound][lazar.features.index(feature)] = entry[feature].flatten.first - else - LOGGER.warn "More than one entry (#{entry[feature].inspect}) for compound #{compound}, feature #{feature}" - end - end - end - - lazar.activities[compound] = [] unless lazar.activities[compound] - training_activities.data_entries[compound][params[:prediction_feature]].each do |value| - case value.to_s - when "true" - lazar.activities[compound] << true - when "false" - lazar.activities[compound] << false - else - lazar.activities[compound] << value.to_f - lazar.prediction_type = "regression" - end - end - end - - if feature_generation_uri.match(/fminer/) - lazar.feature_calculation_algorithm = "substructure_match" - else - halt 404, "External feature generation services not yet supported" - end - - lazar.metadata[OT.dependentVariables] = params[:prediction_feature] - lazar.metadata[OT.trainingDataset] = dataset_uri - lazar.metadata[OT.featureDataset] = feature_dataset_uri + # Create a new lazar model + # @param [optional,Hash] params Parameters for the lazar algorithm (OpenTox::Algorithm::Lazar) + # @return [OpenTox::Model::Lazar] lazar model + def self.create(params) + lazar_algorithm = OpenTox::Algorithm::Generic.new File.join( CONFIG[:services]["opentox-algorithm"],"lazar") + model_uri = lazar_algorithm.run(params) + OpenTox::Model::Lazar.find(model_uri) + end - lazar.parameters = { - "dataset_uri" => dataset_uri, - "prediction_feature" => prediction_feature, - "feature_generation_uri" => feature_generation_uri - } - - model_uri = lazar.save - LOGGER.info model_uri + " created #{Time.now}" - model_uri +=begin + # Create a new lazar model and return task + # @param [optional,Hash] params Parameters for the lazar algorithm (OpenTox::Algorithm::Lazar) + # @return [OpenTox::Task] Task for lazar model creation + def self.create_task(params) + task_uri = RestClientWrapper.post(File.join(CONFIG[:services]["opentox-algorithm"],"lazar"), {}, params, false) + Task.find(task_uri) + #model_uri = lazar_algorithm.run(params) + #OpenTox::Model::Lazar.new(model_uri) + end +=end + def parameter(param) + @metadata[OT.parameters].collect{|p| p[OT.paramValue] if p[DC.title] == param}.compact.first end def predict_dataset(dataset_uri) @prediction_dataset = Dataset.create @prediction_dataset.add_metadata({ - OT.hasSource => @lazar.uri, - DC.creator => @lazar.uri, - DC.title => URI.decode(File.basename( @metadata[OT.dependentVariables] )) + OT.hasSource => @uri, + DC.creator => @uri, + DC.title => URI.decode(File.basename( @metadata[OT.dependentVariables] )), + OT.parameters => [{DC.title => "dataset_uri", OT.paramValue => dataset_uri}] }) - @prediction_dataset.add_parameters({"dataset_uri" => dataset_uri}) - Dataset.new(dataset_uri).load_compounds.each do |compound_uri| + d = Dataset.new(dataset_uri) + d.load_compounds + d.compounds.each do |compound_uri| predict(compound_uri,false) end @prediction_dataset.save - @prediction_dataset.uri + @prediction_dataset end + # Predict a compound + # @param [String] compound_uri Compound URI + # @param [optinal,Boolean] verbose Verbose prediction (output includes neighbors and features) + # @return [OpenTox::Dataset] Dataset with prediction def predict(compound_uri,verbose=false) @compound = Compound.new compound_uri + features = {} unless @prediction_dataset + #@prediction_dataset = cached_prediction + #return @prediction_dataset if cached_prediction @prediction_dataset = Dataset.create @prediction_dataset.add_metadata( { - OT.hasSource => @lazar.uri, - DC.creator => @lazar.uri, - DC.title => URI.decode(File.basename( @metadata[OT.dependentVariables] )) + OT.hasSource => @uri, + DC.creator => @uri, + # TODO: fix dependentVariable + DC.title => URI.decode(File.basename( @metadata[OT.dependentVariables] )), + OT.parameters => [{DC.title => "compound_uri", OT.paramValue => compound_uri}] } ) - @prediction_dataset.add_parameters( {"compound_uri" => compound_uri} ) end - neighbors - eval @prediction_algorithm - - if @prediction + return @prediction_dataset if database_activity - feature_uri = File.join( @prediction_dataset.uri, "feature", @prediction_dataset.compounds.size) - @prediction_dataset.add @compound.uri, feature_uri, @prediction + neighbors + prediction = eval("#{@prediction_algorithm}(@neighbors,{:similarity_algorithm => @similarity_algorithm, :p_values => @p_values})") + + prediction_feature_uri = File.join( @prediction_dataset.uri, "feature", "prediction", File.basename(@metadata[OT.dependentVariables]),@prediction_dataset.compounds.size.to_s) + # TODO: fix dependentVariable + @prediction_dataset.metadata[OT.dependentVariables] = prediction_feature_uri + + if @neighbors.size == 0 + @prediction_dataset.add_feature(prediction_feature_uri, { + OT.hasSource => @uri, + DC.creator => @uri, + DC.title => URI.decode(File.basename( @metadata[OT.dependentVariables] )), + OT.error => "No similar compounds in training dataset.", + OT.parameters => [{DC.title => "compound_uri", OT.paramValue => compound_uri}] + }) + @prediction_dataset.add @compound.uri, prediction_feature_uri, prediction[:prediction] - feature_metadata = @prediction_dataset.metadata - feature_metadata[DC.title] = File.basename(@metadata[OT.dependentVariables]) - feature_metadata[OT.prediction] = @prediction - feature_metadata[OT.confidence] = @confidence - @prediction_dataset.add_feature(feature_uri, feature_metadata) + else + @prediction_dataset.add_feature(prediction_feature_uri, { + OT.hasSource => @uri, + DC.creator => @uri, + DC.title => URI.decode(File.basename( @metadata[OT.dependentVariables] )), + OT.prediction => prediction[:prediction], + OT.confidence => prediction[:confidence], + OT.parameters => [{DC.title => "compound_uri", OT.paramValue => compound_uri}] + }) + @prediction_dataset.add @compound.uri, prediction_feature_uri, prediction[:prediction] if verbose - if @compound_features + if @feature_calculation_algorithm == "Substructure.match" + f = 0 + @compound_features.each do |feature| + feature_uri = File.join( @prediction_dataset.uri, "feature", "descriptor", f.to_s) + features[feature] = feature_uri + @prediction_dataset.add_feature(feature_uri, { + OT.smarts => feature, + OT.p_value => @p_values[feature], + OT.effect => @effects[feature] + }) + @prediction_dataset.add @compound.uri, feature_uri, true + f+=1 + end + else @compound_features.each do |feature| + features[feature] = feature @prediction_dataset.add @compound.uri, feature, true end end n = 0 - @neighbors.sort{|a,b| a[:similarity] <=> b[:similarity]}.each do |neighbor| - neighbor_uri = File.join( @prediction_dataset.uri, "feature/neighbor", n ) - @prediction_dataset.add @compound.uri, neighbor_uri, true - @prediction_dataset.add_feature(neighbor, { + @neighbors.each do |neighbor| + neighbor_uri = File.join( @prediction_dataset.uri, "feature", "neighbor", n.to_s ) + @prediction_dataset.add_feature(neighbor_uri, { OT.compound => neighbor[:compound], OT.similarity => neighbor[:similarity], OT.activity => neighbor[:activity] }) + @prediction_dataset.add @compound.uri, neighbor_uri, true + f = 0 unless f + neighbor[:features].each do |feature| + if @feature_calculation_algorithm == "Substructure.match" + feature_uri = File.join( @prediction_dataset.uri, "feature", "descriptor", f.to_s) unless feature_uri = features[feature] + else + feature_uri = feature + end + @prediction_dataset.add neighbor[:compound], feature_uri, true + unless features.has_key? feature + features[feature] = feature_uri + @prediction_dataset.add_feature(feature_uri, { + OT.smarts => feature, + OT.p_value => @p_values[feature], + OT.effect => @effects[feature] + }) + f+=1 + end + end n+=1 end + # what happens with dataset predictions? end end - @prediction_dataset.save - @prediction_dataset.uri - end - - def weighted_majority_vote - conf = 0.0 - @neighbors.each do |neighbor| - case neighbor[:activity].to_s - when 'true' - conf += OpenTox::Algorithm.gauss(neighbor[:similarity]) - when 'false' - conf -= OpenTox::Algorithm.gauss(neighbor[:similarity]) - end - end - if conf > 0.0 - @prediction = true - elsif conf < 0.0 - @prediction = false - else - @prediction = nil - end - @confidence = conf/@neighbors.size if @neighbors.size > 0 - end - - def local_svm_regression - sims = @neighbors.collect{ |n| n[:similarity] } # similarity values between query and neighbors - conf = sims.inject{|sum,x| sum + x } - acts = @neighbors.collect do |n| - act = n[:activity] - # TODO: check this in model creation - raise "0 values not allowed in training dataset. log10 is calculated internally." if act.to_f == 0 - Math.log10(act.to_f) - end # activities of neighbors for supervised learning - - neighbor_matches = @neighbors.collect{ |n| n[:features] } # as in classification: URIs of matches - gram_matrix = [] # square matrix of similarities between neighbors; implements weighted tanimoto kernel - if neighbor_matches.size == 0 - raise "No neighbors found" - else - # gram matrix - (0..(neighbor_matches.length-1)).each do |i| - gram_matrix[i] = [] - # lower triangle - (0..(i-1)).each do |j| - sim = OpenTox::Algorithm.weighted_tanimoto(neighbor_matches[i], neighbor_matches[j], @lazar.p_values) - gram_matrix[i] << OpenTox::Algorithm.gauss(sim) - end - # diagonal element - gram_matrix[i][i] = 1.0 - # upper triangle - ((i+1)..(neighbor_matches.length-1)).each do |j| - sim = OpenTox::Algorithm.weighted_tanimoto(neighbor_matches[i], neighbor_matches[j], @lazar.p_values) # double calculation? - gram_matrix[i] << OpenTox::Algorithm.gauss(sim) - end - end - @r = RinRuby.new(false,false) # global R instance leads to Socket errors after a large number of requests - @r.eval "library('kernlab')" # this requires R package "kernlab" to be installed - LOGGER.debug "Setting R data ..." - # set data - @r.gram_matrix = gram_matrix.flatten - @r.n = neighbor_matches.size - @r.y = acts - @r.sims = sims - - LOGGER.debug "Preparing R data ..." - # prepare data - @r.eval "y<-as.vector(y)" - @r.eval "gram_matrix<-as.kernelMatrix(matrix(gram_matrix,n,n))" - @r.eval "sims<-as.vector(sims)" - - # model + support vectors - LOGGER.debug "Creating SVM model ..." - @r.eval "model<-ksvm(gram_matrix, y, kernel=matrix, type=\"nu-svr\", nu=0.8)" - @r.eval "sv<-as.vector(SVindex(model))" - @r.eval "sims<-sims[sv]" - @r.eval "sims<-as.kernelMatrix(matrix(sims,1))" - LOGGER.debug "Predicting ..." - @r.eval "p<-predict(model,sims)[1,1]" - @prediction = 10**(@r.p.to_f) - LOGGER.debug "Prediction is: '" + prediction.to_s + "'." - @r.quit # free R - end - @confidence = conf/@neighbors.size if @neighbors.size > 0 - + @prediction_dataset.save + @prediction_dataset end + # Find neighbors and store them as object variable def neighbors - @compound_features = eval(@feature_calculation_algorithm) if @feature_calculation_algorithm + @compound_features = eval("#{@feature_calculation_algorithm}(@compound,@features)") if @feature_calculation_algorithm - @neighbors = {} - @activities.each do |training_compound,activities| - @training_compound = training_compound - sim = eval(@similarity_algorithm) + @neighbors = [] + @fingerprints.each do |training_compound,training_features| + #@activities.each do |training_compound,activities| + sim = eval("#{@similarity_algorithm}(@compound_features,training_features,@p_values)") if sim > @min_sim - activities.each do |act| + @activities[training_compound].each do |act| @neighbors << { - :compound => @training_compound, + :compound => training_compound, :similarity => sim, - :features => @fingerprints[@training_compound], + :features => training_features, :activity => act } end @@ -356,55 +244,63 @@ module OpenTox end - def tanimoto - OpenTox::Algorithm.tanimoto(@compound_features,@fingerprints[@training_compound]) - end - - def weighted_tanimoto - OpenTox::Algorithm.tanimoto(@compound_features,@fingerprints[@training_compound],@p_values) - end - - def euclid - OpenTox::Algorithm.tanimoto(@compound_features,@fingerprints[@training_compound]) - end - - def weighted_euclid - OpenTox::Algorithm.tanimoto(@compound_features,@fingerprints[@training_compound],@p_values) - end - - def substructure_match - @compound.match(@features) - end - - def database_search - #TODO add features method to dataset - Dataset.new(@metadata[OT.featureDataset]).features(@compound.uri) +=begin + def cached_prediction + dataset_uri = PredictionCache.find(:model_uri => @uri, :compound_uri => @compound.uri).dataset_uri) + return false unless dataset_uri + @prediction_dataset = Dataset.find(dataset_uri) + return false unless @prediction_dataset + LOGGER.debug "Serving cached prediction" + true end +=end - def database_activity(compound_uri) - prediction = OpenTox::Dataset.new - # find database activities - if @activities[compound_uri] - @activities[compound_uri].each { |act| prediction.add compound_uri, @metadata[OT.dependentVariables], act } - prediction.add_metadata(OT.hasSource => @metadata[OT.trainingDataset]) - prediction + # Find database activities and store them in @prediction_dataset + # @return [Boolean] true if compound has databasse activities, false if not + def database_activity + if @activities[@compound.uri] + @activities[@compound.uri].each { |act| @prediction_dataset.add @compound.uri, @metadata[OT.dependentVariables], act } + @prediction_dataset.add_metadata(OT.hasSource => @metadata[OT.trainingDataset]) + @prediction_dataset.save + true else - nil + false end end + # Save model at model service def save - RestClientWrapper.post(@uri,{:content_type => "application/x-yaml"},self.to_yaml) - end - - def self.all - RestClientWrapper.get(CONFIG[:services]["opentox-model"]).to_s.split("\n") + self.uri = RestClientWrapper.post(@uri,{:content_type => "application/x-yaml"},self.to_yaml) end + # Delete model at model service def delete RestClientWrapper.delete @uri unless @uri == CONFIG[:services]["opentox-model"] end +=begin +=end + +=begin + def self.create_from_dataset(dataset_uri,feature_dataset_uri,prediction_feature=nil) + training_activities = OpenTox::Dataset.find(dataset_uri) + training_features = OpenTox::Dataset.find(feature_dataset_uri) + unless prediction_feature # try to read prediction_feature from dataset + raise "#{training_activities.features.size} features in dataset #{dataset_uri}. Please provide a prediction_feature parameter." unless training_activities.features.size == 1 + prediction_feature = training_activities.features.keys.first + params[:prediction_feature] = prediction_feature + end + lazar = Lazar.new + training_features = OpenTox::Dataset.new(feature_dataset_uri) + case training_features.feature_type + when "classification" + lazar.similarity_algorithm = "weighted_tanimoto" + when "regression" + lazar.similarity_algorithm = "weighted_euclid" + end + end +=end + end end end diff --git a/lib/opentox-ruby-api-wrapper.rb b/lib/opentox-ruby-api-wrapper.rb index 9dc1372..9f9ff26 100644 --- a/lib/opentox-ruby-api-wrapper.rb +++ b/lib/opentox-ruby-api-wrapper.rb @@ -1,4 +1,4 @@ -['rubygems', 'sinatra', 'sinatra/url_for', 'rest_client', 'yaml', 'cgi', 'spork', 'environment'].each do |lib| +['rubygems', 'sinatra', 'sinatra/url_for', 'rest_client', 'yaml', 'cgi', 'spork', 'overwrite', 'environment'].each do |lib| require lib end @@ -8,6 +8,6 @@ rescue LoadError puts "Please install Openbabel with 'rake openbabel:install' in the compound component" end -['opentox', 'compound','dataset', 'parser','serializer', 'algorithm','model','task','validation','feature', 'ot-logger', 'overwrite', 'rest_client_wrapper'].each do |lib| +['opentox', 'compound','dataset', 'parser','serializer', 'algorithm','model','task','validation','feature', 'rest_client_wrapper'].each do |lib| require lib end diff --git a/lib/opentox.rb b/lib/opentox.rb index 3b7fa65..90683e5 100644 --- a/lib/opentox.rb +++ b/lib/opentox.rb @@ -1,7 +1,7 @@ module OpenTox attr_reader :uri - attr_accessor :metadata, :parameters + attr_accessor :metadata # Initialize OpenTox object with optional uri # @param [optional, String] URI @@ -30,11 +30,9 @@ module OpenTox @metadata end - # Load parameters from URI - #def load_parameters - #@parameters = Parser::Owl::Generic.new(@uri).parameters - #@parameters - #end + def add_metadata(metadata) + metadata.each { |k,v| @metadata[k] = v } + end # Get OWL-DL representation in RDF/XML format # @return [application/rdf+xml] RDF/XML representation diff --git a/lib/ot-logger.rb b/lib/ot-logger.rb deleted file mode 100644 index df38d77..0000000 --- a/lib/ot-logger.rb +++ /dev/null @@ -1,48 +0,0 @@ -require 'logger' -# logging -class MyLogger < Logger - - def pwd - path = Dir.pwd.to_s - index = path.rindex(/\//) - return path if index==nil - path[(index+1)..-1] - end - - def trace() - lines = caller(0) - n = 2 - line = lines[n] - - while (line =~ /spork.rb/ or line =~ /as_task/ or line =~ /ot-logger.rb/) - n += 1 - line = lines[n] - end - - index = line.rindex(/\/.*\.rb/) - return line if index==nil - line[index..-1] - end - - def format(msg) - pwd.ljust(18)+" :: "+msg.to_s+" :: "+trace+" :: "+($sinatra ? $sinatra.request.env['REMOTE_ADDR'] : nil).to_s - end - - def debug(msg) - super format(msg) - end - - def info(msg) - super format(msg) - end - - def warn(msg) - super format(msg) - end - - def error(msg) - super format(msg) - end - -end - diff --git a/lib/overwrite.rb b/lib/overwrite.rb index 2e4c396..f39fec3 100644 --- a/lib/overwrite.rb +++ b/lib/overwrite.rb @@ -34,3 +34,53 @@ class String end end end + +require 'logger' +# logging +#class Logger +class OTLogger < Logger + + def pwd + path = Dir.pwd.to_s + index = path.rindex(/\//) + return path if index==nil + path[(index+1)..-1] + end + + def trace() + lines = caller(0) + n = 2 + line = lines[n] + + while (line =~ /spork.rb/ or line =~ /create/ or line =~ /ot-logger.rb/) + n += 1 + line = lines[n] + end + + index = line.rindex(/\/.*\.rb/) + return line if index==nil + line[index..-1] + end + + def format(msg) + pwd.ljust(18)+" :: "+msg.to_s+" :: "+trace+" :: "+($sinatra ? $sinatra.request.env['REMOTE_ADDR'] : nil).to_s + end + + def debug(msg) + super format(msg) + end + + def info(msg) + super format(msg) + end + + def warn(msg) + super format(msg) + end + + def error(msg) + super format(msg) + end + +end + diff --git a/lib/parser.rb b/lib/parser.rb index 4d8e729..b727412 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -116,7 +116,7 @@ module OpenTox end end load_features - @dataset.metadata = metadata + @dataset.metadata = load_metadata @dataset end @@ -253,10 +253,8 @@ module OpenTox when OT.NumericFeature @dataset.add compound.uri, feature, value.to_f when OT.StringFeature - # TODO: insert ?? @dataset.add compound.uri, feature, value.to_s @activity_errors << smiles+", "+row.join(", ") - #return false end end end diff --git a/lib/rest_client_wrapper.rb b/lib/rest_client_wrapper.rb index 49549b5..5f5273b 100644 --- a/lib/rest_client_wrapper.rb +++ b/lib/rest_client_wrapper.rb @@ -80,6 +80,7 @@ module OpenTox raise "content-type not set" unless res.content_type res.code = result.code + # TODO: Ambit returns task representation with 200 instead of result URI return res if res.code==200 || !wait while (res.code==201 || res.code==202) @@ -108,11 +109,12 @@ module OpenTox task = nil case res.content_type - when /application\/rdf\+xml|application\/x-yaml/ - task = OpenTox::Task.from_data(res, res.content_type, res.code, base_uri) + when /application\/rdf\+xml/ + task = OpenTox::Task.from_rdfxml(res) + when /yaml/ + task = OpenTox::Task.from_yaml(res) when /text\// - raise "uri list has more than one entry, should be a task" if res.content_type=~/text\/uri-list/ and - res.split("\n").size > 1 #if uri list contains more then one uri, its not a task + raise "uri list has more than one entry, should be a task" if res.content_type=~/text\/uri-list/ and res.split("\n").size > 1 #if uri list contains more then one uri, its not a task task = OpenTox::Task.find(res.to_s) if res.to_s.uri? else raise "unknown content-type for task: '"+res.content_type.to_s+"'" #+"' content: "+res[0..200].to_s @@ -122,7 +124,7 @@ module OpenTox task.wait_for_completion raise task.description unless task.completed? # maybe task was cancelled / error - res = WrapperResult.new task.resultURI + res = WrapperResult.new task.result_uri res.code = task.http_code res.content_type = "text/uri-list" return res @@ -152,8 +154,8 @@ module OpenTox # we are either in a task, or in sinatra # PENDING: always return yaml for now - if $self_task #this global var in Task.as_task to mark that the current process is running in a task - raise error.to_yaml # the error is caught, logged, and task state is set to error in Task.as_task + if $self_task #this global var in Task.create to mark that the current process is running in a task + raise error.to_yaml # the error is caught, logged, and task state is set to error in Task.create #elsif $sinatra #else halt sinatra #$sinatra.halt(502,error.to_yaml) elsif defined?(halt) diff --git a/lib/serializer.rb b/lib/serializer.rb index 31aa0d1..9b3af39 100644 --- a/lib/serializer.rb +++ b/lib/serializer.rb @@ -25,6 +25,7 @@ module OpenTox OT.FeatureValue => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , OT.Algorithm => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , OT.Parameter => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , + OT.Task => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , OT.compound => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , OT.feature => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , @@ -42,6 +43,8 @@ module OpenTox OT.isA => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , OT.Warnings => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , XSD.anyURI => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.hasStatus => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.resultURI => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , OT.hasSource => { RDF["type"] => [{ "type" => "uri", "value" => OWL.DatatypeProperty }] } , OT.value => { RDF["type"] => [{ "type" => "uri", "value" => OWL.DatatypeProperty }] } , @@ -100,17 +103,21 @@ module OpenTox # @param [String] uri Algorithm URI def add_algorithm(uri,metadata) @object[uri] = { RDF["type"] => [{ "type" => "uri", "value" => OT.Algorithm }] } - LOGGER.debug @object[uri] add_metadata uri, metadata - LOGGER.debug @object[uri] end # Add a model # @param [String] uri Model URI - def add_model(uri,metadata,parameters) + def add_model(uri,metadata) @object[uri] = { RDF["type"] => [{ "type" => "uri", "value" => OT.Model }] } add_metadata uri, metadata - add_parameters uri, parameters + end + + # Add a task + # @param [String] uri Model URI + def add_task(uri,metadata) + @object[uri] = { RDF["type"] => [{ "type" => "uri", "value" => OT.Task }] } + add_metadata uri, metadata end # Add metadata @@ -204,7 +211,7 @@ module OpenTox # @return [text/plain] Object OWL-DL in RDF/XML format def to_rdfxml Tempfile.open("owl-serializer"){|f| f.write(self.to_ntriples); @path = f.path} - `rapper -i ntriples -o rdfxml #{@path} 2>/dev/null` + `rapper -i ntriples -f 'xmlns:ot="#{OT.uri}"' -f 'xmlns:dc="#{DC.uri}"' -f 'xmlns:rdf="#{RDF.uri}"' -f 'xmlns:owl="#{OWL.uri}"' -o rdfxml #{@path} 2>/dev/null` end # Convert to JSON as specified in http://n2.talis.com/wiki/RDF_JSON_Specification @@ -290,7 +297,11 @@ module OpenTox entries.each do |feature, values| i = features.index(feature)+1 values.each do |value| - row[i] = value #TODO overwrites duplicated values + if row[i] + row[i] = "#{row[i]} #{value}" # multiple values + else + row[i] = value + end end end @rows << row diff --git a/lib/task.rb b/lib/task.rb index 96ee719..5b2b5d9 100644 --- a/lib/task.rb +++ b/lib/task.rb @@ -2,39 +2,183 @@ $self_task=nil module OpenTox + # Class for handling asynchronous tasks class Task - attr_accessor :uri, :date, :title, :creator, :description, :hasStatus, :percentageCompleted, :resultURI, :due_to_time, :http_code - - # due_to_time is only set in local tasks - TASK_ATTRIBS = [ :uri, :date, :title, :creator, :description, :hasStatus, :percentageCompleted, :resultURI, :due_to_time ] - TASK_ATTRIBS.each{ |a| attr_accessor(a) } - attr_accessor :http_code + include OpenTox + attr_accessor :http_code, :due_to_time - private - def initialize(uri) - @uri = uri.to_s.strip + def initialize(uri=nil) + super uri + @metadata = { + DC.title => "", + DC.date => "", + OT.hasStatus => "Running", + OT.percentageCompleted => "0", + OT.resultURI => "", + DC.creator => "", # not mandatory according to API + DC.description => "", # not mandatory according to API + } end - - # create is private now, use OpenTox::Task.as_task - def self.create( params ) + + # Create a new task for the code in the block. Catches halts and exceptions and sets task state to error if necessary. The block has to return the URI of the created resource. + # @example + # task = OpenTox::Task.create do + # # this code will be executed as a task + # model = OpenTox::Algorithm.run(params) # this can be time consuming + # model.uri # Important: return URI of the created resource + # end + # task.status # returns "Running", because tasks are forked + # @param [String] title Task title + # @param [String] creator Task creator + # @return [OPenTox::Task] Task + def self.create( title=nil, creator=nil, max_duration=DEFAULT_TASK_MAX_DURATION, description=nil ) + + # measure current memory consumption + memory = `free -m|sed -n '2p'`.split + free_memory = memory[3].to_i + memory[6].to_i # include cache + if free_memory < 20 # require at least 200 M free memory + LOGGER.warn "Cannot start task - not enough memory left (#{free_memory} M free)" + raise "Insufficient memory to start a new task" + end + + cpu_load = `cat /proc/loadavg`.split(/\s+/)[0..2].collect{|c| c.to_f} + nr_cpu_cores = `cat /proc/cpuinfo |grep "cpu cores"|cut -d ":" -f2|tr -d " "`.split("\n").collect{|c| c.to_i}.inject{|sum,n| sum+n} + if cpu_load[0] > nr_cpu_cores and cpu_load[0] > cpu_load[1] and cpu_load[1] > cpu_load[2] # average CPU load of the last minute is high and CPU load is increasing + LOGGER.warn "Cannot start task - CPU load too high (#{cpu_load.join(", ")})" + raise "Server too busy to start a new task" + end + + params = {:title=>title, :creator=>creator, :max_duration=>max_duration, :description=>description } task_uri = RestClientWrapper.post(CONFIG[:services]["opentox-task"], params, nil, false).to_s - Task.find(task_uri.chomp) - end + task = Task.new(task_uri.chomp) + + task_pid = Spork.spork(:logger => LOGGER) do + LOGGER.debug "Task #{task.uri} started #{Time.now}" + $self_task = task + + begin + result = catch(:halt) do + yield task + end + # catching halt, set task state to error + if result && result.is_a?(Array) && result.size==2 && result[0]>202 + LOGGER.error "task was halted: "+result.inspect + task.error(result[1]) + return + end + LOGGER.debug "Task #{task.uri} done #{Time.now} -> "+result.to_s + task.completed(result) + rescue => ex + LOGGER.error "task failed: "+ex.message + LOGGER.error ": "+ex.backtrace.join("\n") + task.error(ex.message) + end + end + task.pid = task_pid + LOGGER.debug "Started task: "+task.uri.to_s + task + end - public - def self.find( uri, accept_header=nil ) + # Find a task for querying, status changes + # @param [String] uri Task URI + # @return [OpenTox::Task] Task object + def self.find(uri) task = Task.new(uri) - task.reload( accept_header ) - return task + task.load_metadata + task + end + + # Get a list of all tasks + # @param [optional, String] uri URI of task service + # @return [text/uri-list] Task URIs + def self.all(uri=CONFIG[:services]["opentox-task"]) + OpenTox.all uri + end + + def self.from_yaml(yaml) + @metadata = YAML.load(yaml) + end + + def self.from_rdfxml(rdfxml) + file = Tempfile.open("ot-rdfxml"){|f| f.write(rdfxml)}.path + parser = Parser::Owl::Generic.new file + @metadata = parser.load_metadata + end + + def to_rdfxml + s = Serializer::Owl.new + s.add_task(@uri,@metadata) + s.to_rdfxml + end + + def status + @metadata[OT.hasStatus] + end + + def result_uri + @metadata[OT.resultURI] + end + + def description + @metadata[DC.description] + end + + def cancel + RestClientWrapper.put(File.join(@uri,'Cancelled')) + load_metadata + end + + def completed(uri) + RestClientWrapper.put(File.join(@uri,'Completed'),{:resultURI => uri}) + load_metadata + end + + def error(description) + RestClientWrapper.put(File.join(@uri,'Error'),{:description => description.to_s[0..2000]}) + load_metadata + end + + def pid=(pid) + RestClientWrapper.put(File.join(@uri,'pid'), {:pid => pid}) + end + + def running? + @metadata[OT.hasStatus] == 'Running' + end + + def completed? + @metadata[OT.hasStatus] == 'Completed' + end + + def error? + @metadata[OT.hasStatus] == 'Error' + end + + def load_metadata + if (CONFIG[:yaml_hosts].include?(URI.parse(uri).host)) + result = RestClientWrapper.get(@uri, {:accept => 'application/x-yaml'}, false) + @metadata = YAML.load result.to_s + @http_code = result.code + else + @metadata = Parser::Owl::Generic.new(@uri).load_metadata + @http_code = RestClientWrapper.get(uri, {:accept => 'application/rdf+xml'}, false).code + end end + # create is private now, use OpenTox::Task.as_task + #def self.create( params ) + #task_uri = RestClientWrapper.post(CONFIG[:services]["opentox-task"], params, nil, false).to_s + #Task.find(task_uri.chomp) + #end + +=begin def self.from_data(data, content_type, code, base_uri) task = Task.new(nil) task.http_code = code task.reload_from_data(data, content_type, base_uri) return task end - + def reload( accept_header=nil ) unless accept_header if (CONFIG[:yaml_hosts].include?(URI.parse(uri).host)) @@ -65,113 +209,45 @@ module OpenTox end raise "uri is null after loading" unless @uri and @uri.to_s.strip.size>0 end - - def cancel - RestClientWrapper.put(File.join(@uri,'Cancelled')) - reload - end - - def completed(uri) - RestClientWrapper.put(File.join(@uri,'Completed'),{:resultURI => uri}) - reload - end - - def error(description) - RestClientWrapper.put(File.join(@uri,'Error'),{:description => description.to_s[0..2000]}) - reload - end - - def pid=(pid) - RestClientWrapper.put(File.join(@uri,'pid'), {:pid => pid}) - end - - def running? - @hasStatus.to_s == 'Running' - end - - def completed? - @hasStatus.to_s == 'Completed' - end - - def error? - @hasStatus.to_s == 'Error' - end +=end # waits for a task, unless time exceeds or state is no longer running def wait_for_completion(dur=0.3) - if (@uri.match(CONFIG[:services]["opentox-task"])) - due_to_time = (@due_to_time.is_a?(Time) ? @due_to_time : Time.parse(@due_to_time)) - running_time = due_to_time - (@date.is_a?(Time) ? @date : Time.parse(@date)) - else - # the date of the external task cannot be trusted, offest to local time might be to big - due_to_time = Time.new + EXTERNAL_TASK_MAX_DURATION - running_time = EXTERNAL_TASK_MAX_DURATION - end + due_to_time = Time.new + DEFAULT_TASK_MAX_DURATION LOGGER.debug "start waiting for task "+@uri.to_s+" at: "+Time.new.to_s+", waiting at least until "+due_to_time.to_s + load_metadata # for extremely fast tasks + check_state while self.running? sleep dur - reload + load_metadata check_state if (Time.new > due_to_time) - raise "max wait time exceeded ("+running_time.to_s+"sec), task: '"+@uri.to_s+"'" + raise "max wait time exceeded ("+DEFAULT_TASK_MAX_DURATION.to_s+"sec), task: '"+@uri.to_s+"'" end end - LOGGER.debug "Task '"+@hasStatus+"': "+@uri.to_s+", Result: "+@resultURI.to_s + LOGGER.debug "Task '"+@metadata[OT.hasStatus]+"': "+@uri.to_s+", Result: "+@metadata[OT.resultURI].to_s end + private def check_state begin - raise "illegal task state, task is completed, resultURI is no URI: '"+@resultURI.to_s+ - "'" unless @resultURI and @resultURI.to_s.uri? if completed? + raise "illegal task state, task is completed, resultURI is no URI: '"+@metadata[OT.resultURI].to_s+ + "'" unless @metadata[OT.resultURI] and @metadata[OT.resultURI].to_s.uri? if completed? if @http_code == 202 - raise "illegal task state, code is 202, but hasStatus is not Running: '"+@hasStatus+"'" unless running? + raise "illegal task state, code is 202, but hasStatus is not Running: '"+@metadata[OT.hasStatus]+"'" unless running? elsif @http_code == 201 - raise "illegal task state, code is 201, but hasStatus is not Completed: '"+@hasStatus+"'" unless completed? - raise "illegal task state, code is 201, resultURI is no task-URI: '"+@resultURI.to_s+ - "'" unless @resultURI and @resultURI.to_s.uri? + raise "illegal task state, code is 201, but hasStatus is not Completed: '"+@metadata[OT.hasStatus]+"'" unless completed? + raise "illegal task state, code is 201, resultURI is no task-URI: '"+@metadata[OT.resultURI].to_s+ + "'" unless @metadata[OT.resultURI] and @metadata[OT.resultURI].to_s.uri? end rescue => ex RestClientWrapper.raise_uri_error(ex.message, @uri) end end - - # returns the task uri - # catches halts and exceptions, task state is set to error then - def self.as_task( title, creator, max_duration=DEFAULT_TASK_MAX_DURATION, description=nil ) - #return yield nil - - params = {:title=>title, :creator=>creator, :max_duration=>max_duration, :description=>description } - task = ::OpenTox::Task.create(params) - task_pid = Spork.spork(:logger => LOGGER) do - LOGGER.debug "Task #{task.uri} started #{Time.now}" - $self_task = task - - begin - result = catch(:halt) do - yield task - end - # catching halt, set task state to error - if result && result.is_a?(Array) && result.size==2 && result[0]>202 - LOGGER.error "task was halted: "+result.inspect - task.error(result[1]) - return - end - LOGGER.debug "Task #{task.uri} done #{Time.now} -> "+result.to_s - task.completed(result) - rescue => ex - LOGGER.error "task failed: "+ex.message - LOGGER.error ": "+ex.backtrace.join("\n") - task.error(ex.message) - end - end - task.pid = task_pid - LOGGER.debug "Started task: "+task.uri.to_s - task.uri - end end diff --git a/lib/validation.rb b/lib/validation.rb index 340332a..76c4529 100644 --- a/lib/validation.rb +++ b/lib/validation.rb @@ -1,20 +1,70 @@ module OpenTox class Validation + include OpenTox - attr_accessor :uri - - def initialize(params) - @uri = OpenTox::RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/crossvalidation"),params,nil,false) - end + attr_accessor :report_uri, :qmrf_report_uri - def self.crossvalidation(params) + def self.create_crossvalidation(params) params[:uri] = File.join(CONFIG[:services]['opentox-validation'], "crossvalidation") params[:num_folds] = 10 unless params[:num_folds] params[:random_seed] = 2 unless params[:random_seed] params[:stratified] = false unless params[:stratified] - OpenTox::Validation.new(params) + uri = OpenTox::RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/crossvalidation"),params,nil,false) + OpenTox::Validation.new(uri) end + def create_report + @report_uri = RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/report/crossvalidation"), :validation_uris => @uri).to_s + @report_uri + end + + def create_qmrf_report + @qmrf_report_uri = RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/reach_report/qmrf"), :model_uri => @uri).to_s + @qmrf_report_uri + end + + def summary(type) + v = YAML.load RestClientWrappper.get(File.join(@uri, 'statistics'),:accept => "application/x-yaml").to_s + + case type + when "classification" + tp=0; tn=0; fp=0; fn=0; n=0 + v[:classification_statistics][:confusion_matrix][:confusion_matrix_cell].each do |cell| + if cell[:confusion_matrix_predicted] == "true" and cell[:confusion_matrix_actual] == "true" + tp = cell[:confusion_matrix_value] + n += tp + elsif cell[:confusion_matrix_predicted] == "false" and cell[:confusion_matrix_actual] == "false" + tn = cell[:confusion_matrix_value] + n += tn + elsif cell[:confusion_matrix_predicted] == "false" and cell[:confusion_matrix_actual] == "true" + fn = cell[:confusion_matrix_value] + n += fn + elsif cell[:confusion_matrix_predicted] == "true" and cell[:confusion_matrix_actual] == "false" + fp = cell[:confusion_matrix_value] + n += fp + end + end + { + :nr_predictions => n, + :true_positives => tp, + :false_positives => fp, + :true_negatives => tn, + :false_negatives => fn, + :correct_predictions => 100*(tp+tn).to_f/n, + :weighted_area_under_roc => v[:classification_statistics][:weighted_area_under_roc].to_f, + :sensitivity => tp.to_f/(tp+fn), + :specificity => tn.to_f/(tn+fp), + } + when "regression" + { + :nr_predictions => v[:num_instances] - v[:num_unpredicted], + :r_square => v[:regression_statistics][:r_square], + :root_mean_squared_error => v[:regression_statistics][:root_mean_squared_error], + :mean_absolute_error => v[:regression_statistics][:mean_absolute_error], + } + end + end + end end -- cgit v1.2.3 From 7c743456c42ffa85e81db6d975ebd7ed260f81f0 Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Wed, 24 Nov 2010 12:17:53 +0100 Subject: Version bump to 0.0.1 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index ec70f75..8acdd82 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.6.6 +0.0.1 -- cgit v1.2.3 From 7067bd44d5c97618ec6a968bbdfe6d6bda12a1cd Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Wed, 24 Nov 2010 13:13:40 +0100 Subject: opentox-ruby-api-wrapper renamed to opentox-ruby --- Rakefile | 64 ++++++++++++++++++++--------------------- bin/yaml2owl.rb | 18 ------------ lib/dataset.rb | 17 ++++++++++- lib/model.rb | 63 +++++++++------------------------------- lib/opentox-ruby-api-wrapper.rb | 13 --------- lib/opentox-ruby.rb | 13 +++++++++ lib/serializer.rb | 10 ++++--- lib/task.rb | 15 ++++++---- 8 files changed, 91 insertions(+), 122 deletions(-) delete mode 100755 bin/yaml2owl.rb delete mode 100644 lib/opentox-ruby-api-wrapper.rb create mode 100644 lib/opentox-ruby.rb diff --git a/Rakefile b/Rakefile index 6838e75..53c6ae9 100644 --- a/Rakefile +++ b/Rakefile @@ -4,45 +4,45 @@ require 'rake' begin require 'jeweler' Jeweler::Tasks.new do |gem| - gem.name = "opentox-ruby-api-wrapper" + gem.name = "opentox-ruby" gem.summary = %Q{Ruby wrapper for the OpenTox REST API} gem.description = %Q{Ruby wrapper for the OpenTox REST API (http://www.opentox.org)} gem.email = "helma@in-silico.ch" gem.homepage = "http://github.com/helma/opentox-ruby-api-wrapper" - gem.authors = ["Christoph Helma, Martin Guetlein"] - # dependencies - [ "sinatra", - "emk-sinatra-url-for", - "sinatra-respond_to", - "sinatra-static-assets", - "rest-client", - "rack", - "rack-contrib", - "rack-flash", - "nokogiri", - "rubyzip", - "roo", - "spreadsheet", - "google-spreadsheet-ruby", - "tmail", - "rinruby", - "rjb" - ].each { |dep| gem.add_dependency dep } - [ "dm-core", - 'dm-serializer', - 'dm-timestamps', - 'dm-types', - 'dm-migrations', - "dm-mysql-adapter", + gem.authors = ["Christoph Helma, Martin Guetlein, Andreas Maunz, Micha Rautenberg, David Vorgrimmler"] + # dependencies + [ "sinatra", + "emk-sinatra-url-for", + "sinatra-respond_to", + "sinatra-static-assets", + "rest-client", + "rack", + "rack-contrib", + "rack-flash", + "nokogiri", + "rubyzip", + "roo", + "spreadsheet", + "google-spreadsheet-ruby", + "tmail", + "rinruby", + "rjb" + ].each { |dep| gem.add_dependency dep } + [ "dm-core", + 'dm-serializer', + 'dm-timestamps', + 'dm-types', + 'dm-migrations', + "dm-mysql-adapter", "dm-validations", - ].each {|dep| gem.add_dependency dep, ">= 1" } - gem.add_dependency "haml", ">=3" - ['cucumber','jeweler'].each { |dep| gem.add_development_dependency dep } - gem.files = FileList["[A-Z]*", "{bin,generators,lib,test}/**/*", 'lib/jeweler/templates/.gitignore'] - gem.files.include %w(lib/tasks/owl.rb, lib/environment.rb, lib/algorithm.rb, lib/compound.rb, lib/dataset.rb, lib/model.rb, lib/validation.rb, lib/templates/*) + ].each {|dep| gem.add_dependency dep, ">= 1" } + gem.add_dependency "haml", ">=3" + ['jeweler'].each { |dep| gem.add_development_dependency dep } + gem.files = FileList["[A-Z]*", "{bin,generators,lib,test}/**/*", 'lib/jeweler/templates/.gitignore'] + #gem.files.include %w(lib/environment.rb, lib/algorithm.rb, lib/compound.rb, lib/dataset.rb, lib/model.rb, lib/validation.rb, lib/templates/*) # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings end - Jeweler::GemcutterTasks.new + Jeweler::GemcutterTasks.new rescue LoadError puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler" end diff --git a/bin/yaml2owl.rb b/bin/yaml2owl.rb deleted file mode 100755 index 1002912..0000000 --- a/bin/yaml2owl.rb +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env ruby -require 'rubygems' -require 'opentox-ruby-api-wrapper' - -input = YAML.load_file(ARGV[0]) -dataset = OpenTox::Dataset.new -dataset.title = input[:title] -dataset.creator = input[:source] -input[:data].each do |c,f| - f.each do |k,v| - v.each do |value| - dataset.add c,k,value - end - end -end -outfile = File.expand_path(File.join(File.dirname(__FILE__),ARGV[0].sub(/yaml/,'owl'))) -dataset.uri = outfile -File.open(outfile,'w+'){|f| f.puts dataset.rdf} diff --git a/lib/dataset.rb b/lib/dataset.rb index 4737ea1..c5704ae 100644 --- a/lib/dataset.rb +++ b/lib/dataset.rb @@ -32,6 +32,21 @@ module OpenTox dataset end + # Create dataset from CSV file (format specification: http://toxcreate.org/help) + # - loads data_entries, compounds, features + # - sets metadata (warnings) for parser errors + # - you will have to set remaining metadata manually + # @param [String] file CSV file path + # @return [OpenTox::Dataset] Dataset object with CSV data + def self.create_from_csv_file(file) + dataset = Dataset.create + parser = Parser::Spreadsheets.new + parser.dataset = dataset + parser.load_csv(File.open(file).read) + dataset.save + dataset + end + # Find a dataset and load all data. This can be time consuming, use Dataset.new together with one of the load_* methods for a fine grained control over data loading. # @param [String] uri Dataset URI # @return [OpenTox::Dataset] Dataset object with all data @@ -299,7 +314,7 @@ module OpenTox def measured_activities(compound) source = @metadata[OT.hasSource] - @data_entries[compound.uri].collect{|f,v| v if f.match(/#{source}/)}.compact + @data_entries[compound.uri].collect{|f,v| v if f.match(/#{source}/)}.compact.flatten end def neighbors(compound) diff --git a/lib/model.rb b/lib/model.rb index c6a2cf4..5654bcc 100644 --- a/lib/model.rb +++ b/lib/model.rb @@ -80,21 +80,16 @@ module OpenTox OpenTox::Model::Lazar.find(model_uri) end -=begin - # Create a new lazar model and return task - # @param [optional,Hash] params Parameters for the lazar algorithm (OpenTox::Algorithm::Lazar) - # @return [OpenTox::Task] Task for lazar model creation - def self.create_task(params) - task_uri = RestClientWrapper.post(File.join(CONFIG[:services]["opentox-algorithm"],"lazar"), {}, params, false) - Task.find(task_uri) - #model_uri = lazar_algorithm.run(params) - #OpenTox::Model::Lazar.new(model_uri) - end -=end + # Get a parameter value + # @param [String] param Parameter name + # @return [String] Parameter value def parameter(param) @metadata[OT.parameters].collect{|p| p[OT.paramValue] if p[DC.title] == param}.compact.first end + # Predict a dataset + # @param [String] dataset_uri Dataset URI + # @return [OpenTox::Dataset] Dataset with predictions def predict_dataset(dataset_uri) @prediction_dataset = Dataset.create @prediction_dataset.add_metadata({ @@ -145,6 +140,7 @@ module OpenTox if @neighbors.size == 0 @prediction_dataset.add_feature(prediction_feature_uri, { + OT.isA => OT.MeasuredFeature, OT.hasSource => @uri, DC.creator => @uri, DC.title => URI.decode(File.basename( @metadata[OT.dependentVariables] )), @@ -155,6 +151,7 @@ module OpenTox else @prediction_dataset.add_feature(prediction_feature_uri, { + OT.isA => OT.ModelPrediction, OT.hasSource => @uri, DC.creator => @uri, DC.title => URI.decode(File.basename( @metadata[OT.dependentVariables] )), @@ -171,8 +168,9 @@ module OpenTox feature_uri = File.join( @prediction_dataset.uri, "feature", "descriptor", f.to_s) features[feature] = feature_uri @prediction_dataset.add_feature(feature_uri, { + OT.isA => OT.Substructure, OT.smarts => feature, - OT.p_value => @p_values[feature], + OT.pValue => @p_values[feature], OT.effect => @effects[feature] }) @prediction_dataset.add @compound.uri, feature_uri, true @@ -190,7 +188,8 @@ module OpenTox @prediction_dataset.add_feature(neighbor_uri, { OT.compound => neighbor[:compound], OT.similarity => neighbor[:similarity], - OT.activity => neighbor[:activity] + OT.measuredActivity => neighbor[:activity], + OT.isA => OT.Neighbor }) @prediction_dataset.add @compound.uri, neighbor_uri, true f = 0 unless f @@ -204,8 +203,9 @@ module OpenTox unless features.has_key? feature features[feature] = feature_uri @prediction_dataset.add_feature(feature_uri, { + OT.isA => OT.Substructure, OT.smarts => feature, - OT.p_value => @p_values[feature], + OT.pValue => @p_values[feature], OT.effect => @effects[feature] }) f+=1 @@ -228,7 +228,6 @@ module OpenTox @neighbors = [] @fingerprints.each do |training_compound,training_features| - #@activities.each do |training_compound,activities| sim = eval("#{@similarity_algorithm}(@compound_features,training_features,@p_values)") if sim > @min_sim @activities[training_compound].each do |act| @@ -244,17 +243,6 @@ module OpenTox end -=begin - def cached_prediction - dataset_uri = PredictionCache.find(:model_uri => @uri, :compound_uri => @compound.uri).dataset_uri) - return false unless dataset_uri - @prediction_dataset = Dataset.find(dataset_uri) - return false unless @prediction_dataset - LOGGER.debug "Serving cached prediction" - true - end -=end - # Find database activities and store them in @prediction_dataset # @return [Boolean] true if compound has databasse activities, false if not def database_activity @@ -278,29 +266,6 @@ module OpenTox RestClientWrapper.delete @uri unless @uri == CONFIG[:services]["opentox-model"] end -=begin -=end - -=begin - def self.create_from_dataset(dataset_uri,feature_dataset_uri,prediction_feature=nil) - training_activities = OpenTox::Dataset.find(dataset_uri) - training_features = OpenTox::Dataset.find(feature_dataset_uri) - unless prediction_feature # try to read prediction_feature from dataset - raise "#{training_activities.features.size} features in dataset #{dataset_uri}. Please provide a prediction_feature parameter." unless training_activities.features.size == 1 - prediction_feature = training_activities.features.keys.first - params[:prediction_feature] = prediction_feature - end - lazar = Lazar.new - training_features = OpenTox::Dataset.new(feature_dataset_uri) - case training_features.feature_type - when "classification" - lazar.similarity_algorithm = "weighted_tanimoto" - when "regression" - lazar.similarity_algorithm = "weighted_euclid" - end - end -=end - end end end diff --git a/lib/opentox-ruby-api-wrapper.rb b/lib/opentox-ruby-api-wrapper.rb deleted file mode 100644 index 9f9ff26..0000000 --- a/lib/opentox-ruby-api-wrapper.rb +++ /dev/null @@ -1,13 +0,0 @@ -['rubygems', 'sinatra', 'sinatra/url_for', 'rest_client', 'yaml', 'cgi', 'spork', 'overwrite', 'environment'].each do |lib| - require lib -end - -begin - require 'openbabel' -rescue LoadError - puts "Please install Openbabel with 'rake openbabel:install' in the compound component" -end - -['opentox', 'compound','dataset', 'parser','serializer', 'algorithm','model','task','validation','feature', 'rest_client_wrapper'].each do |lib| - require lib -end diff --git a/lib/opentox-ruby.rb b/lib/opentox-ruby.rb new file mode 100644 index 0000000..9f9ff26 --- /dev/null +++ b/lib/opentox-ruby.rb @@ -0,0 +1,13 @@ +['rubygems', 'sinatra', 'sinatra/url_for', 'rest_client', 'yaml', 'cgi', 'spork', 'overwrite', 'environment'].each do |lib| + require lib +end + +begin + require 'openbabel' +rescue LoadError + puts "Please install Openbabel with 'rake openbabel:install' in the compound component" +end + +['opentox', 'compound','dataset', 'parser','serializer', 'algorithm','model','task','validation','feature', 'rest_client_wrapper'].each do |lib| + require lib +end diff --git a/lib/serializer.rb b/lib/serializer.rb index 9b3af39..495702a 100644 --- a/lib/serializer.rb +++ b/lib/serializer.rb @@ -40,11 +40,13 @@ module OpenTox DC.contributor => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , DC.creator => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , DC.description => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + DC.date => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , OT.isA => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , OT.Warnings => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , XSD.anyURI => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , OT.hasStatus => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , OT.resultURI => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.percentageCompleted => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , OT.hasSource => { RDF["type"] => [{ "type" => "uri", "value" => OWL.DatatypeProperty }] } , OT.value => { RDF["type"] => [{ "type" => "uri", "value" => OWL.DatatypeProperty }] } , @@ -125,10 +127,7 @@ module OpenTox def add_metadata(uri,metadata) id = 0 metadata.each do |u,v| - if v.is_a? String - @object[uri] = {} unless @object[uri] - @object[uri][u] = [{"type" => type(v), "value" => v }] - elsif v.is_a? Array and u == OT.parameters + if v.is_a? Array and u == OT.parameters @object[uri][u] = [] unless @object[uri][u] v.each do |value| id+=1 @@ -139,6 +138,9 @@ module OpenTox @object[genid][name] = [{"type" => type(entry), "value" => entry }] end end + else # v.is_a? String + @object[uri] = {} unless @object[uri] + @object[uri][u] = [{"type" => type(v), "value" => v }] end end end diff --git a/lib/task.rb b/lib/task.rb index 5b2b5d9..5b59395 100644 --- a/lib/task.rb +++ b/lib/task.rb @@ -33,24 +33,29 @@ module OpenTox # @return [OPenTox::Task] Task def self.create( title=nil, creator=nil, max_duration=DEFAULT_TASK_MAX_DURATION, description=nil ) + params = {:title=>title, :creator=>creator, :max_duration=>max_duration, :description=>description } + task_uri = RestClientWrapper.post(CONFIG[:services]["opentox-task"], params, nil, false).to_s + task = Task.new(task_uri.chomp) + # measure current memory consumption memory = `free -m|sed -n '2p'`.split free_memory = memory[3].to_i + memory[6].to_i # include cache if free_memory < 20 # require at least 200 M free memory LOGGER.warn "Cannot start task - not enough memory left (#{free_memory} M free)" - raise "Insufficient memory to start a new task" + task.cancel + return task + #raise "Insufficient memory to start a new task" end cpu_load = `cat /proc/loadavg`.split(/\s+/)[0..2].collect{|c| c.to_f} nr_cpu_cores = `cat /proc/cpuinfo |grep "cpu cores"|cut -d ":" -f2|tr -d " "`.split("\n").collect{|c| c.to_i}.inject{|sum,n| sum+n} if cpu_load[0] > nr_cpu_cores and cpu_load[0] > cpu_load[1] and cpu_load[1] > cpu_load[2] # average CPU load of the last minute is high and CPU load is increasing LOGGER.warn "Cannot start task - CPU load too high (#{cpu_load.join(", ")})" - raise "Server too busy to start a new task" + task.cancel + return task + #raise "Server too busy to start a new task" end - params = {:title=>title, :creator=>creator, :max_duration=>max_duration, :description=>description } - task_uri = RestClientWrapper.post(CONFIG[:services]["opentox-task"], params, nil, false).to_s - task = Task.new(task_uri.chomp) task_pid = Spork.spork(:logger => LOGGER) do LOGGER.debug "Task #{task.uri} started #{Time.now}" -- cgit v1.2.3 From 8ff5d7de9683d9bbe6103dd42d10aefbf2f4a021 Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Wed, 24 Nov 2010 14:43:20 +0100 Subject: opentox-ruby gem in config.ru --- README.rdoc | 8 ++++---- Rakefile | 5 +++-- opentox-ruby-api-wrapper.gemspec | 16 ++++++---------- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/README.rdoc b/README.rdoc index e337907..45cc5f6 100644 --- a/README.rdoc +++ b/README.rdoc @@ -1,4 +1,4 @@ -= opentox-ruby-api-wrapper += opentox-ruby Ruby wrapper for the OpenTox REST API (http://www.opentox.org) @@ -10,14 +10,14 @@ Run the following if you haven't already: Install the gem: - sudo gem install helma-opentox-ruby-api-wrapper + sudo gem install helma-opentox-ruby == Usage - adjust the settings in $HOME/.opentox/config -- require 'opentox-ruby-api-wrapper' in your ruby application +- require 'opentox-ruby' in your ruby application - consult the rdoc API documentation for details == Copyright -Copyright (c) 2009 Christoph Helma. See LICENSE for details. +Copyright (c) 2009-2010 Christoph Helma. See LICENSE for details. diff --git a/Rakefile b/Rakefile index 53c6ae9..2578bb4 100644 --- a/Rakefile +++ b/Rakefile @@ -8,7 +8,7 @@ begin gem.summary = %Q{Ruby wrapper for the OpenTox REST API} gem.description = %Q{Ruby wrapper for the OpenTox REST API (http://www.opentox.org)} gem.email = "helma@in-silico.ch" - gem.homepage = "http://github.com/helma/opentox-ruby-api-wrapper" + gem.homepage = "http://github.com/helma/opentox-ruby" gem.authors = ["Christoph Helma, Martin Guetlein, Andreas Maunz, Micha Rautenberg, David Vorgrimmler"] # dependencies [ "sinatra", @@ -24,6 +24,7 @@ begin "roo", "spreadsheet", "google-spreadsheet-ruby", + "yajl-ruby", "tmail", "rinruby", "rjb" @@ -80,7 +81,7 @@ Rake::RDocTask.new do |rdoc| end rdoc.rdoc_dir = 'rdoc' - rdoc.title = "opentox-ruby-api-wrapper #{version}" + rdoc.title = "opentox-ruby #{version}" rdoc.rdoc_files.include('README*') rdoc.rdoc_files.include('lib/**/*.rb') end diff --git a/opentox-ruby-api-wrapper.gemspec b/opentox-ruby-api-wrapper.gemspec index 58f0b46..fd00851 100644 --- a/opentox-ruby-api-wrapper.gemspec +++ b/opentox-ruby-api-wrapper.gemspec @@ -5,11 +5,11 @@ Gem::Specification.new do |s| s.name = %q{opentox-ruby-api-wrapper} - s.version = "1.6.6" + s.version = "0.0.1" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Christoph Helma, Martin Guetlein"] - s.date = %q{2010-09-14} + s.date = %q{2010-11-24} s.description = %q{Ruby wrapper for the OpenTox REST API (http://www.opentox.org)} s.email = %q{helma@in-silico.ch} s.executables = ["opentox-install-ubuntu.sh", "yaml2owl.rb", "opentox-install-debian.sh"] @@ -30,24 +30,23 @@ Gem::Specification.new do |s| "lib/config/config_ru.rb", "lib/dataset.rb", "lib/environment.rb", - "lib/features.rb", + "lib/feature.rb", "lib/helper.rb", "lib/model.rb", "lib/opentox-ruby-api-wrapper.rb", "lib/opentox.owl", - "lib/ot-logger.rb", + "lib/opentox.rb", "lib/overwrite.rb", - "lib/owl-serializer.rb", - "lib/owl.rb", "lib/owl.rb.RDF", "lib/owl.rb.nt", "lib/owl.rb.rdfxml.initial", "lib/owl.rb.redland", + "lib/parser.rb", "lib/rest_client_wrapper.rb", + "lib/serializer.rb", "lib/spork.rb", "lib/task.rb", "lib/templates/config.yaml", - "lib/utils.rb", "lib/validation.rb" ] s.homepage = %q{http://github.com/helma/opentox-ruby-api-wrapper} @@ -71,7 +70,6 @@ Gem::Specification.new do |s| s.add_runtime_dependency(%q, [">= 0"]) s.add_runtime_dependency(%q, [">= 0"]) s.add_runtime_dependency(%q, [">= 0"]) - s.add_runtime_dependency(%q, [">= 0"]) s.add_runtime_dependency(%q, [">= 0"]) s.add_runtime_dependency(%q, [">= 0"]) s.add_runtime_dependency(%q, [">= 0"]) @@ -99,7 +97,6 @@ Gem::Specification.new do |s| s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) @@ -128,7 +125,6 @@ Gem::Specification.new do |s| s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) -- cgit v1.2.3 From d3190ba5fd87db05bdf3219dae00afaf31257718 Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Tue, 30 Nov 2010 12:43:04 +0100 Subject: Accept: text/uri-list for running algoritms --- lib/algorithm.rb | 2 +- lib/task.rb | 6 +- opentox-ruby-api-wrapper.gemspec | 146 --------------------------------------- opentox-ruby.gemspec | 133 +++++++++++++++++++++++++++++++++++ 4 files changed, 137 insertions(+), 150 deletions(-) delete mode 100644 opentox-ruby-api-wrapper.gemspec create mode 100644 opentox-ruby.gemspec diff --git a/lib/algorithm.rb b/lib/algorithm.rb index a6fa4a7..a2f7786 100644 --- a/lib/algorithm.rb +++ b/lib/algorithm.rb @@ -15,7 +15,7 @@ module OpenTox # @param [optional,Hash] params Algorithm parameters # @return [String] URI of new resource (dataset, model, ...) def run(params=nil) - RestClientWrapper.post(@uri, params).to_s + RestClientWrapper.post(@uri, {:accept => 'text/uri-list'}, params).to_s end # Get OWL-DL representation in RDF/XML format diff --git a/lib/task.rb b/lib/task.rb index 5b59395..17f95e6 100644 --- a/lib/task.rb +++ b/lib/task.rb @@ -243,10 +243,10 @@ module OpenTox "'" unless @metadata[OT.resultURI] and @metadata[OT.resultURI].to_s.uri? if completed? if @http_code == 202 - raise "illegal task state, code is 202, but hasStatus is not Running: '"+@metadata[OT.hasStatus]+"'" unless running? + raise "#{@uri}: illegal task state, code is 202, but hasStatus is not Running: '"+@metadata[OT.hasStatus]+"'" unless running? elsif @http_code == 201 - raise "illegal task state, code is 201, but hasStatus is not Completed: '"+@metadata[OT.hasStatus]+"'" unless completed? - raise "illegal task state, code is 201, resultURI is no task-URI: '"+@metadata[OT.resultURI].to_s+ + raise "#{@uri}: illegal task state, code is 201, but hasStatus is not Completed: '"+@metadata[OT.hasStatus]+"'" unless completed? + raise "#{@uri}: illegal task state, code is 201, resultURI is no task-URI: '"+@metadata[OT.resultURI].to_s+ "'" unless @metadata[OT.resultURI] and @metadata[OT.resultURI].to_s.uri? end rescue => ex diff --git a/opentox-ruby-api-wrapper.gemspec b/opentox-ruby-api-wrapper.gemspec deleted file mode 100644 index fd00851..0000000 --- a/opentox-ruby-api-wrapper.gemspec +++ /dev/null @@ -1,146 +0,0 @@ -# Generated by jeweler -# DO NOT EDIT THIS FILE DIRECTLY -# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command -# -*- encoding: utf-8 -*- - -Gem::Specification.new do |s| - s.name = %q{opentox-ruby-api-wrapper} - s.version = "0.0.1" - - s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= - s.authors = ["Christoph Helma, Martin Guetlein"] - s.date = %q{2010-11-24} - s.description = %q{Ruby wrapper for the OpenTox REST API (http://www.opentox.org)} - s.email = %q{helma@in-silico.ch} - s.executables = ["opentox-install-ubuntu.sh", "yaml2owl.rb", "opentox-install-debian.sh"] - s.extra_rdoc_files = [ - "LICENSE", - "README.rdoc" - ] - s.files = [ - "LICENSE", - "README.rdoc", - "Rakefile", - "VERSION", - "bin/opentox-install-debian.sh", - "bin/opentox-install-ubuntu.sh", - "bin/yaml2owl.rb", - "lib/algorithm.rb", - "lib/compound.rb", - "lib/config/config_ru.rb", - "lib/dataset.rb", - "lib/environment.rb", - "lib/feature.rb", - "lib/helper.rb", - "lib/model.rb", - "lib/opentox-ruby-api-wrapper.rb", - "lib/opentox.owl", - "lib/opentox.rb", - "lib/overwrite.rb", - "lib/owl.rb.RDF", - "lib/owl.rb.nt", - "lib/owl.rb.rdfxml.initial", - "lib/owl.rb.redland", - "lib/parser.rb", - "lib/rest_client_wrapper.rb", - "lib/serializer.rb", - "lib/spork.rb", - "lib/task.rb", - "lib/templates/config.yaml", - "lib/validation.rb" - ] - s.homepage = %q{http://github.com/helma/opentox-ruby-api-wrapper} - s.rdoc_options = ["--charset=UTF-8"] - s.require_paths = ["lib"] - s.rubygems_version = %q{1.3.7} - s.summary = %q{Ruby wrapper for the OpenTox REST API} - - if s.respond_to? :specification_version then - current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION - s.specification_version = 3 - - if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then - s.add_runtime_dependency(%q, [">= 0"]) - s.add_runtime_dependency(%q, [">= 0"]) - s.add_runtime_dependency(%q, [">= 0"]) - s.add_runtime_dependency(%q, [">= 0"]) - s.add_runtime_dependency(%q, [">= 0"]) - s.add_runtime_dependency(%q, [">= 0"]) - s.add_runtime_dependency(%q, [">= 0"]) - s.add_runtime_dependency(%q, [">= 0"]) - s.add_runtime_dependency(%q, [">= 0"]) - s.add_runtime_dependency(%q, [">= 0"]) - s.add_runtime_dependency(%q, [">= 0"]) - s.add_runtime_dependency(%q, [">= 0"]) - s.add_runtime_dependency(%q, [">= 0"]) - s.add_runtime_dependency(%q, [">= 0"]) - s.add_runtime_dependency(%q, [">= 0"]) - s.add_runtime_dependency(%q, [">= 0"]) - s.add_runtime_dependency(%q, [">= 1"]) - s.add_runtime_dependency(%q, [">= 1"]) - s.add_runtime_dependency(%q, [">= 1"]) - s.add_runtime_dependency(%q, [">= 1"]) - s.add_runtime_dependency(%q, [">= 1"]) - s.add_runtime_dependency(%q, [">= 1"]) - s.add_runtime_dependency(%q, [">= 1"]) - s.add_runtime_dependency(%q, [">= 3"]) - s.add_development_dependency(%q, [">= 0"]) - s.add_development_dependency(%q, [">= 0"]) - else - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 1"]) - s.add_dependency(%q, [">= 1"]) - s.add_dependency(%q, [">= 1"]) - s.add_dependency(%q, [">= 1"]) - s.add_dependency(%q, [">= 1"]) - s.add_dependency(%q, [">= 1"]) - s.add_dependency(%q, [">= 1"]) - s.add_dependency(%q, [">= 3"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - end - else - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 1"]) - s.add_dependency(%q, [">= 1"]) - s.add_dependency(%q, [">= 1"]) - s.add_dependency(%q, [">= 1"]) - s.add_dependency(%q, [">= 1"]) - s.add_dependency(%q, [">= 1"]) - s.add_dependency(%q, [">= 1"]) - s.add_dependency(%q, [">= 3"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - end -end - diff --git a/opentox-ruby.gemspec b/opentox-ruby.gemspec new file mode 100644 index 0000000..e89af96 --- /dev/null +++ b/opentox-ruby.gemspec @@ -0,0 +1,133 @@ +# Generated by jeweler +# DO NOT EDIT THIS FILE DIRECTLY +# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = %q{opentox-ruby} + s.version = "0.0.1" + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.authors = ["Christoph Helma, Martin Guetlein, Andreas Maunz, Micha Rautenberg, David Vorgrimmler"] + s.date = %q{2010-11-24} + s.description = %q{Ruby wrapper for the OpenTox REST API (http://www.opentox.org)} + s.email = %q{helma@in-silico.ch} + s.executables = ["opentox-install-ubuntu.sh", "opentox-install-debian.sh"] + s.extra_rdoc_files = [ + "LICENSE", + "README.rdoc" + ] + s.files = [ + "LICENSE", + "README.rdoc", + "Rakefile", + "VERSION", + "bin/opentox-install-debian.sh", + "bin/opentox-install-ubuntu.sh", + "lib/algorithm.rb", + "lib/compound.rb", + "lib/config/config_ru.rb", + "lib/dataset.rb", + "lib/environment.rb", + "lib/helper.rb", + "lib/model.rb", + "lib/opentox.owl", + "lib/overwrite.rb", + "lib/rest_client_wrapper.rb", + "lib/spork.rb", + "lib/task.rb", + "lib/templates/config.yaml", + "lib/validation.rb" + ] + s.homepage = %q{http://github.com/helma/opentox-ruby-api-wrapper} + s.rdoc_options = ["--charset=UTF-8"] + s.require_paths = ["lib"] + s.rubygems_version = %q{1.3.7} + s.summary = %q{Ruby wrapper for the OpenTox REST API} + + if s.respond_to? :specification_version then + current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION + s.specification_version = 3 + + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + s.add_runtime_dependency(%q, [">= 0"]) + s.add_runtime_dependency(%q, [">= 0"]) + s.add_runtime_dependency(%q, [">= 0"]) + s.add_runtime_dependency(%q, [">= 0"]) + s.add_runtime_dependency(%q, [">= 0"]) + s.add_runtime_dependency(%q, [">= 0"]) + s.add_runtime_dependency(%q, [">= 0"]) + s.add_runtime_dependency(%q, [">= 0"]) + s.add_runtime_dependency(%q, [">= 0"]) + s.add_runtime_dependency(%q, [">= 0"]) + s.add_runtime_dependency(%q, [">= 0"]) + s.add_runtime_dependency(%q, [">= 0"]) + s.add_runtime_dependency(%q, [">= 0"]) + s.add_runtime_dependency(%q, [">= 0"]) + s.add_runtime_dependency(%q, [">= 0"]) + s.add_runtime_dependency(%q, [">= 0"]) + s.add_runtime_dependency(%q, [">= 1"]) + s.add_runtime_dependency(%q, [">= 1"]) + s.add_runtime_dependency(%q, [">= 1"]) + s.add_runtime_dependency(%q, [">= 1"]) + s.add_runtime_dependency(%q, [">= 1"]) + s.add_runtime_dependency(%q, [">= 1"]) + s.add_runtime_dependency(%q, [">= 1"]) + s.add_runtime_dependency(%q, [">= 3"]) + s.add_development_dependency(%q, [">= 0"]) + else + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 1"]) + s.add_dependency(%q, [">= 1"]) + s.add_dependency(%q, [">= 1"]) + s.add_dependency(%q, [">= 1"]) + s.add_dependency(%q, [">= 1"]) + s.add_dependency(%q, [">= 1"]) + s.add_dependency(%q, [">= 1"]) + s.add_dependency(%q, [">= 3"]) + s.add_dependency(%q, [">= 0"]) + end + else + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 1"]) + s.add_dependency(%q, [">= 1"]) + s.add_dependency(%q, [">= 1"]) + s.add_dependency(%q, [">= 1"]) + s.add_dependency(%q, [">= 1"]) + s.add_dependency(%q, [">= 1"]) + s.add_dependency(%q, [">= 1"]) + s.add_dependency(%q, [">= 3"]) + s.add_dependency(%q, [">= 0"]) + end +end + -- cgit v1.2.3 From b328664155eabd959b8b9cf40a53d9fa8b9efa81 Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Tue, 30 Nov 2010 12:43:38 +0100 Subject: Version bump to 0.0.2 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 8acdd82..4e379d2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.1 +0.0.2 -- cgit v1.2.3 From 351694975c611999856c722d8cc3ae971811bc7b Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Tue, 30 Nov 2010 13:51:06 +0100 Subject: gemspec built --- opentox-ruby.gemspec | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/opentox-ruby.gemspec b/opentox-ruby.gemspec index e89af96..9320dae 100644 --- a/opentox-ruby.gemspec +++ b/opentox-ruby.gemspec @@ -5,11 +5,11 @@ Gem::Specification.new do |s| s.name = %q{opentox-ruby} - s.version = "0.0.1" + s.version = "0.0.2" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Christoph Helma, Martin Guetlein, Andreas Maunz, Micha Rautenberg, David Vorgrimmler"] - s.date = %q{2010-11-24} + s.date = %q{2010-11-30} s.description = %q{Ruby wrapper for the OpenTox REST API (http://www.opentox.org)} s.email = %q{helma@in-silico.ch} s.executables = ["opentox-install-ubuntu.sh", "opentox-install-debian.sh"] @@ -29,17 +29,22 @@ Gem::Specification.new do |s| "lib/config/config_ru.rb", "lib/dataset.rb", "lib/environment.rb", + "lib/feature.rb", "lib/helper.rb", "lib/model.rb", + "lib/opentox-ruby.rb", "lib/opentox.owl", + "lib/opentox.rb", "lib/overwrite.rb", + "lib/parser.rb", "lib/rest_client_wrapper.rb", + "lib/serializer.rb", "lib/spork.rb", "lib/task.rb", "lib/templates/config.yaml", "lib/validation.rb" ] - s.homepage = %q{http://github.com/helma/opentox-ruby-api-wrapper} + s.homepage = %q{http://github.com/helma/opentox-ruby} s.rdoc_options = ["--charset=UTF-8"] s.require_paths = ["lib"] s.rubygems_version = %q{1.3.7} @@ -63,6 +68,7 @@ Gem::Specification.new do |s| s.add_runtime_dependency(%q, [">= 0"]) s.add_runtime_dependency(%q, [">= 0"]) s.add_runtime_dependency(%q, [">= 0"]) + s.add_runtime_dependency(%q, [">= 0"]) s.add_runtime_dependency(%q, [">= 0"]) s.add_runtime_dependency(%q, [">= 0"]) s.add_runtime_dependency(%q, [">= 0"]) @@ -89,6 +95,7 @@ Gem::Specification.new do |s| s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) @@ -116,6 +123,7 @@ Gem::Specification.new do |s| s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) -- cgit v1.2.3 From 64e5d2890f42cef112fbe768f6bfd54b746686de Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Fri, 3 Dec 2010 14:37:09 +0100 Subject: underscore method added to String class --- lib/compound.rb | 6 ++++++ lib/overwrite.rb | 44 ++++++++++++++++++++++++++------------------ 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/lib/compound.rb b/lib/compound.rb index 6834860..a85507b 100644 --- a/lib/compound.rb +++ b/lib/compound.rb @@ -68,6 +68,12 @@ module OpenTox c end + # Get InChI + # @return [String] InChI string + def to_inchi + @inchi + end + # Get (canonical) smiles # @return [String] Smiles string def to_smiles diff --git a/lib/overwrite.rb b/lib/overwrite.rb index f39fec3..8d787a6 100644 --- a/lib/overwrite.rb +++ b/lib/overwrite.rb @@ -13,26 +13,34 @@ class Sinatra::Base end class String - def task_uri? - self.uri? && !self.match(/task/).nil? - end - - def dataset_uri? - self.uri? && !self.match(/dataset/).nil? - end - - def self.model_uri? - self.uri? && !self.match(/model/).nil? - end + def task_uri? + self.uri? && !self.match(/task/).nil? + end + + def dataset_uri? + self.uri? && !self.match(/dataset/).nil? + end + + def self.model_uri? + self.uri? && !self.match(/model/).nil? + end - def uri? - begin - u = URI::parse(self) - return (u.scheme!=nil and u.host!=nil) - rescue URI::InvalidURIError - return false - end + def uri? + begin + u = URI::parse(self) + return (u.scheme!=nil and u.host!=nil) + rescue URI::InvalidURIError + return false end + end + + def underscore + self.gsub(/::/, '/'). + gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). + gsub(/([a-z\d])([A-Z])/,'\1_\2'). + tr("-", "_"). + downcase + end end require 'logger' -- cgit v1.2.3 From bb99bb49636db1d3f07b6f540dc8624a677ade2f Mon Sep 17 00:00:00 2001 From: mr Date: Mon, 6 Dec 2010 12:09:06 +0100 Subject: insert basic a&a libs to development branch --- lib/authorization.rb | 291 +++++++++++++++++++++++++++++++++++++++++++++++++++ lib/environment.rb | 2 + lib/helper.rb | 72 ++++++++++--- lib/opentox-ruby.rb | 2 +- lib/policy.rb | 242 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 592 insertions(+), 17 deletions(-) create mode 100644 lib/authorization.rb create mode 100644 lib/policy.rb diff --git a/lib/authorization.rb b/lib/authorization.rb new file mode 100644 index 0000000..0cba96a --- /dev/null +++ b/lib/authorization.rb @@ -0,0 +1,291 @@ +module OpenTox + + #Module for Authorization and Authentication + #@example Authentication + # require "opentox-ruby-api-wrapper" + # OpenTox::Authorization::AA_SERVER = "https://opensso.in-silico.ch" #if not set in .opentox/conf/[environment].yaml + # token = OpenTox::Authorization.authenticate("benutzer", "passwort") + #@see http://www.opentox.org/dev/apis/api-1.2/AA OpenTox A&A API 1.2 specification + + module Authorization + + #Helper Class AA to create and send default policies out of xml templates + #@example Creating a default policy to a URI + # aa=OpenTox::Authorization::AA.new(tok) + # xml=aa.get_xml('http://uri....') + # OpenTox::Authorization.create_policy(xml,tok) + + class AA + attr_accessor :user, :token_id, :policy + + #Generates AA object - requires token_id + # @param [String] token_id + def initialize(token_id) + @user = Authorization.get_user(token_id) + @token_id = token_id + @policy = Policies.new() + end + + #Cleans AA Policies and loads default xml file into policy attribute + #set uri and user, returns Policyfile(XML) for open-sso + # @param [String] URI to create a policy for + def get_xml(uri) + @policy.drop_policies + @policy.load_default_policy(@user, uri) + return @policy.to_xml + end + + #Loads and sends Policyfile(XML) to open-sso server + # @param [String] URI to create a policy for + def send(uri) + xml = get_xml(uri) + ret = false + ret = Authorization.create_policy(xml, @token_id) + LOGGER.debug "Policy send with token_id: #{@token_id}" + LOGGER.warn "Not created Policy is: #{xml}" if !ret + ret + end + + end + + #Returns the open-sso server set in the config file .opentox/config/[environment].yaml + # @return [String, nil] the openSSO server URI or nil + def self.server + return AA_SERVER + end + + #Authentication against OpenSSO. Returns token. Requires Username and Password. + # @param [String, String]Username,Password + # @return [String, nil] gives token_id or nil + def self.authenticate(user, pw) + return true if !AA_SERVER + begin + resource = RestClient::Resource.new("#{AA_SERVER}/auth/authenticate") + out = resource.post(:username=>user, :password => pw).sub("token.id=","").sub("\n","") + return out + rescue + return nil + end + end + + #Logout on opensso. Make token invalid. Requires token + # @param [String]token_id the token_id + # @return [Boolean] true if logout is OK + def self.logout(token_id) + begin + resource = RestClient::Resource.new("#{AA_SERVER}/auth/logout") + resource.post(:subjectid => token_id) + return true + rescue + return false + end + end + + #Authorization against OpenSSO for a URI with request-method (action) [GET/POST/PUT/DELETE] + # @param [String,String,String]uri,action,token_id + # @return [Boolean, nil] returns true, false or nil (if authorization-request fails). + def self.authorize(uri, action, token_id) + return true if !AA_SERVER + begin + resource = RestClient::Resource.new("#{AA_SERVER}/auth/authorize") + return true if resource.post(:uri => uri, :action => action, :subjectid => token_id) == "boolean=true\n" + rescue + return nil + end + end + + #Checks if a token is a valid token + # @param [String]token_id token_id from openSSO session + # @return [Boolean] token_id is valid or not. + def self.is_token_valid(token_id) + return true if !AA_SERVER + begin + resource = RestClient::Resource.new("#{AA_SERVER}/auth/isTokenValid") + return true if resource.post(:tokenid => token_id) == "boolean=true\n" + rescue + return false + end + end + + #Returns array with all policies of the token owner + # @param [String]token_id requires token_id + # @return [Array, nil] returns an Array of policy names or nil if request fails + def self.list_policies(token_id) + begin + resource = RestClient::Resource.new("#{AA_SERVER}/pol") + out = resource.get(:subjectid => token_id) + return out.split("\n") + rescue + return nil + end + end + + #Returns a policy in xml-format + # @param [String, String]policy,token_id + # @return [String] XML of the policy + def self.list_policy(policy, token_id) + begin + resource = RestClient::Resource.new("#{AA_SERVER}/pol") + return resource.get(:subjectid => token_id,:id => policy) + rescue + return nil + end + end + + #Returns the owner (who created the first policy) of an URI + # @param [String, String]uri,token_id + # return [String, nil]owner,nil returns owner of the URI + def self.get_uri_owner(uri, token_id) + begin + resource = RestClient::Resource.new("#{AA_SERVER}/pol") + return resource.get(:uri => uri, :subjectid => token_id).sub("\n","") + rescue + return nil + end + end + + #Checks if a policy exists to a URI. Requires URI and token. + # @param [String, String]uri,token_id + # return [Boolean] + def self.uri_has_policy(uri, token_id) + owner = get_uri_owner(uri, token_id) + return true if owner and owner != "null" + false + end + + #List all policynames for a URI. Requires URI and token. + # @param [String, String]uri,token_id + # return [Array, nil] returns an Array of policy names or nil if request fails + def self.list_uri_policies(uri, token_id) + begin + resource = RestClient::Resource.new("#{AA_SERVER}/pol") + out = resource.get(:uri => uri, :polnames => true, :subjectid => token_id) + policies = []; notfirstline = false + out.split("\n").each do |line| + policies << line if notfirstline + notfirstline = true + end + return policies + rescue + return nil + end + end + + #Sends a policy in xml-format to opensso server. Requires policy-xml and token. + # @param [String, String]policyxml,token_id + # return [Boolean] returns true if policy is created + def self.create_policy(policy, token_id) + begin +# resource = RestClient::Resource.new("#{AA_SERVER}/Pol/opensso-pol") + LOGGER.debug "OpenTox::Authorization.create_policy policy: #{policy[168,43]} with token:" + token_id.to_s + " length: " + token_id.length.to_s +# return true if resource.post(policy, :subjectid => token_id, :content_type => "application/xml") + return true if RestClientWrapper.post("#{AA_SERVER}/pol", {:subjectid => token_id, :content_type => "application/xml"}, policy) + rescue + return false + end + end + + #Deletes a policy + # @param [String, String]policyname,token_id + # @return [Boolean,nil] + def self.delete_policy(policy, token_id) + begin + resource = RestClient::Resource.new("#{AA_SERVER}/pol") + LOGGER.debug "OpenTox::Authorization.delete_policy policy: #{policy} with token: #{token_id}" + return true if resource.delete(:subjectid => token_id, :id => policy) + rescue + return nil + end + end + + #Returns array of all possible LDAP-Groups + # @param [String]token_id + # @return [Array] + def self.list_groups(token_id) + begin + resource = RestClient::Resource.new("#{AA_SERVER}/opensso/identity/search") + grps = resource.post(:admin => token_id, :attributes_names => "objecttype", :attributes_values_objecttype => "group") + grps.split("\n").collect{|x| x.sub("string=","")} + rescue + [] + end + end + + #Returns array of the LDAP-Groups of an user + # @param [String]token_id + # @return [Array] gives array of LDAP groups of a user + def self.list_user_groups(user, token_id) + begin + resource = RestClient::Resource.new("#{AA_SERVER}/opensso/identity/read") + out = resource.post(:name => user, :admin => token_id, :attributes_names => "group") + grps = [] + out.split("\n").each do |line| + grps << line.sub("identitydetails.group=","") if line.include?("identitydetails.group=") + end + return grps + rescue + [] + end + end + + #Returns the owner (user id) of a token + # @param [String]token_id + # @return [String]user + def self.get_user(token_id) + begin + resource = RestClient::Resource.new("#{AA_SERVER}/opensso/identity/attributes") + out = resource.post(:subjectid => token_id, :attributes_names => "uid") + user = ""; check = false + out.split("\n").each do |line| + if check + user = line.sub("userdetails.attribute.value=","") if line.include?("userdetails.attribute.value=") + check = false + end + check = true if line.include?("userdetails.attribute.name=uid") + end + return user + rescue + nil + end + end + + #Send default policy with Authorization::AA class + # @param [String, String]URI,token_id + def self.send_policy(uri, token_id) + return true if !AA_SERVER + aa = Authorization::AA.new(token_id) + ret = aa.send(uri) + LOGGER.debug "OpenTox::Authorization send policy for URI: #{uri} | token_id: #{token_id} - policy created: #{ret}" + ret + end + + #Deletes all policies of an URI + # @param [String, String]URI,token_id + # @return [Boolean] + def self.delete_policies_from_uri(uri, token_id) + policies = list_uri_policies(uri, token_id) + policies.each do |policy| + ret = delete_policy(policy, token_id) + LOGGER.debug "OpenTox::Authorization delete policy: #{policy} - with result: #{ret}" + end + return true + end + + #Checks (if token_id is valid) if a policy exist and create default policy if not + def self.check_policy(uri, token_id) + token_valid = OpenTox::Authorization.is_token_valid(token_id) + LOGGER.debug "OpenTox::Authorization.check_policy with uri: #{uri}, token_id: #{token_id} is valid: #{token_valid}" + if uri and token_valid + if !uri_has_policy(uri, token_id) + return send_policy(uri, token_id) + else + LOGGER.debug "OpenTox::Authorization.check_policy URI: #{uri} has already a Policy." + end + end + true + end + + end +end + + diff --git a/lib/environment.rb b/lib/environment.rb index 4f1cc80..1761d92 100644 --- a/lib/environment.rb +++ b/lib/environment.rb @@ -83,6 +83,8 @@ class OwlNamespace end +AA_SERVER = CONFIG[:authorization] ? (CONFIG[:authorization][:server] ? CONFIG[:authorization][:server] : nil) : nil + RDF = OwlNamespace.new 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' OWL = OwlNamespace.new 'http://www.w3.org/2002/07/owl#' DC = OwlNamespace.new 'http://purl.org/dc/elements/1.1/' diff --git a/lib/helper.rb b/lib/helper.rb index a9f451e..b69f9b4 100644 --- a/lib/helper.rb +++ b/lib/helper.rb @@ -1,26 +1,66 @@ helpers do # Authentification - def protected! - response['WWW-Authenticate'] = %(Basic realm="Testing HTTP Auth") and \ + def protected!(token_id) + if env["session"] + flash[:notice] = "You don't have access to this section: " and \ + redirect back and \ + return unless authorized?(token_id) + end throw(:halt, [401, "Not authorized\n"]) and \ - return unless authorized? + return unless authorized?(token_id) end - - def authorized? - @auth ||= Rack::Auth::Basic::Request.new(request.env) - @auth.provided? && @auth.basic? && @auth.credentials && @auth.credentials == ['api', API_KEY] + + def authorized?(token_id) + case request.env['REQUEST_METHOD'] + when "DELETE", "PUT" + ret = OpenTox::Authorization.authorize(request.env['SCRIPT_URI'], request.env['REQUEST_METHOD'], token_id) + LOGGER.debug "OpenTox helpers OpenTox::Authorization authorized? method: #{request.env['REQUEST_METHOD']} , URI: #{request.env['SCRIPT_URI']}, token_id: #{token_id} with return #{ret}." + return ret + when "POST" + if OpenTox::Authorization.is_token_valid(token_id) + LOGGER.debug "OpenTox helpers OpenTox::Authorization.is_token_valid: true" + return true + end + LOGGER.warn "OpenTox helpers POST on #{request.env['SCRIPT_URI']} with token_id: #{token_id} false." + end + LOGGER.debug "Not authorized for: 1. #{request['SCRIPT_URI']} 2. #{request.env['SCRIPT_URI']} with Method: #{request.env['REQUEST_METHOD']} with Token #{token_id}" + LOGGER.debug "Request infos: #{request.inspect}" + return false end - -=begin - def xml(object) - builder do |xml| - xml.instruct! - object.to_xml - end - end -=end + def unprotected_requests + case env['REQUEST_URI'] + when /\/login$|\/logout$|\/predict$|\/upload$/ + return true + when /\/compound|\/feature|\/task|\/toxcreate/ #to fix: read from config | validation should be protected + return true + else + return false + end + end + + def check_token_id(token_id) + return false if !token_id + return true if token_id.size > 62 + false + end +end +before do + + unless unprotected_requests or env['REQUEST_METHOD'] == "GET" + begin + token_id = session[:token_id] if session[:token_id] + token_id = params[:token_id] if params[:token_id] and !check_token_id(token_id) + token_id = request.env['HTTP_TOKEN_ID'] if request.env['HTTP_TOKEN_ID'] and !check_token_id(token_id) + # see http://rack.rubyforge.org/doc/SPEC.html + rescue + LOGGER.debug "OpenTox api wrapper: helper before filter: NO token_id." + token_id = "" + end + protected!(token_id) if AA_SERVER + end + end diff --git a/lib/opentox-ruby.rb b/lib/opentox-ruby.rb index 9f9ff26..c0bff95 100644 --- a/lib/opentox-ruby.rb +++ b/lib/opentox-ruby.rb @@ -8,6 +8,6 @@ rescue LoadError puts "Please install Openbabel with 'rake openbabel:install' in the compound component" end -['opentox', 'compound','dataset', 'parser','serializer', 'algorithm','model','task','validation','feature', 'rest_client_wrapper'].each do |lib| +['opentox', 'compound','dataset', 'parser','serializer', 'algorithm','model','task','validation','feature', 'rest_client_wrapper', 'authorization', 'policy', 'helper'].each do |lib| require lib end diff --git a/lib/policy.rb b/lib/policy.rb new file mode 100644 index 0000000..0ef8298 --- /dev/null +++ b/lib/policy.rb @@ -0,0 +1,242 @@ +module OpenTox + require "rexml/document" + + #Module for policy-processing + # @see also http://www.opentox.org/dev/apis/api-1.2/AA for opentox API specs + # Class Policies corresponds to container of an xml-policy-fle + class Policies + + attr_accessor :name, :policies + + def initialize() + @policies = {} + end + + #create new policy instance with name + # @param [String]name of the policy + def new_policy(name) + @policies[name] = Policy.new(name) + end + + #drop a specific policy in a policies instance + # @param [String]name of the policy + # @return [Boolean] + def drop_policy(name) + return true if @policies.delete(name) + end + + #drop all policies in a policies instance + def drop_policies + @policies.each do |name, policy| + drop_policy(name) + end + return true + end + + #loads a default policy template in policies instance + def load_default_policy(user, uri, group="member") + template = case user + when "guest", "anonymous" then "default_guest_policy" + else "default_policy" + end + xml = File.read(File.join(File.dirname(__FILE__), "templates/#{template}.xml")) + self.load_xml(xml) + datestring = Time.now.strftime("%Y-%m-%d-%H-%M-%S-x") + rand(1000).to_s + + @policies["policy_user"].name = "policy_user_#{user}_#{datestring}" + @policies["policy_user"].rules["rule_user"].uri = uri + @policies["policy_user"].rules["rule_user"].name = "rule_user_#{user}_#{datestring}" + @policies["policy_user"].subjects["subject_user"].name = "subject_user_#{user}_#{datestring}" + @policies["policy_user"].subjects["subject_user"].value = "uid=#{user},ou=people,dc=opentox,dc=org" + @policies["policy_user"].subject_group = "subjects_user_#{user}_#{datestring}" + + @policies["policy_group"].name = "policy_group_#{group}_#{datestring}" + @policies["policy_group"].rules["rule_group"].uri = uri + @policies["policy_group"].rules["rule_group"].name = "rule_group_#{group}_#{datestring}" + @policies["policy_group"].subjects["subject_group"].name = "subject_group_#{group}_#{datestring}" + @policies["policy_group"].subjects["subject_group"].value = "cn=#{group},ou=groups,dc=opentox,dc=org" + @policies["policy_group"].subject_group = "subjects_#{group}_#{datestring}" + return true + end + + #loads a xml template + def load_xml(xml) + rexml = REXML::Document.new(xml) + rexml.elements.each("Policies/Policy") do |pol| #Policies + policy_name = pol.attributes["name"] + new_policy(policy_name) + #@policies[policy_name] = Policy.new(policy_name) + rexml.elements.each("Policies/Policy[@name='#{policy_name}']/Rule") do |r| #Rules + rule_name = r.attributes["name"] + uri = rexml.elements["Policies/Policy[@name='#{policy_name}']/Rule[@name='#{rule_name}']/ResourceName"].attributes["name"] + @policies[policy_name].rules[rule_name] = @policies[policy_name].new_rule(rule_name, uri) + rexml.elements.each("Policies/Policy[@name='#{policy_name}']/Rule[@name='#{rule_name}']/AttributeValuePair") do |attribute_pairs| + action=nil; value=nil; + attribute_pairs.each_element do |elem| + action = elem.attributes["name"] if elem.attributes["name"] + value = elem.text if elem.text + end + if action and value + case action + when "GET" + @policies[policy_name].rules[rule_name].get = value + when "POST" + @policies[policy_name].rules[rule_name].post = value + when "PUT" + @policies[policy_name].rules[rule_name].put = value + when "DELETE" + @policies[policy_name].rules[rule_name].delete = value + end + end + end + end + rexml.elements.each("Policies/Policy[@name='#{policy_name}']/Subjects") do |subjects| #Subjects + @policies[policy_name].subject_group = subjects.attributes["name"] + rexml.elements.each("Policies/Policy[@name='#{policy_name}']/Subjects[@name='#{@policies[policy_name].subject_group}']/Subject") do |s| #Subject + subject_name = s.attributes["name"] + subject_type = s.attributes["type"] + subject_value = rexml.elements["Policies/Policy[@name='#{policy_name}']/Subjects[@name='#{@policies[policy_name].subject_group}']/Subject[@name='#{subject_name}']/AttributeValuePair/Value"].text + @policies[policy_name].new_subject(subject_name, subject_type, subject_value) if subject_name and subject_type and subject_value + end + end + end + end + + #generates xml from policies instance + def to_xml + doc = REXML::Document.new() + doc << REXML::DocType.new("Policies", "PUBLIC \"-//Sun Java System Access Manager7.1 2006Q3\n Admin CLI DTD//EN\" \"jar://com/sun/identity/policy/policyAdmin.dtd\"") + doc.add_element(REXML::Element.new("Policies")) + + @policies.each do |name, pol| + policy = REXML::Element.new("Policy") + policy.attributes["name"] = pol.name + policy.attributes["referralPolicy"] = false + policy.attributes["active"] = true + @policies[name].rules.each do |r,rl| + rule = @policies[name].rules[r] + out_rule = REXML::Element.new("Rule") + out_rule.attributes["name"] = rule.name + servicename = REXML::Element.new("ServiceName") + servicename.attributes["name"]="iPlanetAMWebAgentService" + out_rule.add_element(servicename) + rescourcename = REXML::Element.new("ResourceName") + rescourcename.attributes["name"] = rule.uri + out_rule.add_element(rescourcename) + + ["get","post","delete","put"].each do |act| + if rule.method(act).call + attribute = REXML::Element.new("Attribute") + attribute.attributes["name"] = act.upcase + attributevaluepair = REXML::Element.new("AttributeValuePair") + attributevaluepair.add_element(attribute) + attributevalue = REXML::Element.new("Value") + attributevaluepair.add_element(attributevalue) + attributevalue.add_text REXML::Text.new(rule.method(act).call) + out_rule.add_element(attributevaluepair) + + end + end + policy.add_element(out_rule) + end + + subjects = REXML::Element.new("Subjects") + subjects.attributes["name"] = pol.subject_group + subjects.attributes["description"] = "" + @policies[name].subjects.each do |subj, subjs| + subject = REXML::Element.new("Subject") + subject.attributes["name"] = pol.subjects[subj].name + subject.attributes["type"] = pol.subjects[subj].type + subject.attributes["includeType"] = "inclusive" + attributevaluepair = REXML::Element.new("AttributeValuePair") + attribute = REXML::Element.new("Attribute") + attribute.attributes["name"] = "Values" + attributevaluepair.add_element(attribute) + attributevalue = REXML::Element.new("Value") + attributevalue.add_text REXML::Text.new(pol.subjects[subj].value) + attributevaluepair.add_element(attributevalue) + subject.add_element(attributevaluepair) + subjects.add_element(subject) + end + policy.add_element(subjects) + doc.root.add_element(policy) + end + out = "" + doc.write(out, 2) + return out + end + + end + + #single policy in a policies instance + class Policy + + attr_accessor :name, :rules, :subject_group, :subjects + + def initialize(name) + @name = name + @rules = {} + @subject_group = "" + @subjects = {} + end + + #create a new rule instance for the policy + def new_rule(name, uri) + @rules[name] = Rule.new(name, uri) + end + + #create a new subject instance for the policy + def new_subject(name, type, value) + @subjects[name] = Subject.new(name, type, value) + end + + #rule inside a policy + class Rule + + attr_accessor :name, :uri, :get, :post, :put, :delete + + def initialize(name, uri) + @name = name + @uri = uri + end + + def rename(new, old) + self[new] = self.delete(old) + self[new].name = new + end + + def get=(value) + @get = check_value(value, @get) + end + + def post=(value) + @post = check_value(value, @post) + end + + def delete=(value) + @delete = check_value(value, @delete) + end + + def put=(value) + @put = check_value(value, @put) + end + + private + #checks if value is allow or deny. returns old value if not valid. + def check_value(new_value, old_value) + return (new_value=="allow" || new_value=="deny" || new_value==nil) ? new_value : old_value + end + end + + class Subject + + attr_accessor :name, :type, :value + + def initialize(name, type, value) + @name = name + @type = type + @value = value + end + end + end +end \ No newline at end of file -- cgit v1.2.3 From c4504c72ffb2920de65399a2dc0a2c29fe04a52d Mon Sep 17 00:00:00 2001 From: mr Date: Thu, 9 Dec 2010 10:46:13 +0100 Subject: A&A implementation --- lib/dataset.rb | 12 +++++--- lib/helper.rb | 4 +-- lib/model.rb | 4 +-- lib/templates/default_guest_policy.xml | 53 ++++++++++++++++++++++++++++++++++ lib/templates/default_policy.xml | 53 ++++++++++++++++++++++++++++++++++ 5 files changed, 118 insertions(+), 8 deletions(-) create mode 100644 lib/templates/default_guest_policy.xml create mode 100644 lib/templates/default_policy.xml diff --git a/lib/dataset.rb b/lib/dataset.rb index c5704ae..bbd8b8b 100644 --- a/lib/dataset.rb +++ b/lib/dataset.rb @@ -6,6 +6,7 @@ module OpenTox include OpenTox attr_reader :features, :compounds, :data_entries, :metadata + attr_accessor :token_id # Create dataset with optional URI. Does not load data into the dataset - you will need to execute one of the load_* methods to pull data from a service or to insert it from other representations. # @example Create an empty dataset @@ -14,8 +15,9 @@ module OpenTox # dataset = OpenTox::Dataset.new("http:://webservices.in-silico/ch/dataset/1") # @param [optional, String] uri Dataset URI # @return [OpenTox::Dataset] Dataset object - def initialize(uri=nil) + def initialize(uri=nil,token_id=nil) super uri + @token_id = token_id @features = {} @compounds = [] @data_entries = {} @@ -26,8 +28,9 @@ module OpenTox # dataset = OpenTox::Dataset.create # @param [optional, String] uri Dataset URI # @return [OpenTox::Dataset] Dataset object - def self.create(uri=CONFIG[:services]["opentox-dataset"]) + def self.create(uri=CONFIG[:services]["opentox-dataset"], token_id=nil) dataset = Dataset.new + dataset.token_id = token_id if token_id dataset.save dataset end @@ -252,7 +255,7 @@ module OpenTox @compounds.uniq! if @uri if (CONFIG[:yaml_hosts].include?(URI.parse(@uri).host)) - RestClientWrapper.post(@uri,{:content_type => "application/x-yaml"},self.to_yaml) + RestClientWrapper.post(@uri,{:content_type => "application/x-yaml", :token_id => @token_id},self.to_yaml) else File.open("ot-post-file.rdf","w+") { |f| f.write(self.to_rdfxml); @path = f.path } task_uri = RestClient.post(@uri, {:file => File.new(@path)},{:accept => "text/uri-list"}).to_s.chomp @@ -262,7 +265,7 @@ module OpenTox end else # create dataset if uri is empty - self.uri = RestClientWrapper.post(CONFIG[:services]["opentox-dataset"],{}).to_s.chomp + self.uri = RestClientWrapper.post(CONFIG[:services]["opentox-dataset"],{:token_id => @token_id}).to_s.chomp end @uri end @@ -279,6 +282,7 @@ module OpenTox @data_entries = dataset.data_entries @compounds = dataset.compounds @features = dataset.features + @token_id = dataset.token_id if @uri self.uri = @uri else diff --git a/lib/helper.rb b/lib/helper.rb index b69f9b4..11f790b 100644 --- a/lib/helper.rb +++ b/lib/helper.rb @@ -17,7 +17,7 @@ helpers do ret = OpenTox::Authorization.authorize(request.env['SCRIPT_URI'], request.env['REQUEST_METHOD'], token_id) LOGGER.debug "OpenTox helpers OpenTox::Authorization authorized? method: #{request.env['REQUEST_METHOD']} , URI: #{request.env['SCRIPT_URI']}, token_id: #{token_id} with return #{ret}." return ret - when "POST" + when "POST", "HEAD" if OpenTox::Authorization.is_token_valid(token_id) LOGGER.debug "OpenTox helpers OpenTox::Authorization.is_token_valid: true" return true @@ -25,7 +25,6 @@ helpers do LOGGER.warn "OpenTox helpers POST on #{request.env['SCRIPT_URI']} with token_id: #{token_id} false." end LOGGER.debug "Not authorized for: 1. #{request['SCRIPT_URI']} 2. #{request.env['SCRIPT_URI']} with Method: #{request.env['REQUEST_METHOD']} with Token #{token_id}" - LOGGER.debug "Request infos: #{request.inspect}" return false end @@ -55,6 +54,7 @@ before do token_id = params[:token_id] if params[:token_id] and !check_token_id(token_id) token_id = request.env['HTTP_TOKEN_ID'] if request.env['HTTP_TOKEN_ID'] and !check_token_id(token_id) # see http://rack.rubyforge.org/doc/SPEC.html + token_id = CGI.unescape(token_id) if token_id.include?("%23") rescue LOGGER.debug "OpenTox api wrapper: helper before filter: NO token_id." token_id = "" diff --git a/lib/model.rb b/lib/model.rb index 5654bcc..5dc4d4a 100644 --- a/lib/model.rb +++ b/lib/model.rb @@ -32,7 +32,7 @@ module OpenTox include Model include Algorithm - attr_accessor :compound, :prediction_dataset, :features, :effects, :activities, :p_values, :fingerprints, :feature_calculation_algorithm, :similarity_algorithm, :prediction_algorithm, :min_sim + attr_accessor :compound, :prediction_dataset, :features, :effects, :activities, :p_values, :fingerprints, :feature_calculation_algorithm, :similarity_algorithm, :prediction_algorithm, :min_sim, :token_id def initialize(uri=nil) @@ -258,7 +258,7 @@ module OpenTox # Save model at model service def save - self.uri = RestClientWrapper.post(@uri,{:content_type => "application/x-yaml"},self.to_yaml) + self.uri = RestClientWrapper.post(@uri,{:content_type => "application/x-yaml", :token_id => @token_id},self.to_yaml) end # Delete model at model service diff --git a/lib/templates/default_guest_policy.xml b/lib/templates/default_guest_policy.xml new file mode 100644 index 0000000..a778070 --- /dev/null +++ b/lib/templates/default_guest_policy.xml @@ -0,0 +1,53 @@ + + + + + + + + + + allow + + + + allow + + + + allow + + + + allow + + + + + + + uid=guest,ou=people,dc=opentox,dc=org + + + + + + + + + + + allow + + + + + + + cn=member,ou=groups,dc=opentox,dc=org + + + + + diff --git a/lib/templates/default_policy.xml b/lib/templates/default_policy.xml new file mode 100644 index 0000000..a778070 --- /dev/null +++ b/lib/templates/default_policy.xml @@ -0,0 +1,53 @@ + + + + + + + + + + allow + + + + allow + + + + allow + + + + allow + + + + + + + uid=guest,ou=people,dc=opentox,dc=org + + + + + + + + + + + allow + + + + + + + cn=member,ou=groups,dc=opentox,dc=org + + + + + -- cgit v1.2.3 From 3dd413a79d8ef32c8bf0426228e34d87bdcd5a6b Mon Sep 17 00:00:00 2001 From: mr Date: Thu, 9 Dec 2010 10:47:28 +0100 Subject: Ontology Service with Endpoint-option-list from http://apps.ideaconsult.net:8080/ontology --- lib/ontology_service.rb | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 lib/ontology_service.rb diff --git a/lib/ontology_service.rb b/lib/ontology_service.rb new file mode 100644 index 0000000..4ff688f --- /dev/null +++ b/lib/ontology_service.rb @@ -0,0 +1,43 @@ +module OpenTox + module OntologyService + module Endpoints + require 'sparql/client' + @sparql = SPARQL::Client.new("http://apps.ideaconsult.net:8080/ontology") + def self.qs(classname="Endpoints") + return "PREFIX ot: + PREFIX ota: + PREFIX owl: + PREFIX dc: + PREFIX rdfs: + PREFIX rdf: + PREFIX otee: + PREFIX toxcast: + select ?Endpoints ?title ?id + where {?Endpoints rdfs:subClassOf otee:#{classname}. + OPTIONAL {?Endpoints dc:title ?title}. + OPTIONAL {?Endpoints dc:identifier ?id}.} + ORDER BY ?title" + end + + def self.make_option_list(endpoint="Endpoints", level=1) + out = "" + results = @sparql.query(qs(endpoint)) rescue results = [] + results.each do |result| + endpointname = result.Endpoints.to_s.split('#').last + title = result.bound?(:title) ? result.title : endpointname + out += "\n" + out += make_option_list(endpointname, level + 1) + end + return out + end + + def self.get_endpoint_selectlist(include_blank=true) + out = "\n" + return out + end + end + end +end \ No newline at end of file -- cgit v1.2.3 From de12a8002ebc0bf4018588deafd5057b6478f414 Mon Sep 17 00:00:00 2001 From: mr Date: Fri, 10 Dec 2010 17:19:35 +0100 Subject: A&A configuration options / request script_uri replaced --- lib/helper.rb | 40 ++++++++++++++++++++-------------------- lib/templates/config.yaml | 13 +++++++++++++ 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/lib/helper.rb b/lib/helper.rb index 11f790b..6247460 100644 --- a/lib/helper.rb +++ b/lib/helper.rb @@ -1,30 +1,32 @@ helpers do - # Authentification + # Authentification def protected!(token_id) - if env["session"] + if env["session"] flash[:notice] = "You don't have access to this section: " and \ redirect back and \ return unless authorized?(token_id) + elsif !env["session"] && token_id + throw(:halt, [401, "Not authorized.\n"]) and \ + redirect back and \ + return unless authorized?(token_id) end - throw(:halt, [401, "Not authorized\n"]) and \ + throw(:halt, [401, "Not authorized.\n"]) and \ return unless authorized?(token_id) end - + def authorized?(token_id) - case request.env['REQUEST_METHOD'] - when "DELETE", "PUT" - ret = OpenTox::Authorization.authorize(request.env['SCRIPT_URI'], request.env['REQUEST_METHOD'], token_id) - LOGGER.debug "OpenTox helpers OpenTox::Authorization authorized? method: #{request.env['REQUEST_METHOD']} , URI: #{request.env['SCRIPT_URI']}, token_id: #{token_id} with return #{ret}." + if CONFIG[:authorization][:authorize_request].include?(request.env['REQUEST_METHOD']) + ret = OpenTox::Authorization.authorize("#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}", request.env['REQUEST_METHOD'], token_id) + LOGGER.debug "OpenTox helpers OpenTox::Authorization authorized? method: #{request.env['REQUEST_METHOD']} , URI: #{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}, token_id: #{token_id} with return #{ret}." return ret - when "POST", "HEAD" + end + if CONFIG[:authorization][:authenticate_request].include?(env['REQUEST_METHOD']) if OpenTox::Authorization.is_token_valid(token_id) - LOGGER.debug "OpenTox helpers OpenTox::Authorization.is_token_valid: true" return true end - LOGGER.warn "OpenTox helpers POST on #{request.env['SCRIPT_URI']} with token_id: #{token_id} false." - end - LOGGER.debug "Not authorized for: 1. #{request['SCRIPT_URI']} 2. #{request.env['SCRIPT_URI']} with Method: #{request.env['REQUEST_METHOD']} with Token #{token_id}" + end + LOGGER.debug "Not authorized for: #{request.env['rack.url_scheme']}://#{request['REQUEST_URI']} with Method: #{request.env['REQUEST_METHOD']} with Token #{token_id}" return false end @@ -32,7 +34,7 @@ helpers do case env['REQUEST_URI'] when /\/login$|\/logout$|\/predict$|\/upload$/ return true - when /\/compound|\/feature|\/task|\/toxcreate/ #to fix: read from config | validation should be protected + when /\/compound|\/feature|\/task|\/toxcreate/ #to fix: read from config | validation should be protected return true else return false @@ -43,24 +45,22 @@ helpers do return false if !token_id return true if token_id.size > 62 false - end + end end before do - - unless unprotected_requests or env['REQUEST_METHOD'] == "GET" + unless unprotected_requests or CONFIG[:authorization][:free_request].include?(env['REQUEST_METHOD']) begin token_id = session[:token_id] if session[:token_id] token_id = params[:token_id] if params[:token_id] and !check_token_id(token_id) token_id = request.env['HTTP_TOKEN_ID'] if request.env['HTTP_TOKEN_ID'] and !check_token_id(token_id) # see http://rack.rubyforge.org/doc/SPEC.html - token_id = CGI.unescape(token_id) if token_id.include?("%23") + token_id = CGI.unescape(token_id) if token_id.include?("%23") rescue - LOGGER.debug "OpenTox api wrapper: helper before filter: NO token_id." + LOGGER.debug "OpenTox ruby api wrapper: helper before filter: NO token_id." token_id = "" end protected!(token_id) if AA_SERVER end - end diff --git a/lib/templates/config.yaml b/lib/templates/config.yaml index 00c00cb..db11006 100644 --- a/lib/templates/config.yaml +++ b/lib/templates/config.yaml @@ -39,3 +39,16 @@ # Uncomment for verbose logging # :logger: debug + +# OpenSSO Authorization +# set ":server: nil" to disable A&A +:authorization: + :server: "https://opensso.in-silico.ch" + :free_request: #not controlled by A&A + - "GET" + :authenticate_request: #only for authenticated user + - "POST" + :authorize_request: #only for authenticated and authorizeduser + - "DELETE" + - "PUT" + \ No newline at end of file -- cgit v1.2.3 From 4c2470353a1e3b69b4260d0052c9c48137ef76d3 Mon Sep 17 00:00:00 2001 From: mr Date: Tue, 14 Dec 2010 12:30:20 +0100 Subject: remove token_id from tables in database --- lib/dataset.rb | 16 ++++++---------- lib/helper.rb | 2 +- lib/model.rb | 10 +++++----- lib/task.rb | 1 + lib/templates/config.yaml | 2 +- 5 files changed, 14 insertions(+), 17 deletions(-) diff --git a/lib/dataset.rb b/lib/dataset.rb index bbd8b8b..b7feeec 100644 --- a/lib/dataset.rb +++ b/lib/dataset.rb @@ -6,7 +6,6 @@ module OpenTox include OpenTox attr_reader :features, :compounds, :data_entries, :metadata - attr_accessor :token_id # Create dataset with optional URI. Does not load data into the dataset - you will need to execute one of the load_* methods to pull data from a service or to insert it from other representations. # @example Create an empty dataset @@ -15,9 +14,8 @@ module OpenTox # dataset = OpenTox::Dataset.new("http:://webservices.in-silico/ch/dataset/1") # @param [optional, String] uri Dataset URI # @return [OpenTox::Dataset] Dataset object - def initialize(uri=nil,token_id=nil) + def initialize(uri=nil) super uri - @token_id = token_id @features = {} @compounds = [] @data_entries = {} @@ -30,8 +28,7 @@ module OpenTox # @return [OpenTox::Dataset] Dataset object def self.create(uri=CONFIG[:services]["opentox-dataset"], token_id=nil) dataset = Dataset.new - dataset.token_id = token_id if token_id - dataset.save + dataset.save(token_id) dataset end @@ -250,22 +247,22 @@ module OpenTox # - creates a new dataset if uri is not set # - overwrites dataset if uri exists # @return [String] Dataset URI - def save + def save(token_id=nil) # TODO: rewrite feature URI's ?? @compounds.uniq! if @uri if (CONFIG[:yaml_hosts].include?(URI.parse(@uri).host)) - RestClientWrapper.post(@uri,{:content_type => "application/x-yaml", :token_id => @token_id},self.to_yaml) + RestClientWrapper.post(@uri,{:content_type => "application/x-yaml", :token_id => token_id},self.to_yaml) else File.open("ot-post-file.rdf","w+") { |f| f.write(self.to_rdfxml); @path = f.path } - task_uri = RestClient.post(@uri, {:file => File.new(@path)},{:accept => "text/uri-list"}).to_s.chomp + task_uri = RestClient.post(@uri, {:file => File.new(@path)},{:accept => "text/uri-list" , :token_id => token_id}).to_s.chomp #task_uri = `curl -X POST -H "Accept:text/uri-list" -F "file=@#{@path};type=application/rdf+xml" http://apps.ideaconsult.net:8080/ambit2/dataset` Task.find(task_uri).wait_for_completion self.uri = RestClientWrapper.get(task_uri,:accept => 'text/uri-list') end else # create dataset if uri is empty - self.uri = RestClientWrapper.post(CONFIG[:services]["opentox-dataset"],{:token_id => @token_id}).to_s.chomp + self.uri = RestClientWrapper.post(CONFIG[:services]["opentox-dataset"],{:token_id => token_id}).to_s.chomp end @uri end @@ -282,7 +279,6 @@ module OpenTox @data_entries = dataset.data_entries @compounds = dataset.compounds @features = dataset.features - @token_id = dataset.token_id if @uri self.uri = @uri else diff --git a/lib/helper.rb b/lib/helper.rb index 6247460..42c35e8 100644 --- a/lib/helper.rb +++ b/lib/helper.rb @@ -32,7 +32,7 @@ helpers do def unprotected_requests case env['REQUEST_URI'] - when /\/login$|\/logout$|\/predict$|\/upload$/ + when /\/login$|\/logout$|\/predict$|\/toxcreate\/models$/ return true when /\/compound|\/feature|\/task|\/toxcreate/ #to fix: read from config | validation should be protected return true diff --git a/lib/model.rb b/lib/model.rb index 5dc4d4a..9c2fb97 100644 --- a/lib/model.rb +++ b/lib/model.rb @@ -111,7 +111,7 @@ module OpenTox # @param [String] compound_uri Compound URI # @param [optinal,Boolean] verbose Verbose prediction (output includes neighbors and features) # @return [OpenTox::Dataset] Dataset with prediction - def predict(compound_uri,verbose=false) + def predict(compound_uri,verbose=false,token_id=nil) @compound = Compound.new compound_uri features = {} @@ -119,7 +119,7 @@ module OpenTox unless @prediction_dataset #@prediction_dataset = cached_prediction #return @prediction_dataset if cached_prediction - @prediction_dataset = Dataset.create + @prediction_dataset = Dataset.create(CONFIG[:services]["opentox-dataset"], token_id) @prediction_dataset.add_metadata( { OT.hasSource => @uri, DC.creator => @uri, @@ -217,7 +217,7 @@ module OpenTox end end - @prediction_dataset.save + @prediction_dataset.save(token_id) @prediction_dataset end @@ -257,8 +257,8 @@ module OpenTox end # Save model at model service - def save - self.uri = RestClientWrapper.post(@uri,{:content_type => "application/x-yaml", :token_id => @token_id},self.to_yaml) + def save(token_id) + self.uri = RestClientWrapper.post(@uri,{:content_type => "application/x-yaml", :token_id => token_id},self.to_yaml) end # Delete model at model service diff --git a/lib/task.rb b/lib/task.rb index 17f95e6..18fba6e 100644 --- a/lib/task.rb +++ b/lib/task.rb @@ -49,6 +49,7 @@ module OpenTox cpu_load = `cat /proc/loadavg`.split(/\s+/)[0..2].collect{|c| c.to_f} nr_cpu_cores = `cat /proc/cpuinfo |grep "cpu cores"|cut -d ":" -f2|tr -d " "`.split("\n").collect{|c| c.to_i}.inject{|sum,n| sum+n} + nr_cpu_cores = 1 if !nr_cpu_cores if cpu_load[0] > nr_cpu_cores and cpu_load[0] > cpu_load[1] and cpu_load[1] > cpu_load[2] # average CPU load of the last minute is high and CPU load is increasing LOGGER.warn "Cannot start task - CPU load too high (#{cpu_load.join(", ")})" task.cancel diff --git a/lib/templates/config.yaml b/lib/templates/config.yaml index db11006..116f462 100644 --- a/lib/templates/config.yaml +++ b/lib/templates/config.yaml @@ -41,7 +41,7 @@ # :logger: debug # OpenSSO Authorization -# set ":server: nil" to disable A&A +# set ":server: " to disable A&A :authorization: :server: "https://opensso.in-silico.ch" :free_request: #not controlled by A&A -- cgit v1.2.3 From 2fb2f4cd34f499f8c9def5e4091cb5998794c595 Mon Sep 17 00:00:00 2001 From: mr Date: Tue, 14 Dec 2010 16:39:01 +0100 Subject: rename token_id to subjectid --- lib/authorization.rb | 132 +++++++++++++++++++++++++-------------------------- lib/dataset.rb | 12 ++--- lib/helper.rb | 40 ++++++++-------- lib/model.rb | 12 ++--- 4 files changed, 98 insertions(+), 98 deletions(-) diff --git a/lib/authorization.rb b/lib/authorization.rb index 0cba96a..f9499e6 100644 --- a/lib/authorization.rb +++ b/lib/authorization.rb @@ -16,13 +16,13 @@ module OpenTox # OpenTox::Authorization.create_policy(xml,tok) class AA - attr_accessor :user, :token_id, :policy + attr_accessor :user, :subjectid, :policy - #Generates AA object - requires token_id - # @param [String] token_id - def initialize(token_id) - @user = Authorization.get_user(token_id) - @token_id = token_id + #Generates AA object - requires subjectid + # @param [String] subjectid + def initialize(subjectid) + @user = Authorization.get_user(subjectid) + @subjectid = subjectid @policy = Policies.new() end @@ -40,8 +40,8 @@ module OpenTox def send(uri) xml = get_xml(uri) ret = false - ret = Authorization.create_policy(xml, @token_id) - LOGGER.debug "Policy send with token_id: #{@token_id}" + ret = Authorization.create_policy(xml, @subjectid) + LOGGER.debug "Policy send with subjectid: #{@subjectid}" LOGGER.warn "Not created Policy is: #{xml}" if !ret ret end @@ -56,7 +56,7 @@ module OpenTox #Authentication against OpenSSO. Returns token. Requires Username and Password. # @param [String, String]Username,Password - # @return [String, nil] gives token_id or nil + # @return [String, nil] gives subjectid or nil def self.authenticate(user, pw) return true if !AA_SERVER begin @@ -69,12 +69,12 @@ module OpenTox end #Logout on opensso. Make token invalid. Requires token - # @param [String]token_id the token_id + # @param [String]subjectid the subjectid # @return [Boolean] true if logout is OK - def self.logout(token_id) + def self.logout(subjectid) begin resource = RestClient::Resource.new("#{AA_SERVER}/auth/logout") - resource.post(:subjectid => token_id) + resource.post(:subjectid => subjectid) return true rescue return false @@ -82,38 +82,38 @@ module OpenTox end #Authorization against OpenSSO for a URI with request-method (action) [GET/POST/PUT/DELETE] - # @param [String,String,String]uri,action,token_id + # @param [String,String,String]uri,action,subjectid # @return [Boolean, nil] returns true, false or nil (if authorization-request fails). - def self.authorize(uri, action, token_id) + def self.authorize(uri, action, subjectid) return true if !AA_SERVER begin resource = RestClient::Resource.new("#{AA_SERVER}/auth/authorize") - return true if resource.post(:uri => uri, :action => action, :subjectid => token_id) == "boolean=true\n" + return true if resource.post(:uri => uri, :action => action, :subjectid => subjectid) == "boolean=true\n" rescue return nil end end #Checks if a token is a valid token - # @param [String]token_id token_id from openSSO session - # @return [Boolean] token_id is valid or not. - def self.is_token_valid(token_id) + # @param [String]subjectid subjectid from openSSO session + # @return [Boolean] subjectid is valid or not. + def self.is_token_valid(subjectid) return true if !AA_SERVER begin resource = RestClient::Resource.new("#{AA_SERVER}/auth/isTokenValid") - return true if resource.post(:tokenid => token_id) == "boolean=true\n" + return true if resource.post(:tokenid => subjectid) == "boolean=true\n" rescue return false end end #Returns array with all policies of the token owner - # @param [String]token_id requires token_id + # @param [String]subjectid requires subjectid # @return [Array, nil] returns an Array of policy names or nil if request fails - def self.list_policies(token_id) + def self.list_policies(subjectid) begin resource = RestClient::Resource.new("#{AA_SERVER}/pol") - out = resource.get(:subjectid => token_id) + out = resource.get(:subjectid => subjectid) return out.split("\n") rescue return nil @@ -121,45 +121,45 @@ module OpenTox end #Returns a policy in xml-format - # @param [String, String]policy,token_id + # @param [String, String]policy,subjectid # @return [String] XML of the policy - def self.list_policy(policy, token_id) + def self.list_policy(policy, subjectid) begin resource = RestClient::Resource.new("#{AA_SERVER}/pol") - return resource.get(:subjectid => token_id,:id => policy) + return resource.get(:subjectid => subjectid,:id => policy) rescue return nil end end #Returns the owner (who created the first policy) of an URI - # @param [String, String]uri,token_id + # @param [String, String]uri,subjectid # return [String, nil]owner,nil returns owner of the URI - def self.get_uri_owner(uri, token_id) + def self.get_uri_owner(uri, subjectid) begin resource = RestClient::Resource.new("#{AA_SERVER}/pol") - return resource.get(:uri => uri, :subjectid => token_id).sub("\n","") + return resource.get(:uri => uri, :subjectid => subjectid).sub("\n","") rescue return nil end end #Checks if a policy exists to a URI. Requires URI and token. - # @param [String, String]uri,token_id + # @param [String, String]uri,subjectid # return [Boolean] - def self.uri_has_policy(uri, token_id) - owner = get_uri_owner(uri, token_id) + def self.uri_has_policy(uri, subjectid) + owner = get_uri_owner(uri, subjectid) return true if owner and owner != "null" false end #List all policynames for a URI. Requires URI and token. - # @param [String, String]uri,token_id + # @param [String, String]uri,subjectid # return [Array, nil] returns an Array of policy names or nil if request fails - def self.list_uri_policies(uri, token_id) + def self.list_uri_policies(uri, subjectid) begin resource = RestClient::Resource.new("#{AA_SERVER}/pol") - out = resource.get(:uri => uri, :polnames => true, :subjectid => token_id) + out = resource.get(:uri => uri, :polnames => true, :subjectid => subjectid) policies = []; notfirstline = false out.split("\n").each do |line| policies << line if notfirstline @@ -172,39 +172,39 @@ module OpenTox end #Sends a policy in xml-format to opensso server. Requires policy-xml and token. - # @param [String, String]policyxml,token_id + # @param [String, String]policyxml,subjectid # return [Boolean] returns true if policy is created - def self.create_policy(policy, token_id) + def self.create_policy(policy, subjectid) begin # resource = RestClient::Resource.new("#{AA_SERVER}/Pol/opensso-pol") - LOGGER.debug "OpenTox::Authorization.create_policy policy: #{policy[168,43]} with token:" + token_id.to_s + " length: " + token_id.length.to_s -# return true if resource.post(policy, :subjectid => token_id, :content_type => "application/xml") - return true if RestClientWrapper.post("#{AA_SERVER}/pol", {:subjectid => token_id, :content_type => "application/xml"}, policy) + LOGGER.debug "OpenTox::Authorization.create_policy policy: #{policy[168,43]} with token:" + subjectid.to_s + " length: " + subjectid.length.to_s +# return true if resource.post(policy, :subjectid => subjectid, :content_type => "application/xml") + return true if RestClientWrapper.post("#{AA_SERVER}/pol", {:subjectid => subjectid, :content_type => "application/xml"}, policy) rescue return false end end #Deletes a policy - # @param [String, String]policyname,token_id + # @param [String, String]policyname,subjectid # @return [Boolean,nil] - def self.delete_policy(policy, token_id) + def self.delete_policy(policy, subjectid) begin resource = RestClient::Resource.new("#{AA_SERVER}/pol") - LOGGER.debug "OpenTox::Authorization.delete_policy policy: #{policy} with token: #{token_id}" - return true if resource.delete(:subjectid => token_id, :id => policy) + LOGGER.debug "OpenTox::Authorization.delete_policy policy: #{policy} with token: #{subjectid}" + return true if resource.delete(:subjectid => subjectid, :id => policy) rescue return nil end end #Returns array of all possible LDAP-Groups - # @param [String]token_id + # @param [String]subjectid # @return [Array] - def self.list_groups(token_id) + def self.list_groups(subjectid) begin resource = RestClient::Resource.new("#{AA_SERVER}/opensso/identity/search") - grps = resource.post(:admin => token_id, :attributes_names => "objecttype", :attributes_values_objecttype => "group") + grps = resource.post(:admin => subjectid, :attributes_names => "objecttype", :attributes_values_objecttype => "group") grps.split("\n").collect{|x| x.sub("string=","")} rescue [] @@ -212,12 +212,12 @@ module OpenTox end #Returns array of the LDAP-Groups of an user - # @param [String]token_id + # @param [String]subjectid # @return [Array] gives array of LDAP groups of a user - def self.list_user_groups(user, token_id) + def self.list_user_groups(user, subjectid) begin resource = RestClient::Resource.new("#{AA_SERVER}/opensso/identity/read") - out = resource.post(:name => user, :admin => token_id, :attributes_names => "group") + out = resource.post(:name => user, :admin => subjectid, :attributes_names => "group") grps = [] out.split("\n").each do |line| grps << line.sub("identitydetails.group=","") if line.include?("identitydetails.group=") @@ -229,12 +229,12 @@ module OpenTox end #Returns the owner (user id) of a token - # @param [String]token_id + # @param [String]subjectid # @return [String]user - def self.get_user(token_id) + def self.get_user(subjectid) begin resource = RestClient::Resource.new("#{AA_SERVER}/opensso/identity/attributes") - out = resource.post(:subjectid => token_id, :attributes_names => "uid") + out = resource.post(:subjectid => subjectid, :attributes_names => "uid") user = ""; check = false out.split("\n").each do |line| if check @@ -250,34 +250,34 @@ module OpenTox end #Send default policy with Authorization::AA class - # @param [String, String]URI,token_id - def self.send_policy(uri, token_id) + # @param [String, String]URI,subjectid + def self.send_policy(uri, subjectid) return true if !AA_SERVER - aa = Authorization::AA.new(token_id) + aa = Authorization::AA.new(subjectid) ret = aa.send(uri) - LOGGER.debug "OpenTox::Authorization send policy for URI: #{uri} | token_id: #{token_id} - policy created: #{ret}" + LOGGER.debug "OpenTox::Authorization send policy for URI: #{uri} | subjectid: #{subjectid} - policy created: #{ret}" ret end #Deletes all policies of an URI - # @param [String, String]URI,token_id + # @param [String, String]URI,subjectid # @return [Boolean] - def self.delete_policies_from_uri(uri, token_id) - policies = list_uri_policies(uri, token_id) + def self.delete_policies_from_uri(uri, subjectid) + policies = list_uri_policies(uri, subjectid) policies.each do |policy| - ret = delete_policy(policy, token_id) + ret = delete_policy(policy, subjectid) LOGGER.debug "OpenTox::Authorization delete policy: #{policy} - with result: #{ret}" end return true end - #Checks (if token_id is valid) if a policy exist and create default policy if not - def self.check_policy(uri, token_id) - token_valid = OpenTox::Authorization.is_token_valid(token_id) - LOGGER.debug "OpenTox::Authorization.check_policy with uri: #{uri}, token_id: #{token_id} is valid: #{token_valid}" + #Checks (if subjectid is valid) if a policy exist and create default policy if not + def self.check_policy(uri, subjectid) + token_valid = OpenTox::Authorization.is_token_valid(subjectid) + LOGGER.debug "OpenTox::Authorization.check_policy with uri: #{uri}, subjectid: #{subjectid} is valid: #{token_valid}" if uri and token_valid - if !uri_has_policy(uri, token_id) - return send_policy(uri, token_id) + if !uri_has_policy(uri, subjectid) + return send_policy(uri, subjectid) else LOGGER.debug "OpenTox::Authorization.check_policy URI: #{uri} has already a Policy." end diff --git a/lib/dataset.rb b/lib/dataset.rb index b7feeec..7c70c9d 100644 --- a/lib/dataset.rb +++ b/lib/dataset.rb @@ -26,9 +26,9 @@ module OpenTox # dataset = OpenTox::Dataset.create # @param [optional, String] uri Dataset URI # @return [OpenTox::Dataset] Dataset object - def self.create(uri=CONFIG[:services]["opentox-dataset"], token_id=nil) + def self.create(uri=CONFIG[:services]["opentox-dataset"], subjectid=nil) dataset = Dataset.new - dataset.save(token_id) + dataset.save(subjectid) dataset end @@ -247,22 +247,22 @@ module OpenTox # - creates a new dataset if uri is not set # - overwrites dataset if uri exists # @return [String] Dataset URI - def save(token_id=nil) + def save(subjectid=nil) # TODO: rewrite feature URI's ?? @compounds.uniq! if @uri if (CONFIG[:yaml_hosts].include?(URI.parse(@uri).host)) - RestClientWrapper.post(@uri,{:content_type => "application/x-yaml", :token_id => token_id},self.to_yaml) + RestClientWrapper.post(@uri,{:content_type => "application/x-yaml", :subjectid => subjectid},self.to_yaml) else File.open("ot-post-file.rdf","w+") { |f| f.write(self.to_rdfxml); @path = f.path } - task_uri = RestClient.post(@uri, {:file => File.new(@path)},{:accept => "text/uri-list" , :token_id => token_id}).to_s.chomp + task_uri = RestClient.post(@uri, {:file => File.new(@path)},{:accept => "text/uri-list" , :subjectid => subjectid}).to_s.chomp #task_uri = `curl -X POST -H "Accept:text/uri-list" -F "file=@#{@path};type=application/rdf+xml" http://apps.ideaconsult.net:8080/ambit2/dataset` Task.find(task_uri).wait_for_completion self.uri = RestClientWrapper.get(task_uri,:accept => 'text/uri-list') end else # create dataset if uri is empty - self.uri = RestClientWrapper.post(CONFIG[:services]["opentox-dataset"],{:token_id => token_id}).to_s.chomp + self.uri = RestClientWrapper.post(CONFIG[:services]["opentox-dataset"],{:subjectid => subjectid}).to_s.chomp end @uri end diff --git a/lib/helper.rb b/lib/helper.rb index 42c35e8..6b616bc 100644 --- a/lib/helper.rb +++ b/lib/helper.rb @@ -1,32 +1,32 @@ helpers do # Authentification - def protected!(token_id) + def protected!(subjectid) if env["session"] flash[:notice] = "You don't have access to this section: " and \ redirect back and \ - return unless authorized?(token_id) - elsif !env["session"] && token_id + return unless authorized?(subjectid) + elsif !env["session"] && subjectid throw(:halt, [401, "Not authorized.\n"]) and \ redirect back and \ - return unless authorized?(token_id) + return unless authorized?(subjectid) end throw(:halt, [401, "Not authorized.\n"]) and \ - return unless authorized?(token_id) + return unless authorized?(subjectid) end - def authorized?(token_id) + def authorized?(subjectid) if CONFIG[:authorization][:authorize_request].include?(request.env['REQUEST_METHOD']) - ret = OpenTox::Authorization.authorize("#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}", request.env['REQUEST_METHOD'], token_id) - LOGGER.debug "OpenTox helpers OpenTox::Authorization authorized? method: #{request.env['REQUEST_METHOD']} , URI: #{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}, token_id: #{token_id} with return #{ret}." + ret = OpenTox::Authorization.authorize("#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}", request.env['REQUEST_METHOD'], subjectid) + LOGGER.debug "OpenTox helpers OpenTox::Authorization authorized? method: #{request.env['REQUEST_METHOD']} , URI: #{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}, subjectid: #{subjectid} with return #{ret}." return ret end if CONFIG[:authorization][:authenticate_request].include?(env['REQUEST_METHOD']) - if OpenTox::Authorization.is_token_valid(token_id) + if OpenTox::Authorization.is_token_valid(subjectid) return true end end - LOGGER.debug "Not authorized for: #{request.env['rack.url_scheme']}://#{request['REQUEST_URI']} with Method: #{request.env['REQUEST_METHOD']} with Token #{token_id}" + LOGGER.debug "Not authorized for: #{request.env['rack.url_scheme']}://#{request['REQUEST_URI']} with Method: #{request.env['REQUEST_METHOD']} with Token #{subjectid}" return false end @@ -41,9 +41,9 @@ helpers do end end - def check_token_id(token_id) - return false if !token_id - return true if token_id.size > 62 + def check_subjectid(subjectid) + return false if !subjectid + return true if subjectid.size > 62 false end end @@ -51,16 +51,16 @@ end before do unless unprotected_requests or CONFIG[:authorization][:free_request].include?(env['REQUEST_METHOD']) begin - token_id = session[:token_id] if session[:token_id] - token_id = params[:token_id] if params[:token_id] and !check_token_id(token_id) - token_id = request.env['HTTP_TOKEN_ID'] if request.env['HTTP_TOKEN_ID'] and !check_token_id(token_id) + subjectid = session[:subjectid] if session[:subjectid] + subjectid = params[:subjectid] if params[:subjectid] and !check_subjectid(subjectid) + subjectid = request.env['HTTP_SUBJECTID'] if request.env['HTTP_SUBJECTID'] and !check_subjectid(subjectid) # see http://rack.rubyforge.org/doc/SPEC.html - token_id = CGI.unescape(token_id) if token_id.include?("%23") + subjectid = CGI.unescape(subjectid) if subjectid.include?("%23") rescue - LOGGER.debug "OpenTox ruby api wrapper: helper before filter: NO token_id." - token_id = "" + LOGGER.debug "OpenTox ruby api wrapper: helper before filter: NO subjectid." + subjectid = "" end - protected!(token_id) if AA_SERVER + protected!(subjectid) if AA_SERVER end end diff --git a/lib/model.rb b/lib/model.rb index 9c2fb97..953bb6c 100644 --- a/lib/model.rb +++ b/lib/model.rb @@ -32,7 +32,7 @@ module OpenTox include Model include Algorithm - attr_accessor :compound, :prediction_dataset, :features, :effects, :activities, :p_values, :fingerprints, :feature_calculation_algorithm, :similarity_algorithm, :prediction_algorithm, :min_sim, :token_id + attr_accessor :compound, :prediction_dataset, :features, :effects, :activities, :p_values, :fingerprints, :feature_calculation_algorithm, :similarity_algorithm, :prediction_algorithm, :min_sim, :subjectid def initialize(uri=nil) @@ -111,7 +111,7 @@ module OpenTox # @param [String] compound_uri Compound URI # @param [optinal,Boolean] verbose Verbose prediction (output includes neighbors and features) # @return [OpenTox::Dataset] Dataset with prediction - def predict(compound_uri,verbose=false,token_id=nil) + def predict(compound_uri,verbose=false,subjectid=nil) @compound = Compound.new compound_uri features = {} @@ -119,7 +119,7 @@ module OpenTox unless @prediction_dataset #@prediction_dataset = cached_prediction #return @prediction_dataset if cached_prediction - @prediction_dataset = Dataset.create(CONFIG[:services]["opentox-dataset"], token_id) + @prediction_dataset = Dataset.create(CONFIG[:services]["opentox-dataset"], subjectid) @prediction_dataset.add_metadata( { OT.hasSource => @uri, DC.creator => @uri, @@ -217,7 +217,7 @@ module OpenTox end end - @prediction_dataset.save(token_id) + @prediction_dataset.save(subjectid) @prediction_dataset end @@ -257,8 +257,8 @@ module OpenTox end # Save model at model service - def save(token_id) - self.uri = RestClientWrapper.post(@uri,{:content_type => "application/x-yaml", :token_id => token_id},self.to_yaml) + def save(subjectid) + self.uri = RestClientWrapper.post(@uri,{:content_type => "application/x-yaml", :subjectid => subjectid},self.to_yaml) end # Delete model at model service -- cgit v1.2.3 From b22110ae0d8e902d700e0a3dc629ebfde1edfe10 Mon Sep 17 00:00:00 2001 From: mr Date: Thu, 16 Dec 2010 10:59:46 +0100 Subject: A&A --- lib/dataset.rb | 18 +++++++++--------- lib/helper.rb | 2 +- lib/model.rb | 18 +++++++++--------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/dataset.rb b/lib/dataset.rb index 7c70c9d..aba7754 100644 --- a/lib/dataset.rb +++ b/lib/dataset.rb @@ -38,12 +38,12 @@ module OpenTox # - you will have to set remaining metadata manually # @param [String] file CSV file path # @return [OpenTox::Dataset] Dataset object with CSV data - def self.create_from_csv_file(file) - dataset = Dataset.create + def self.create_from_csv_file(file, subjectid=nil) + dataset = Dataset.create(CONFIG[:services]["opentox-dataset"], subjectid) parser = Parser::Spreadsheets.new parser.dataset = dataset parser.load_csv(File.open(file).read) - dataset.save + dataset.save(subjectid) dataset end @@ -89,8 +89,8 @@ module OpenTox # - you will have to set remaining metadata manually # @param [String] csv CSV representation of the dataset # @return [OpenTox::Dataset] Dataset object with CSV data - def load_csv(csv) - save unless @uri # get a uri for creating features + def load_csv(csv, subjectid=nil) + save(subjectid) unless @uri # get a uri for creating features parser = Parser::Spreadsheets.new parser.dataset = self parser.load_csv(csv) @@ -102,8 +102,8 @@ module OpenTox # - you will have to set remaining metadata manually # @param [Excel] book Excel workbook object (created with roo gem) # @return [OpenTox::Dataset] Dataset object with Excel data - def load_spreadsheet(book) - save unless @uri # get a uri for creating features + def load_spreadsheet(book, subjectid=nil) + save(subjectid) unless @uri # get a uri for creating features parser = Parser::Spreadsheets.new parser.dataset = self parser.load_spreadsheet(book) @@ -268,8 +268,8 @@ module OpenTox end # Delete dataset at the dataset service - def delete - RestClientWrapper.delete @uri + def delete(subjectid=nil) + RestClientWrapper.delete(@uri, :subjectid => subjectid) end private diff --git a/lib/helper.rb b/lib/helper.rb index 6b616bc..965b4ad 100644 --- a/lib/helper.rb +++ b/lib/helper.rb @@ -57,7 +57,7 @@ before do # see http://rack.rubyforge.org/doc/SPEC.html subjectid = CGI.unescape(subjectid) if subjectid.include?("%23") rescue - LOGGER.debug "OpenTox ruby api wrapper: helper before filter: NO subjectid." + LOGGER.debug "OpenTox ruby api wrapper: helper before filter: NO subjectid for URI: #{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}" subjectid = "" end protected!(subjectid) if AA_SERVER diff --git a/lib/model.rb b/lib/model.rb index 953bb6c..c645bdc 100644 --- a/lib/model.rb +++ b/lib/model.rb @@ -90,8 +90,8 @@ module OpenTox # Predict a dataset # @param [String] dataset_uri Dataset URI # @return [OpenTox::Dataset] Dataset with predictions - def predict_dataset(dataset_uri) - @prediction_dataset = Dataset.create + def predict_dataset(dataset_uri, subjectid=nil) + @prediction_dataset = Dataset.create(CONFIG[:services]["opentox-dataset"], subjectid) @prediction_dataset.add_metadata({ OT.hasSource => @uri, DC.creator => @uri, @@ -101,9 +101,9 @@ module OpenTox d = Dataset.new(dataset_uri) d.load_compounds d.compounds.each do |compound_uri| - predict(compound_uri,false) + predict(compound_uri,false,subjectid) end - @prediction_dataset.save + @prediction_dataset.save(subjectid) @prediction_dataset end @@ -129,7 +129,7 @@ module OpenTox } ) end - return @prediction_dataset if database_activity + return @prediction_dataset if database_activity(subjectid) neighbors prediction = eval("#{@prediction_algorithm}(@neighbors,{:similarity_algorithm => @similarity_algorithm, :p_values => @p_values})") @@ -245,11 +245,11 @@ module OpenTox # Find database activities and store them in @prediction_dataset # @return [Boolean] true if compound has databasse activities, false if not - def database_activity + def database_activity(subjectid) if @activities[@compound.uri] @activities[@compound.uri].each { |act| @prediction_dataset.add @compound.uri, @metadata[OT.dependentVariables], act } @prediction_dataset.add_metadata(OT.hasSource => @metadata[OT.trainingDataset]) - @prediction_dataset.save + @prediction_dataset.save(subjectid) true else false @@ -262,8 +262,8 @@ module OpenTox end # Delete model at model service - def delete - RestClientWrapper.delete @uri unless @uri == CONFIG[:services]["opentox-model"] + def delete(subjectid) + RestClientWrapper.delete(@uri, :subjectid => subjectid) unless @uri == CONFIG[:services]["opentox-model"] end end -- cgit v1.2.3 From 4c089275d34ba42014e1add97a41ccf351790260 Mon Sep 17 00:00:00 2001 From: mr Date: Wed, 5 Jan 2011 10:30:54 +0100 Subject: Authorization for GET requests --- lib/dataset.rb | 30 +++++++++++++++--------------- lib/feature.rb | 4 ++-- lib/helper.rb | 31 ++++++++++++++++++++----------- lib/model.rb | 4 ++-- 4 files changed, 39 insertions(+), 30 deletions(-) diff --git a/lib/dataset.rb b/lib/dataset.rb index aba7754..52b41a7 100644 --- a/lib/dataset.rb +++ b/lib/dataset.rb @@ -14,7 +14,7 @@ module OpenTox # dataset = OpenTox::Dataset.new("http:://webservices.in-silico/ch/dataset/1") # @param [optional, String] uri Dataset URI # @return [OpenTox::Dataset] Dataset object - def initialize(uri=nil) + def initialize(uri=nil,subjectid=nil) super uri @features = {} @compounds = [] @@ -27,7 +27,7 @@ module OpenTox # @param [optional, String] uri Dataset URI # @return [OpenTox::Dataset] Dataset object def self.create(uri=CONFIG[:services]["opentox-dataset"], subjectid=nil) - dataset = Dataset.new + dataset = Dataset.new(nil,subjectid) dataset.save(subjectid) dataset end @@ -50,17 +50,17 @@ module OpenTox # Find a dataset and load all data. This can be time consuming, use Dataset.new together with one of the load_* methods for a fine grained control over data loading. # @param [String] uri Dataset URI # @return [OpenTox::Dataset] Dataset object with all data - def self.find(uri) - dataset = Dataset.new(uri) - dataset.load_all + def self.find(uri, subjectid=nil) + dataset = Dataset.new(uri, subjectid) + dataset.load_all(subjectid) dataset end # Get all datasets from a service # @param [optional,String] uri URI of the dataset service, defaults to service specified in configuration # @return [Array] Array of dataset object without data (use one of the load_* methods to pull data from the server) - def self.all(uri=CONFIG[:services]["opentox-dataset"]) - RestClientWrapper.get(uri,:accept => "text/uri-list").to_s.each_line.collect{|u| Dataset.new(u)} + def self.all(uri=CONFIG[:services]["opentox-dataset"], subjectid=nil) + RestClientWrapper.get(uri,{:accept => "text/uri-list",:subjectid => subjectid}).to_s.each_line.collect{|u| Dataset.new(u)} end # Load YAML representation into the dataset @@ -118,9 +118,9 @@ module OpenTox end # Load all data (metadata, data_entries, compounds and features) from URI - def load_all + def load_all(subjectid=nil) if (CONFIG[:yaml_hosts].include?(URI.parse(@uri).host)) - copy YAML.load(RestClientWrapper.get(@uri, :accept => "application/x-yaml")) + copy YAML.load(RestClientWrapper.get(@uri, {:accept => "application/x-yaml", :subjectid => subjectid})) else parser = Parser::Owl::Dataset.new(@uri) copy parser.load_uri @@ -129,8 +129,8 @@ module OpenTox # Load and return only compound URIs from the dataset service # @return [Array] Compound URIs in the dataset - def load_compounds - RestClientWrapper.get(File.join(uri,"compounds"),:accept=> "text/uri-list").to_s.each_line do |compound_uri| + def load_compounds(subjectid=nil) + RestClientWrapper.get(File.join(uri,"compounds"),{:accept=> "text/uri-list", :subjectid => subjectid}).to_s.each_line do |compound_uri| @compounds << compound_uri.chomp end @compounds.uniq! @@ -258,7 +258,7 @@ module OpenTox task_uri = RestClient.post(@uri, {:file => File.new(@path)},{:accept => "text/uri-list" , :subjectid => subjectid}).to_s.chomp #task_uri = `curl -X POST -H "Accept:text/uri-list" -F "file=@#{@path};type=application/rdf+xml" http://apps.ideaconsult.net:8080/ambit2/dataset` Task.find(task_uri).wait_for_completion - self.uri = RestClientWrapper.get(task_uri,:accept => 'text/uri-list') + self.uri = RestClientWrapper.get(task_uri,{:accept => 'text/uri-list', :subjectid => subjectid}) end else # create dataset if uri is empty @@ -293,9 +293,9 @@ module OpenTox # Find a prediction dataset and load all data. # @param [String] uri Prediction dataset URI # @return [OpenTox::Dataset] Prediction dataset object with all data - def self.find(uri) - prediction = LazarPrediction.new(uri) - prediction.load_all + def self.find(uri, subjectid=nil) + prediction = LazarPrediction.new(uri, subjectid) + prediction.load_all(subjectid) prediction end diff --git a/lib/feature.rb b/lib/feature.rb index 9e28077..349f8ae 100644 --- a/lib/feature.rb +++ b/lib/feature.rb @@ -2,10 +2,10 @@ module OpenTox class Feature include OpenTox - def self.find(uri) + def self.find(uri, subjectid=nil) feature = Feature.new uri if (CONFIG[:yaml_hosts].include?(URI.parse(uri).host)) - feature.add_metadata YAML.load(RestClientWrapper.get(uri,:accept => "application/x-yaml")) + feature.add_metadata YAML.load(RestClientWrapper.get(uri,{:accept => "application/x-yaml", :subjectid => subjectid})) else feature.add_metadata Parser::Owl::Dataset.new(uri).load_metadata end diff --git a/lib/helper.rb b/lib/helper.rb index 965b4ad..cb80018 100644 --- a/lib/helper.rb +++ b/lib/helper.rb @@ -15,21 +15,35 @@ helpers do return unless authorized?(subjectid) end + #Check Authorization for URI with method and subjectid. def authorized?(subjectid) + uri = clean_uri("#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}") if CONFIG[:authorization][:authorize_request].include?(request.env['REQUEST_METHOD']) - ret = OpenTox::Authorization.authorize("#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}", request.env['REQUEST_METHOD'], subjectid) - LOGGER.debug "OpenTox helpers OpenTox::Authorization authorized? method: #{request.env['REQUEST_METHOD']} , URI: #{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}, subjectid: #{subjectid} with return #{ret}." + ret = OpenTox::Authorization.authorize(uri, request.env['REQUEST_METHOD'], subjectid) + LOGGER.debug "OpenTox helpers OpenTox::Authorization authorized? method: #{request.env['REQUEST_METHOD']} , URI: #{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}, subjectid: #{subjectid} with return >>#{ret}<<" return ret end if CONFIG[:authorization][:authenticate_request].include?(env['REQUEST_METHOD']) - if OpenTox::Authorization.is_token_valid(subjectid) - return true - end + return true if OpenTox::Authorization.is_token_valid(subjectid) end LOGGER.debug "Not authorized for: #{request.env['rack.url_scheme']}://#{request['REQUEST_URI']} with Method: #{request.env['REQUEST_METHOD']} with Token #{subjectid}" return false end + #cleans URI from querystring and file-extension. Sets port 80 to emptystring + # @param [String] uri + def clean_uri(uri) + out = URI.parse(uri) + "#{out.scheme}:" + (out.port != 80 ? out.port : "") + "//#{out.host}#{out.path.chomp(File.extname(out.path))}" + end + + def check_subjectid(subjectid) + return false if !subjectid + return true if subjectid.size > 62 + false + end + + #unprotected uris for login/logout, webapplication ... def unprotected_requests case env['REQUEST_URI'] when /\/login$|\/logout$|\/predict$|\/toxcreate\/models$/ @@ -41,18 +55,13 @@ helpers do end end - def check_subjectid(subjectid) - return false if !subjectid - return true if subjectid.size > 62 - false - end end before do unless unprotected_requests or CONFIG[:authorization][:free_request].include?(env['REQUEST_METHOD']) begin subjectid = session[:subjectid] if session[:subjectid] - subjectid = params[:subjectid] if params[:subjectid] and !check_subjectid(subjectid) + subjectid = params[:subjectid] if params[:subjectid] and !check_subjectid(subjectid) subjectid = request.env['HTTP_SUBJECTID'] if request.env['HTTP_SUBJECTID'] and !check_subjectid(subjectid) # see http://rack.rubyforge.org/doc/SPEC.html subjectid = CGI.unescape(subjectid) if subjectid.include?("%23") diff --git a/lib/model.rb b/lib/model.rb index c645bdc..32f5604 100644 --- a/lib/model.rb +++ b/lib/model.rb @@ -67,8 +67,8 @@ module OpenTox # Find a lazar model # @param [String] uri Model URI # @return [OpenTox::Model::Lazar] lazar model - def self.find(uri) - YAML.load RestClientWrapper.get(uri,:accept => 'application/x-yaml') + def self.find(uri, subjectid=nil) + YAML.load RestClientWrapper.get(uri,{:accept => 'application/x-yaml', :subjectid => subjectid}) end # Create a new lazar model -- cgit v1.2.3 From a0bcb593e95320bff832f5cca9b9f4c105c817d3 Mon Sep 17 00:00:00 2001 From: mr Date: Mon, 10 Jan 2011 17:04:20 +0100 Subject: A&A --- lib/authorization.rb | 4 +++- lib/dataset.rb | 48 ++++++++++++++++++++++++------------------------ 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/lib/authorization.rb b/lib/authorization.rb index f9499e6..dab228a 100644 --- a/lib/authorization.rb +++ b/lib/authorization.rb @@ -114,7 +114,9 @@ module OpenTox begin resource = RestClient::Resource.new("#{AA_SERVER}/pol") out = resource.get(:subjectid => subjectid) - return out.split("\n") + return out.split("\n") + rescue RestClient::InternalServerError => e + raise e.response rescue return nil end diff --git a/lib/dataset.rb b/lib/dataset.rb index 7c70c9d..52b41a7 100644 --- a/lib/dataset.rb +++ b/lib/dataset.rb @@ -14,7 +14,7 @@ module OpenTox # dataset = OpenTox::Dataset.new("http:://webservices.in-silico/ch/dataset/1") # @param [optional, String] uri Dataset URI # @return [OpenTox::Dataset] Dataset object - def initialize(uri=nil) + def initialize(uri=nil,subjectid=nil) super uri @features = {} @compounds = [] @@ -27,7 +27,7 @@ module OpenTox # @param [optional, String] uri Dataset URI # @return [OpenTox::Dataset] Dataset object def self.create(uri=CONFIG[:services]["opentox-dataset"], subjectid=nil) - dataset = Dataset.new + dataset = Dataset.new(nil,subjectid) dataset.save(subjectid) dataset end @@ -38,29 +38,29 @@ module OpenTox # - you will have to set remaining metadata manually # @param [String] file CSV file path # @return [OpenTox::Dataset] Dataset object with CSV data - def self.create_from_csv_file(file) - dataset = Dataset.create + def self.create_from_csv_file(file, subjectid=nil) + dataset = Dataset.create(CONFIG[:services]["opentox-dataset"], subjectid) parser = Parser::Spreadsheets.new parser.dataset = dataset parser.load_csv(File.open(file).read) - dataset.save + dataset.save(subjectid) dataset end # Find a dataset and load all data. This can be time consuming, use Dataset.new together with one of the load_* methods for a fine grained control over data loading. # @param [String] uri Dataset URI # @return [OpenTox::Dataset] Dataset object with all data - def self.find(uri) - dataset = Dataset.new(uri) - dataset.load_all + def self.find(uri, subjectid=nil) + dataset = Dataset.new(uri, subjectid) + dataset.load_all(subjectid) dataset end # Get all datasets from a service # @param [optional,String] uri URI of the dataset service, defaults to service specified in configuration # @return [Array] Array of dataset object without data (use one of the load_* methods to pull data from the server) - def self.all(uri=CONFIG[:services]["opentox-dataset"]) - RestClientWrapper.get(uri,:accept => "text/uri-list").to_s.each_line.collect{|u| Dataset.new(u)} + def self.all(uri=CONFIG[:services]["opentox-dataset"], subjectid=nil) + RestClientWrapper.get(uri,{:accept => "text/uri-list",:subjectid => subjectid}).to_s.each_line.collect{|u| Dataset.new(u)} end # Load YAML representation into the dataset @@ -89,8 +89,8 @@ module OpenTox # - you will have to set remaining metadata manually # @param [String] csv CSV representation of the dataset # @return [OpenTox::Dataset] Dataset object with CSV data - def load_csv(csv) - save unless @uri # get a uri for creating features + def load_csv(csv, subjectid=nil) + save(subjectid) unless @uri # get a uri for creating features parser = Parser::Spreadsheets.new parser.dataset = self parser.load_csv(csv) @@ -102,8 +102,8 @@ module OpenTox # - you will have to set remaining metadata manually # @param [Excel] book Excel workbook object (created with roo gem) # @return [OpenTox::Dataset] Dataset object with Excel data - def load_spreadsheet(book) - save unless @uri # get a uri for creating features + def load_spreadsheet(book, subjectid=nil) + save(subjectid) unless @uri # get a uri for creating features parser = Parser::Spreadsheets.new parser.dataset = self parser.load_spreadsheet(book) @@ -118,9 +118,9 @@ module OpenTox end # Load all data (metadata, data_entries, compounds and features) from URI - def load_all + def load_all(subjectid=nil) if (CONFIG[:yaml_hosts].include?(URI.parse(@uri).host)) - copy YAML.load(RestClientWrapper.get(@uri, :accept => "application/x-yaml")) + copy YAML.load(RestClientWrapper.get(@uri, {:accept => "application/x-yaml", :subjectid => subjectid})) else parser = Parser::Owl::Dataset.new(@uri) copy parser.load_uri @@ -129,8 +129,8 @@ module OpenTox # Load and return only compound URIs from the dataset service # @return [Array] Compound URIs in the dataset - def load_compounds - RestClientWrapper.get(File.join(uri,"compounds"),:accept=> "text/uri-list").to_s.each_line do |compound_uri| + def load_compounds(subjectid=nil) + RestClientWrapper.get(File.join(uri,"compounds"),{:accept=> "text/uri-list", :subjectid => subjectid}).to_s.each_line do |compound_uri| @compounds << compound_uri.chomp end @compounds.uniq! @@ -258,7 +258,7 @@ module OpenTox task_uri = RestClient.post(@uri, {:file => File.new(@path)},{:accept => "text/uri-list" , :subjectid => subjectid}).to_s.chomp #task_uri = `curl -X POST -H "Accept:text/uri-list" -F "file=@#{@path};type=application/rdf+xml" http://apps.ideaconsult.net:8080/ambit2/dataset` Task.find(task_uri).wait_for_completion - self.uri = RestClientWrapper.get(task_uri,:accept => 'text/uri-list') + self.uri = RestClientWrapper.get(task_uri,{:accept => 'text/uri-list', :subjectid => subjectid}) end else # create dataset if uri is empty @@ -268,8 +268,8 @@ module OpenTox end # Delete dataset at the dataset service - def delete - RestClientWrapper.delete @uri + def delete(subjectid=nil) + RestClientWrapper.delete(@uri, :subjectid => subjectid) end private @@ -293,9 +293,9 @@ module OpenTox # Find a prediction dataset and load all data. # @param [String] uri Prediction dataset URI # @return [OpenTox::Dataset] Prediction dataset object with all data - def self.find(uri) - prediction = LazarPrediction.new(uri) - prediction.load_all + def self.find(uri, subjectid=nil) + prediction = LazarPrediction.new(uri, subjectid) + prediction.load_all(subjectid) prediction end -- cgit v1.2.3 From 57cab7b2e22b4f07ee7f53afb15d05873abeca6d Mon Sep 17 00:00:00 2001 From: mr Date: Mon, 10 Jan 2011 17:04:49 +0100 Subject: A&A --- lib/helper.rb | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/lib/helper.rb b/lib/helper.rb index 6b616bc..857c5b5 100644 --- a/lib/helper.rb +++ b/lib/helper.rb @@ -15,25 +15,42 @@ helpers do return unless authorized?(subjectid) end + #Check Authorization for URI with method and subjectid. def authorized?(subjectid) + uri = clean_uri("#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}") if CONFIG[:authorization][:authorize_request].include?(request.env['REQUEST_METHOD']) - ret = OpenTox::Authorization.authorize("#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}", request.env['REQUEST_METHOD'], subjectid) - LOGGER.debug "OpenTox helpers OpenTox::Authorization authorized? method: #{request.env['REQUEST_METHOD']} , URI: #{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}, subjectid: #{subjectid} with return #{ret}." + ret = OpenTox::Authorization.authorize(uri, request.env['REQUEST_METHOD'], subjectid) + LOGGER.debug "OpenTox helpers OpenTox::Authorization authorized? method: #{request.env['REQUEST_METHOD']} , URI: #{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}, subjectid: #{subjectid} with return >>#{ret}<<" return ret end if CONFIG[:authorization][:authenticate_request].include?(env['REQUEST_METHOD']) - if OpenTox::Authorization.is_token_valid(subjectid) - return true - end + return true if OpenTox::Authorization.is_token_valid(subjectid) end LOGGER.debug "Not authorized for: #{request.env['rack.url_scheme']}://#{request['REQUEST_URI']} with Method: #{request.env['REQUEST_METHOD']} with Token #{subjectid}" return false end + #cleans URI from querystring and file-extension. Sets port 80 to emptystring + # @param [String] uri + def clean_uri(uri) + out = URI.parse(uri) + out.path = out.path[0, out.path.rindex(/[0-9]/) + 1] if out.path.rindex(/[0-9]/) #cuts after id for a&a + "#{out.scheme}:" + (out.port != 80 ? out.port : "") + "//#{out.host}#{out.path}" + end + + def check_subjectid(subjectid) + return false if !subjectid + return true if subjectid.size > 62 + false + end + + #unprotected uris for login/logout, webapplication ... def unprotected_requests case env['REQUEST_URI'] when /\/login$|\/logout$|\/predict$|\/toxcreate\/models$/ return true + when /\/features/ + return false when /\/compound|\/feature|\/task|\/toxcreate/ #to fix: read from config | validation should be protected return true else @@ -41,23 +58,18 @@ helpers do end end - def check_subjectid(subjectid) - return false if !subjectid - return true if subjectid.size > 62 - false - end end before do unless unprotected_requests or CONFIG[:authorization][:free_request].include?(env['REQUEST_METHOD']) begin subjectid = session[:subjectid] if session[:subjectid] - subjectid = params[:subjectid] if params[:subjectid] and !check_subjectid(subjectid) + subjectid = params[:subjectid] if params[:subjectid] and !check_subjectid(subjectid) subjectid = request.env['HTTP_SUBJECTID'] if request.env['HTTP_SUBJECTID'] and !check_subjectid(subjectid) # see http://rack.rubyforge.org/doc/SPEC.html subjectid = CGI.unescape(subjectid) if subjectid.include?("%23") rescue - LOGGER.debug "OpenTox ruby api wrapper: helper before filter: NO subjectid." + LOGGER.debug "OpenTox ruby api wrapper: helper before filter: NO subjectid for URI: #{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}" subjectid = "" end protected!(subjectid) if AA_SERVER -- cgit v1.2.3 From 7327b1632cdaafd2d49d1ba8703a962f3c0e00d6 Mon Sep 17 00:00:00 2001 From: mr Date: Mon, 10 Jan 2011 17:05:07 +0100 Subject: A&A --- lib/model.rb | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/model.rb b/lib/model.rb index 953bb6c..32f5604 100644 --- a/lib/model.rb +++ b/lib/model.rb @@ -67,8 +67,8 @@ module OpenTox # Find a lazar model # @param [String] uri Model URI # @return [OpenTox::Model::Lazar] lazar model - def self.find(uri) - YAML.load RestClientWrapper.get(uri,:accept => 'application/x-yaml') + def self.find(uri, subjectid=nil) + YAML.load RestClientWrapper.get(uri,{:accept => 'application/x-yaml', :subjectid => subjectid}) end # Create a new lazar model @@ -90,8 +90,8 @@ module OpenTox # Predict a dataset # @param [String] dataset_uri Dataset URI # @return [OpenTox::Dataset] Dataset with predictions - def predict_dataset(dataset_uri) - @prediction_dataset = Dataset.create + def predict_dataset(dataset_uri, subjectid=nil) + @prediction_dataset = Dataset.create(CONFIG[:services]["opentox-dataset"], subjectid) @prediction_dataset.add_metadata({ OT.hasSource => @uri, DC.creator => @uri, @@ -101,9 +101,9 @@ module OpenTox d = Dataset.new(dataset_uri) d.load_compounds d.compounds.each do |compound_uri| - predict(compound_uri,false) + predict(compound_uri,false,subjectid) end - @prediction_dataset.save + @prediction_dataset.save(subjectid) @prediction_dataset end @@ -129,7 +129,7 @@ module OpenTox } ) end - return @prediction_dataset if database_activity + return @prediction_dataset if database_activity(subjectid) neighbors prediction = eval("#{@prediction_algorithm}(@neighbors,{:similarity_algorithm => @similarity_algorithm, :p_values => @p_values})") @@ -245,11 +245,11 @@ module OpenTox # Find database activities and store them in @prediction_dataset # @return [Boolean] true if compound has databasse activities, false if not - def database_activity + def database_activity(subjectid) if @activities[@compound.uri] @activities[@compound.uri].each { |act| @prediction_dataset.add @compound.uri, @metadata[OT.dependentVariables], act } @prediction_dataset.add_metadata(OT.hasSource => @metadata[OT.trainingDataset]) - @prediction_dataset.save + @prediction_dataset.save(subjectid) true else false @@ -262,8 +262,8 @@ module OpenTox end # Delete model at model service - def delete - RestClientWrapper.delete @uri unless @uri == CONFIG[:services]["opentox-model"] + def delete(subjectid) + RestClientWrapper.delete(@uri, :subjectid => subjectid) unless @uri == CONFIG[:services]["opentox-model"] end end -- cgit v1.2.3 From ecdd0347a347bd2ac5fa9e6a41ec7475b007309d Mon Sep 17 00:00:00 2001 From: mr Date: Mon, 10 Jan 2011 17:47:09 +0100 Subject: A&A extent --- lib/feature.rb | 4 ++-- lib/model.rb | 8 ++++---- lib/policy.rb | 9 +++++++++ lib/task.rb | 12 ++++++------ 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/lib/feature.rb b/lib/feature.rb index 9e28077..349f8ae 100644 --- a/lib/feature.rb +++ b/lib/feature.rb @@ -2,10 +2,10 @@ module OpenTox class Feature include OpenTox - def self.find(uri) + def self.find(uri, subjectid=nil) feature = Feature.new uri if (CONFIG[:yaml_hosts].include?(URI.parse(uri).host)) - feature.add_metadata YAML.load(RestClientWrapper.get(uri,:accept => "application/x-yaml")) + feature.add_metadata YAML.load(RestClientWrapper.get(uri,{:accept => "application/x-yaml", :subjectid => subjectid})) else feature.add_metadata Parser::Owl::Dataset.new(uri).load_metadata end diff --git a/lib/model.rb b/lib/model.rb index 32f5604..7aa3f5c 100644 --- a/lib/model.rb +++ b/lib/model.rb @@ -60,8 +60,8 @@ module OpenTox # Get URIs of all lazar models # @return [Array] List of lazar model URIs - def self.all - RestClientWrapper.get(CONFIG[:services]["opentox-model"]).to_s.split("\n") + def self.all(subjectid=nil) + RestClientWrapper.get(CONFIG[:services]["opentox-model"], :subjectid => subjectid).to_s.split("\n") end # Find a lazar model @@ -77,7 +77,7 @@ module OpenTox def self.create(params) lazar_algorithm = OpenTox::Algorithm::Generic.new File.join( CONFIG[:services]["opentox-algorithm"],"lazar") model_uri = lazar_algorithm.run(params) - OpenTox::Model::Lazar.find(model_uri) + OpenTox::Model::Lazar.find(model_uri, params[:subjectid]) end # Get a parameter value @@ -98,7 +98,7 @@ module OpenTox DC.title => URI.decode(File.basename( @metadata[OT.dependentVariables] )), OT.parameters => [{DC.title => "dataset_uri", OT.paramValue => dataset_uri}] }) - d = Dataset.new(dataset_uri) + d = Dataset.new(dataset_uri,subjectid) d.load_compounds d.compounds.each do |compound_uri| predict(compound_uri,false,subjectid) diff --git a/lib/policy.rb b/lib/policy.rb index 0ef8298..9c81fbd 100644 --- a/lib/policy.rb +++ b/lib/policy.rb @@ -33,6 +33,15 @@ module OpenTox return true end + #drop all policies in a policies instance + def names + out = [] + @policies.each do |name, policy| + out << name + end + return out + end + #loads a default policy template in policies instance def load_default_policy(user, uri, group="member") template = case user diff --git a/lib/task.rb b/lib/task.rb index 18fba6e..9cf909f 100644 --- a/lib/task.rb +++ b/lib/task.rb @@ -50,12 +50,12 @@ module OpenTox cpu_load = `cat /proc/loadavg`.split(/\s+/)[0..2].collect{|c| c.to_f} nr_cpu_cores = `cat /proc/cpuinfo |grep "cpu cores"|cut -d ":" -f2|tr -d " "`.split("\n").collect{|c| c.to_i}.inject{|sum,n| sum+n} nr_cpu_cores = 1 if !nr_cpu_cores - if cpu_load[0] > nr_cpu_cores and cpu_load[0] > cpu_load[1] and cpu_load[1] > cpu_load[2] # average CPU load of the last minute is high and CPU load is increasing - LOGGER.warn "Cannot start task - CPU load too high (#{cpu_load.join(", ")})" - task.cancel - return task - #raise "Server too busy to start a new task" - end + #if cpu_load[0] > nr_cpu_cores and cpu_load[0] > cpu_load[1] and cpu_load[1] > cpu_load[2] # average CPU load of the last minute is high and CPU load is increasing + # LOGGER.warn "Cannot start task - CPU load too high (#{cpu_load.join(", ")})" + # task.cancel + # return task + # #raise "Server too busy to start a new task" + #end task_pid = Spork.spork(:logger => LOGGER) do -- cgit v1.2.3 From 2aafed7543287c420a5aa2e751b8c74ad771d14c Mon Sep 17 00:00:00 2001 From: mr Date: Thu, 13 Jan 2011 12:01:19 +0100 Subject: A&A for GET requests --- lib/dataset.rb | 22 +++++++++++----------- lib/model.rb | 2 +- lib/opentox.rb | 8 ++++---- lib/parser.rb | 17 ++++++++++------- 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/lib/dataset.rb b/lib/dataset.rb index 52b41a7..a85c2b5 100644 --- a/lib/dataset.rb +++ b/lib/dataset.rb @@ -60,7 +60,7 @@ module OpenTox # @param [optional,String] uri URI of the dataset service, defaults to service specified in configuration # @return [Array] Array of dataset object without data (use one of the load_* methods to pull data from the server) def self.all(uri=CONFIG[:services]["opentox-dataset"], subjectid=nil) - RestClientWrapper.get(uri,{:accept => "text/uri-list",:subjectid => subjectid}).to_s.each_line.collect{|u| Dataset.new(u)} + RestClientWrapper.get(uri,{:accept => "text/uri-list",:subjectid => subjectid}).to_s.each_line.collect{|u| Dataset.new(u, subjectid)} end # Load YAML representation into the dataset @@ -77,10 +77,10 @@ module OpenTox # Load RDF/XML representation from a file # @param [String] file File with RDF/XML representation of the dataset # @return [OpenTox::Dataset] Dataset object with RDF/XML data - def load_rdfxml_file(file) - parser = Parser::Owl::Dataset.new @uri + def load_rdfxml_file(file, subjectid=nil) + parser = Parser::Owl::Dataset.new @uri, subjectid parser.uri = file.path - copy parser.load_uri + copy parser.load_uri(subjectid) end # Load CSV string (format specification: http://toxcreate.org/help) @@ -111,8 +111,8 @@ module OpenTox # Load and return only metadata of a Dataset object # @return [Hash] Metadata of the dataset - def load_metadata - add_metadata Parser::Owl::Dataset.new(@uri).load_metadata + def load_metadata(subjectid=nil) + add_metadata Parser::Owl::Dataset.new(@uri, subjectid).load_metadata(subjectid) self.uri = @uri if @uri # keep uri @metadata end @@ -122,8 +122,8 @@ module OpenTox if (CONFIG[:yaml_hosts].include?(URI.parse(@uri).host)) copy YAML.load(RestClientWrapper.get(@uri, {:accept => "application/x-yaml", :subjectid => subjectid})) else - parser = Parser::Owl::Dataset.new(@uri) - copy parser.load_uri + parser = Parser::Owl::Dataset.new(@uri, subjectid) + copy parser.load_uri(subjectid) end end @@ -138,9 +138,9 @@ module OpenTox # Load and return only features from the dataset service # @return [Hash] Features of the dataset - def load_features - parser = Parser::Owl::Dataset.new(@uri) - @features = parser.load_features + def load_features(subjectid=nil) + parser = Parser::Owl::Dataset.new(@uri, subjectid) + @features = parser.load_features(subjectid) @features end diff --git a/lib/model.rb b/lib/model.rb index 7aa3f5c..6ef4af2 100644 --- a/lib/model.rb +++ b/lib/model.rb @@ -99,7 +99,7 @@ module OpenTox OT.parameters => [{DC.title => "dataset_uri", OT.paramValue => dataset_uri}] }) d = Dataset.new(dataset_uri,subjectid) - d.load_compounds + d.load_compounds(subjectid) d.compounds.each do |compound_uri| predict(compound_uri,false,subjectid) end diff --git a/lib/opentox.rb b/lib/opentox.rb index 90683e5..f1af5c3 100644 --- a/lib/opentox.rb +++ b/lib/opentox.rb @@ -19,14 +19,14 @@ module OpenTox # Get all objects from a service # @return [Array] List of available URIs - def self.all(uri) - RestClientWrapper.get(uri,:accept => "text/uri-list").to_s.split(/\n/) + def self.all(uri, subjectid=nil) + RestClientWrapper.get(uri,:accept => "text/uri-list", :subjectid => subjectid).to_s.split(/\n/) end # Load (and return) metadata from object URI # @return [Hash] Metadata - def load_metadata - @metadata = Parser::Owl::Generic.new(@uri).load_metadata + def load_metadata(subjectid=nil) + @metadata = Parser::Owl::Generic.new(@uri).load_metadata(subjectid) @metadata end diff --git a/lib/parser.rb b/lib/parser.rb index b727412..a913cf2 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -29,14 +29,14 @@ module OpenTox # Read metadata from opentox service # @return [Hash] Object metadata - def load_metadata + def load_metadata(subjectid=nil) if @dataset uri = File.join(@uri,"metadata") else uri = @uri end - + uri += "?subjectid=#{CGI.escape(subjectid)}" if subjectid statements = [] parameter_ids = [] `rapper -i rdfxml -o ntriples #{uri} 2>/dev/null`.each_line do |line| @@ -71,9 +71,9 @@ module OpenTox # Create a new OWL-DL dataset parser # @param uri Dataset URI # @return [OpenTox::Parser::Owl::Dataset] OWL-DL parser - def initialize(uri) + def initialize(uri, subjectid=nil) super uri - @dataset = ::OpenTox::Dataset.new(@uri) + @dataset = ::OpenTox::Dataset.new(@uri, subjectid) end # Read data from dataset service. Files can be parsed by setting #uri to a filename (after initialization with a real URI) @@ -87,12 +87,14 @@ module OpenTox # dataset = parser.load_uri # dataset.save # @return [Hash] Internal dataset representation - def load_uri + def load_uri(subjectid=nil) + uri = @uri + uri += "?subjectid=#{CGI.escape(subjectid)}" if subjectid data = {} feature_values = {} feature = {} other_statements = {} - `rapper -i rdfxml -o ntriples #{@uri} 2>/dev/null`.each_line do |line| + `rapper -i rdfxml -o ntriples #{uri} 2>/dev/null`.each_line do |line| triple = line.chomp.split(' ',3) triple = triple[0..2].collect{|i| i.sub(/\s+.$/,'').gsub(/[<>"]/,'')} case triple[1] @@ -122,8 +124,9 @@ module OpenTox # Read only features from a dataset service. # @return [Hash] Internal features representation - def load_features + def load_features(subjectid=nil) uri = File.join(@uri,"features") + uri += "?subjectid=#{CGI.escape(subjectid)}" if subjectid statements = [] features = Set.new `rapper -i rdfxml -o ntriples #{uri} 2>/dev/null`.each_line do |line| -- cgit v1.2.3 From f2ca545448ab8a6f654309f23cfce9416b2e9856 Mon Sep 17 00:00:00 2001 From: mguetlein Date: Thu, 13 Jan 2011 14:02:58 +0100 Subject: find methods for algorithm and model, split method for dataset, feature_type method for model and feature, perform single predicitons in resuce block, add to-html.rb, fix handling of rest-client-wrapper --- lib/algorithm.rb | 14 ++++++ lib/dataset.rb | 33 +++++++++++++- lib/feature.rb | 23 +++++++++- lib/model.rb | 76 +++++++++++++++++++++++++++++++- lib/opentox-ruby.rb | 2 +- lib/overwrite.rb | 18 +++++++- lib/rest_client_wrapper.rb | 18 +++----- lib/task.rb | 106 ++++++++++++++++++++++++++++++++++++++++++++- lib/to-html.rb | 80 ++++++++++++++++++++++++++++++++++ 9 files changed, 351 insertions(+), 19 deletions(-) create mode 100755 lib/to-html.rb diff --git a/lib/algorithm.rb b/lib/algorithm.rb index a2f7786..0aa86e6 100644 --- a/lib/algorithm.rb +++ b/lib/algorithm.rb @@ -29,6 +29,20 @@ module OpenTox # Generic Algorithm class, should work with all OpenTox webservices class Generic include Algorithm + + # Find Generic Opentox Algorithm via URI, and loads metadata + # @param [String] uri Algorithm URI + # @return [OpenTox::Algorithm::Generic] Algorithm instance, nil if alogrithm was not found + def self.find(uri) + alg = Generic.new(uri) + alg.load_metadata + if alg.metadata==nil or alg.metadata.size==0 + nil + else + alg + end + end + end # Fminer algorithms (https://github.com/amaunz/fminer2) diff --git a/lib/dataset.rb b/lib/dataset.rb index aba7754..d45c821 100644 --- a/lib/dataset.rb +++ b/lib/dataset.rb @@ -46,7 +46,7 @@ module OpenTox dataset.save(subjectid) dataset end - + # Find a dataset and load all data. This can be time consuming, use Dataset.new together with one of the load_* methods for a fine grained control over data loading. # @param [String] uri Dataset URI # @return [OpenTox::Dataset] Dataset object with all data @@ -242,6 +242,37 @@ module OpenTox def add_feature_metadata(feature,metadata) metadata.each { |k,v| @features[feature][k] = v } end + + # Add a new compound + # @param [String] compound Compound URI + def add_compound (compound) + @compounds << compound unless @compounds.include? compound + end + + # Creates a new dataset, by splitting the current dataset, i.e. using only a subset of compounds and features + # @param [Array] compounds List of compound URIs + # @param [Array] features List of feature URIs + # @param [Hash] metadata Hash containing the metadata for the new dataset + # @return [OpenTox::Dataset] newly created dataset, already saved + def split( compounds, features, metadata) + LOGGER.debug "split dataset using "+compounds.size.to_s+"/"+@compounds.size.to_s+" compounds" + raise "no new compounds selected" unless compounds and compounds.size>0 + dataset = OpenTox::Dataset.create + if features.size==0 + compounds.each{ |c| dataset.add_compound(c) } + else + compounds.each do |c| + features.each do |f| + @data_entries[c][f].each do |v| + dataset.add(c,f,v) + end + end + end + end + dataset.add_metadata(metadata) + dataset.save + dataset + end # Save dataset at the dataset service # - creates a new dataset if uri is not set diff --git a/lib/feature.rb b/lib/feature.rb index 9e28077..de7c757 100644 --- a/lib/feature.rb +++ b/lib/feature.rb @@ -1,7 +1,7 @@ module OpenTox class Feature include OpenTox - + def self.find(uri) feature = Feature.new uri if (CONFIG[:yaml_hosts].include?(URI.parse(uri).host)) @@ -11,5 +11,26 @@ module OpenTox end feature end + + # provides domain (possible target values) of classification feature + # @return [Array] list with possible target values + def domain + #TODO derieve from metadata / ontology + return [true, false] + end + + # provides feature type, possible types are "regression" or "classification" + # @return [String] feature type, unknown if OT.isA property is unknown/ not set + def feature_type + case metadata[OT.isA] + when /NominalFeature/ + "classification" + when /NumericFeature/ + "regression" + else + "unknown" + end + end + end end diff --git a/lib/model.rb b/lib/model.rb index c645bdc..fb266e0 100644 --- a/lib/model.rb +++ b/lib/model.rb @@ -24,8 +24,76 @@ module OpenTox # Generic OpenTox model class for all API compliant services class Generic include Model + + # Find Generic Opentox Model via URI, and loads metadata + # @param [String] uri Model URI + # @return [OpenTox::Model::Generic] Model instance, nil if model was not found + def self.find(uri) + model = Generic.new(uri) + model.load_metadata + if model.metadata==nil or model.metadata.size==0 + nil + else + model + end + end + + # provides feature type, possible types are "regression" or "classification" + # @return [String] feature type, "unknown" if type could not be estimated + def feature_type + # dynamically perform restcalls if necessary + load_metadata if @metadata==nil or @metadata.size==0 or (@metadata.size==1 && @metadata.values[0]==@uri) + @dependentVariable = OpenTox::Feature.find( @metadata[OT.dependentVariables] ) unless @dependentVariable + + [@dependentVariable.feature_type, @metadata[OT.isA], @metadata[DC.title], @uri].each do |type| + case type + when /(?i)classification/ + return "classification" + when /(?i)regression/ + return "regression" + end + end + raise "unknown model "+[@dependentVariable.feature_type, @metadata[OT.isA], @metadata[DC.title], @uri].inspect + end + +# def classification? +# # TODO test on various services / request to ontology service needed? +# # TODO replace bool (for classification/regression) with string value (more types are coming) +# #raise "classification?: type: "+@type.to_s+", title: "+@title.to_s+", uri: "+@uri.to_s+" "+((@uri =~ /class/) != nil).to_s +# +# load_metadata if @metadata==nil or @metadata.size==0 or (@metadata.size==1 && @metadata.values[0]==@uri) +# @dependentVariable = OpenTox::Feature.find( @metadata[OT.dependentVariables] ) unless @dependentVariable +# case @dependentVariable.feature_type +# when "classification" +# return true +# when "regression" +# return false +# end +# +# if @metadata[OT.isA] =~ /(?i)classification/ +# return true +# end +# +# if @metadata[DC.title] =~ /(?i)classification/ +# return true +# elsif @metadata[DC.title] =~ /(?i)regression/ +# return false +# elsif @uri =~/ntua/ and @metadata[DC.title] =~ /mlr/ +# return false +# elsif @uri =~/tu-muenchen/ and @metadata[DC.title] =~ /regression|M5P|GaussP/ +# return false +# elsif @uri =~/ambit2/ and @metadata[DC.title] =~ /pKa/ || @metadata[DC.title] =~ /Regression|Caco/ +# return false +# elsif @uri =~/majority/ +# return (@uri =~ /class/) != nil +# else +# raise "unknown model, uri:'"+@uri.to_s+"' title:'"+@metadata[DC.title].to_s+"'" +# end +# end +# end + end - + # Lazy Structure Activity Relationship class class Lazar @@ -101,7 +169,11 @@ module OpenTox d = Dataset.new(dataset_uri) d.load_compounds d.compounds.each do |compound_uri| - predict(compound_uri,false,subjectid) + begin + predict(compound_uri,false,subjectid) + rescue => ex + LOGGER.warn "prediction for compound "+compound_uri.to_s+" failed: "+ex.message + end end @prediction_dataset.save(subjectid) @prediction_dataset diff --git a/lib/opentox-ruby.rb b/lib/opentox-ruby.rb index c0bff95..fb3803b 100644 --- a/lib/opentox-ruby.rb +++ b/lib/opentox-ruby.rb @@ -8,6 +8,6 @@ rescue LoadError puts "Please install Openbabel with 'rake openbabel:install' in the compound component" end -['opentox', 'compound','dataset', 'parser','serializer', 'algorithm','model','task','validation','feature', 'rest_client_wrapper', 'authorization', 'policy', 'helper'].each do |lib| +['opentox', 'compound','dataset', 'parser','serializer', 'algorithm','model','task','validation','feature', 'rest_client_wrapper', 'authorization', 'policy', 'helper', 'to-html'].each do |lib| require lib end diff --git a/lib/overwrite.rb b/lib/overwrite.rb index 8d787a6..e5ed5c3 100644 --- a/lib/overwrite.rb +++ b/lib/overwrite.rb @@ -2,6 +2,22 @@ # hack: store sinatra in global var to make url_for and halt methods accessible before{ $sinatra = self unless $sinatra } +# handle errors manually +# this is to return 502, when an error occurs during a rest-call (see rest_client_wrapper.rb) +set :raise_errors, Proc.new { false } +set :show_exceptions, false +error do + # try if the error is an OpenTox::Error + if OpenTox::Error.parse(request.env['sinatra.error'].to_s) + # if true, this error comes from rest_client_wrapper, halt with 502 + # (502 is defined in OT API as Error coming from other service) + halt 502,request.env['sinatra.error'] + else + # else, raise exception, this will return 500 = internal error + raise request.env['sinatra.error'] + end +end + class Sinatra::Base # overwriting halt to log halts (!= 202) def halt(*response) @@ -60,7 +76,7 @@ class OTLogger < Logger n = 2 line = lines[n] - while (line =~ /spork.rb/ or line =~ /create/ or line =~ /ot-logger.rb/) + while (line =~ /spork.rb/ or line =~ /create/ or line =~ /overwrite.rb/) n += 1 line = lines[n] end diff --git a/lib/rest_client_wrapper.rb b/lib/rest_client_wrapper.rb index 5f5273b..2f0e215 100644 --- a/lib/rest_client_wrapper.rb +++ b/lib/rest_client_wrapper.rb @@ -115,7 +115,7 @@ module OpenTox task = OpenTox::Task.from_yaml(res) when /text\// raise "uri list has more than one entry, should be a task" if res.content_type=~/text\/uri-list/ and res.split("\n").size > 1 #if uri list contains more then one uri, its not a task - task = OpenTox::Task.find(res.to_s) if res.to_s.uri? + task = OpenTox::Task.find(res.to_s.chomp) if res.to_s.uri? else raise "unknown content-type for task: '"+res.content_type.to_s+"'" #+"' content: "+res[0..200].to_s end @@ -151,18 +151,14 @@ module OpenTox File.new(File.join(error_dir,file_name+"_"+time+"_"+count.to_s),"w").puts(body) # handle error - # we are either in a task, or in sinatra # PENDING: always return yaml for now - if $self_task #this global var in Task.create to mark that the current process is running in a task - raise error.to_yaml # the error is caught, logged, and task state is set to error in Task.create - #elsif $sinatra #else halt sinatra - #$sinatra.halt(502,error.to_yaml) - elsif defined?(halt) - halt(502,error.to_yaml) - else #for testing purposes (if classes used directly) - raise error.to_yaml - end + # raising OpenTox::Error + # to handle the error yourself, put rest-call in begin, rescue block + # if the error is not caught: + # if we are in a task, the error is caught, logged, and task state is set to error in Task.as_task + # if we are in a default call, the error is handled in overwrite.rb to return 502 (according to OT API) + raise error.to_yaml end end end diff --git a/lib/task.rb b/lib/task.rb index 18fba6e..dcbff3f 100644 --- a/lib/task.rb +++ b/lib/task.rb @@ -1,4 +1,3 @@ -$self_task=nil module OpenTox @@ -60,7 +59,6 @@ module OpenTox task_pid = Spork.spork(:logger => LOGGER) do LOGGER.debug "Task #{task.uri} started #{Time.now}" - $self_task = task begin result = catch(:halt) do @@ -254,7 +252,111 @@ module OpenTox RestClientWrapper.raise_uri_error(ex.message, @uri) end end + + public + #hint: do not overwrite percentageCompleted=, this is used in toYaml + def progress(pct) +# #puts "task := "+pct.to_s +# raise "no numeric >= 0 and <= 100 : '"+pct.to_s+"'" unless pct.is_a?(Numeric) and pct>=0 and pct<=100 +# RestClientWrapper.put(File.join(@uri,'Running'),{:percentageCompleted => pct}) +# reload + end + + end + + # Convenience class to split a (sub)task into subtasks + # + # example: + # a crossvalidation is split into creating datasets and performing the validations + # creating the dataset is 1/3 of the work, perform the validations is 2/3: + # Task.as_task do |task| + # create_datasets( SubTask.new(task, 0, 33) ) + # perfom_validations( SubTask.new(task, 33, 100) ) + # end + # inside the create_datasets / perform_validations you can use subtask.progress() + # with vals from 0-100 + # + # note that you can split a subtask into further subtasks + class SubTask + + def initialize(task, min, max) + raise "not a task or subtask" unless task.is_a?(Task) or task.is_a?(SubTask) + raise "invalid max ("+max.to_s+"), min ("+min.to_s+") params" unless + min.is_a?(Numeric) and max.is_a?(Numeric) and min >= 0 and max <= 100 and max > min + @task = task + @min = min + @max = max + @delta = max - min + end + # convenience method to handle null tasks + def self.create(task, min, max) + if task + SubTask.new(task, min, max) + else + nil + end + end + + def progress(pct) + raise "no numeric >= 0 and <= 100 : '"+pct.to_s+"'" unless pct.is_a?(Numeric) and pct>=0 and pct<=100 + #puts "subtask := "+pct.to_s+" -> task := "+(@min + @delta * pct.to_f * 0.01).to_s + @task.progress( @min + @delta * pct.to_f * 0.01 ) + end + + def running?() + @task.running? + end + end + + + # The David Gallagher feature: + # a fake sub task to keep the progress bar movin for external jobs + # note: param could be a subtask + # + # usage (for a call that is normally finished in under 60 seconds): + # fsk = FakeSubTask.new(task, 60) + # external_lib_call.start + # external_lib_call.wait_until_finished + # fsk.finished + # + # what happens: + # the FakeSubTask updates the task.progress each second until + # runtime is up or the finished mehtod is called + # + # example if the param runtime is too low: + # 25% .. 50% .. 75% .. 100% .. 100% .. 100% .. 100% .. 100% + # example if the param runtime is too high: + # 5% .. 10% .. 15% .. 20% .. 25% .. 30% .. 35% .. 100% + # the latter example is better (keep the bar movin!) + # -> better make a conservative runtime estimate + class FakeSubTask + + def initialize(task, runtime) + @task = task + @thread = Thread.new do + timeleft = runtime + while (timeleft > 0 and @task.running?) + sleep 1 + timeleft -= 1 + @task.progress( (runtime - timeleft) / runtime.to_f * 100 ) + end + end + end + + # convenience method to handle null tasks + def self.create(task, runtime) + if task + FakeSubTask.new(task, runtime) + else + nil + end + end + + def finished + @thread.exit + @task.progress(100) if @task.running? + end end end diff --git a/lib/to-html.rb b/lib/to-html.rb new file mode 100755 index 0000000..1bc1496 --- /dev/null +++ b/lib/to-html.rb @@ -0,0 +1,80 @@ + +OT_LOGO = "http://opentox.informatik.uni-freiburg.de/ot-logo.png" + + +class String + + # encloses URI in text with with link tag + # @return [String] new text with marked links + def link_urls + self.gsub(/(?i)http:\/\/[^\r\n\s']*/, '\0') + end +end + +module OpenTox + + # produces a html page for making web services browser friendly + # format of text (=string params) is preserved (e.g. line breaks) + # urls are marked as links + # @example post params: + # [ [ [:mandatory_param_1], [:mandatory_param_2], [:optional_param,"default_value"] ], + # [ [:alteranative_mandatory_param_1], [:alteranative_mandatory_param_2] ] + # ] + # @param [String] text this is the actual content, + # @param [optional,String] related_links info on related resources + # @param [optional,String] description general info + # @param [optional,Array] post_params, array of arrays containing info on POST operation, see example + # @return [String] html page + def self.text_to_html( text, related_links=nil, description=nil, post_params=nil ) + + title = $sinatra.url_for($sinatra.request.env['PATH_INFO'], :full) if $sinatra + + html = < +EOF + html.chomp! + html += ""+title+"" if title + html += < + +EOF + html.chomp! + html += "

Description

"+description.link_urls+"

" if description + html += "

Related links

"+related_links.link_urls+"

" if related_links + if post_params + html += "

POST parameters

" + count = 0 + post_params.each do |p| + html += "

alternatively:

" if count > 0 + html += "

" + p.each do |k,v| + html += "" + end + html += "
paramdefault_value
"+k.to_s+""+(v!=nil ? v.to_s : "mandatory")+"

" + count += 1 + end + end + html += "

Content

" if description || related_links + html += < +

+EOF + html.chomp! + html += text.link_urls + html += < + + + +EOF + html + end + +end + +#puts OpenTox.text_to_html("bla") \ No newline at end of file -- cgit v1.2.3 From 1db377c898a49417c669a52aaf75014f6a31158f Mon Sep 17 00:00:00 2001 From: mguetlein Date: Thu, 13 Jan 2011 14:10:59 +0100 Subject: remove old classification? in model.rb, add ie hack to overwrite --- lib/model.rb | 36 ------------------------------------ lib/overwrite.rb | 6 +++++- 2 files changed, 5 insertions(+), 37 deletions(-) diff --git a/lib/model.rb b/lib/model.rb index fb266e0..1671ba7 100644 --- a/lib/model.rb +++ b/lib/model.rb @@ -56,42 +56,6 @@ module OpenTox raise "unknown model "+[@dependentVariable.feature_type, @metadata[OT.isA], @metadata[DC.title], @uri].inspect end -# def classification? -# # TODO test on various services / request to ontology service needed? -# # TODO replace bool (for classification/regression) with string value (more types are coming) -# #raise "classification?: type: "+@type.to_s+", title: "+@title.to_s+", uri: "+@uri.to_s+" "+((@uri =~ /class/) != nil).to_s -# -# load_metadata if @metadata==nil or @metadata.size==0 or (@metadata.size==1 && @metadata.values[0]==@uri) -# @dependentVariable = OpenTox::Feature.find( @metadata[OT.dependentVariables] ) unless @dependentVariable -# case @dependentVariable.feature_type -# when "classification" -# return true -# when "regression" -# return false -# end -# -# if @metadata[OT.isA] =~ /(?i)classification/ -# return true -# end -# -# if @metadata[DC.title] =~ /(?i)classification/ -# return true -# elsif @metadata[DC.title] =~ /(?i)regression/ -# return false -# elsif @uri =~/ntua/ and @metadata[DC.title] =~ /mlr/ -# return false -# elsif @uri =~/tu-muenchen/ and @metadata[DC.title] =~ /regression|M5P|GaussP/ -# return false -# elsif @uri =~/ambit2/ and @metadata[DC.title] =~ /pKa/ || @metadata[DC.title] =~ /Regression|Caco/ -# return false -# elsif @uri =~/majority/ -# return (@uri =~ /class/) != nil -# else -# raise "unknown model, uri:'"+@uri.to_s+"' title:'"+@metadata[DC.title].to_s+"'" -# end -# end -# end - end # Lazy Structure Activity Relationship class diff --git a/lib/overwrite.rb b/lib/overwrite.rb index e5ed5c3..ffeba21 100644 --- a/lib/overwrite.rb +++ b/lib/overwrite.rb @@ -1,6 +1,10 @@ # class overwrites aka monkey patches # hack: store sinatra in global var to make url_for and halt methods accessible -before{ $sinatra = self unless $sinatra } +before { + $sinatra = self unless $sinatra + # stupid internet explorer does not ask for text/html, add this manually + request.env['HTTP_ACCEPT'] += ";text/html" if request.env["HTTP_USER_AGENT"]=~/MSIE/ +} # handle errors manually # this is to return 502, when an error occurs during a rest-call (see rest_client_wrapper.rb) -- cgit v1.2.3 From 97e3942191e1ab8f084ba8da475749a9609c37aa Mon Sep 17 00:00:00 2001 From: mguetlein Date: Fri, 14 Jan 2011 14:54:14 +0100 Subject: add percentage completed support --- lib/algorithm.rb | 5 ++-- lib/model.rb | 12 ++++++-- lib/rest_client_wrapper.rb | 68 ++++++++++++++++++++++++++++++++++------------ lib/task.rb | 33 +++++++++++++--------- 4 files changed, 83 insertions(+), 35 deletions(-) diff --git a/lib/algorithm.rb b/lib/algorithm.rb index 0aa86e6..58a2640 100644 --- a/lib/algorithm.rb +++ b/lib/algorithm.rb @@ -13,9 +13,10 @@ module OpenTox # Execute algorithm with parameters, please consult the OpenTox API and the webservice documentation for acceptable parameters # @param [optional,Hash] params Algorithm parameters + # @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly # @return [String] URI of new resource (dataset, model, ...) - def run(params=nil) - RestClientWrapper.post(@uri, {:accept => 'text/uri-list'}, params).to_s + def run(params=nil, waiting_task=nil) + RestClientWrapper.post(@uri, {:accept => 'text/uri-list'}, params, waiting_task).to_s end # Get OWL-DL representation in RDF/XML format diff --git a/lib/model.rb b/lib/model.rb index 1671ba7..e95c78c 100644 --- a/lib/model.rb +++ b/lib/model.rb @@ -6,15 +6,16 @@ module OpenTox # Run a model with parameters # @param [Hash] params Parameters for OpenTox model + # @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly # @return [text/uri-list] Task or resource URI - def run(params) + def run( params, waiting_task=nil ) if CONFIG[:yaml_hosts].include?(URI.parse(@uri).host) accept = 'application/x-yaml' else accept = 'application/rdf+xml' end begin - RestClientWrapper.post(@uri,{:accept => accept},params).to_s + RestClientWrapper.post(@uri,{:accept => accept},params,waiting_task).to_s rescue => e LOGGER.error "Failed to run #{@uri} with #{params.inspect} (#{e.inspect})" raise "Failed to run #{@uri} with #{params.inspect}" @@ -121,8 +122,10 @@ module OpenTox # Predict a dataset # @param [String] dataset_uri Dataset URI + # @param [optional,subjectid] + # @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly # @return [OpenTox::Dataset] Dataset with predictions - def predict_dataset(dataset_uri, subjectid=nil) + def predict_dataset(dataset_uri, subjectid=nil, waiting_task=nil) @prediction_dataset = Dataset.create(CONFIG[:services]["opentox-dataset"], subjectid) @prediction_dataset.add_metadata({ OT.hasSource => @uri, @@ -132,9 +135,12 @@ module OpenTox }) d = Dataset.new(dataset_uri) d.load_compounds + count = 0 d.compounds.each do |compound_uri| begin predict(compound_uri,false,subjectid) + count += 1 + waiting_task.progress( count/d.compounds.size.to_f*100.0 ) if waiting_task rescue => ex LOGGER.warn "prediction for compound "+compound_uri.to_s+" failed: "+ex.message end diff --git a/lib/rest_client_wrapper.rb b/lib/rest_client_wrapper.rb index 2f0e215..920a828 100644 --- a/lib/rest_client_wrapper.rb +++ b/lib/rest_client_wrapper.rb @@ -34,33 +34,67 @@ module OpenTox class RestClientWrapper - def self.get(uri, headers=nil, wait=true) - execute( "get", uri, headers, nil, wait) + # performs a GET REST call + # raises OpenTox::Error if call fails (rescued in overwrite.rb -> halt 502) + # per default: waits for Task to finish and returns result URI of Task + # @param [String] uri destination URI + # @param [optional,Hash] headers contains params like accept-header + # @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly + # @param [wait,Boolean] wait set to false to NOT wait for task if result is task + # @return [OpenTox::WrapperResult] a String containing the result-body of the REST call + def self.get(uri, headers=nil, waiting_task=nil, wait=true ) + execute( "get", uri, headers, nil, waiting_task, wait) end - def self.post(uri, headers, payload=nil, wait=true) - execute( "post", uri, headers, payload, wait ) + # performs a POST REST call + # raises OpenTox::Error if call fails (rescued in overwrite.rb -> halt 502) + # per default: waits for Task to finish and returns result URI of Task + # @param [String] uri destination URI + # @param [optional,Hash] headers contains params like accept-header + # @param [optional,String] payload data posted to the service + # @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly + # @param [wait,Boolean] wait set to false to NOT wait for task if result is task + # @return [OpenTox::WrapperResult] a String containing the result-body of the REST call + def self.post(uri, headers, payload=nil, waiting_task=nil, wait=true ) + execute( "post", uri, headers, payload, waiting_task, wait ) end + # performs a PUT REST call + # raises OpenTox::Error if call fails (rescued in overwrite.rb -> halt 502) + # @param [String] uri destination URI + # @param [optional,Hash] headers contains params like accept-header + # @param [optional,String] payload data put to the service + # @return [OpenTox::WrapperResult] a String containing the result-body of the REST call def self.put(uri, headers, payload=nil ) execute( "put", uri, headers, payload ) end - def self.delete(uri, headers=nil) + # performs a DELETE REST call + # raises OpenTox::Error if call fails (rescued in overwrite.rb -> halt 502) + # @param [String] uri destination URI + # @param [optional,Hash] headers contains params like accept-header + # @return [OpenTox::WrapperResult] a String containing the result-body of the REST call + def self.delete(uri, headers=nil ) execute( "delete", uri, headers, nil) end + # raises an Error message (rescued in overwrite.rb -> halt 502) + # usage: if the return value of a call is invalid + # @param [String] error_msg the error message + # @param [String] uri destination URI that is responsible for the error + # @param [optional,Hash] headers sent to the URI + # @param [optional,String] payload data sent to the URI def self.raise_uri_error(error_msg, uri, headers=nil, payload=nil) - do_halt( "-", error_msg, uri, headers, payload ) + raise_ot_error( "-", error_msg, uri, headers, payload ) end private - def self.execute( rest_call, uri, headers, payload=nil, wait=true ) + def self.execute( rest_call, uri, headers, payload=nil, waiting_task=nil, wait=true ) - do_halt 400,"uri is null",uri,headers,payload unless uri - do_halt 400,"not a uri",uri,headers,payload unless uri.to_s.uri? - do_halt 400,"headers are no hash",uri,headers,payload unless headers==nil or headers.is_a?(Hash) - do_halt 400,"nil headers for post not allowed, use {}",uri,headers,payload if rest_call=="post" and headers==nil + raise_ot_error 400,"uri is null",uri,headers,payload unless uri + raise_ot_error 400,"not a uri",uri,headers,payload unless uri.to_s.uri? + raise_ot_error 400,"headers are no hash",uri,headers,payload unless headers==nil or headers.is_a?(Hash) + raise_ot_error 400,"nil headers for post not allowed, use {}",uri,headers,payload if rest_call=="post" and headers==nil headers.each{ |k,v| headers.delete(k) if v==nil } if headers #remove keys with empty values, as this can cause problems begin @@ -84,13 +118,13 @@ module OpenTox return res if res.code==200 || !wait while (res.code==201 || res.code==202) - res = wait_for_task(res, uri) + res = wait_for_task(res, uri, waiting_task) end raise "illegal status code: '"+res.code.to_s+"'" unless res.code==200 return res rescue RestClient::RequestTimeout => ex - do_halt 408,ex.message,uri,headers,payload + raise_ot_error 408,ex.message,uri,headers,payload rescue => ex #raise ex #raise "'"+ex.message+"' uri: "+uri.to_s @@ -101,11 +135,11 @@ module OpenTox code = 500 msg = ex.to_s end - do_halt code,msg,uri,headers,payload + raise_ot_error code,msg,uri,headers,payload end end - def self.wait_for_task( res, base_uri ) + def self.wait_for_task( res, base_uri, waiting_task=nil ) task = nil case res.content_type @@ -121,7 +155,7 @@ module OpenTox end LOGGER.debug "result is a task '"+task.uri.to_s+"', wait for completion" - task.wait_for_completion + task.wait_for_completion waiting_task raise task.description unless task.completed? # maybe task was cancelled / error res = WrapperResult.new task.result_uri @@ -130,7 +164,7 @@ module OpenTox return res end - def self.do_halt( code, body, uri, headers, payload=nil ) + def self.raise_ot_error( code, body, uri, headers, payload=nil ) #build error causing_errors = Error.parse(body) diff --git a/lib/task.rb b/lib/task.rb index dcbff3f..d701c82 100644 --- a/lib/task.rb +++ b/lib/task.rb @@ -12,7 +12,7 @@ module OpenTox DC.title => "", DC.date => "", OT.hasStatus => "Running", - OT.percentageCompleted => "0", + OT.percentageCompleted => 0.0, OT.resultURI => "", DC.creator => "", # not mandatory according to API DC.description => "", # not mandatory according to API @@ -160,12 +160,12 @@ module OpenTox def load_metadata if (CONFIG[:yaml_hosts].include?(URI.parse(uri).host)) - result = RestClientWrapper.get(@uri, {:accept => 'application/x-yaml'}, false) + result = RestClientWrapper.get(@uri, {:accept => 'application/x-yaml'}, nil, false) @metadata = YAML.load result.to_s @http_code = result.code else @metadata = Parser::Owl::Generic.new(@uri).load_metadata - @http_code = RestClientWrapper.get(uri, {:accept => 'application/rdf+xml'}, false).code + @http_code = RestClientWrapper.get(uri, {:accept => 'application/rdf+xml'}, nil, false).code end end @@ -216,7 +216,9 @@ module OpenTox =end # waits for a task, unless time exceeds or state is no longer running - def wait_for_completion(dur=0.3) + # @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly + # @param [optional,Numeric] dur seconds pausing before cheking again for completion + def wait_for_completion( waiting_task=nil, dur=0.3) due_to_time = Time.new + DEFAULT_TASK_MAX_DURATION LOGGER.debug "start waiting for task "+@uri.to_s+" at: "+Time.new.to_s+", waiting at least until "+due_to_time.to_s @@ -226,6 +228,8 @@ module OpenTox while self.running? sleep dur load_metadata + # if another (sub)task is waiting for self, set progress accordingly + waiting_task.progress(@metadata[OT.percentageCompleted]) if waiting_task check_state if (Time.new > due_to_time) raise "max wait time exceeded ("+DEFAULT_TASK_MAX_DURATION.to_s+"sec), task: '"+@uri.to_s+"'" @@ -234,6 +238,18 @@ module OpenTox LOGGER.debug "Task '"+@metadata[OT.hasStatus]+"': "+@uri.to_s+", Result: "+@metadata[OT.resultURI].to_s end + + # updates percentageCompleted value (can only be increased) + # task has to be running + # @param [Numeric] pct value between 0 and 100 + def progress(pct) + #puts "task := "+pct.to_s + raise "no numeric >= 0 and <= 100 : '"+pct.to_s+"'" unless pct.is_a?(Numeric) and pct>=0 and pct<=100 + if (pct > @metadata[OT.percentageCompleted] + 0.0001) + RestClientWrapper.put(File.join(@uri,'Running'),{:percentageCompleted => pct}) + load_metadata + end + end private def check_state @@ -252,15 +268,6 @@ module OpenTox RestClientWrapper.raise_uri_error(ex.message, @uri) end end - - public - #hint: do not overwrite percentageCompleted=, this is used in toYaml - def progress(pct) -# #puts "task := "+pct.to_s -# raise "no numeric >= 0 and <= 100 : '"+pct.to_s+"'" unless pct.is_a?(Numeric) and pct>=0 and pct<=100 -# RestClientWrapper.put(File.join(@uri,'Running'),{:percentageCompleted => pct}) -# reload - end end -- cgit v1.2.3 From 9197d6a6503b3995e6f9499840e91a9ed6d3a1db Mon Sep 17 00:00:00 2001 From: mr Date: Tue, 18 Jan 2011 13:07:52 +0100 Subject: get subjectid from api-wrapper helper --- lib/environment.rb | 3 +++ lib/helper.rb | 13 ++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/environment.rb b/lib/environment.rb index 1761d92..203ebc6 100644 --- a/lib/environment.rb +++ b/lib/environment.rb @@ -84,6 +84,9 @@ class OwlNamespace end 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] RDF = OwlNamespace.new 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' OWL = OwlNamespace.new 'http://www.w3.org/2002/07/owl#' diff --git a/lib/helper.rb b/lib/helper.rb index 857c5b5..cc643f3 100644 --- a/lib/helper.rb +++ b/lib/helper.rb @@ -17,16 +17,18 @@ helpers do #Check Authorization for URI with method and subjectid. def authorized?(subjectid) + request_method = request.env['REQUEST_METHOD'] uri = clean_uri("#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}") - if CONFIG[:authorization][:authorize_request].include?(request.env['REQUEST_METHOD']) - ret = OpenTox::Authorization.authorize(uri, request.env['REQUEST_METHOD'], subjectid) - LOGGER.debug "OpenTox helpers OpenTox::Authorization authorized? method: #{request.env['REQUEST_METHOD']} , URI: #{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}, subjectid: #{subjectid} with return >>#{ret}<<" + request_method = "GET" if request_method == "POST" && uri =~ /\/model\/\d+\/?$/ + if CONFIG[:authorization][:authorize_request].include?(request_method) + ret = OpenTox::Authorization.authorize(uri, request_method, subjectid) + LOGGER.debug "OpenTox helpers OpenTox::Authorization authorized? method: #{request_method} , URI: #{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}, subjectid: #{subjectid} with return >>#{ret}<<" return ret end - if CONFIG[:authorization][:authenticate_request].include?(env['REQUEST_METHOD']) + if CONFIG[:authorization][:authenticate_request].include?(request_method) return true if OpenTox::Authorization.is_token_valid(subjectid) end - LOGGER.debug "Not authorized for: #{request.env['rack.url_scheme']}://#{request['REQUEST_URI']} with Method: #{request.env['REQUEST_METHOD']} with Token #{subjectid}" + LOGGER.debug "Not authorized for: #{uri} with Method: #{request.env['REQUEST_METHOD']}/#{request_method} with Token #{subjectid}" return false end @@ -68,6 +70,7 @@ before do subjectid = request.env['HTTP_SUBJECTID'] if request.env['HTTP_SUBJECTID'] and !check_subjectid(subjectid) # see http://rack.rubyforge.org/doc/SPEC.html subjectid = CGI.unescape(subjectid) if subjectid.include?("%23") + @subjectid = subjectid rescue LOGGER.debug "OpenTox ruby api wrapper: helper before filter: NO subjectid for URI: #{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}" subjectid = "" -- cgit v1.2.3 From dbd302164b74de2b241627bcc205de7245ea0da1 Mon Sep 17 00:00:00 2001 From: mr Date: Tue, 18 Jan 2011 17:15:14 +0100 Subject: refactoring A&A --- lib/authorization.rb | 6 +++++- lib/helper.rb | 30 ++++++++++++++---------------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/lib/authorization.rb b/lib/authorization.rb index dab228a..7e898cc 100644 --- a/lib/authorization.rb +++ b/lib/authorization.rb @@ -286,7 +286,11 @@ module OpenTox end true end - + + class << self + alias :token_valid? :is_token_valid + end + end end diff --git a/lib/helper.rb b/lib/helper.rb index cc643f3..5fe1857 100644 --- a/lib/helper.rb +++ b/lib/helper.rb @@ -3,18 +3,21 @@ helpers do # Authentification def protected!(subjectid) if env["session"] - flash[:notice] = "You don't have access to this section: " and \ - redirect back and \ - return unless authorized?(subjectid) + unless authorized?(subjectid) + flash[:notice] = "You don't have access to this section: " + redirect back + end elsif !env["session"] && subjectid - throw(:halt, [401, "Not authorized.\n"]) and \ - redirect back and \ - return unless authorized?(subjectid) + unless authorized?(subjectid) + throw(:halt, [401, "Not authorized.\n"]) + redirect back + end + else + throw(:halt, [401, "Not authorized.\n"]) unless authorized?(subjectid) end - throw(:halt, [401, "Not authorized.\n"]) and \ - return unless authorized?(subjectid) end + #Check Authorization for URI with method and subjectid. def authorized?(subjectid) request_method = request.env['REQUEST_METHOD'] @@ -40,12 +43,6 @@ helpers do "#{out.scheme}:" + (out.port != 80 ? out.port : "") + "//#{out.host}#{out.path}" end - def check_subjectid(subjectid) - return false if !subjectid - return true if subjectid.size > 62 - false - end - #unprotected uris for login/logout, webapplication ... def unprotected_requests case env['REQUEST_URI'] @@ -65,9 +62,10 @@ end before do unless unprotected_requests or CONFIG[:authorization][:free_request].include?(env['REQUEST_METHOD']) begin + subjectid = nil subjectid = session[:subjectid] if session[:subjectid] - subjectid = params[:subjectid] if params[:subjectid] and !check_subjectid(subjectid) - subjectid = request.env['HTTP_SUBJECTID'] if request.env['HTTP_SUBJECTID'] and !check_subjectid(subjectid) + subjectid = params[:subjectid] if params[:subjectid] and !subjectid + subjectid = request.env['HTTP_SUBJECTID'] if request.env['HTTP_SUBJECTID'] and !subjectid # see http://rack.rubyforge.org/doc/SPEC.html subjectid = CGI.unescape(subjectid) if subjectid.include?("%23") @subjectid = subjectid -- cgit v1.2.3 From 23d96df630689d122c023d76ec1d40d7688d2c96 Mon Sep 17 00:00:00 2001 From: mguetlein Date: Wed, 19 Jan 2011 15:59:12 +0100 Subject: extend authorization and rdf serialization for validation --- lib/authorization.rb | 48 ++++++++++++++++---- lib/dataset.rb | 7 +-- lib/error.rb | 11 +++++ lib/helper.rb | 25 ++++++----- lib/opentox-ruby.rb | 3 +- lib/overwrite.rb | 10 +++-- lib/policy.rb | 10 +++++ lib/serializer.rb | 124 +++++++++++++++++++++++++++++++++++++++++++++++++++ lib/to-html.rb | 3 +- 9 files changed, 213 insertions(+), 28 deletions(-) create mode 100644 lib/error.rb diff --git a/lib/authorization.rb b/lib/authorization.rb index f9499e6..c33f712 100644 --- a/lib/authorization.rb +++ b/lib/authorization.rb @@ -132,6 +132,20 @@ module OpenTox end end + # Lists policies alongside with affected uris + # @param [String] subjectid + # @return [Hash] keys: all policies of the subjectid owner, values: uris affected by those policies + def self.list_policy_uris( subjectid ) + names = list_policies(subjectid) + policies = {} + names.each do |n| + p = OpenTox::Policies.new + p.load_xml( list_policy(n, subjectid) ) + policies[n] = p.uris + end + policies + end + #Returns the owner (who created the first policy) of an URI # @param [String, String]uri,subjectid # return [String, nil]owner,nil returns owner of the URI @@ -271,21 +285,37 @@ module OpenTox return true end - #Checks (if subjectid is valid) if a policy exist and create default policy if not + # Checks (if subjectid is valid) if a policy exist and create default policy if not + # @param [String] uri + # @param [String] subjectid + # @return [Boolean] true if policy checked/created successfully (or no uri/subjectid given), false else def self.check_policy(uri, subjectid) + return true unless uri and subjectid token_valid = OpenTox::Authorization.is_token_valid(subjectid) LOGGER.debug "OpenTox::Authorization.check_policy with uri: #{uri}, subjectid: #{subjectid} is valid: #{token_valid}" - if uri and token_valid - if !uri_has_policy(uri, subjectid) - return send_policy(uri, subjectid) - else - LOGGER.debug "OpenTox::Authorization.check_policy URI: #{uri} has already a Policy." + # check if subjectid is valid + unless token_valid + # abort if invalid + LOGGER.error "OpenTox::Authorization.check_policy, subjectid NOT valid: #{subjectid}" + return false + end + + if !uri_has_policy(uri, subjectid) + # if no policy exists, create a policy, return result of send policy + send_policy(uri, subjectid) + else + LOGGER.debug "OpenTox::Authorization.check_policy URI: #{uri} has already a Policy." + # if policy exists check for POST rights + if authorize(uri, "POST", subjectid) + true + else + LOGGER.error "OpenTox::Authorization.check_policy, already exists, but no POST-authorization with subjectid: #{subjectid}" + false end end - true - end + end - end + end end diff --git a/lib/dataset.rb b/lib/dataset.rb index d45c821..ae86f5f 100644 --- a/lib/dataset.rb +++ b/lib/dataset.rb @@ -253,11 +253,12 @@ module OpenTox # @param [Array] compounds List of compound URIs # @param [Array] features List of feature URIs # @param [Hash] metadata Hash containing the metadata for the new dataset + # @param [String] subjectid # @return [OpenTox::Dataset] newly created dataset, already saved - def split( compounds, features, metadata) + def split( compounds, features, metadata, subjectid=nil) LOGGER.debug "split dataset using "+compounds.size.to_s+"/"+@compounds.size.to_s+" compounds" raise "no new compounds selected" unless compounds and compounds.size>0 - dataset = OpenTox::Dataset.create + dataset = OpenTox::Dataset.create(CONFIG[:services]["opentox-dataset"],subjectid) if features.size==0 compounds.each{ |c| dataset.add_compound(c) } else @@ -270,7 +271,7 @@ module OpenTox end end dataset.add_metadata(metadata) - dataset.save + dataset.save(subjectid) dataset end diff --git a/lib/error.rb b/lib/error.rb new file mode 100644 index 0000000..87e1a5d --- /dev/null +++ b/lib/error.rb @@ -0,0 +1,11 @@ +module OpenTox + + class NotFoundError < RuntimeError + + end + + class BadRequestError < RuntimeError + + end + +end \ No newline at end of file diff --git a/lib/helper.rb b/lib/helper.rb index 965b4ad..bb0279e 100644 --- a/lib/helper.rb +++ b/lib/helper.rb @@ -3,22 +3,24 @@ helpers do # Authentification def protected!(subjectid) if env["session"] - flash[:notice] = "You don't have access to this section: " and \ - redirect back and \ - return unless authorized?(subjectid) + unless authorized?(subjectid) + flash[:notice] = "You don't have access to this section: " + redirect back + end elsif !env["session"] && subjectid - throw(:halt, [401, "Not authorized.\n"]) and \ - redirect back and \ - return unless authorized?(subjectid) + unless authorized?(subjectid) + throw(:halt, [401, "Not authorized.\n"]) + redirect back + end + else + throw(:halt, [401, "Not authorized.\n"]) unless authorized?(subjectid) end - throw(:halt, [401, "Not authorized.\n"]) and \ - return unless authorized?(subjectid) end def authorized?(subjectid) if CONFIG[:authorization][:authorize_request].include?(request.env['REQUEST_METHOD']) ret = OpenTox::Authorization.authorize("#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}", request.env['REQUEST_METHOD'], subjectid) - LOGGER.debug "OpenTox helpers OpenTox::Authorization authorized? method: #{request.env['REQUEST_METHOD']} , URI: #{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}, subjectid: #{subjectid} with return #{ret}." + LOGGER.debug "OpenTox helpers OpenTox::Authorization authorized? method: #{request.env['REQUEST_METHOD']}, URI: #{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}, subjectid: #{subjectid} with return #{ret}." return ret end if CONFIG[:authorization][:authenticate_request].include?(env['REQUEST_METHOD']) @@ -49,7 +51,7 @@ helpers do end before do - unless unprotected_requests or CONFIG[:authorization][:free_request].include?(env['REQUEST_METHOD']) + unless !AA_SERVER or unprotected_requests or CONFIG[:authorization][:free_request].include?(env['REQUEST_METHOD']) begin subjectid = session[:subjectid] if session[:subjectid] subjectid = params[:subjectid] if params[:subjectid] and !check_subjectid(subjectid) @@ -60,7 +62,8 @@ before do LOGGER.debug "OpenTox ruby api wrapper: helper before filter: NO subjectid for URI: #{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}" subjectid = "" end - protected!(subjectid) if AA_SERVER + @subjectid = subjectid + protected!(subjectid) end end diff --git a/lib/opentox-ruby.rb b/lib/opentox-ruby.rb index fb3803b..fc1732d 100644 --- a/lib/opentox-ruby.rb +++ b/lib/opentox-ruby.rb @@ -8,6 +8,7 @@ rescue LoadError puts "Please install Openbabel with 'rake openbabel:install' in the compound component" end -['opentox', 'compound','dataset', 'parser','serializer', 'algorithm','model','task','validation','feature', 'rest_client_wrapper', 'authorization', 'policy', 'helper', 'to-html'].each do |lib| +['opentox', 'compound','dataset', 'parser','serializer', 'algorithm','model','task','validation','feature', + 'rest_client_wrapper', 'authorization', 'policy', 'helper', 'to-html', 'error' ].each do |lib| require lib end diff --git a/lib/overwrite.rb b/lib/overwrite.rb index ffeba21..720ed77 100644 --- a/lib/overwrite.rb +++ b/lib/overwrite.rb @@ -1,7 +1,11 @@ # class overwrites aka monkey patches -# hack: store sinatra in global var to make url_for and halt methods accessible +# hack: store sinatra instance in global var $url_provider to make url_for and halt methods accessible before { - $sinatra = self unless $sinatra + raise "should not happen, url provider already differently initialized "+ + $url_provider.request.host.to_s+" != "+self.request.host.to_s if + $url_provider and $url_provider.request.host!=self.request.host and + $url_provider.request.script_name!=self.request.script_name + $url_provider = self # stupid internet explorer does not ask for text/html, add this manually request.env['HTTP_ACCEPT'] += ";text/html" if request.env["HTTP_USER_AGENT"]=~/MSIE/ } @@ -91,7 +95,7 @@ class OTLogger < Logger end def format(msg) - pwd.ljust(18)+" :: "+msg.to_s+" :: "+trace+" :: "+($sinatra ? $sinatra.request.env['REMOTE_ADDR'] : nil).to_s + pwd.ljust(18)+" :: "+msg.to_s+" :: "+trace end def debug(msg) diff --git a/lib/policy.rb b/lib/policy.rb index 0ef8298..08bf6ed 100644 --- a/lib/policy.rb +++ b/lib/policy.rb @@ -32,6 +32,11 @@ module OpenTox end return true end + + # @return [Array] set of arrays affected by policies + def uris + @policies.collect{ |k,v| v.uris }.flatten.uniq + end #loads a default policy template in policies instance def load_default_policy(user, uri, group="member") @@ -190,6 +195,11 @@ module OpenTox @subjects[name] = Subject.new(name, type, value) end + # @return [Array] set of uris affected by policy + def uris + @rules.collect{ |k,v| v.uri }.uniq + end + #rule inside a policy class Rule diff --git a/lib/serializer.rb b/lib/serializer.rb index 495702a..03c2639 100644 --- a/lib/serializer.rb +++ b/lib/serializer.rb @@ -26,6 +26,15 @@ module OpenTox OT.Algorithm => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , OT.Parameter => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , OT.Task => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , + #classes for validation + OT.Validation => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , + OT.ClassificationStatistics => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , + OT.ConfusionMatrix => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , + OT.ConfusionMatrixCell => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , + OT.ClassValueStatistics => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , + OT.RegressionStatistics => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , + OT.Crossvalidation => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , + OT.CrossvalidationInfo => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , OT.compound => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , OT.feature => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , @@ -34,6 +43,22 @@ module OpenTox OT.values => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , OT.algorithm => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , OT.parameters => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , + #object props for validation# + OT.model => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , + OT.trainingDataset => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , + OT.predictionFeature => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , + OT.predictionDataset => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , + OT.crossvalidation => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , + OT.testTargetDataset => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , + OT.testDataset => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , + OT.classificationStatistics => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , + OT.confusionMatrix => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , + OT.confusionMatrixCell => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , + OT.classValueStatistics => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , + OT.regressionStatistics => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , + OT.validation => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , + OT.crossvalidationInfo => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , + OT.dataset => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , DC.title => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , DC.identifier => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , @@ -47,6 +72,47 @@ module OpenTox OT.hasStatus => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , OT.resultURI => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , OT.percentageCompleted => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + # annotation props for validation + OT.numUnpredicted => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.crossvalidationFold => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.numInstances => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.numWithoutClass => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.percentWithoutClass => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.percentUnpredicted => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.confusionMatrixActual => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.confusionMatrixPredicted => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.confusionMatrixValue => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.numIncorrect => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.percentCorrect => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.numCorrect => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.accuracy => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.trueNegativeRate => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.truePositiveRate => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.falseNegativeRate => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.falsePositiveRate => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.numTrueNegatives => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.numTruePositives => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.numFalseNegatives => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.numFalsePositives => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.classValue => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.precision => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.areaUnderRoc => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.weightedAreaUnderRoc => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.fMeasure => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.percentIncorrect => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.validationType => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.realRuntime => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.sampleCorrelationCoefficient => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.targetVarianceActual => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.targetVariancePredicted => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.meanAbsoluteError => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.sumSquaredError => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.rootMeanSquaredError => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.rSquare => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.stratified => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.numFolds => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.randomSeed => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.reportType => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , OT.hasSource => { RDF["type"] => [{ "type" => "uri", "value" => OWL.DatatypeProperty }] } , OT.value => { RDF["type"] => [{ "type" => "uri", "value" => OWL.DatatypeProperty }] } , @@ -121,6 +187,64 @@ module OpenTox @object[uri] = { RDF["type"] => [{ "type" => "uri", "value" => OT.Task }] } add_metadata uri, metadata end + + # Add a resource defined by resource_class and content + # (see documentation of add_content for example) + # @param [String] uri of resource + # @param [String] resource class, e.g. OT.Validation + # @param [Hash] content as hash + def add_resource(uri, resource_class, content) + @object[uri] = { RDF["type"] => [{ "type" => "uri", "value" => resource_class }] } + @@content_id = 1 + add_content uri, content + end + + private + @@content_id = 1 + + # Recursiv function to add content + # @example + # { DC.description => "bla", + # OT.similar_resources => [ "http://uri1", "http://uri2" ], + # OT.matrixCells => + # [ { RDF.type => OT.MatrixCell, OT.cellIndex=1 OT.cellValue => "xy" }, + # { RDF.type => OT.MatrixCell, OT.cellIndex=2 OT.cellValue => "z" } ], + # OT.info => { RDF.type => OT.ImportantInfo, + # DC.description => "blub" } + # } + # @param [String] uri + # @param [Hash] content as hash, uri must already have been added to @object + def add_content(uri, hash) + raise "content is no hash: "+hash.class.to_s unless hash.is_a?(Hash) + hash.each do |u,v| + if v.is_a? Hash + # value is again a hash, i.e. a new owl class is added + # first make sure type (==class) is set + type = v[RDF.type] + raise "type missing for "+u.to_s+" content:\n"+v.inspect unless type + raise "class unknown "+type.to_s+" (for "+u.to_s+")" unless @object.has_key?(type) + # create new node and add to current uri + genid = "_:#{type.split('#')[-1]}#{@@content_id}" + @@content_id += 1 + @object[uri] = {} unless @object[uri] + @object[uri][u] = [{ "type" => "bnode", "value" => genid }] + # add content to new class + add_hash(genid,v) + elsif v.is_a? Array + # value is an array, i.e. a list of values with property is added + v.each{ |vv| add_hash( uri, { u => vv } ) } + else # v.is_a? String + # simple string value + @object[uri] = {} unless @object[uri] + @object[uri][u] = [] unless @object[uri][u] + raise "property unknown "+u.to_s if !@object.has_key?(u) and u!=RDF.type + # use << to allow different values for one property + @object[uri][u] << {"type" => type(v), "value" => v } + end + end + end + + public # Add metadata # @param [Hash] metadata diff --git a/lib/to-html.rb b/lib/to-html.rb index 1bc1496..e9764ef 100755 --- a/lib/to-html.rb +++ b/lib/to-html.rb @@ -27,7 +27,8 @@ module OpenTox # @return [String] html page def self.text_to_html( text, related_links=nil, description=nil, post_params=nil ) - title = $sinatra.url_for($sinatra.request.env['PATH_INFO'], :full) if $sinatra + # TODO add title as parameter + title = nil #$sinatra.url_for($sinatra.request.env['PATH_INFO'], :full) if $sinatra html = < -- cgit v1.2.3 From 9d06bd3024139f2bfee4722c7536ee4ffa99fe32 Mon Sep 17 00:00:00 2001 From: mguetlein Date: Thu, 20 Jan 2011 11:29:53 +0100 Subject: implemented new error handling, still TODO rdf-support, replace halts --- lib/error.rb | 60 +++++++++++++++++++++++++++--- lib/opentox-ruby.rb | 4 +- lib/overwrite.rb | 44 +++++++++++----------- lib/rest_client_wrapper.rb | 91 ++++++++++------------------------------------ 4 files changed, 99 insertions(+), 100 deletions(-) diff --git a/lib/error.rb b/lib/error.rb index 87e1a5d..b72ce7e 100644 --- a/lib/error.rb +++ b/lib/error.rb @@ -1,11 +1,61 @@ -module OpenTox - class NotFoundError < RuntimeError - +# adding additional fields to Exception class to format errors according to OT-API +class Exception + attr_accessor :creator, :errorCause, :id +end + +module OpenTox + + class NotAuthorizedError < Exception end - class BadRequestError < RuntimeError - + class NotFoundError < Exception end + class BadRequestError < Exception + end + + class RestCallError < Exception + attr_accessor :code, :body, :uri, :payload, :headers + end + + class ErrorReport + + # formats error according to accept-header, yaml is default + # ( sets content-type in response accordingly ) + # @param [Exception] error + # @param |Sinatra::Request, optional] request + # @param [Sinatra::Response, optiona,] response, optional to set content-type + # @return [String] formated error + def self.format(error, request=nil, response=nil) + # sets current uri + error.creator = "#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}" if request + accept = request.env['HTTP_ACCEPT'].to_s if request + case accept + # when /rdf/ + # TODO add error to rdf + when /html/ + response['Content-Type'] = 'text/html' if response + OpenTox.text_to_html error.to_yaml + else + response['Content-Type'] = 'application/x-yaml' if response + error.to_yaml + end + end + + # trys to parse error from text + # @return [Exception] Exception if parsing sucessfull, nil otherwise + def self.parse( body ) + begin + err = YAML.load(body) + if err and err.is_a?(Exception) + return err + else + return nil + end + rescue + return nil + end + end + end end \ No newline at end of file diff --git a/lib/opentox-ruby.rb b/lib/opentox-ruby.rb index fc1732d..735b845 100644 --- a/lib/opentox-ruby.rb +++ b/lib/opentox-ruby.rb @@ -1,4 +1,4 @@ -['rubygems', 'sinatra', 'sinatra/url_for', 'rest_client', 'yaml', 'cgi', 'spork', 'overwrite', 'environment'].each do |lib| +['rubygems', 'sinatra', 'sinatra/url_for', 'rest_client', 'yaml', 'cgi', 'spork', 'error', 'overwrite', 'environment'].each do |lib| require lib end @@ -9,6 +9,6 @@ rescue LoadError end ['opentox', 'compound','dataset', 'parser','serializer', 'algorithm','model','task','validation','feature', - 'rest_client_wrapper', 'authorization', 'policy', 'helper', 'to-html', 'error' ].each do |lib| + 'rest_client_wrapper', 'authorization', 'policy', 'helper', 'to-html' ].each do |lib| require lib end diff --git a/lib/overwrite.rb b/lib/overwrite.rb index 720ed77..2f9fabd 100644 --- a/lib/overwrite.rb +++ b/lib/overwrite.rb @@ -10,30 +10,30 @@ before { request.env['HTTP_ACCEPT'] += ";text/html" if request.env["HTTP_USER_AGENT"]=~/MSIE/ } -# handle errors manually -# this is to return 502, when an error occurs during a rest-call (see rest_client_wrapper.rb) -set :raise_errors, Proc.new { false } -set :show_exceptions, false -error do - # try if the error is an OpenTox::Error - if OpenTox::Error.parse(request.env['sinatra.error'].to_s) - # if true, this error comes from rest_client_wrapper, halt with 502 - # (502 is defined in OT API as Error coming from other service) - halt 502,request.env['sinatra.error'] +# Error handling +# Errors are logged as error and formated according to acccept-header +# Non OpenTox::Errors (defined in error.rb) are handled as internal error (500), stacktrace is logged +# IMPT: set sinatra settings :show_exceptions + :raise_errors to false in config.ru, otherwise Rack::Showexceptions takes over +error Exception do + error = request.env['sinatra.error'] + # log error to logfile + LOGGER.error error.class.to_s+": "+error.message + case error.class + when OpenTox::BadRequestError + code = 400 + when OpenTox::NotAuthorizedError + code = 401 + when OpenTox::NotFoundError + code = 404 + when OpenTox::RestCallError + code = 502 else - # else, raise exception, this will return 500 = internal error - raise request.env['sinatra.error'] - end -end - -class Sinatra::Base - # overwriting halt to log halts (!= 202) - def halt(*response) - LOGGER.error "halt "+response.first.to_s+" "+(response.size>1 ? response[1].to_s : "") if response and response.first and response.first >= 300 - # orig sinatra code: - response = response.first if response.length == 1 - throw :halt, response + # (unwanted RuntimeExceptions as well as sth. like 'raise "invalid state"' is handled here) + code = 500 + # log backtrace for debugging + LOGGER.error error.backtrace.join("\n") end + halt code,OpenTox::ErrorReport.format(error,request,response) end class String diff --git a/lib/rest_client_wrapper.rb b/lib/rest_client_wrapper.rb index 920a828..5bc8072 100644 --- a/lib/rest_client_wrapper.rb +++ b/lib/rest_client_wrapper.rb @@ -1,32 +1,4 @@ module OpenTox - - #PENDING: implement ot error api, move to own file - class Error - - attr_accessor :code, :body, :uri, :payload, :headers - - def initialize(code, body, uri, payload, headers) - self.code = code - self.body = body.to_s[0..1000] - self.uri = uri - self.payload = payload - self.headers = headers - end - - def self.parse(error_array_string) - begin - err = YAML.load(error_array_string) - if err and err.is_a?(Array) and err.size>0 and err[0].is_a?(Error) - return err - else - return nil - end - rescue - return nil - end - end - - end class WrapperResult < String attr_accessor :content_type, :code @@ -85,16 +57,16 @@ module OpenTox # @param [optional,Hash] headers sent to the URI # @param [optional,String] payload data sent to the URI def self.raise_uri_error(error_msg, uri, headers=nil, payload=nil) - raise_ot_error( "-", error_msg, uri, headers, payload ) + raise_ot_error( nil, error_msg, nil, uri, headers, payload ) end private def self.execute( rest_call, uri, headers, payload=nil, waiting_task=nil, wait=true ) - raise_ot_error 400,"uri is null",uri,headers,payload unless uri - raise_ot_error 400,"not a uri",uri,headers,payload unless uri.to_s.uri? - raise_ot_error 400,"headers are no hash",uri,headers,payload unless headers==nil or headers.is_a?(Hash) - raise_ot_error 400,"nil headers for post not allowed, use {}",uri,headers,payload if rest_call=="post" and headers==nil + raise OpenTox::BadRequestError.new "uri is null" unless uri + raise OpenTox::BadRequestError.new "not a uri: "+uri.to_s unless uri.to_s.uri? + raise OpenTox::BadRequestError.new "headers are no hash: "+headers.inspect unless headers==nil or headers.is_a?(Hash) + raise OpenTox::BadRequestError.new "nil headers for post not allowed, use {}" if rest_call=="post" and headers==nil headers.each{ |k,v| headers.delete(k) if v==nil } if headers #remove keys with empty values, as this can cause problems begin @@ -124,18 +96,11 @@ module OpenTox return res rescue RestClient::RequestTimeout => ex - raise_ot_error 408,ex.message,uri,headers,payload + raise_ot_error 408,ex.message,nil,ex.uri,headers,payload + rescue RestClient::ExceptionWithResponse => ex + raise_ot_error ex.http_code,ex.message,ex.http_body,uri,headers,payload rescue => ex - #raise ex - #raise "'"+ex.message+"' uri: "+uri.to_s - begin - code = ex.http_code - msg = ex.http_body - rescue - code = 500 - msg = ex.to_s - end - raise_ot_error code,msg,uri,headers,payload + raise_ot_error 500,ex.message,nil,uri,headers,payload end end @@ -164,35 +129,19 @@ module OpenTox return res end - def self.raise_ot_error( code, body, uri, headers, payload=nil ) - - #build error - causing_errors = Error.parse(body) - if causing_errors - error = causing_errors + [Error.new(code, "subsequent error", uri, payload, headers)] + def self.raise_ot_error( code, message, body, uri, headers, payload=nil ) + error = OpenTox::RestCallError.new("REST call returned error: '"+message.to_s+"'") + error.code = code + error.uri = uri + error.headers = headers + error.payload = payload + parsed = OpenTox::ErrorReport.parse(body) if body + if parsed + error.errorCause = parsed else - error = [Error.new(code, body, uri, payload, headers)] + error.body = body end - - #debug utility: write error to file - error_dir = "/tmp/ot_errors" - FileUtils.mkdir(error_dir) unless File.exist?(error_dir) - raise "could not create error dir" unless File.exist?(error_dir) and File.directory?(error_dir) - file_name = "error" - time=Time.now.strftime("%m.%d.%Y-%H:%M:%S") - count = 1 - count+=1 while File.exist?(File.join(error_dir,file_name+"_"+time+"_"+count.to_s)) - File.new(File.join(error_dir,file_name+"_"+time+"_"+count.to_s),"w").puts(body) - - # handle error - # PENDING: always return yaml for now - - # raising OpenTox::Error - # to handle the error yourself, put rest-call in begin, rescue block - # if the error is not caught: - # if we are in a task, the error is caught, logged, and task state is set to error in Task.as_task - # if we are in a default call, the error is handled in overwrite.rb to return 502 (according to OT API) - raise error.to_yaml + raise error end end end -- cgit v1.2.3 From 0e759bee80e2668e1fec7b741a4ea18015f98b84 Mon Sep 17 00:00:00 2001 From: mguetlein Date: Fri, 21 Jan 2011 11:52:53 +0100 Subject: simplify error handling once again, and adding http code --- lib/error.rb | 18 +++++++++++++----- lib/overwrite.rb | 19 +++---------------- lib/rest_client_wrapper.rb | 10 +++++----- 3 files changed, 21 insertions(+), 26 deletions(-) diff --git a/lib/error.rb b/lib/error.rb index b72ce7e..e47ad62 100644 --- a/lib/error.rb +++ b/lib/error.rb @@ -1,22 +1,27 @@ # adding additional fields to Exception class to format errors according to OT-API class Exception - attr_accessor :creator, :errorCause, :id + attr_accessor :creator, :errorCause, :id, :http_code + def http_code; 500; end end module OpenTox - class NotAuthorizedError < Exception + class BadRequestError < Exception + def http_code; 400; end end - class NotFoundError < Exception + class NotAuthorizedError < Exception + def http_code; 401; end end - class BadRequestError < Exception + class NotFoundError < Exception + def http_code; 404; end end class RestCallError < Exception - attr_accessor :code, :body, :uri, :payload, :headers + attr_accessor :rest_code, :rest_body, :rest_uri, :rest_payload, :rest_headers + def http_code; 502; end end class ErrorReport @@ -30,6 +35,9 @@ module OpenTox def self.format(error, request=nil, response=nil) # sets current uri error.creator = "#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}" if request + # bit of a hack: set instance attribute in order to add it for the to_yaml conversion + error.http_code = error.http_code + accept = request.env['HTTP_ACCEPT'].to_s if request case accept # when /rdf/ diff --git a/lib/overwrite.rb b/lib/overwrite.rb index 2f9fabd..83d8099 100644 --- a/lib/overwrite.rb +++ b/lib/overwrite.rb @@ -18,22 +18,9 @@ error Exception do error = request.env['sinatra.error'] # log error to logfile LOGGER.error error.class.to_s+": "+error.message - case error.class - when OpenTox::BadRequestError - code = 400 - when OpenTox::NotAuthorizedError - code = 401 - when OpenTox::NotFoundError - code = 404 - when OpenTox::RestCallError - code = 502 - else - # (unwanted RuntimeExceptions as well as sth. like 'raise "invalid state"' is handled here) - code = 500 - # log backtrace for debugging - LOGGER.error error.backtrace.join("\n") - end - halt code,OpenTox::ErrorReport.format(error,request,response) + # log backtrace only if code is 500 -> unwanted (Runtime)Exceptions and internal errors (see error.rb) + LOGGER.error error.backtrace.join("\n") if error.http_code==500 + halt error.http_code,OpenTox::ErrorReport.format(error,request,response) end class String diff --git a/lib/rest_client_wrapper.rb b/lib/rest_client_wrapper.rb index 5bc8072..3d7b72e 100644 --- a/lib/rest_client_wrapper.rb +++ b/lib/rest_client_wrapper.rb @@ -131,15 +131,15 @@ module OpenTox def self.raise_ot_error( code, message, body, uri, headers, payload=nil ) error = OpenTox::RestCallError.new("REST call returned error: '"+message.to_s+"'") - error.code = code - error.uri = uri - error.headers = headers - error.payload = payload + error.rest_code = code + error.rest_uri = uri + error.rest_headers = headers + error.rest_payload = payload parsed = OpenTox::ErrorReport.parse(body) if body if parsed error.errorCause = parsed else - error.body = body + error.rest_body = body end raise error end -- cgit v1.2.3 From 59dba52a30de35da0122cd6c25777573faa5ffc3 Mon Sep 17 00:00:00 2001 From: mguetlein Date: Mon, 24 Jan 2011 17:19:56 +0100 Subject: fix error handling --- lib/error.rb | 67 ++++++++++++++++-------------------------- lib/overwrite.rb | 18 ++++++++++-- lib/rest_client_wrapper.rb | 72 +++++++++++++++++++++++++++++----------------- lib/task.rb | 36 +++++++++++------------ 4 files changed, 102 insertions(+), 91 deletions(-) diff --git a/lib/error.rb b/lib/error.rb index e47ad62..8a57bd0 100644 --- a/lib/error.rb +++ b/lib/error.rb @@ -1,69 +1,52 @@ # adding additional fields to Exception class to format errors according to OT-API class Exception - attr_accessor :creator, :errorCause, :id, :http_code + attr_accessor :errorCause def http_code; 500; end end module OpenTox - class BadRequestError < Exception + class BadRequestError < RuntimeError def http_code; 400; end end - class NotAuthorizedError < Exception + class NotAuthorizedError < RuntimeError def http_code; 401; end end - class NotFoundError < Exception + class NotFoundError < RuntimeError def http_code; 404; end end - class RestCallError < Exception - attr_accessor :rest_code, :rest_body, :rest_uri, :rest_payload, :rest_headers + class RestCallError < RuntimeError + attr_accessor :rest_params def http_code; 502; end end class ErrorReport - # formats error according to accept-header, yaml is default - # ( sets content-type in response accordingly ) - # @param [Exception] error - # @param |Sinatra::Request, optional] request - # @param [Sinatra::Response, optiona,] response, optional to set content-type - # @return [String] formated error - def self.format(error, request=nil, response=nil) - # sets current uri - error.creator = "#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}" if request - # bit of a hack: set instance attribute in order to add it for the to_yaml conversion - error.http_code = error.http_code - - accept = request.env['HTTP_ACCEPT'].to_s if request - case accept - # when /rdf/ - # TODO add error to rdf - when /html/ - response['Content-Type'] = 'text/html' if response - OpenTox.text_to_html error.to_yaml - else - response['Content-Type'] = 'application/x-yaml' if response - error.to_yaml - end + # TODO replace params with URIs (errorCause -> OT.errorCause) + attr_reader :message, :actor, :errorCause, :http_code, :errorDetails, :errorType + + # creates a error report object, from an ruby-exception object + # @param [Exception] error + # @param [String] actor, URI of the call that cause the error + def initialize( error, actor ) + @http_code = error.http_code + @errorType = error.class.to_s + @message = error.message + @actor = actor + @errorCause = error.errorCause if error.errorCause + @rest_params = error.rest_params if error.is_a?(OpenTox::RestCallError) and error.rest_params + end + + def self.from_rdf(rdf) + raise "not yet implemented" end - # trys to parse error from text - # @return [Exception] Exception if parsing sucessfull, nil otherwise - def self.parse( body ) - begin - err = YAML.load(body) - if err and err.is_a?(Exception) - return err - else - return nil - end - rescue - return nil - end + def self.to_rdf + raise "not yet implemented" end end end \ No newline at end of file diff --git a/lib/overwrite.rb b/lib/overwrite.rb index 83d8099..4fa0829 100644 --- a/lib/overwrite.rb +++ b/lib/overwrite.rb @@ -19,8 +19,22 @@ error Exception do # log error to logfile LOGGER.error error.class.to_s+": "+error.message # log backtrace only if code is 500 -> unwanted (Runtime)Exceptions and internal errors (see error.rb) - LOGGER.error error.backtrace.join("\n") if error.http_code==500 - halt error.http_code,OpenTox::ErrorReport.format(error,request,response) + LOGGER.error ":\n"+error.backtrace.join("\n") if error.http_code==500 + + actor = "#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}" + rep = OpenTox::ErrorReport.new(error, actor) + + case request.env['HTTP_ACCEPT'] + when /rdf/ + content_type 'application/rdf+xml' + halt error.http_code,rep.to_xml + when /html/ + content_type 'text/html' + halt error.http_code,(OpenTox.text_to_html rep.to_yaml) + else + content_type 'application/x-yaml' + halt error.http_code,rep.to_yaml + end end class String diff --git a/lib/rest_client_wrapper.rb b/lib/rest_client_wrapper.rb index 3d7b72e..7c2d719 100644 --- a/lib/rest_client_wrapper.rb +++ b/lib/rest_client_wrapper.rb @@ -50,16 +50,6 @@ module OpenTox execute( "delete", uri, headers, nil) end - # raises an Error message (rescued in overwrite.rb -> halt 502) - # usage: if the return value of a call is invalid - # @param [String] error_msg the error message - # @param [String] uri destination URI that is responsible for the error - # @param [optional,Hash] headers sent to the URI - # @param [optional,String] payload data sent to the URI - def self.raise_uri_error(error_msg, uri, headers=nil, payload=nil) - raise_ot_error( nil, error_msg, nil, uri, headers, payload ) - end - private def self.execute( rest_call, uri, headers, payload=nil, waiting_task=nil, wait=true ) @@ -70,7 +60,7 @@ module OpenTox headers.each{ |k,v| headers.delete(k) if v==nil } if headers #remove keys with empty values, as this can cause problems begin - #LOGGER.debug "RestCall: "+rest_call.to_s+" "+uri.to_s+" "+headers.inspect + #LOGGER.debug "RestCall: "+rest_call.to_s+" "+uri.to_s+" "+headers.inspect+" "+payload.inspect resource = RestClient::Resource.new(uri,{:timeout => 60}) if payload result = resource.send(rest_call, payload, headers) @@ -86,6 +76,7 @@ module OpenTox raise "content-type not set" unless res.content_type res.code = result.code + #LOGGER.debug "RestCall result: "+res.to_s+" "+res.code.to_s+" "+res.content_type.to_s # TODO: Ambit returns task representation with 200 instead of result URI return res if res.code==200 || !wait @@ -96,11 +87,16 @@ module OpenTox return res rescue RestClient::RequestTimeout => ex - raise_ot_error 408,ex.message,nil,ex.uri,headers,payload + received_error ex.message, 408, nil, {:rest_uri => uri, :headers => headers} rescue RestClient::ExceptionWithResponse => ex - raise_ot_error ex.http_code,ex.message,ex.http_body,uri,headers,payload + # error comming from a different webservice, + received_error ex.http_body, ex.http_code, ex.response.net_http_res.content_type, {:rest_uri => uri, :headers => headers} + rescue OpenTox::RestCallError => ex + # already a rest-error, probably comes from wait_for_task, just pass through + raise ex rescue => ex - raise_ot_error 500,ex.message,nil,uri,headers,payload + # some internal error occuring in rest_client_wrapper, just pass through + raise ex end end @@ -121,27 +117,49 @@ module OpenTox LOGGER.debug "result is a task '"+task.uri.to_s+"', wait for completion" task.wait_for_completion waiting_task - raise task.description unless task.completed? # maybe task was cancelled / error - + unless task.completed? # maybe task was cancelled / error + if task.errorReport + received_error task.errorReport, task.http_code, nil, {:rest_uri => task.uri, :rest_code => task.http_code} + else + raise "task status: '"+task.status.to_s+"' but errorReport nil" + end + end + res = WrapperResult.new task.result_uri res.code = task.http_code res.content_type = "text/uri-list" return res end - def self.raise_ot_error( code, message, body, uri, headers, payload=nil ) - error = OpenTox::RestCallError.new("REST call returned error: '"+message.to_s+"'") - error.rest_code = code - error.rest_uri = uri - error.rest_headers = headers - error.rest_payload = payload - parsed = OpenTox::ErrorReport.parse(body) if body - if parsed - error.errorCause = parsed + def self.received_error( body, code, content_type=nil, params=nil ) + + # try to parse body + report = nil + if body.is_a?(OpenTox::ErrorReport) + report = body + else + case content_type + when /yaml/ + report = YAML.load(body) + when /rdf/ + report = OpenTox::ErrorReport.from_rdf(body) + end + end + + unless report + # parsing was not successfull + # raise 'plain' RestCallError + err = OpenTox::RestCallError.new("REST call returned error: '"+body.to_s+"'") + err.rest_params = params + raise err else - error.rest_body = body + # parsing sucessfull + # raise RestCallError with parsed report as error cause + err = OpenTox::RestCallError.new("REST call subsequent error") + err.errorCause = report + err.rest_params = params + raise err end - raise error end end end diff --git a/lib/task.rb b/lib/task.rb index d701c82..06d290f 100644 --- a/lib/task.rb +++ b/lib/task.rb @@ -56,26 +56,17 @@ module OpenTox #raise "Server too busy to start a new task" end - task_pid = Spork.spork(:logger => LOGGER) do LOGGER.debug "Task #{task.uri} started #{Time.now}" - begin - result = catch(:halt) do - yield task - end - # catching halt, set task state to error - if result && result.is_a?(Array) && result.size==2 && result[0]>202 - LOGGER.error "task was halted: "+result.inspect - task.error(result[1]) - return - end + result = yield task LOGGER.debug "Task #{task.uri} done #{Time.now} -> "+result.to_s task.completed(result) - rescue => ex - LOGGER.error "task failed: "+ex.message - LOGGER.error ": "+ex.backtrace.join("\n") - task.error(ex.message) + rescue => error + LOGGER.error "task failed: "+error.class.to_s+": "+error.message + # log backtrace only if code is 500 -> unwanted (Runtime)Exceptions and internal errors (see error.rb) + LOGGER.error ":\n"+error.backtrace.join("\n") if error.http_code==500 + task.error(OpenTox::ErrorReport.new(error, creator)) end end task.pid = task_pid @@ -127,6 +118,10 @@ module OpenTox @metadata[DC.description] end + def errorReport + @metadata[OT.errorReport] + end + def cancel RestClientWrapper.put(File.join(@uri,'Cancelled')) load_metadata @@ -137,8 +132,9 @@ module OpenTox load_metadata end - def error(description) - RestClientWrapper.put(File.join(@uri,'Error'),{:description => description.to_s[0..2000]}) + def error(error_report) + raise "no error report" unless error_report.is_a?(OpenTox::ErrorReport) + RestClientWrapper.put(File.join(@uri,'Error'),{:errorReport => error_report.to_yaml}) load_metadata end @@ -236,7 +232,7 @@ module OpenTox end end - LOGGER.debug "Task '"+@metadata[OT.hasStatus]+"': "+@uri.to_s+", Result: "+@metadata[OT.resultURI].to_s + LOGGER.debug "Task '"+@metadata[OT.hasStatus].to_s+"': "+@uri.to_s+", Result: "+@metadata[OT.resultURI].to_s end # updates percentageCompleted value (can only be increased) @@ -250,7 +246,7 @@ module OpenTox load_metadata end end - + private def check_state begin @@ -265,7 +261,7 @@ module OpenTox "'" unless @metadata[OT.resultURI] and @metadata[OT.resultURI].to_s.uri? end rescue => ex - RestClientWrapper.raise_uri_error(ex.message, @uri) + raise OpenTox::BadRequestError.new ex.message+" (task-uri:"+@uri+")" end end -- cgit v1.2.3 From 8cfe8ad608e59d3536cd5403a70743a51cb901ee Mon Sep 17 00:00:00 2001 From: mguetlein Date: Mon, 24 Jan 2011 19:35:36 +0100 Subject: fix task to rdf --- lib/error.rb | 18 ++++++++++++++++-- lib/overwrite.rb | 2 +- lib/serializer.rb | 11 ++++++++--- lib/task.rb | 7 +++++++ 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/lib/error.rb b/lib/error.rb index 8a57bd0..d3fd19b 100644 --- a/lib/error.rb +++ b/lib/error.rb @@ -40,13 +40,27 @@ module OpenTox @errorCause = error.errorCause if error.errorCause @rest_params = error.rest_params if error.is_a?(OpenTox::RestCallError) and error.rest_params end + + def rdf_content() + c = { + RDF.type => OT.ErrorReport, + OT.statusCode => @http_code, + OT.message => @message, + OT.actor => @actor, + OT.errorCode => @errorType, + } + c[OT.errorCause] = @errorCause.rdf_content if @errorCause + c + end def self.from_rdf(rdf) raise "not yet implemented" end - def self.to_rdf - raise "not yet implemented" + def to_rdfxml + s = Serializer::Owl.new + s.add_resource(CONFIG[:services]["opentox-task"]+"/tmpId/ErrorReport/tmpId", OT.errorReport, rdf_content) + s.to_rdfxml end end end \ No newline at end of file diff --git a/lib/overwrite.rb b/lib/overwrite.rb index 4fa0829..e52618c 100644 --- a/lib/overwrite.rb +++ b/lib/overwrite.rb @@ -27,7 +27,7 @@ error Exception do case request.env['HTTP_ACCEPT'] when /rdf/ content_type 'application/rdf+xml' - halt error.http_code,rep.to_xml + halt error.http_code,rep.to_rdfxml when /html/ content_type 'text/html' halt error.http_code,(OpenTox.text_to_html rep.to_yaml) diff --git a/lib/serializer.rb b/lib/serializer.rb index 03c2639..44b4414 100644 --- a/lib/serializer.rb +++ b/lib/serializer.rb @@ -14,7 +14,7 @@ module OpenTox def initialize @object = { - # this should come from opntox.owl + # this should come from opentox.owl OT.Compound => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , OT.Feature => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , OT.NominalFeature => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , @@ -35,6 +35,7 @@ module OpenTox OT.RegressionStatistics => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , OT.Crossvalidation => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , OT.CrossvalidationInfo => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , + OT.ErrorReport => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } , OT.compound => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , OT.feature => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } , @@ -113,6 +114,10 @@ module OpenTox OT.numFolds => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , OT.randomSeed => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , OT.reportType => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.message => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.statusCode => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.actor => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , + OT.errorCode => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } , OT.hasSource => { RDF["type"] => [{ "type" => "uri", "value" => OWL.DatatypeProperty }] } , OT.value => { RDF["type"] => [{ "type" => "uri", "value" => OWL.DatatypeProperty }] } , @@ -229,10 +234,10 @@ module OpenTox @object[uri] = {} unless @object[uri] @object[uri][u] = [{ "type" => "bnode", "value" => genid }] # add content to new class - add_hash(genid,v) + add_content(genid,v) elsif v.is_a? Array # value is an array, i.e. a list of values with property is added - v.each{ |vv| add_hash( uri, { u => vv } ) } + v.each{ |vv| add_content( uri, { u => vv } ) } else # v.is_a? String # simple string value @object[uri] = {} unless @object[uri] diff --git a/lib/task.rb b/lib/task.rb index 06d290f..4d1ee90 100644 --- a/lib/task.rb +++ b/lib/task.rb @@ -102,7 +102,9 @@ module OpenTox def to_rdfxml s = Serializer::Owl.new + @metadata[OT.errorReport] = @uri+"/ErrorReport/tmpId" if @error_report s.add_task(@uri,@metadata) + s.add_resource(@uri+"/ErrorReport/tmpId", OT.errorReport, @error_report.rdf_content) if @error_report s.to_rdfxml end @@ -138,6 +140,11 @@ module OpenTox load_metadata end + # not stored just for to_rdf + def add_error_report( error_report ) + @error_report = error_report + end + def pid=(pid) RestClientWrapper.put(File.join(@uri,'pid'), {:pid => pid}) end -- cgit v1.2.3 From b8e919f933b4137043c6facf56b5ad20249fdd2b Mon Sep 17 00:00:00 2001 From: mguetlein Date: Tue, 25 Jan 2011 12:50:41 +0100 Subject: add backtrace to error if configured in .yaml config --- lib/error.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/error.rb b/lib/error.rb index d3fd19b..e5c460d 100644 --- a/lib/error.rb +++ b/lib/error.rb @@ -39,6 +39,15 @@ module OpenTox @actor = actor @errorCause = error.errorCause if error.errorCause @rest_params = error.rest_params if error.is_a?(OpenTox::RestCallError) and error.rest_params + @backtrace = error.backtrace.join("\n") if CONFIG[:backtrace] + end + + # overwrite sorting to make easier readable + def to_yaml_properties + p = super + p = ( p - ["@backtrace"]) + ["@backtrace"] if @backtrace + p = ( p - ["@errorCause"]) + ["@errorCause"] if @errorCause + p end def rdf_content() -- cgit v1.2.3 From bbb753ffe7a428dc4bdfef59fdd703d077aefbfb Mon Sep 17 00:00:00 2001 From: mguetlein Date: Tue, 25 Jan 2011 14:18:24 +0100 Subject: do not catch error in model.run --- lib/model.rb | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/model.rb b/lib/model.rb index e95c78c..efa273b 100644 --- a/lib/model.rb +++ b/lib/model.rb @@ -14,12 +14,7 @@ module OpenTox else accept = 'application/rdf+xml' end - begin - RestClientWrapper.post(@uri,{:accept => accept},params,waiting_task).to_s - rescue => e - LOGGER.error "Failed to run #{@uri} with #{params.inspect} (#{e.inspect})" - raise "Failed to run #{@uri} with #{params.inspect}" - end + RestClientWrapper.post(@uri,{:accept => accept},params,waiting_task).to_s end # Generic OpenTox model class for all API compliant services -- cgit v1.2.3 From ddcf8597a13ea6f03c697c78d224376ff36c7ea3 Mon Sep 17 00:00:00 2001 From: mguetlein Date: Tue, 25 Jan 2011 16:20:28 +0100 Subject: replace halt with raise NotAuthorized --- lib/helper.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/helper.rb b/lib/helper.rb index bb0279e..ff5e908 100644 --- a/lib/helper.rb +++ b/lib/helper.rb @@ -9,11 +9,10 @@ helpers do end elsif !env["session"] && subjectid unless authorized?(subjectid) - throw(:halt, [401, "Not authorized.\n"]) - redirect back + raise OpenTox::NotAuthorizedError.new "Not authorized" end else - throw(:halt, [401, "Not authorized.\n"]) unless authorized?(subjectid) + raise OpenTox::NotAuthorizedError.new "Not authorized" unless authorized?(subjectid) end end -- cgit v1.2.3 From ce93b07bb253df3c548c59bacc869839aa78bb4c Mon Sep 17 00:00:00 2001 From: mguetlein Date: Wed, 26 Jan 2011 15:54:05 +0100 Subject: add whitlisting concept for A&A, some minor modifications --- lib/authorization.rb | 42 +++++++++++++++++++++++++++++++++++++++++- lib/error.rb | 13 ++++++++++++- lib/helper.rb | 12 +----------- lib/model.rb | 10 +++++----- 4 files changed, 59 insertions(+), 18 deletions(-) diff --git a/lib/authorization.rb b/lib/authorization.rb index 5bc690a..c6f39c1 100644 --- a/lib/authorization.rb +++ b/lib/authorization.rb @@ -322,7 +322,47 @@ module OpenTox alias :token_valid? :is_token_valid end - end + #Check Authorization for URI with method and subjectid. + def self.authorized?(uri, request_method, subjectid) + return true if OpenTox::Authorization.whitelisted?(uri, request_method) + if CONFIG[:authorization][:authorize_request].include?(request_method) + ret = OpenTox::Authorization.authorize(uri, request_method, subjectid) + LOGGER.debug "OpenTox helpers OpenTox::Authorization authorized? method: #{request_method} , URI: #{uri}, subjectid: #{subjectid} with return >>#{ret}<<" + return ret + end + if CONFIG[:authorization][:authenticate_request].include?(request_method) + return true if OpenTox::Authorization.is_token_valid(subjectid) + end + LOGGER.debug "Not authorized for: #{uri} with Method: #{request_method} with Token #{subjectid}" + return false + end + + @@whitelist = {} + + private + def self.whitelisted?(uri, request_method) + return false unless @@whitelist[request_method] + @@whitelist[request_method].each do |r| + return true if r.match(uri) + end + return false + end + + public + def self.whitelist(uri_match, request_method) + if uri_match.is_a?(Regexp) + uri_regex = uri_match + elsif uri_match.is_a?(String) + uri_regex = Regexp.new("^"+uri_match+"$") + else + raise "uri-match param is neither string(->exact uri match) nor regexp: "+uri_match.class + end + LOGGER.info("whitelisted "+request_method+" "+uri_regex.to_s) + @@whitelist[request_method] = [] unless @@whitelist[request_method] + @@whitelist[request_method] << uri_regex + end + + end end diff --git a/lib/error.rb b/lib/error.rb index e5c460d..8c666f3 100644 --- a/lib/error.rb +++ b/lib/error.rb @@ -39,7 +39,7 @@ module OpenTox @actor = actor @errorCause = error.errorCause if error.errorCause @rest_params = error.rest_params if error.is_a?(OpenTox::RestCallError) and error.rest_params - @backtrace = error.backtrace.join("\n") if CONFIG[:backtrace] + @backtrace = error.backtrace.short_backtrace if CONFIG[:backtrace] end # overwrite sorting to make easier readable @@ -72,4 +72,15 @@ module OpenTox s.to_rdfxml end end +end + +class Array + def short_backtrace + short = [] + each do |c| + break if c =~ /sinatra\/base/ + short << c + end + short.join("\n") + end end \ No newline at end of file diff --git a/lib/helper.rb b/lib/helper.rb index e82c8fb..afeeb43 100644 --- a/lib/helper.rb +++ b/lib/helper.rb @@ -16,22 +16,12 @@ helpers do end end - #Check Authorization for URI with method and subjectid. def authorized?(subjectid) request_method = request.env['REQUEST_METHOD'] uri = clean_uri("#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}") request_method = "GET" if request_method == "POST" && uri =~ /\/model\/\d+\/?$/ - if CONFIG[:authorization][:authorize_request].include?(request_method) - ret = OpenTox::Authorization.authorize(uri, request_method, subjectid) - LOGGER.debug "OpenTox helpers OpenTox::Authorization authorized? method: #{request_method} , URI: #{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}, subjectid: #{subjectid} with return >>#{ret}<<" - return ret - end - if CONFIG[:authorization][:authenticate_request].include?(request_method) - return true if OpenTox::Authorization.is_token_valid(subjectid) - end - LOGGER.debug "Not authorized for: #{uri} with Method: #{request.env['REQUEST_METHOD']}/#{request_method} with Token #{subjectid}" - return false + return OpenTox::Authorization.authorized?(uri, request_method, subjectid) end #cleans URI from querystring and file-extension. Sets port 80 to emptystring diff --git a/lib/model.rb b/lib/model.rb index 85be1b5..741eea6 100644 --- a/lib/model.rb +++ b/lib/model.rb @@ -24,9 +24,9 @@ module OpenTox # Find Generic Opentox Model via URI, and loads metadata # @param [String] uri Model URI # @return [OpenTox::Model::Generic] Model instance, nil if model was not found - def self.find(uri) + def self.find(uri,subjectid=nil) model = Generic.new(uri) - model.load_metadata + model.load_metadata(subjectid) if model.metadata==nil or model.metadata.size==0 nil else @@ -36,10 +36,10 @@ module OpenTox # provides feature type, possible types are "regression" or "classification" # @return [String] feature type, "unknown" if type could not be estimated - def feature_type + def feature_type(subjectid=nil) # dynamically perform restcalls if necessary - load_metadata if @metadata==nil or @metadata.size==0 or (@metadata.size==1 && @metadata.values[0]==@uri) - @dependentVariable = OpenTox::Feature.find( @metadata[OT.dependentVariables] ) unless @dependentVariable + load_metadata(subjectid) if @metadata==nil or @metadata.size==0 or (@metadata.size==1 && @metadata.values[0]==@uri) + @dependentVariable = OpenTox::Feature.find( @metadata[OT.dependentVariables],subjectid ) unless @dependentVariable [@dependentVariable.feature_type, @metadata[OT.isA], @metadata[DC.title], @uri].each do |type| case type -- cgit v1.2.3 From 53a6d76d44543ba8109bc6fa1a609e30dd7e91ff Mon Sep 17 00:00:00 2001 From: mguetlein Date: Wed, 26 Jan 2011 16:08:57 +0100 Subject: documented new autorization function --- lib/authorization.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/authorization.rb b/lib/authorization.rb index c6f39c1..6a8a174 100644 --- a/lib/authorization.rb +++ b/lib/authorization.rb @@ -322,7 +322,11 @@ module OpenTox alias :token_valid? :is_token_valid end - #Check Authorization for URI with method and subjectid. + # Check Authorization for a resource (identified via URI) with method and subjectid. + # @param [String] uri + # @param [String] request_method, should be GET, POST, PUT, DELETE + # @param [String] subjectid + # @return [Boolean] true if access granted, else otherwise def self.authorized?(uri, request_method, subjectid) return true if OpenTox::Authorization.whitelisted?(uri, request_method) if CONFIG[:authorization][:authorize_request].include?(request_method) @@ -349,6 +353,9 @@ module OpenTox end public + # adds uri/regexp-for-matching-uri to the whitelist for a request-method (i.e. access will be granted without cheking the A&A service) + # @param [String or Regexp] uri_match if string match must be ecaxt + # @param [String] request_method, must be GET, POST, PUT, DELETE def self.whitelist(uri_match, request_method) if uri_match.is_a?(Regexp) uri_regex = uri_match @@ -357,7 +364,7 @@ module OpenTox else raise "uri-match param is neither string(->exact uri match) nor regexp: "+uri_match.class end - LOGGER.info("whitelisted "+request_method+" "+uri_regex.to_s) + LOGGER.info("whitelisted "+request_method.to_s+" "+uri_regex.to_s) @@whitelist[request_method] = [] unless @@whitelist[request_method] @@whitelist[request_method] << uri_regex end -- cgit v1.2.3 From 171ab814d15b9504ef9892ba5f194de8bc019f46 Mon Sep 17 00:00:00 2001 From: mguetlein Date: Wed, 26 Jan 2011 16:16:09 +0100 Subject: minor fix --- lib/authorization.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/authorization.rb b/lib/authorization.rb index 6a8a174..a6253b7 100644 --- a/lib/authorization.rb +++ b/lib/authorization.rb @@ -362,7 +362,7 @@ module OpenTox elsif uri_match.is_a?(String) uri_regex = Regexp.new("^"+uri_match+"$") else - raise "uri-match param is neither string(->exact uri match) nor regexp: "+uri_match.class + raise "uri-match param is neither string(->exact uri match) nor regexp: "+uri_match.class.to_s end LOGGER.info("whitelisted "+request_method.to_s+" "+uri_regex.to_s) @@whitelist[request_method] = [] unless @@whitelist[request_method] -- cgit v1.2.3 From e1a067953dd9139b01aaebe42ff158a944240540 Mon Sep 17 00:00:00 2001 From: mguetlein Date: Fri, 28 Jan 2011 12:20:08 +0100 Subject: extend whitelisting, get feature_type from algorithm --- lib/algorithm.rb | 5 +++-- lib/authorization.rb | 18 +++++++++++++----- lib/dataset.rb | 1 + lib/feature.rb | 3 ++- lib/model.rb | 24 ++++++++++++++++-------- lib/task.rb | 20 +++++++++++++++++--- 6 files changed, 52 insertions(+), 19 deletions(-) diff --git a/lib/algorithm.rb b/lib/algorithm.rb index 58a2640..ee3109c 100644 --- a/lib/algorithm.rb +++ b/lib/algorithm.rb @@ -34,9 +34,10 @@ module OpenTox # Find Generic Opentox Algorithm via URI, and loads metadata # @param [String] uri Algorithm URI # @return [OpenTox::Algorithm::Generic] Algorithm instance, nil if alogrithm was not found - def self.find(uri) + def self.find(uri, subjectid) + return nil unless uri alg = Generic.new(uri) - alg.load_metadata + alg.load_metadata( subjectid ) if alg.metadata==nil or alg.metadata.size==0 nil else diff --git a/lib/authorization.rb b/lib/authorization.rb index a6253b7..1573da3 100644 --- a/lib/authorization.rb +++ b/lib/authorization.rb @@ -328,7 +328,10 @@ module OpenTox # @param [String] subjectid # @return [Boolean] true if access granted, else otherwise def self.authorized?(uri, request_method, subjectid) - return true if OpenTox::Authorization.whitelisted?(uri, request_method) + if OpenTox::Authorization.whitelisted?(uri, request_method) + LOGGER.debug "whitelisted! "+uri.to_s + return true + end if CONFIG[:authorization][:authorize_request].include?(request_method) ret = OpenTox::Authorization.authorize(uri, request_method, subjectid) LOGGER.debug "OpenTox helpers OpenTox::Authorization authorized? method: #{request_method} , URI: #{uri}, subjectid: #{subjectid} with return >>#{ret}<<" @@ -346,8 +349,12 @@ module OpenTox private def self.whitelisted?(uri, request_method) return false unless @@whitelist[request_method] - @@whitelist[request_method].each do |r| - return true if r.match(uri) + @@whitelist[request_method].each do |regexp,invert| + if invert + return true if !regexp.match(uri) + else + return true if regexp.match(uri) + end end return false end @@ -356,7 +363,8 @@ module OpenTox # adds uri/regexp-for-matching-uri to the whitelist for a request-method (i.e. access will be granted without cheking the A&A service) # @param [String or Regexp] uri_match if string match must be ecaxt # @param [String] request_method, must be GET, POST, PUT, DELETE - def self.whitelist(uri_match, request_method) + # @param [Boolean,optional] invert, set to true if you want to whitelist everything that does not match (careful!) + def self.whitelist(uri_match, request_method, invert=false) if uri_match.is_a?(Regexp) uri_regex = uri_match elsif uri_match.is_a?(String) @@ -366,7 +374,7 @@ module OpenTox end LOGGER.info("whitelisted "+request_method.to_s+" "+uri_regex.to_s) @@whitelist[request_method] = [] unless @@whitelist[request_method] - @@whitelist[request_method] << uri_regex + @@whitelist[request_method] << [ uri_regex, invert ] end end diff --git a/lib/dataset.rb b/lib/dataset.rb index 640e3da..9c20968 100644 --- a/lib/dataset.rb +++ b/lib/dataset.rb @@ -51,6 +51,7 @@ module OpenTox # @param [String] uri Dataset URI # @return [OpenTox::Dataset] Dataset object with all data def self.find(uri, subjectid=nil) + return nil unless uri dataset = Dataset.new(uri, subjectid) dataset.load_all(subjectid) dataset diff --git a/lib/feature.rb b/lib/feature.rb index 28ac0c5..be063dd 100644 --- a/lib/feature.rb +++ b/lib/feature.rb @@ -3,7 +3,8 @@ module OpenTox include OpenTox def self.find(uri, subjectid=nil) - feature = Feature.new uri + return nil unless uri + feature = Feature.new uri if (CONFIG[:yaml_hosts].include?(URI.parse(uri).host)) feature.add_metadata YAML.load(RestClientWrapper.get(uri,{:accept => "application/x-yaml", :subjectid => subjectid})) else diff --git a/lib/model.rb b/lib/model.rb index 741eea6..80d7ec4 100644 --- a/lib/model.rb +++ b/lib/model.rb @@ -8,13 +8,16 @@ module OpenTox # @param [Hash] params Parameters for OpenTox model # @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly # @return [text/uri-list] Task or resource URI - def run( params, waiting_task=nil ) - if CONFIG[:yaml_hosts].include?(URI.parse(@uri).host) - accept = 'application/x-yaml' - else - accept = 'application/rdf+xml' + def run( params, accept_header=nil, waiting_task=nil ) + unless accept_header + if CONFIG[:yaml_hosts].include?(URI.parse(@uri).host) + accept_header = 'application/x-yaml' + else + accept_header = 'application/rdf+xml' + end end - RestClientWrapper.post(@uri,{:accept => accept},params,waiting_task).to_s + LOGGER.info "running model "+@uri.to_s+", params: "+params.inspect+", accept: "+accept_header.to_s + RestClientWrapper.post(@uri,{:accept => accept_header},params,waiting_task).to_s end # Generic OpenTox model class for all API compliant services @@ -25,6 +28,7 @@ module OpenTox # @param [String] uri Model URI # @return [OpenTox::Model::Generic] Model instance, nil if model was not found def self.find(uri,subjectid=nil) + return nil unless uri model = Generic.new(uri) model.load_metadata(subjectid) if model.metadata==nil or model.metadata.size==0 @@ -39,9 +43,12 @@ module OpenTox def feature_type(subjectid=nil) # dynamically perform restcalls if necessary load_metadata(subjectid) if @metadata==nil or @metadata.size==0 or (@metadata.size==1 && @metadata.values[0]==@uri) + + @algorithm = OpenTox::Algorithm::Generic.find(@metadata[OT.algorithm], subjectid) unless @algorithm + algorithm_title = @algorithm ? @algorithm.metadata[DC.title] : nil @dependentVariable = OpenTox::Feature.find( @metadata[OT.dependentVariables],subjectid ) unless @dependentVariable - [@dependentVariable.feature_type, @metadata[OT.isA], @metadata[DC.title], @uri].each do |type| + [@dependentVariable.feature_type, @metadata[OT.isA], @metadata[DC.title], @uri, algorithm_title].each do |type| case type when /(?i)classification/ return "classification" @@ -49,7 +56,8 @@ module OpenTox return "regression" end end - raise "unknown model "+[@dependentVariable.feature_type, @metadata[OT.isA], @metadata[DC.title], @uri].inspect + raise "unknown model "+[@dependentVariable.feature_type, @metadata[OT.isA], + @metadata[DC.title], @uri, algorithm_title].inspect end end diff --git a/lib/task.rb b/lib/task.rb index 3c6aba5..74940de 100644 --- a/lib/task.rb +++ b/lib/task.rb @@ -78,6 +78,7 @@ module OpenTox # @param [String] uri Task URI # @return [OpenTox::Task] Task object def self.find(uri) + return nil unless uri task = Task.new(uri) task.load_metadata task @@ -94,10 +95,23 @@ module OpenTox @metadata = YAML.load(yaml) end + def self.from_rdfxml(rdfxml) - file = Tempfile.open("ot-rdfxml"){|f| f.write(rdfxml)}.path + file = Tempfile.new("ot-rdfxml") + file.puts rdfxml + file.close + file = "file://"+file.path + + # PENDING + raise "Parse from file not working: what is the base-object-uri??? (omitted in triples)" + parser = Parser::Owl::Generic.new file - @metadata = parser.load_metadata + metadata = parser.load_metadata + puts metadata.inspect + + task = Task.new(uri) + task.add_metadata(metadata) + task end def to_rdfxml @@ -232,7 +246,7 @@ module OpenTox sleep dur load_metadata # if another (sub)task is waiting for self, set progress accordingly - waiting_task.progress(@metadata[OT.percentageCompleted]) if waiting_task + waiting_task.progress(@metadata[OT.percentageCompleted].to_f) if waiting_task check_state if (Time.new > due_to_time) raise "max wait time exceeded ("+DEFAULT_TASK_MAX_DURATION.to_s+"sec), task: '"+@uri.to_s+"'" -- cgit v1.2.3 From 3aaae5a3fe341073fc0537606aababe387d830e0 Mon Sep 17 00:00:00 2001 From: mguetlein Date: Fri, 28 Jan 2011 13:56:33 +0100 Subject: reorderd Autohorizaion.authorize? --- lib/authorization.rb | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/authorization.rb b/lib/authorization.rb index 1573da3..b4c1ee5 100644 --- a/lib/authorization.rb +++ b/lib/authorization.rb @@ -329,19 +329,20 @@ module OpenTox # @return [Boolean] true if access granted, else otherwise def self.authorized?(uri, request_method, subjectid) if OpenTox::Authorization.whitelisted?(uri, request_method) - LOGGER.debug "whitelisted! "+uri.to_s - return true - end - if CONFIG[:authorization][:authorize_request].include?(request_method) + LOGGER.debug "authorized? >>true<< (uris is whitelisted), method: #{request_method}, URI: #{uri}, subjectid: #{subjectid}" + true + elsif CONFIG[:authorization][:authorize_request].include?(request_method) ret = OpenTox::Authorization.authorize(uri, request_method, subjectid) - LOGGER.debug "OpenTox helpers OpenTox::Authorization authorized? method: #{request_method} , URI: #{uri}, subjectid: #{subjectid} with return >>#{ret}<<" - return ret + LOGGER.debug "authorized? >>#{ret}<< (uri authorized), method: #{request_method}, URI: #{uri}, subjectid: #{subjectid}" + ret + elsif CONFIG[:authorization][:authenticate_request].include?(request_method) + ret = OpenTox::Authorization.is_token_valid(subjectid) + LOGGER.debug "authorized? >>#{ret}<< (token is valid), method: #{request_method}, URI: #{uri}, subjectid: #{subjectid}" + ret + else + LOGGER.debug "authorized? >>true<< (request is free), method: #{request_method}, URI: #{uri}, subjectid: #{subjectid}" + true end - if CONFIG[:authorization][:authenticate_request].include?(request_method) - return true if OpenTox::Authorization.is_token_valid(subjectid) - end - LOGGER.debug "Not authorized for: #{uri} with Method: #{request_method} with Token #{subjectid}" - return false end @@whitelist = {} -- cgit v1.2.3 From 26c0b93a02fddb60175747f7733d13e973257cd8 Mon Sep 17 00:00:00 2001 From: mr Date: Tue, 1 Feb 2011 16:34:20 +0100 Subject: A&A for validations --- lib/authorization.rb | 79 ++++++++++++++++++++++++++++------------------------ lib/helper.rb | 20 ++++--------- lib/model.rb | 4 +-- lib/validation.rb | 12 ++++---- 4 files changed, 56 insertions(+), 59 deletions(-) diff --git a/lib/authorization.rb b/lib/authorization.rb index b4c1ee5..12be037 100644 --- a/lib/authorization.rb +++ b/lib/authorization.rb @@ -328,55 +328,60 @@ module OpenTox # @param [String] subjectid # @return [Boolean] true if access granted, else otherwise def self.authorized?(uri, request_method, subjectid) - if OpenTox::Authorization.whitelisted?(uri, request_method) - LOGGER.debug "authorized? >>true<< (uris is whitelisted), method: #{request_method}, URI: #{uri}, subjectid: #{subjectid}" - true - elsif CONFIG[:authorization][:authorize_request].include?(request_method) - ret = OpenTox::Authorization.authorize(uri, request_method, subjectid) - LOGGER.debug "authorized? >>#{ret}<< (uri authorized), method: #{request_method}, URI: #{uri}, subjectid: #{subjectid}" - ret + if CONFIG[:authorization][:free_request].include?(request_method) + #LOGGER.debug "authorized? >>true<< (request is free), method: #{request_method}, URI: #{uri}, subjectid: #{subjectid}" + true + elsif OpenTox::Authorization.free_uri?(uri, request_method) + #LOGGER.debug "authorized? >>true<< (uris is free_uri), method: #{request_method}, URI: #{uri}, subjectid: #{subjectid}" + true elsif CONFIG[:authorization][:authenticate_request].include?(request_method) ret = OpenTox::Authorization.is_token_valid(subjectid) - LOGGER.debug "authorized? >>#{ret}<< (token is valid), method: #{request_method}, URI: #{uri}, subjectid: #{subjectid}" + #LOGGER.debug "authorized? >>#{ret}<< (token is in/valid), method: #{request_method}, URI: #{uri}, subjectid: #{subjectid}" + ret + elsif OpenTox::Authorization.authorize_exception?(uri, request_method) + ret = OpenTox::Authorization.is_token_valid(subjectid) + #LOGGER.debug "authorized? >>#{ret}<< (uris is authorize exception, token is in/valid), method: #{request_method}, URI: #{uri}, subjectid: #{subjectid}" + ret + elsif CONFIG[:authorization][:authorize_request].include?(request_method) + ret = OpenTox::Authorization.authorize(uri, request_method, subjectid) + LOGGER.debug "authorized? >>#{ret}<< (uri (not) authorized), method: #{request_method}, URI: #{uri}, subjectid: #{subjectid}" ret else - LOGGER.debug "authorized? >>true<< (request is free), method: #{request_method}, URI: #{uri}, subjectid: #{subjectid}" - true + LOGGER.error "invalid request/uri method: #{request_method}, URI: #{uri}, subjectid: #{subjectid}" + false end end - @@whitelist = {} - private - def self.whitelisted?(uri, request_method) - return false unless @@whitelist[request_method] - @@whitelist[request_method].each do |regexp,invert| - if invert - return true if !regexp.match(uri) - else - return true if regexp.match(uri) + def self.free_uri?(uri, request_method) + if CONFIG[:authorization][:free_uris] + CONFIG[:authorization][:free_uris].each do |request_methods,uris| + LOGGER.info "free uris "+request_methods.inspect+" -> "+uris.inspect + if request_methods and uris and request_methods.include?(request_method.to_sym) + uris.each do |u| + return true if u.match uri + end + end end - end + end return false end - public - # adds uri/regexp-for-matching-uri to the whitelist for a request-method (i.e. access will be granted without cheking the A&A service) - # @param [String or Regexp] uri_match if string match must be ecaxt - # @param [String] request_method, must be GET, POST, PUT, DELETE - # @param [Boolean,optional] invert, set to true if you want to whitelist everything that does not match (careful!) - def self.whitelist(uri_match, request_method, invert=false) - if uri_match.is_a?(Regexp) - uri_regex = uri_match - elsif uri_match.is_a?(String) - uri_regex = Regexp.new("^"+uri_match+"$") - else - raise "uri-match param is neither string(->exact uri match) nor regexp: "+uri_match.class.to_s - end - LOGGER.info("whitelisted "+request_method.to_s+" "+uri_regex.to_s) - @@whitelist[request_method] = [] unless @@whitelist[request_method] - @@whitelist[request_method] << [ uri_regex, invert ] - end + def self.authorize_exception?(uri, request_method) + if CONFIG[:authorization][:authorize_exceptions] + CONFIG[:authorization][:authorize_exceptions].each do |request_methods,uris| + if request_methods and uris and request_methods.include?(request_method.to_sym) + uris.each do |u| + return true if u.match uri + end + end + end + end + return false + end + + + end end diff --git a/lib/helper.rb b/lib/helper.rb index afeeb43..0bb489c 100644 --- a/lib/helper.rb +++ b/lib/helper.rb @@ -9,6 +9,7 @@ helpers do end elsif !env["session"] && subjectid unless authorized?(subjectid) + LOGGER.debug "URI not authorized: #{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']} with request: #{request.env['REQUEST_METHOD']}" raise OpenTox::NotAuthorizedError.new "Not authorized" end else @@ -29,27 +30,18 @@ helpers do def clean_uri(uri) out = URI.parse(uri) out.path = out.path[0, out.path.rindex(/[0-9]/) + 1] if out.path.rindex(/[0-9]/) #cuts after id for a&a - "#{out.scheme}:" + (out.port != 80 ? out.port : "") + "//#{out.host}#{out.path}" + "#{out.scheme}:" + (out.port != 80 ? out.port : "") + "//#{out.host}#{out.path.chomp('/')}" end - #unprotected uris for login/logout, webapplication ... - def unprotected_requests - case env['REQUEST_URI'] - when /\/login$|\/logout$|\/predict$|\/toxcreate\/models$/ - return true - when /\/features/ - return false - when /\/compound|\/feature|\/task|\/toxcreate/ #to fix: read from config | validation should be protected - return true - else - return false - end + #unprotected uri for login + def login_requests + return env['REQUEST_URI'] =~ /\/login$/ end end before do - unless !AA_SERVER or unprotected_requests or CONFIG[:authorization][:free_request].include?(env['REQUEST_METHOD']) + unless !AA_SERVER or login_requests or CONFIG[:authorization][:free_request].include?(env['REQUEST_METHOD']) begin subjectid = nil subjectid = session[:subjectid] if session[:subjectid] diff --git a/lib/model.rb b/lib/model.rb index 80d7ec4..0073ea4 100644 --- a/lib/model.rb +++ b/lib/model.rb @@ -46,7 +46,7 @@ module OpenTox @algorithm = OpenTox::Algorithm::Generic.find(@metadata[OT.algorithm], subjectid) unless @algorithm algorithm_title = @algorithm ? @algorithm.metadata[DC.title] : nil - @dependentVariable = OpenTox::Feature.find( @metadata[OT.dependentVariables],subjectid ) unless @dependentVariable + @dependentVariable = OpenTox::Feature.find( @metadata[OT.dependentVariables], subjectid) unless @dependentVariable [@dependentVariable.feature_type, @metadata[OT.isA], @metadata[DC.title], @uri, algorithm_title].each do |type| case type @@ -137,7 +137,7 @@ module OpenTox OT.parameters => [{DC.title => "dataset_uri", OT.paramValue => dataset_uri}] }) d = Dataset.new(dataset_uri,subjectid) - d.load_compounds + d.load_compounds(subjectid) count = 0 d.compounds.each do |compound_uri| begin diff --git a/lib/validation.rb b/lib/validation.rb index 76c4529..23b246b 100644 --- a/lib/validation.rb +++ b/lib/validation.rb @@ -13,18 +13,18 @@ module OpenTox OpenTox::Validation.new(uri) end - def create_report - @report_uri = RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/report/crossvalidation"), :validation_uris => @uri).to_s + def create_report(subjectid=nil) + @report_uri = OpenTox::RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/report/crossvalidation"), {:validation_uris => @uri, :subjectid => subjectid}).to_s @report_uri end - def create_qmrf_report - @qmrf_report_uri = RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/reach_report/qmrf"), :model_uri => @uri).to_s + def create_qmrf_report(subjectid=nil) + @qmrf_report_uri = OpenTox::RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/reach_report/qmrf"), {:model_uri => @uri, :subjectid => subjectid}).to_s @qmrf_report_uri end - def summary(type) - v = YAML.load RestClientWrappper.get(File.join(@uri, 'statistics'),:accept => "application/x-yaml").to_s + def summary(type, subjectid=nil) + v = YAML.load OpenTox::RestClientWrapper.get(File.join(@uri, 'statistics'),{:accept => "application/x-yaml", :subjectid => subjectid}).to_s case type when "classification" -- cgit v1.2.3 From 55f81bb50e76e99f370516ec8625a5aae902e898 Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Wed, 2 Feb 2011 14:06:40 +0100 Subject: hpricot removed from dependencies, a+a for validation --- lib/validation.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/validation.rb b/lib/validation.rb index 76c4529..23b246b 100644 --- a/lib/validation.rb +++ b/lib/validation.rb @@ -13,18 +13,18 @@ module OpenTox OpenTox::Validation.new(uri) end - def create_report - @report_uri = RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/report/crossvalidation"), :validation_uris => @uri).to_s + def create_report(subjectid=nil) + @report_uri = OpenTox::RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/report/crossvalidation"), {:validation_uris => @uri, :subjectid => subjectid}).to_s @report_uri end - def create_qmrf_report - @qmrf_report_uri = RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/reach_report/qmrf"), :model_uri => @uri).to_s + def create_qmrf_report(subjectid=nil) + @qmrf_report_uri = OpenTox::RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/reach_report/qmrf"), {:model_uri => @uri, :subjectid => subjectid}).to_s @qmrf_report_uri end - def summary(type) - v = YAML.load RestClientWrappper.get(File.join(@uri, 'statistics'),:accept => "application/x-yaml").to_s + def summary(type, subjectid=nil) + v = YAML.load OpenTox::RestClientWrapper.get(File.join(@uri, 'statistics'),{:accept => "application/x-yaml", :subjectid => subjectid}).to_s case type when "classification" -- cgit v1.2.3 From 70aee6e9dfece2760fc6d616e7151f41cc7625bf Mon Sep 17 00:00:00 2001 From: mguetlein Date: Wed, 2 Feb 2011 17:11:24 +0100 Subject: resclient wrapper: headers <-> payload, error report from rdf --- lib/algorithm.rb | 2 +- lib/authorization.rb | 7 ++++++- lib/dataset.rb | 2 +- lib/environment.rb | 4 ++-- lib/error.rb | 41 +++++++++++++++++++++++++++-------------- lib/model.rb | 4 ++-- lib/overwrite.rb | 7 +++---- lib/parser.rb | 33 ++++++++++++++++++++++++++++++++- lib/rest_client_wrapper.rb | 39 ++++++++++++++++++++++----------------- lib/task.rb | 9 ++++----- lib/validation.rb | 2 +- 11 files changed, 101 insertions(+), 49 deletions(-) diff --git a/lib/algorithm.rb b/lib/algorithm.rb index ee3109c..ae05e16 100644 --- a/lib/algorithm.rb +++ b/lib/algorithm.rb @@ -16,7 +16,7 @@ module OpenTox # @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly # @return [String] URI of new resource (dataset, model, ...) def run(params=nil, waiting_task=nil) - RestClientWrapper.post(@uri, {:accept => 'text/uri-list'}, params, waiting_task).to_s + RestClientWrapper.post(@uri, params, {:accept => 'text/uri-list'}, waiting_task).to_s end # Get OWL-DL representation in RDF/XML format diff --git a/lib/authorization.rb b/lib/authorization.rb index b4c1ee5..dd7dc12 100644 --- a/lib/authorization.rb +++ b/lib/authorization.rb @@ -195,7 +195,7 @@ module OpenTox # resource = RestClient::Resource.new("#{AA_SERVER}/Pol/opensso-pol") LOGGER.debug "OpenTox::Authorization.create_policy policy: #{policy[168,43]} with token:" + subjectid.to_s + " length: " + subjectid.length.to_s # return true if resource.post(policy, :subjectid => subjectid, :content_type => "application/xml") - return true if RestClientWrapper.post("#{AA_SERVER}/pol", {:subjectid => subjectid, :content_type => "application/xml"}, policy) + return true if RestClientWrapper.post("#{AA_SERVER}/pol", policy, {:subjectid => subjectid, :content_type => "application/xml"}) rescue return false end @@ -381,4 +381,9 @@ module OpenTox end end +# PENDING delete as soon as new free uri handling is merged +# this allows GET access to all URIS that do NOT end with / or // +OpenTox::Authorization.whitelist( /\/[0-9]+(\/?)$/, "GET", true ) +OpenTox::Authorization.whitelist( /\/[0-9]+(\/?)$/, "POST", true ) + diff --git a/lib/dataset.rb b/lib/dataset.rb index 9c20968..a4716dc 100644 --- a/lib/dataset.rb +++ b/lib/dataset.rb @@ -285,7 +285,7 @@ module OpenTox @compounds.uniq! if @uri if (CONFIG[:yaml_hosts].include?(URI.parse(@uri).host)) - RestClientWrapper.post(@uri,{:content_type => "application/x-yaml", :subjectid => subjectid},self.to_yaml) + RestClientWrapper.post(@uri,self.to_yaml,{:content_type => "application/x-yaml", :subjectid => subjectid}) else File.open("ot-post-file.rdf","w+") { |f| f.write(self.to_rdfxml); @path = f.path } task_uri = RestClient.post(@uri, {:file => File.new(@path)},{:accept => "text/uri-list" , :subjectid => subjectid}).to_s.chomp diff --git a/lib/environment.rb b/lib/environment.rb index 203ebc6..b30b3f3 100644 --- a/lib/environment.rb +++ b/lib/environment.rb @@ -54,8 +54,8 @@ else end # Regular expressions for parsing classification data -TRUE_REGEXP = /^(true|active|1|1.0)$/i -FALSE_REGEXP = /^(false|inactive|0|0.0)$/i +TRUE_REGEXP = /^(true|active|1|1.0|tox)$/i +FALSE_REGEXP = /^(false|inactive|0|0.0|low tox)$/i # Task durations DEFAULT_TASK_MAX_DURATION = 36000 diff --git a/lib/error.rb b/lib/error.rb index 8c666f3..49756d5 100644 --- a/lib/error.rb +++ b/lib/error.rb @@ -18,6 +18,10 @@ module OpenTox class NotFoundError < RuntimeError def http_code; 404; end end + + class ServiceUnavailableError < RuntimeError + def http_code; 503; end + end class RestCallError < RuntimeError attr_accessor :rest_params @@ -28,18 +32,31 @@ module OpenTox # TODO replace params with URIs (errorCause -> OT.errorCause) attr_reader :message, :actor, :errorCause, :http_code, :errorDetails, :errorType + + private + def initialize( http_code, erroType, message, actor, errorCause, rest_params=nil, backtrace=nil ) + @http_code = http_code + @errorType = erroType + @message = message + @actor = actor + @errorCause = errorCause + @rest_params = rest_params + @backtrace = backtrace + end + public # creates a error report object, from an ruby-exception object - # @param [Exception] error - # @param [String] actor, URI of the call that cause the error - def initialize( error, actor ) - @http_code = error.http_code - @errorType = error.class.to_s - @message = error.message - @actor = actor - @errorCause = error.errorCause if error.errorCause - @rest_params = error.rest_params if error.is_a?(OpenTox::RestCallError) and error.rest_params - @backtrace = error.backtrace.short_backtrace if CONFIG[:backtrace] + # @param [Exception] error + # @param [String] actor, URI of the call that cause the error + def self.create( error, actor ) + rest_params = error.rest_params if error.is_a?(OpenTox::RestCallError) and error.rest_params + backtrace = error.backtrace.short_backtrace if CONFIG[:backtrace] + ErrorReport.new( error.http_code, error.class.to_s, error.message, actor, error.errorCause, rest_params, backtrace ) + end + + def self.from_rdf(rdf) + metadata = OpenTox::Parser::Owl.metadata_from_rdf( rdf, OT.ErrorReport ) + ErrorReport.new(metadata[OT.statusCode], metadata[OT.errorCode], metadata[OT.message], metadata[OT.actor], metadata[OT.errorCause]) end # overwrite sorting to make easier readable @@ -61,10 +78,6 @@ module OpenTox c[OT.errorCause] = @errorCause.rdf_content if @errorCause c end - - def self.from_rdf(rdf) - raise "not yet implemented" - end def to_rdfxml s = Serializer::Owl.new diff --git a/lib/model.rb b/lib/model.rb index 80d7ec4..7cf52ad 100644 --- a/lib/model.rb +++ b/lib/model.rb @@ -17,7 +17,7 @@ module OpenTox end end LOGGER.info "running model "+@uri.to_s+", params: "+params.inspect+", accept: "+accept_header.to_s - RestClientWrapper.post(@uri,{:accept => accept_header},params,waiting_task).to_s + RestClientWrapper.post(@uri,params,{:accept => accept_header},waiting_task).to_s end # Generic OpenTox model class for all API compliant services @@ -303,7 +303,7 @@ module OpenTox # Save model at model service def save(subjectid) - self.uri = RestClientWrapper.post(@uri,{:content_type => "application/x-yaml", :subjectid => subjectid},self.to_yaml) + self.uri = RestClientWrapper.post(@uri,self.to_yaml,{:content_type => "application/x-yaml", :subjectid => subjectid}) end # Delete model at model service diff --git a/lib/overwrite.rb b/lib/overwrite.rb index e52618c..1e1cc43 100644 --- a/lib/overwrite.rb +++ b/lib/overwrite.rb @@ -16,13 +16,12 @@ before { # IMPT: set sinatra settings :show_exceptions + :raise_errors to false in config.ru, otherwise Rack::Showexceptions takes over error Exception do error = request.env['sinatra.error'] - # log error to logfile + # log error message and backtrace to logfile LOGGER.error error.class.to_s+": "+error.message - # log backtrace only if code is 500 -> unwanted (Runtime)Exceptions and internal errors (see error.rb) - LOGGER.error ":\n"+error.backtrace.join("\n") if error.http_code==500 + LOGGER.error ":\n"+error.backtrace.join("\n") actor = "#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}" - rep = OpenTox::ErrorReport.new(error, actor) + rep = OpenTox::ErrorReport.create(error, actor) case request.env['HTTP_ACCEPT'] when /rdf/ diff --git a/lib/parser.rb b/lib/parser.rb index a913cf2..e055eec 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -30,7 +30,6 @@ module OpenTox # Read metadata from opentox service # @return [Hash] Object metadata def load_metadata(subjectid=nil) - if @dataset uri = File.join(@uri,"metadata") else @@ -55,6 +54,38 @@ module OpenTox end @metadata end + + # loads metadata from rdf-data + # @param [String] rdf + # @param [String] type of the info (e.g. OT.Task, OT.ErrorReport) needed to get the subject-uri + # @return [Hash] metadata + def self.metadata_from_rdf( rdf, type ) + # write to file and read convert with rapper into tripples + file = Tempfile.new("ot-rdfxml") + file.puts rdf + file.close + file = "file://"+file.path + #puts "cmd: rapper -i rdfxml -o ntriples #{file} 2>/dev/null" + triples = `rapper -i rdfxml -o ntriples #{file} 2>/dev/null` + + # load uri via type + uri = nil + triples.each_line do |line| + triple = line.to_triple + if triple[1] == RDF['type'] and triple[2]==type + raise "uri already set, two uris found with type: "+type.to_s if uri + uri = triple[0] + end + end + + # load metadata + metadata = {} + triples.each_line do |line| + triple = line.to_triple + metadata[triple[1]] = triple[2].split('^^').first if triple[0] == uri and triple[1] != RDF['type'] + end + metadata + end # Generic parser for all OpenTox classes class Generic diff --git a/lib/rest_client_wrapper.rb b/lib/rest_client_wrapper.rb index 7c2d719..f59dce7 100644 --- a/lib/rest_client_wrapper.rb +++ b/lib/rest_client_wrapper.rb @@ -14,21 +14,21 @@ module OpenTox # @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly # @param [wait,Boolean] wait set to false to NOT wait for task if result is task # @return [OpenTox::WrapperResult] a String containing the result-body of the REST call - def self.get(uri, headers=nil, waiting_task=nil, wait=true ) - execute( "get", uri, headers, nil, waiting_task, wait) + def self.get(uri, headers={}, waiting_task=nil, wait=true ) + execute( "get", uri, nil, headers, waiting_task, wait) end # performs a POST REST call # raises OpenTox::Error if call fails (rescued in overwrite.rb -> halt 502) # per default: waits for Task to finish and returns result URI of Task # @param [String] uri destination URI - # @param [optional,Hash] headers contains params like accept-header # @param [optional,String] payload data posted to the service + # @param [optional,Hash] headers contains params like accept-header # @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly # @param [wait,Boolean] wait set to false to NOT wait for task if result is task # @return [OpenTox::WrapperResult] a String containing the result-body of the REST call - def self.post(uri, headers, payload=nil, waiting_task=nil, wait=true ) - execute( "post", uri, headers, payload, waiting_task, wait ) + def self.post(uri, payload=nil, headers={}, waiting_task=nil, wait=true ) + execute( "post", uri, payload, headers, waiting_task, wait ) end # performs a PUT REST call @@ -37,8 +37,8 @@ module OpenTox # @param [optional,Hash] headers contains params like accept-header # @param [optional,String] payload data put to the service # @return [OpenTox::WrapperResult] a String containing the result-body of the REST call - def self.put(uri, headers, payload=nil ) - execute( "put", uri, headers, payload ) + def self.put(uri, payload=nil, headers={} ) + execute( "put", uri, payload, headers ) end # performs a DELETE REST call @@ -47,36 +47,41 @@ module OpenTox # @param [optional,Hash] headers contains params like accept-header # @return [OpenTox::WrapperResult] a String containing the result-body of the REST call def self.delete(uri, headers=nil ) - execute( "delete", uri, headers, nil) + execute( "delete", uri, nil, headers) end private - def self.execute( rest_call, uri, headers, payload=nil, waiting_task=nil, wait=true ) + def self.execute( rest_call, uri, payload=nil, headers={}, waiting_task=nil, wait=true ) raise OpenTox::BadRequestError.new "uri is null" unless uri raise OpenTox::BadRequestError.new "not a uri: "+uri.to_s unless uri.to_s.uri? raise OpenTox::BadRequestError.new "headers are no hash: "+headers.inspect unless headers==nil or headers.is_a?(Hash) raise OpenTox::BadRequestError.new "nil headers for post not allowed, use {}" if rest_call=="post" and headers==nil headers.each{ |k,v| headers.delete(k) if v==nil } if headers #remove keys with empty values, as this can cause problems + raise OpenTox::BadRequestError.new "accept should go into the headers" if payload and payload.is_a?(Hash) and payload[:accept] + raise OpenTox::BadRequestError.new "content_type should go into the headers" if payload and payload.is_a?(Hash) and payload[:content_type] + + # PENDING needed for NUTA, until we finally agree on how to send subjectid + headers[:subjectid] = payload.delete(:subjectid) if uri=~/ntua/ and payload and payload.is_a?(Hash) and payload.has_key?(:subjectid) begin #LOGGER.debug "RestCall: "+rest_call.to_s+" "+uri.to_s+" "+headers.inspect+" "+payload.inspect - resource = RestClient::Resource.new(uri,{:timeout => 60}) - if payload + resource = RestClient::Resource.new(uri,{:timeout => 60}) + if rest_call=="post" || rest_call=="put" result = resource.send(rest_call, payload, headers) - elsif headers - result = resource.send(rest_call, headers) else - result = resource.send(rest_call) + result = resource.send(rest_call, headers) end + # PENDING NTUA does return errors with 200 + raise RestClient::ExceptionWithResponse.new(result) if uri=~/ntua/ and result.body =~ /about.*http:\/\/anonymous.org\/error/ + # result is a string, with the additional fields content_type and code res = WrapperResult.new(result.body) res.content_type = result.headers[:content_type] raise "content-type not set" unless res.content_type res.code = result.code - #LOGGER.debug "RestCall result: "+res.to_s+" "+res.code.to_s+" "+res.content_type.to_s # TODO: Ambit returns task representation with 200 instead of result URI return res if res.code==200 || !wait @@ -87,10 +92,10 @@ module OpenTox return res rescue RestClient::RequestTimeout => ex - received_error ex.message, 408, nil, {:rest_uri => uri, :headers => headers} + received_error ex.message, 408, nil, {:rest_uri => uri, :headers => headers, :payload => payload} rescue RestClient::ExceptionWithResponse => ex # error comming from a different webservice, - received_error ex.http_body, ex.http_code, ex.response.net_http_res.content_type, {:rest_uri => uri, :headers => headers} + received_error ex.http_body, ex.http_code, ex.response.net_http_res.content_type, {:rest_uri => uri, :headers => headers, :payload => payload} rescue OpenTox::RestCallError => ex # already a rest-error, probably comes from wait_for_task, just pass through raise ex diff --git a/lib/task.rb b/lib/task.rb index 74940de..f635b43 100644 --- a/lib/task.rb +++ b/lib/task.rb @@ -33,7 +33,7 @@ module OpenTox def self.create( title=nil, creator=nil, max_duration=DEFAULT_TASK_MAX_DURATION, description=nil ) params = {:title=>title, :creator=>creator, :max_duration=>max_duration, :description=>description } - task_uri = RestClientWrapper.post(CONFIG[:services]["opentox-task"], params, nil, false).to_s + task_uri = RestClientWrapper.post(CONFIG[:services]["opentox-task"], params, {}, false).to_s task = Task.new(task_uri.chomp) # measure current memory consumption @@ -64,9 +64,8 @@ module OpenTox task.completed(result) rescue => error LOGGER.error "task failed: "+error.class.to_s+": "+error.message - # log backtrace only if code is 500 -> unwanted (Runtime)Exceptions and internal errors (see error.rb) - LOGGER.error ":\n"+error.backtrace.join("\n") if error.http_code==500 - task.error(OpenTox::ErrorReport.new(error, creator)) + LOGGER.error ":\n"+error.backtrace.join("\n") + task.error(OpenTox::ErrorReport.create(error, creator)) end end task.pid = task_pid @@ -188,7 +187,7 @@ module OpenTox # create is private now, use OpenTox::Task.as_task #def self.create( params ) - #task_uri = RestClientWrapper.post(CONFIG[:services]["opentox-task"], params, nil, false).to_s + #task_uri = RestClientWrapper.post(CONFIG[:services]["opentox-task"], params, {}, false).to_s #Task.find(task_uri.chomp) #end diff --git a/lib/validation.rb b/lib/validation.rb index 76c4529..83be91a 100644 --- a/lib/validation.rb +++ b/lib/validation.rb @@ -9,7 +9,7 @@ module OpenTox params[:num_folds] = 10 unless params[:num_folds] params[:random_seed] = 2 unless params[:random_seed] params[:stratified] = false unless params[:stratified] - uri = OpenTox::RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/crossvalidation"),params,nil,false) + uri = OpenTox::RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/crossvalidation"),params,{},false) OpenTox::Validation.new(uri) end -- cgit v1.2.3 From 4ca97288ad2a270c34dc4f18634ee40915a45462 Mon Sep 17 00:00:00 2001 From: mguetlein Date: Thu, 3 Feb 2011 09:45:18 +0100 Subject: return task method for sinatra --- lib/overwrite.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/overwrite.rb b/lib/overwrite.rb index 1e1cc43..7b53122 100644 --- a/lib/overwrite.rb +++ b/lib/overwrite.rb @@ -36,6 +36,27 @@ error Exception do end end +class Sinatra::Base + + def return_task( task ) + code = task.running? ? 202 : 200 + case request.env['HTTP_ACCEPT'] + when /rdf/ + response['Content-Type'] = "application/rdf+xml" + halt code,task.to_rdfxml + when /yaml/ + response['Content-Type'] = "application/rdf+xml" + halt code,task.to_yaml # PENDING differs from task-webservice + when /html/ + response['Content-Type'] = "text/html" + halt code,OpenTox.text_to_html(task.to_yaml) + else # default /uri-list/ + response['Content-Type'] = "text/uri-list" + halt code,task.uri+"\n" + end + end +end + class String def task_uri? self.uri? && !self.match(/task/).nil? -- cgit v1.2.3 From 34ef45bf1f87c787e3ddaccc03a36a5fa2d54c7f Mon Sep 17 00:00:00 2001 From: mguetlein Date: Thu, 3 Feb 2011 18:10:48 +0100 Subject: update /refactor validation.rb --- lib/algorithm.rb | 2 +- lib/rest_client_wrapper.rb | 7 +- lib/task.rb | 3 +- lib/validation.rb | 180 ++++++++++++++++++++++++++++++++------------- 4 files changed, 134 insertions(+), 58 deletions(-) diff --git a/lib/algorithm.rb b/lib/algorithm.rb index ae05e16..bfa9860 100644 --- a/lib/algorithm.rb +++ b/lib/algorithm.rb @@ -34,7 +34,7 @@ module OpenTox # Find Generic Opentox Algorithm via URI, and loads metadata # @param [String] uri Algorithm URI # @return [OpenTox::Algorithm::Generic] Algorithm instance, nil if alogrithm was not found - def self.find(uri, subjectid) + def self.find(uri, subjectid=nil) return nil unless uri alg = Generic.new(uri) alg.load_metadata( subjectid ) diff --git a/lib/rest_client_wrapper.rb b/lib/rest_client_wrapper.rb index f59dce7..658f111 100644 --- a/lib/rest_client_wrapper.rb +++ b/lib/rest_client_wrapper.rb @@ -55,11 +55,12 @@ module OpenTox raise OpenTox::BadRequestError.new "uri is null" unless uri raise OpenTox::BadRequestError.new "not a uri: "+uri.to_s unless uri.to_s.uri? - raise OpenTox::BadRequestError.new "headers are no hash: "+headers.inspect unless headers==nil or headers.is_a?(Hash) - raise OpenTox::BadRequestError.new "nil headers for post not allowed, use {}" if rest_call=="post" and headers==nil - headers.each{ |k,v| headers.delete(k) if v==nil } if headers #remove keys with empty values, as this can cause problems + raise "headers are no hash: "+headers.inspect unless headers==nil or headers.is_a?(Hash) raise OpenTox::BadRequestError.new "accept should go into the headers" if payload and payload.is_a?(Hash) and payload[:accept] raise OpenTox::BadRequestError.new "content_type should go into the headers" if payload and payload.is_a?(Hash) and payload[:content_type] + raise "__waiting_task__ must be 'nil' or '(sub)task', is "+waiting_task.class.to_s if + waiting_task!=nil and !(waiting_task.is_a?(Task) || waiting_task.is_a?(SubTask)) + headers.each{ |k,v| headers.delete(k) if v==nil } if headers #remove keys with empty values, as this can cause problems # PENDING needed for NUTA, until we finally agree on how to send subjectid headers[:subjectid] = payload.delete(:subjectid) if uri=~/ntua/ and payload and payload.is_a?(Hash) and payload.has_key?(:subjectid) diff --git a/lib/task.rb b/lib/task.rb index f635b43..9c52299 100644 --- a/lib/task.rb +++ b/lib/task.rb @@ -33,7 +33,7 @@ module OpenTox def self.create( title=nil, creator=nil, max_duration=DEFAULT_TASK_MAX_DURATION, description=nil ) params = {:title=>title, :creator=>creator, :max_duration=>max_duration, :description=>description } - task_uri = RestClientWrapper.post(CONFIG[:services]["opentox-task"], params, {}, false).to_s + task_uri = RestClientWrapper.post(CONFIG[:services]["opentox-task"], params, {}, nil, false).to_s task = Task.new(task_uri.chomp) # measure current memory consumption @@ -284,7 +284,6 @@ module OpenTox raise OpenTox::BadRequestError.new ex.message+" (task-uri:"+@uri+")" end end - end # Convenience class to split a (sub)task into subtasks diff --git a/lib/validation.rb b/lib/validation.rb index 83be91a..b1ccb7b 100644 --- a/lib/validation.rb +++ b/lib/validation.rb @@ -1,70 +1,146 @@ module OpenTox - class Validation + class Crossvalidation include OpenTox - attr_accessor :report_uri, :qmrf_report_uri + attr_reader :report + + # find crossvalidation, raises error if not found + # @param [String] uri + # @param [String,optional] subjectid + # @return [OpenTox::Crossvalidation] + def self.find( uri, subjectid=nil ) + # PENDING load crossvalidation data? + OpenTox::RestClientWrapper.get(uri,{:subjectid => subjectid}) + Crossvalidation.new(uri) + end - def self.create_crossvalidation(params) - params[:uri] = File.join(CONFIG[:services]['opentox-validation'], "crossvalidation") + # creates a crossvalidations, waits until it finishes, may take some time + # @param [Hash] params + # @param [String,optional] subjectid + # @param [OpenTox::Task,optional] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly + # @return [OpenTox::Crossvalidation] + def self.create( params, subjectid=nil, waiting_task=nil ) + params[:uri] = File.join(CONFIG[:services]['opentox-validation'], "crossvalidation") params[:num_folds] = 10 unless params[:num_folds] - params[:random_seed] = 2 unless params[:random_seed] - params[:stratified] = false unless params[:stratified] - uri = OpenTox::RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/crossvalidation"),params,{},false) - OpenTox::Validation.new(uri) - end - - def create_report - @report_uri = RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/report/crossvalidation"), :validation_uris => @uri).to_s - @report_uri + params[:random_seed] = 2 unless params[:random_seed] + params[:stratified] = false unless params[:stratified] + params[:subjectid] = subjectid if subjectid + uri = OpenTox::RestClientWrapper.post( File.join(CONFIG[:services]["opentox-validation"],"/crossvalidation"), + params,{:content_type => "text/uri-list"},waiting_task ) + Crossvalidation.new(uri) end - def create_qmrf_report - @qmrf_report_uri = RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/reach_report/qmrf"), :model_uri => @uri).to_s - @qmrf_report_uri + # looks for report for this crossvalidation, creates a report if no report is found + # @param [String,optional] subjectid + # @param [OpenTox::Task,optional] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly + # @return [OpenTox::CrossvalidationReport] + def find_or_create_report( subjectid=nil, waiting_task=nil ) + @report = CrossvalidationReport.find_for_crossvalidation(self, subjectid) unless @report + @report = CrossvalidationReport.create(self, subjectid, waiting_task) unless @report + @report end - def summary(type) - v = YAML.load RestClientWrappper.get(File.join(@uri, 'statistics'),:accept => "application/x-yaml").to_s - - case type - when "classification" - tp=0; tn=0; fp=0; fn=0; n=0 - v[:classification_statistics][:confusion_matrix][:confusion_matrix_cell].each do |cell| - if cell[:confusion_matrix_predicted] == "true" and cell[:confusion_matrix_actual] == "true" - tp = cell[:confusion_matrix_value] - n += tp - elsif cell[:confusion_matrix_predicted] == "false" and cell[:confusion_matrix_actual] == "false" - tn = cell[:confusion_matrix_value] - n += tn - elsif cell[:confusion_matrix_predicted] == "false" and cell[:confusion_matrix_actual] == "true" - fn = cell[:confusion_matrix_value] - n += fn - elsif cell[:confusion_matrix_predicted] == "true" and cell[:confusion_matrix_actual] == "false" - fp = cell[:confusion_matrix_value] - n += fp + # PENDING: creates summary as used for ToxCreate + def summary + v = YAML.load RestClientWrapper.get(File.join(@uri, 'statistics'),:accept => "application/x-yaml").to_s + if v[OT.classificationStatistics] + res = { + :nr_predictions => v[OT.numInstances] - v[OT.numUnpredicted], + :correct_predictions => v[OT.classificationStatistics][OT.percentCorrect], + :weighted_area_under_roc => v[OT.classificationStatistics][OT.weightedAreaUnderRoc], + } + v[OT.classificationStatistics][OT.classValueStatistics].each do |s| + if s[OT.classValue].to_s=="true" + res[:true_positives] = s[OT.numTruePositives] + res[:false_positives] = s[OT.numFalsePositives] + res[:true_negatives] = s[OT.numTrueNegatives] + res[:false_negatives] = s[OT.numFalseNegatives] + res[:sensitivity] = s[OT.truePositiveRate] + res[:specificity] = s[OT.falsePositiveRate] + break end end + res + elsif v[OT.regressionStatistics] { - :nr_predictions => n, - :true_positives => tp, - :false_positives => fp, - :true_negatives => tn, - :false_negatives => fn, - :correct_predictions => 100*(tp+tn).to_f/n, - :weighted_area_under_roc => v[:classification_statistics][:weighted_area_under_roc].to_f, - :sensitivity => tp.to_f/(tp+fn), - :specificity => tn.to_f/(tn+fp), - } - when "regression" - { - :nr_predictions => v[:num_instances] - v[:num_unpredicted], - :r_square => v[:regression_statistics][:r_square], - :root_mean_squared_error => v[:regression_statistics][:root_mean_squared_error], - :mean_absolute_error => v[:regression_statistics][:mean_absolute_error], + :nr_predictions => v[OT.numInstances] - v[OT.numUnpredicted], + :r_square => v[OT.regressionStatistics][OT.rSquare], + :root_mean_squared_error => v[OT.regressionStatistics][OT.rootMeanSquaredError], + :mean_absolute_error => v[OT.regressionStatistics][OT.meanAbsoluteError], } end end + end - end + class CrossvalidationReport + include OpenTox + + # finds CrossvalidationReport via uri, raises error if not found + # @param [String] uri + # @param [String,optional] subjectid + # @return [OpenTox::CrossvalidationReport] + def self.find( uri, subjectid=nil ) + # PENDING load report data? + OpenTox::RestClientWrapper.get(uri,{:subjectid => subjectid}) + CrossvalidationReport.new(uri) + end + + # finds CrossvalidationReport for a particular crossvalidation + # @param [OpenTox::Crossvalidation] + # @param [String,optional] subjectid + # @return [OpenTox::CrossvalidationReport] nil if no report found + def self.find_for_crossvalidation( crossvalidation, subjectid=nil ) + uris = RestClientWrapper.get(File.join(CONFIG[:services]["opentox-validation"], + "/report/crossvalidation?crossvalidation="+crossvalidation.uri), {:subjectid => subjectid}).chomp.split("\n") + uris.size==0 ? nil : CrossvalidationReport.new(uris[-1]) + end + + # creates a crossvalidation report via crossvalidation + # @param [OpenTox::Crossvalidation] + # @param [String,optional] subjectid + # @param [OpenTox::Task,optional] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly + # @return [OpenTox::CrossvalidationReport] + def self.create( crossvalidation, subjectid=nil, waiting_task=nil ) + uri = RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/report/crossvalidation"), + { :validation_uris => crossvalidation.uri, :subjectid => subjectid }, {}, waiting_task ) + CrossvalidationReport.new(uri) + end + end + + class QMRFReport + include OpenTox + + # finds QMRFReport, raises Error if not found + # @param [String] uri + # @param [String,optional] subjectid + # @return [OpenTox::QMRFReport] + def self.find( uri, subjectid=nil ) + # PENDING load crossvalidation data? + OpenTox::RestClientWrapper.get(uri,{:subjectid => subjectid}) + QMRFReport.new(uri) + end + + # finds QMRF report for a particular model + # @param [OpenTox::Crossvalidation] + # @param [String,optional] subjectid + # @return [OpenTox::QMRFReport] nil if no report found + def self.find_for_model( model, subjectid=nil ) + uris = RestClientWrapper.get(File.join(CONFIG[:services]["opentox-validation"], + "/reach_report/qmrf?model="+model.uri), {:subjectid => subjectid}).chomp.split("\n") + uris.size==0 ? nil : QMRFReport.new(uris[-1]) + end + + # creates a qmrf report via model + # @param [OpenTox::Model] + # @param [String,optional] subjectid + # @param [OpenTox::Task,optional] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly + # @return [OpenTox::QMRFReport] + def self.create( model, subjectid=nil, waiting_task=nil ) + uri = RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/reach_report/qmrf"), + { :model_uri => model.uri, :subjectid => subjectid }, {}, waiting_task ) + QMRFReport.new(uri) + end + end + end -- cgit v1.2.3 From e035b7136b8c2df70de980379695fbfeaf070290 Mon Sep 17 00:00:00 2001 From: mguetlein Date: Fri, 4 Feb 2011 09:32:22 +0100 Subject: add missing subjectid param --- lib/validation.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/validation.rb b/lib/validation.rb index b1ccb7b..d70bba2 100644 --- a/lib/validation.rb +++ b/lib/validation.rb @@ -41,8 +41,8 @@ module OpenTox end # PENDING: creates summary as used for ToxCreate - def summary - v = YAML.load RestClientWrapper.get(File.join(@uri, 'statistics'),:accept => "application/x-yaml").to_s + def summary( subjectid=nil ) + v = YAML.load RestClientWrapper.get(File.join(@uri, 'statistics'),{:accept => "application/x-yaml", :subjectid => subjectid}).to_s if v[OT.classificationStatistics] res = { :nr_predictions => v[OT.numInstances] - v[OT.numUnpredicted], -- cgit v1.2.3 From 5ea8356992751af875812f0e244f08760eb30aee Mon Sep 17 00:00:00 2001 From: mguetlein Date: Fri, 4 Feb 2011 16:13:25 +0100 Subject: added delete to opentox objects, cosmetics --- lib/opentox.rb | 5 +++++ lib/validation.rb | 12 ++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/opentox.rb b/lib/opentox.rb index f1af5c3..1992896 100644 --- a/lib/opentox.rb +++ b/lib/opentox.rb @@ -43,5 +43,10 @@ module OpenTox s.to_rdfxml end + # deletes the resource, deletion should have worked when no RestCallError raised + def delete(subjectid=nil) + RestClientWrapper.delete(uri,:subjectid => subjectid) + end + end diff --git a/lib/validation.rb b/lib/validation.rb index d70bba2..c256d1d 100644 --- a/lib/validation.rb +++ b/lib/validation.rb @@ -1,8 +1,8 @@ module OpenTox - class Crossvalidation + class Crossvalidation include OpenTox - attr_reader :report + attr_reader :report # find crossvalidation, raises error if not found # @param [String] uri @@ -15,17 +15,13 @@ module OpenTox end # creates a crossvalidations, waits until it finishes, may take some time - # @param [Hash] params + # @param [Hash] params (required:algorithm_uri,dataset_uri,prediction_feature, optional:algorithm_params,num_folds(10),random_seed(1),stratified(false)) # @param [String,optional] subjectid # @param [OpenTox::Task,optional] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly # @return [OpenTox::Crossvalidation] def self.create( params, subjectid=nil, waiting_task=nil ) - params[:uri] = File.join(CONFIG[:services]['opentox-validation'], "crossvalidation") - params[:num_folds] = 10 unless params[:num_folds] - params[:random_seed] = 2 unless params[:random_seed] - params[:stratified] = false unless params[:stratified] params[:subjectid] = subjectid if subjectid - uri = OpenTox::RestClientWrapper.post( File.join(CONFIG[:services]["opentox-validation"],"/crossvalidation"), + uri = OpenTox::RestClientWrapper.post( File.join(CONFIG[:services]["opentox-validation"],"crossvalidation"), params,{:content_type => "text/uri-list"},waiting_task ) Crossvalidation.new(uri) end -- cgit v1.2.3 From 9848197e9e0830c569ae2addcd404c59c0a53180 Mon Sep 17 00:00:00 2001 From: mguetlein Date: Mon, 7 Feb 2011 10:07:34 +0100 Subject: add Dataset.exist as find loads all data --- lib/dataset.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/dataset.rb b/lib/dataset.rb index a4716dc..02b89cb 100644 --- a/lib/dataset.rb +++ b/lib/dataset.rb @@ -56,6 +56,19 @@ module OpenTox dataset.load_all(subjectid) dataset end + + # replaces find as exist check, takes not as long, does NOT raise an un-authorized exception + # @param [String] uri Dataset URI + # @return [Boolean] true if dataset exists and user has get rights, false else + def self.exist?(uri, subjectid=nil) + return false unless uri + dataset = Dataset.new(uri, subjectid) + begin + dataset.load_metadata( subjectid ).size > 0 + rescue + false + end + end # Get all datasets from a service # @param [optional,String] uri URI of the dataset service, defaults to service specified in configuration -- cgit v1.2.3 From 281a0bade2ca1d1bb040c54704650b69f6da24a5 Mon Sep 17 00:00:00 2001 From: mguetlein Date: Mon, 7 Feb 2011 15:35:53 +0100 Subject: .find method raises error if opentox-object not found, extending validation.rb --- lib/algorithm.rb | 11 ++--- lib/model.rb | 11 ++--- lib/task.rb | 1 + lib/validation.rb | 144 +++++++++++++++++++++++++++++++++++++----------------- 4 files changed, 108 insertions(+), 59 deletions(-) diff --git a/lib/algorithm.rb b/lib/algorithm.rb index bfa9860..af8dfaf 100644 --- a/lib/algorithm.rb +++ b/lib/algorithm.rb @@ -31,18 +31,15 @@ module OpenTox class Generic include Algorithm - # Find Generic Opentox Algorithm via URI, and loads metadata + # Find Generic Opentox Algorithm via URI, and loads metadata, could raise NotFound/NotAuthorized error # @param [String] uri Algorithm URI - # @return [OpenTox::Algorithm::Generic] Algorithm instance, nil if alogrithm was not found + # @return [OpenTox::Algorithm::Generic] Algorithm instance def self.find(uri, subjectid=nil) return nil unless uri alg = Generic.new(uri) alg.load_metadata( subjectid ) - if alg.metadata==nil or alg.metadata.size==0 - nil - else - alg - end + raise "cannot load algorithm metadata" if alg.metadata==nil or alg.metadata.size==0 + alg end end diff --git a/lib/model.rb b/lib/model.rb index 7cf52ad..ae793e8 100644 --- a/lib/model.rb +++ b/lib/model.rb @@ -24,18 +24,15 @@ module OpenTox class Generic include Model - # Find Generic Opentox Model via URI, and loads metadata + # Find Generic Opentox Model via URI, and loads metadata, could raise NotFound/NotAuthorized error # @param [String] uri Model URI - # @return [OpenTox::Model::Generic] Model instance, nil if model was not found + # @return [OpenTox::Model::Generic] Model instance def self.find(uri,subjectid=nil) return nil unless uri model = Generic.new(uri) model.load_metadata(subjectid) - if model.metadata==nil or model.metadata.size==0 - nil - else - model - end + raise "could not load model metadata" if model.metadata==nil or model.metadata.size==0 + model end # provides feature type, possible types are "regression" or "classification" diff --git a/lib/task.rb b/lib/task.rb index 9c52299..73d880e 100644 --- a/lib/task.rb +++ b/lib/task.rb @@ -80,6 +80,7 @@ module OpenTox return nil unless uri task = Task.new(uri) task.load_metadata + raise "could not load task metadata" if task.metadata==nil or task.metadata.size==0 task end diff --git a/lib/validation.rb b/lib/validation.rb index c256d1d..f0c5297 100644 --- a/lib/validation.rb +++ b/lib/validation.rb @@ -1,4 +1,63 @@ module OpenTox + class Validation + include OpenTox + + # find validation, raises error if not found + # @param [String] uri + # @param [String,optional] subjectid + # @return [OpenTox::Validation] + def self.find( uri, subjectid=nil ) + val = Validation.new(uri) + val.load_metadata( subjectid ) + val + end + + # creates a validation object from crossvaldiation statistics, raise error if not found + # (as crossvaldiation statistics are returned as an average valdidation over all folds) + # @param [String] crossvalidation uri + # @param [String,optional] subjectid + # @return [OpenTox::Validation] + def self.from_cv_statistics( crossvalidation_uri, subjectid=nil ) + find( File.join(crossvalidation_uri, 'statistics'),subjectid ) + end + + # loads metadata via yaml from validation object + # fields (like for example the validated model) can be acces via validation.metadata[OT.model] + def load_metadata( subjectid=nil ) + @metadata = YAML.load(OpenTox::RestClientWrapper.get(uri,{:subjectid => subjectid, :accept => "application/x-yaml"})) + end + + # PENDING: creates summary as used for ToxCreate + def summary + if @metadata[OT.classificationStatistics] + res = { + :nr_predictions => @metadata[OT.numInstances] - @metadata[OT.numUnpredicted], + :correct_predictions => @metadata[OT.classificationStatistics][OT.percentCorrect], + :weighted_area_under_roc => @metadata[OT.classificationStatistics][OT.weightedAreaUnderRoc], + } + @metadata[OT.classificationStatistics][OT.classValueStatistics].each do |s| + if s[OT.classValue].to_s=="true" + res[:true_positives] = s[OT.numTruePositives] + res[:false_positives] = s[OT.numFalsePositives] + res[:true_negatives] = s[OT.numTrueNegatives] + res[:false_negatives] = s[OT.numFalseNegatives] + res[:sensitivity] = s[OT.truePositiveRate] + res[:specificity] = s[OT.falsePositiveRate] + break + end + end + res + elsif @metadata[OT.regressionStatistics] + { + :nr_predictions => @metadata[OT.numInstances] - @metadata[OT.numUnpredicted], + :r_square => @metadata[OT.regressionStatistics][OT.rSquare], + :root_mean_squared_error => @metadata[OT.regressionStatistics][OT.rootMeanSquaredError], + :mean_absolute_error => @metadata[OT.regressionStatistics][OT.meanAbsoluteError], + } + end + end + end + class Crossvalidation include OpenTox @@ -9,9 +68,9 @@ module OpenTox # @param [String,optional] subjectid # @return [OpenTox::Crossvalidation] def self.find( uri, subjectid=nil ) - # PENDING load crossvalidation data? - OpenTox::RestClientWrapper.get(uri,{:subjectid => subjectid}) - Crossvalidation.new(uri) + cv = Crossvalidation.new(uri) + cv.load_metadata( subjectid ) + cv end # creates a crossvalidations, waits until it finishes, may take some time @@ -31,42 +90,37 @@ module OpenTox # @param [OpenTox::Task,optional] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly # @return [OpenTox::CrossvalidationReport] def find_or_create_report( subjectid=nil, waiting_task=nil ) - @report = CrossvalidationReport.find_for_crossvalidation(self, subjectid) unless @report - @report = CrossvalidationReport.create(self, subjectid, waiting_task) unless @report + @report = CrossvalidationReport.find_for_crossvalidation(@uri, subjectid) unless @report + @report = CrossvalidationReport.create(@uri, subjectid, waiting_task) unless @report @report end - + + # loads metadata via yaml from crossvalidation object + # fields (like for example the validations) can be acces via validation.metadata[OT.validation] + def load_metadata( subjectid=nil ) + @metadata = YAML.load(OpenTox::RestClientWrapper.get(uri,{:subjectid => subjectid, :accept => "application/x-yaml"})) + end + # PENDING: creates summary as used for ToxCreate def summary( subjectid=nil ) - v = YAML.load RestClientWrapper.get(File.join(@uri, 'statistics'),{:accept => "application/x-yaml", :subjectid => subjectid}).to_s - if v[OT.classificationStatistics] - res = { - :nr_predictions => v[OT.numInstances] - v[OT.numUnpredicted], - :correct_predictions => v[OT.classificationStatistics][OT.percentCorrect], - :weighted_area_under_roc => v[OT.classificationStatistics][OT.weightedAreaUnderRoc], - } - v[OT.classificationStatistics][OT.classValueStatistics].each do |s| - if s[OT.classValue].to_s=="true" - res[:true_positives] = s[OT.numTruePositives] - res[:false_positives] = s[OT.numFalsePositives] - res[:true_negatives] = s[OT.numTrueNegatives] - res[:false_negatives] = s[OT.numFalseNegatives] - res[:sensitivity] = s[OT.truePositiveRate] - res[:specificity] = s[OT.falsePositiveRate] - break - end - end - res - elsif v[OT.regressionStatistics] - { - :nr_predictions => v[OT.numInstances] - v[OT.numUnpredicted], - :r_square => v[OT.regressionStatistics][OT.rSquare], - :root_mean_squared_error => v[OT.regressionStatistics][OT.rootMeanSquaredError], - :mean_absolute_error => v[OT.regressionStatistics][OT.meanAbsoluteError], - } - end + Validation.from_cv_statistics( @uri, subjectid ).summary end end + + class ValidationReport + include OpenTox + + # finds ValidationReport for a particular validation + # @param [String] crossvalidation uri + # @param [String,optional] subjectid + # @return [OpenTox::ValidationReport] nil if no report found + def self.find_for_validation( validation_uri, subjectid=nil ) + uris = RestClientWrapper.get(File.join(CONFIG[:services]["opentox-validation"], + "/report/validation?validation="+validation_uri), {:subjectid => subjectid}).chomp.split("\n") + uris.size==0 ? nil : ValidationReport.new(uris[-1]) + end + + end class CrossvalidationReport include OpenTox @@ -82,23 +136,23 @@ module OpenTox end # finds CrossvalidationReport for a particular crossvalidation - # @param [OpenTox::Crossvalidation] + # @param [String] crossvalidation uri # @param [String,optional] subjectid # @return [OpenTox::CrossvalidationReport] nil if no report found - def self.find_for_crossvalidation( crossvalidation, subjectid=nil ) + def self.find_for_crossvalidation( crossvalidation_uri, subjectid=nil ) uris = RestClientWrapper.get(File.join(CONFIG[:services]["opentox-validation"], - "/report/crossvalidation?crossvalidation="+crossvalidation.uri), {:subjectid => subjectid}).chomp.split("\n") + "/report/crossvalidation?crossvalidation="+crossvalidation_uri), {:subjectid => subjectid}).chomp.split("\n") uris.size==0 ? nil : CrossvalidationReport.new(uris[-1]) end # creates a crossvalidation report via crossvalidation - # @param [OpenTox::Crossvalidation] + # @param [String] crossvalidation uri # @param [String,optional] subjectid # @param [OpenTox::Task,optional] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly # @return [OpenTox::CrossvalidationReport] - def self.create( crossvalidation, subjectid=nil, waiting_task=nil ) + def self.create( crossvalidation_uri, subjectid=nil, waiting_task=nil ) uri = RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/report/crossvalidation"), - { :validation_uris => crossvalidation.uri, :subjectid => subjectid }, {}, waiting_task ) + { :validation_uris => crossvalidation_uri, :subjectid => subjectid }, {}, waiting_task ) CrossvalidationReport.new(uri) end end @@ -117,23 +171,23 @@ module OpenTox end # finds QMRF report for a particular model - # @param [OpenTox::Crossvalidation] + # @param [String] model_uri # @param [String,optional] subjectid # @return [OpenTox::QMRFReport] nil if no report found - def self.find_for_model( model, subjectid=nil ) + def self.find_for_model( model_uri, subjectid=nil ) uris = RestClientWrapper.get(File.join(CONFIG[:services]["opentox-validation"], - "/reach_report/qmrf?model="+model.uri), {:subjectid => subjectid}).chomp.split("\n") + "/reach_report/qmrf?model="+model_uri), {:subjectid => subjectid}).chomp.split("\n") uris.size==0 ? nil : QMRFReport.new(uris[-1]) end # creates a qmrf report via model - # @param [OpenTox::Model] + # @param [String] model_uri # @param [String,optional] subjectid # @param [OpenTox::Task,optional] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly # @return [OpenTox::QMRFReport] - def self.create( model, subjectid=nil, waiting_task=nil ) + def self.create( model_uri, subjectid=nil, waiting_task=nil ) uri = RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/reach_report/qmrf"), - { :model_uri => model.uri, :subjectid => subjectid }, {}, waiting_task ) + { :model_uri => model_uri, :subjectid => subjectid }, {}, waiting_task ) QMRFReport.new(uri) end end -- cgit v1.2.3 From c37cc91893457cb91ddb2a32b9ac76090bd6c521 Mon Sep 17 00:00:00 2001 From: mguetlein Date: Tue, 8 Feb 2011 08:57:31 +0100 Subject: not using rapper directly, use rest client and tmp-file --- lib/model.rb | 4 ++-- lib/parser.rb | 12 ++++++++++-- lib/task.rb | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/model.rb b/lib/model.rb index ae793e8..64d178f 100644 --- a/lib/model.rb +++ b/lib/model.rb @@ -31,7 +31,7 @@ module OpenTox return nil unless uri model = Generic.new(uri) model.load_metadata(subjectid) - raise "could not load model metadata" if model.metadata==nil or model.metadata.size==0 + raise "could not load model metadata '"+uri.to_s+"'" if model.metadata==nil or model.metadata.size==0 model end @@ -134,7 +134,7 @@ module OpenTox OT.parameters => [{DC.title => "dataset_uri", OT.paramValue => dataset_uri}] }) d = Dataset.new(dataset_uri,subjectid) - d.load_compounds + d.load_compounds(subjectid) count = 0 d.compounds.each do |compound_uri| begin diff --git a/lib/parser.rb b/lib/parser.rb index e055eec..27dfeee 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -35,10 +35,18 @@ module OpenTox else uri = @uri end - uri += "?subjectid=#{CGI.escape(subjectid)}" if subjectid + # avoid using rapper directly because of 2 reasons: + # * http errors wont be noticed + # * subjectid cannot be sent as header + ##uri += "?subjectid=#{CGI.escape(subjectid)}" if subjectid + ## `rapper -i rdfxml -o ntriples #{uri} 2>/dev/null`.each_line do |line| + file = Tempfile.new("ot-rdfxml") + file.puts OpenTox::RestClientWrapper.get @uri,{:subjectid => subjectid,:accept => "application/rdf+xml"} + file.close + file = "file://"+file.path statements = [] parameter_ids = [] - `rapper -i rdfxml -o ntriples #{uri} 2>/dev/null`.each_line do |line| + `rapper -i rdfxml -o ntriples #{file} 2>/dev/null`.each_line do |line| triple = line.to_triple @metadata[triple[1]] = triple[2].split('^^').first if triple[0] == @uri and triple[1] != RDF['type'] statements << triple diff --git a/lib/task.rb b/lib/task.rb index 73d880e..ca18d7b 100644 --- a/lib/task.rb +++ b/lib/task.rb @@ -303,7 +303,7 @@ module OpenTox class SubTask def initialize(task, min, max) - raise "not a task or subtask" unless task.is_a?(Task) or task.is_a?(SubTask) + raise "not a task or subtask" if task!=nil and !(task.is_a?(Task) or task.is_a?(SubTask)) raise "invalid max ("+max.to_s+"), min ("+min.to_s+") params" unless min.is_a?(Numeric) and max.is_a?(Numeric) and min >= 0 and max <= 100 and max > min @task = task -- cgit v1.2.3 From e4a32e37ab8708aa8ae4dbfc6069e5ea75928f3f Mon Sep 17 00:00:00 2001 From: mr Date: Tue, 8 Feb 2011 12:58:30 +0100 Subject: manually insert code from mguetlein repository | restclientwrapper.post event with changed method call --- lib/algorithm.rb | 15 ++- lib/authorization.rb | 11 +-- lib/dataset.rb | 15 ++- lib/environment.rb | 4 +- lib/error.rb | 37 +++++-- lib/helper.rb | 5 +- lib/model.rb | 17 ++-- lib/opentox.rb | 5 + lib/overwrite.rb | 28 +++++- lib/parser.rb | 44 ++++++++- lib/rest_client_wrapper.rb | 44 +++++---- lib/task.rb | 22 +++-- lib/validation.rb | 240 ++++++++++++++++++++++++++++++++++----------- 13 files changed, 357 insertions(+), 130 deletions(-) diff --git a/lib/algorithm.rb b/lib/algorithm.rb index ee3109c..af8dfaf 100644 --- a/lib/algorithm.rb +++ b/lib/algorithm.rb @@ -16,7 +16,7 @@ module OpenTox # @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly # @return [String] URI of new resource (dataset, model, ...) def run(params=nil, waiting_task=nil) - RestClientWrapper.post(@uri, {:accept => 'text/uri-list'}, params, waiting_task).to_s + RestClientWrapper.post(@uri, params, {:accept => 'text/uri-list'}, waiting_task).to_s end # Get OWL-DL representation in RDF/XML format @@ -31,18 +31,15 @@ module OpenTox class Generic include Algorithm - # Find Generic Opentox Algorithm via URI, and loads metadata + # Find Generic Opentox Algorithm via URI, and loads metadata, could raise NotFound/NotAuthorized error # @param [String] uri Algorithm URI - # @return [OpenTox::Algorithm::Generic] Algorithm instance, nil if alogrithm was not found - def self.find(uri, subjectid) + # @return [OpenTox::Algorithm::Generic] Algorithm instance + def self.find(uri, subjectid=nil) return nil unless uri alg = Generic.new(uri) alg.load_metadata( subjectid ) - if alg.metadata==nil or alg.metadata.size==0 - nil - else - alg - end + raise "cannot load algorithm metadata" if alg.metadata==nil or alg.metadata.size==0 + alg end end diff --git a/lib/authorization.rb b/lib/authorization.rb index 12be037..16f1ee4 100644 --- a/lib/authorization.rb +++ b/lib/authorization.rb @@ -192,10 +192,10 @@ module OpenTox # return [Boolean] returns true if policy is created def self.create_policy(policy, subjectid) begin -# resource = RestClient::Resource.new("#{AA_SERVER}/Pol/opensso-pol") + resource = RestClient::Resource.new("#{AA_SERVER}/Pol/opensso-pol") LOGGER.debug "OpenTox::Authorization.create_policy policy: #{policy[168,43]} with token:" + subjectid.to_s + " length: " + subjectid.length.to_s -# return true if resource.post(policy, :subjectid => subjectid, :content_type => "application/xml") - return true if RestClientWrapper.post("#{AA_SERVER}/pol", {:subjectid => subjectid, :content_type => "application/xml"}, policy) + return true if resource.post(policy, :subjectid => subjectid, :content_type => "application/xml") + #return true if RestClientWrapper.post("#{AA_SERVER}/pol", {:subjectid => subjectid, :content_type => "application/xml"}, policy) rescue return false end @@ -306,7 +306,6 @@ module OpenTox # if no policy exists, create a policy, return result of send policy send_policy(uri, subjectid) else - LOGGER.debug "OpenTox::Authorization.check_policy URI: #{uri} has already a Policy." # if policy exists check for POST rights if authorize(uri, "POST", subjectid) true @@ -356,7 +355,6 @@ module OpenTox def self.free_uri?(uri, request_method) if CONFIG[:authorization][:free_uris] CONFIG[:authorization][:free_uris].each do |request_methods,uris| - LOGGER.info "free uris "+request_methods.inspect+" -> "+uris.inspect if request_methods and uris and request_methods.include?(request_method.to_sym) uris.each do |u| return true if u.match uri @@ -380,9 +378,6 @@ module OpenTox return false end - - - end end diff --git a/lib/dataset.rb b/lib/dataset.rb index 9c20968..02b89cb 100644 --- a/lib/dataset.rb +++ b/lib/dataset.rb @@ -56,6 +56,19 @@ module OpenTox dataset.load_all(subjectid) dataset end + + # replaces find as exist check, takes not as long, does NOT raise an un-authorized exception + # @param [String] uri Dataset URI + # @return [Boolean] true if dataset exists and user has get rights, false else + def self.exist?(uri, subjectid=nil) + return false unless uri + dataset = Dataset.new(uri, subjectid) + begin + dataset.load_metadata( subjectid ).size > 0 + rescue + false + end + end # Get all datasets from a service # @param [optional,String] uri URI of the dataset service, defaults to service specified in configuration @@ -285,7 +298,7 @@ module OpenTox @compounds.uniq! if @uri if (CONFIG[:yaml_hosts].include?(URI.parse(@uri).host)) - RestClientWrapper.post(@uri,{:content_type => "application/x-yaml", :subjectid => subjectid},self.to_yaml) + RestClientWrapper.post(@uri,self.to_yaml,{:content_type => "application/x-yaml", :subjectid => subjectid}) else File.open("ot-post-file.rdf","w+") { |f| f.write(self.to_rdfxml); @path = f.path } task_uri = RestClient.post(@uri, {:file => File.new(@path)},{:accept => "text/uri-list" , :subjectid => subjectid}).to_s.chomp diff --git a/lib/environment.rb b/lib/environment.rb index 203ebc6..b30b3f3 100644 --- a/lib/environment.rb +++ b/lib/environment.rb @@ -54,8 +54,8 @@ else end # Regular expressions for parsing classification data -TRUE_REGEXP = /^(true|active|1|1.0)$/i -FALSE_REGEXP = /^(false|inactive|0|0.0)$/i +TRUE_REGEXP = /^(true|active|1|1.0|tox)$/i +FALSE_REGEXP = /^(false|inactive|0|0.0|low tox)$/i # Task durations DEFAULT_TASK_MAX_DURATION = 36000 diff --git a/lib/error.rb b/lib/error.rb index 8c666f3..d086928 100644 --- a/lib/error.rb +++ b/lib/error.rb @@ -18,6 +18,10 @@ module OpenTox class NotFoundError < RuntimeError def http_code; 404; end end + + class ServiceUnavailableError < RuntimeError + def http_code; 503; end + end class RestCallError < RuntimeError attr_accessor :rest_params @@ -28,18 +32,31 @@ module OpenTox # TODO replace params with URIs (errorCause -> OT.errorCause) attr_reader :message, :actor, :errorCause, :http_code, :errorDetails, :errorType + + private + def initialize( http_code, erroType, message, actor, errorCause, rest_params=nil, backtrace=nil ) + @http_code = http_code + @errorType = erroType + @message = message + @actor = actor + @errorCause = errorCause + @rest_params = rest_params + @backtrace = backtrace + end + public # creates a error report object, from an ruby-exception object - # @param [Exception] error - # @param [String] actor, URI of the call that cause the error - def initialize( error, actor ) - @http_code = error.http_code - @errorType = error.class.to_s - @message = error.message - @actor = actor - @errorCause = error.errorCause if error.errorCause - @rest_params = error.rest_params if error.is_a?(OpenTox::RestCallError) and error.rest_params - @backtrace = error.backtrace.short_backtrace if CONFIG[:backtrace] + # @param [Exception] error + # @param [String] actor, URI of the call that cause the error + def self.create( error, actor ) + rest_params = error.rest_params if error.is_a?(OpenTox::RestCallError) and error.rest_params + backtrace = error.backtrace.short_backtrace if CONFIG[:backtrace] + ErrorReport.new( error.http_code, error.class.to_s, error.message, actor, error.errorCause, rest_params, backtrace ) + end + + def self.from_rdf(rdf) + metadata = OpenTox::Parser::Owl.metadata_from_rdf( rdf, OT.ErrorReport ) + ErrorReport.new(metadata[OT.statusCode], metadata[OT.errorCode], metadata[OT.message], metadata[OT.actor], metadata[OT.errorCause]) end # overwrite sorting to make easier readable diff --git a/lib/helper.rb b/lib/helper.rb index 0bb489c..6ca3901 100644 --- a/lib/helper.rb +++ b/lib/helper.rb @@ -28,8 +28,9 @@ helpers do #cleans URI from querystring and file-extension. Sets port 80 to emptystring # @param [String] uri def clean_uri(uri) + uri = uri.sub(" ", "%20") out = URI.parse(uri) - out.path = out.path[0, out.path.rindex(/[0-9]/) + 1] if out.path.rindex(/[0-9]/) #cuts after id for a&a + out.path = out.path[0, out.path.index(/[0-9]/)] if out.path.index(/[0-9]/) #cuts after id for a&a "#{out.scheme}:" + (out.port != 80 ? out.port : "") + "//#{out.host}#{out.path.chomp('/')}" end @@ -51,7 +52,7 @@ before do subjectid = CGI.unescape(subjectid) if subjectid.include?("%23") @subjectid = subjectid rescue - LOGGER.debug "OpenTox ruby api wrapper: helper before filter: NO subjectid for URI: #{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}" + #LOGGER.debug "OpenTox ruby api wrapper: helper before filter: NO subjectid for URI: #{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}" subjectid = "" end @subjectid = subjectid diff --git a/lib/model.rb b/lib/model.rb index 0073ea4..64d178f 100644 --- a/lib/model.rb +++ b/lib/model.rb @@ -17,25 +17,22 @@ module OpenTox end end LOGGER.info "running model "+@uri.to_s+", params: "+params.inspect+", accept: "+accept_header.to_s - RestClientWrapper.post(@uri,{:accept => accept_header},params,waiting_task).to_s + RestClientWrapper.post(@uri,params,{:accept => accept_header},waiting_task).to_s end # Generic OpenTox model class for all API compliant services class Generic include Model - # Find Generic Opentox Model via URI, and loads metadata + # Find Generic Opentox Model via URI, and loads metadata, could raise NotFound/NotAuthorized error # @param [String] uri Model URI - # @return [OpenTox::Model::Generic] Model instance, nil if model was not found + # @return [OpenTox::Model::Generic] Model instance def self.find(uri,subjectid=nil) return nil unless uri model = Generic.new(uri) model.load_metadata(subjectid) - if model.metadata==nil or model.metadata.size==0 - nil - else - model - end + raise "could not load model metadata '"+uri.to_s+"'" if model.metadata==nil or model.metadata.size==0 + model end # provides feature type, possible types are "regression" or "classification" @@ -46,7 +43,7 @@ module OpenTox @algorithm = OpenTox::Algorithm::Generic.find(@metadata[OT.algorithm], subjectid) unless @algorithm algorithm_title = @algorithm ? @algorithm.metadata[DC.title] : nil - @dependentVariable = OpenTox::Feature.find( @metadata[OT.dependentVariables], subjectid) unless @dependentVariable + @dependentVariable = OpenTox::Feature.find( @metadata[OT.dependentVariables],subjectid ) unless @dependentVariable [@dependentVariable.feature_type, @metadata[OT.isA], @metadata[DC.title], @uri, algorithm_title].each do |type| case type @@ -303,7 +300,7 @@ module OpenTox # Save model at model service def save(subjectid) - self.uri = RestClientWrapper.post(@uri,{:content_type => "application/x-yaml", :subjectid => subjectid},self.to_yaml) + self.uri = RestClientWrapper.post(@uri,self.to_yaml,{:content_type => "application/x-yaml", :subjectid => subjectid}) end # Delete model at model service diff --git a/lib/opentox.rb b/lib/opentox.rb index f1af5c3..1992896 100644 --- a/lib/opentox.rb +++ b/lib/opentox.rb @@ -43,5 +43,10 @@ module OpenTox s.to_rdfxml end + # deletes the resource, deletion should have worked when no RestCallError raised + def delete(subjectid=nil) + RestClientWrapper.delete(uri,:subjectid => subjectid) + end + end diff --git a/lib/overwrite.rb b/lib/overwrite.rb index e52618c..7b53122 100644 --- a/lib/overwrite.rb +++ b/lib/overwrite.rb @@ -16,13 +16,12 @@ before { # IMPT: set sinatra settings :show_exceptions + :raise_errors to false in config.ru, otherwise Rack::Showexceptions takes over error Exception do error = request.env['sinatra.error'] - # log error to logfile + # log error message and backtrace to logfile LOGGER.error error.class.to_s+": "+error.message - # log backtrace only if code is 500 -> unwanted (Runtime)Exceptions and internal errors (see error.rb) - LOGGER.error ":\n"+error.backtrace.join("\n") if error.http_code==500 + LOGGER.error ":\n"+error.backtrace.join("\n") actor = "#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}" - rep = OpenTox::ErrorReport.new(error, actor) + rep = OpenTox::ErrorReport.create(error, actor) case request.env['HTTP_ACCEPT'] when /rdf/ @@ -37,6 +36,27 @@ error Exception do end end +class Sinatra::Base + + def return_task( task ) + code = task.running? ? 202 : 200 + case request.env['HTTP_ACCEPT'] + when /rdf/ + response['Content-Type'] = "application/rdf+xml" + halt code,task.to_rdfxml + when /yaml/ + response['Content-Type'] = "application/rdf+xml" + halt code,task.to_yaml # PENDING differs from task-webservice + when /html/ + response['Content-Type'] = "text/html" + halt code,OpenTox.text_to_html(task.to_yaml) + else # default /uri-list/ + response['Content-Type'] = "text/uri-list" + halt code,task.uri+"\n" + end + end +end + class String def task_uri? self.uri? && !self.match(/task/).nil? diff --git a/lib/parser.rb b/lib/parser.rb index a913cf2..1a872a0 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -36,10 +36,18 @@ module OpenTox else uri = @uri end - uri += "?subjectid=#{CGI.escape(subjectid)}" if subjectid + # avoid using rapper directly because of 2 reasons: + # * http errors wont be noticed + # * subjectid cannot be sent as header + ##uri += "?subjectid=#{CGI.escape(subjectid)}" if subjectid + ## `rapper -i rdfxml -o ntriples #{uri} 2>/dev/null`.each_line do |line| + file = Tempfile.new("ot-rdfxml") + file.puts OpenTox::RestClientWrapper.get @uri,{:subjectid => subjectid,:accept => "application/rdf+xml"} + file.close + file = "file://"+file.path statements = [] parameter_ids = [] - `rapper -i rdfxml -o ntriples #{uri} 2>/dev/null`.each_line do |line| + `rapper -i rdfxml -o ntriples #{file} 2>/dev/null`.each_line do |line| triple = line.to_triple @metadata[triple[1]] = triple[2].split('^^').first if triple[0] == @uri and triple[1] != RDF['type'] statements << triple @@ -55,6 +63,38 @@ module OpenTox end @metadata end + + # loads metadata from rdf-data + # @param [String] rdf + # @param [String] type of the info (e.g. OT.Task, OT.ErrorReport) needed to get the subject-uri + # @return [Hash] metadata + def self.metadata_from_rdf( rdf, type ) + # write to file and read convert with rapper into tripples + file = Tempfile.new("ot-rdfxml") + file.puts rdf + file.close + file = "file://"+file.path + #puts "cmd: rapper -i rdfxml -o ntriples #{file} 2>/dev/null" + triples = `rapper -i rdfxml -o ntriples #{file} 2>/dev/null` + + # load uri via type + uri = nil + triples.each_line do |line| + triple = line.to_triple + if triple[1] == RDF['type'] and triple[2]==type + raise "uri already set, two uris found with type: "+type.to_s if uri + uri = triple[0] + end + end + + # load metadata + metadata = {} + triples.each_line do |line| + triple = line.to_triple + metadata[triple[1]] = triple[2].split('^^').first if triple[0] == uri and triple[1] != RDF['type'] + end + metadata + end # Generic parser for all OpenTox classes class Generic diff --git a/lib/rest_client_wrapper.rb b/lib/rest_client_wrapper.rb index 7c2d719..658f111 100644 --- a/lib/rest_client_wrapper.rb +++ b/lib/rest_client_wrapper.rb @@ -14,21 +14,21 @@ module OpenTox # @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly # @param [wait,Boolean] wait set to false to NOT wait for task if result is task # @return [OpenTox::WrapperResult] a String containing the result-body of the REST call - def self.get(uri, headers=nil, waiting_task=nil, wait=true ) - execute( "get", uri, headers, nil, waiting_task, wait) + def self.get(uri, headers={}, waiting_task=nil, wait=true ) + execute( "get", uri, nil, headers, waiting_task, wait) end # performs a POST REST call # raises OpenTox::Error if call fails (rescued in overwrite.rb -> halt 502) # per default: waits for Task to finish and returns result URI of Task # @param [String] uri destination URI - # @param [optional,Hash] headers contains params like accept-header # @param [optional,String] payload data posted to the service + # @param [optional,Hash] headers contains params like accept-header # @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly # @param [wait,Boolean] wait set to false to NOT wait for task if result is task # @return [OpenTox::WrapperResult] a String containing the result-body of the REST call - def self.post(uri, headers, payload=nil, waiting_task=nil, wait=true ) - execute( "post", uri, headers, payload, waiting_task, wait ) + def self.post(uri, payload=nil, headers={}, waiting_task=nil, wait=true ) + execute( "post", uri, payload, headers, waiting_task, wait ) end # performs a PUT REST call @@ -37,8 +37,8 @@ module OpenTox # @param [optional,Hash] headers contains params like accept-header # @param [optional,String] payload data put to the service # @return [OpenTox::WrapperResult] a String containing the result-body of the REST call - def self.put(uri, headers, payload=nil ) - execute( "put", uri, headers, payload ) + def self.put(uri, payload=nil, headers={} ) + execute( "put", uri, payload, headers ) end # performs a DELETE REST call @@ -47,36 +47,42 @@ module OpenTox # @param [optional,Hash] headers contains params like accept-header # @return [OpenTox::WrapperResult] a String containing the result-body of the REST call def self.delete(uri, headers=nil ) - execute( "delete", uri, headers, nil) + execute( "delete", uri, nil, headers) end private - def self.execute( rest_call, uri, headers, payload=nil, waiting_task=nil, wait=true ) + def self.execute( rest_call, uri, payload=nil, headers={}, waiting_task=nil, wait=true ) raise OpenTox::BadRequestError.new "uri is null" unless uri raise OpenTox::BadRequestError.new "not a uri: "+uri.to_s unless uri.to_s.uri? - raise OpenTox::BadRequestError.new "headers are no hash: "+headers.inspect unless headers==nil or headers.is_a?(Hash) - raise OpenTox::BadRequestError.new "nil headers for post not allowed, use {}" if rest_call=="post" and headers==nil + raise "headers are no hash: "+headers.inspect unless headers==nil or headers.is_a?(Hash) + raise OpenTox::BadRequestError.new "accept should go into the headers" if payload and payload.is_a?(Hash) and payload[:accept] + raise OpenTox::BadRequestError.new "content_type should go into the headers" if payload and payload.is_a?(Hash) and payload[:content_type] + raise "__waiting_task__ must be 'nil' or '(sub)task', is "+waiting_task.class.to_s if + waiting_task!=nil and !(waiting_task.is_a?(Task) || waiting_task.is_a?(SubTask)) headers.each{ |k,v| headers.delete(k) if v==nil } if headers #remove keys with empty values, as this can cause problems + # PENDING needed for NUTA, until we finally agree on how to send subjectid + headers[:subjectid] = payload.delete(:subjectid) if uri=~/ntua/ and payload and payload.is_a?(Hash) and payload.has_key?(:subjectid) + begin #LOGGER.debug "RestCall: "+rest_call.to_s+" "+uri.to_s+" "+headers.inspect+" "+payload.inspect - resource = RestClient::Resource.new(uri,{:timeout => 60}) - if payload + resource = RestClient::Resource.new(uri,{:timeout => 60}) + if rest_call=="post" || rest_call=="put" result = resource.send(rest_call, payload, headers) - elsif headers - result = resource.send(rest_call, headers) else - result = resource.send(rest_call) + result = resource.send(rest_call, headers) end + # PENDING NTUA does return errors with 200 + raise RestClient::ExceptionWithResponse.new(result) if uri=~/ntua/ and result.body =~ /about.*http:\/\/anonymous.org\/error/ + # result is a string, with the additional fields content_type and code res = WrapperResult.new(result.body) res.content_type = result.headers[:content_type] raise "content-type not set" unless res.content_type res.code = result.code - #LOGGER.debug "RestCall result: "+res.to_s+" "+res.code.to_s+" "+res.content_type.to_s # TODO: Ambit returns task representation with 200 instead of result URI return res if res.code==200 || !wait @@ -87,10 +93,10 @@ module OpenTox return res rescue RestClient::RequestTimeout => ex - received_error ex.message, 408, nil, {:rest_uri => uri, :headers => headers} + received_error ex.message, 408, nil, {:rest_uri => uri, :headers => headers, :payload => payload} rescue RestClient::ExceptionWithResponse => ex # error comming from a different webservice, - received_error ex.http_body, ex.http_code, ex.response.net_http_res.content_type, {:rest_uri => uri, :headers => headers} + received_error ex.http_body, ex.http_code, ex.response.net_http_res.content_type, {:rest_uri => uri, :headers => headers, :payload => payload} rescue OpenTox::RestCallError => ex # already a rest-error, probably comes from wait_for_task, just pass through raise ex diff --git a/lib/task.rb b/lib/task.rb index 74940de..1a684df 100644 --- a/lib/task.rb +++ b/lib/task.rb @@ -33,7 +33,7 @@ module OpenTox def self.create( title=nil, creator=nil, max_duration=DEFAULT_TASK_MAX_DURATION, description=nil ) params = {:title=>title, :creator=>creator, :max_duration=>max_duration, :description=>description } - task_uri = RestClientWrapper.post(CONFIG[:services]["opentox-task"], params, nil, false).to_s + task_uri = RestClientWrapper.post(CONFIG[:services]["opentox-task"], params, {}, nil, false).to_s task = Task.new(task_uri.chomp) # measure current memory consumption @@ -64,9 +64,8 @@ module OpenTox task.completed(result) rescue => error LOGGER.error "task failed: "+error.class.to_s+": "+error.message - # log backtrace only if code is 500 -> unwanted (Runtime)Exceptions and internal errors (see error.rb) - LOGGER.error ":\n"+error.backtrace.join("\n") if error.http_code==500 - task.error(OpenTox::ErrorReport.new(error, creator)) + LOGGER.error ":\n"+error.backtrace.join("\n") + task.error(OpenTox::ErrorReport.create(error, creator)) end end task.pid = task_pid @@ -81,9 +80,20 @@ module OpenTox return nil unless uri task = Task.new(uri) task.load_metadata + raise "could not load task metadata" if task.metadata==nil or task.metadata.size==0 task end + # Find a task for querying, status changes + # @param [String] uri Task URI + # @return [OpenTox::Task] Task object + def self.exist?(uri) + begin + return find(uri) + rescue + end + end + # Get a list of all tasks # @param [optional, String] uri URI of task service # @return [text/uri-list] Task URIs @@ -188,7 +198,7 @@ module OpenTox # create is private now, use OpenTox::Task.as_task #def self.create( params ) - #task_uri = RestClientWrapper.post(CONFIG[:services]["opentox-task"], params, nil, false).to_s + #task_uri = RestClientWrapper.post(CONFIG[:services]["opentox-task"], params, {}, false).to_s #Task.find(task_uri.chomp) #end @@ -304,7 +314,7 @@ module OpenTox class SubTask def initialize(task, min, max) - raise "not a task or subtask" unless task.is_a?(Task) or task.is_a?(SubTask) + raise "not a task or subtask" if task!=nil and !(task.is_a?(Task) or task.is_a?(SubTask)) raise "invalid max ("+max.to_s+"), min ("+min.to_s+") params" unless min.is_a?(Numeric) and max.is_a?(Numeric) and min >= 0 and max <= 100 and max > min @task = task diff --git a/lib/validation.rb b/lib/validation.rb index 23b246b..2ea19a9 100644 --- a/lib/validation.rb +++ b/lib/validation.rb @@ -1,70 +1,196 @@ module OpenTox - class Validation + class Validation include OpenTox - - attr_accessor :report_uri, :qmrf_report_uri - - def self.create_crossvalidation(params) - params[:uri] = File.join(CONFIG[:services]['opentox-validation'], "crossvalidation") - params[:num_folds] = 10 unless params[:num_folds] - params[:random_seed] = 2 unless params[:random_seed] - params[:stratified] = false unless params[:stratified] - uri = OpenTox::RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/crossvalidation"),params,nil,false) - OpenTox::Validation.new(uri) - end - - def create_report(subjectid=nil) - @report_uri = OpenTox::RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/report/crossvalidation"), {:validation_uris => @uri, :subjectid => subjectid}).to_s - @report_uri + + # find validation, raises error if not found + # @param [String] uri + # @param [String,optional] subjectid + # @return [OpenTox::Validation] + def self.find( uri, subjectid=nil ) + val = Validation.new(uri) + val.load_metadata( subjectid ) + val end - - def create_qmrf_report(subjectid=nil) - @qmrf_report_uri = OpenTox::RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/reach_report/qmrf"), {:model_uri => @uri, :subjectid => subjectid}).to_s - @qmrf_report_uri + + # creates a validation object from crossvaldiation statistics, raise error if not found + # (as crossvaldiation statistics are returned as an average valdidation over all folds) + # @param [String] crossvalidation uri + # @param [String,optional] subjectid + # @return [OpenTox::Validation] + def self.from_cv_statistics( crossvalidation_uri, subjectid=nil ) + find( File.join(crossvalidation_uri, 'statistics'),subjectid ) end - - def summary(type, subjectid=nil) - v = YAML.load OpenTox::RestClientWrapper.get(File.join(@uri, 'statistics'),{:accept => "application/x-yaml", :subjectid => subjectid}).to_s - - case type - when "classification" - tp=0; tn=0; fp=0; fn=0; n=0 - v[:classification_statistics][:confusion_matrix][:confusion_matrix_cell].each do |cell| - if cell[:confusion_matrix_predicted] == "true" and cell[:confusion_matrix_actual] == "true" - tp = cell[:confusion_matrix_value] - n += tp - elsif cell[:confusion_matrix_predicted] == "false" and cell[:confusion_matrix_actual] == "false" - tn = cell[:confusion_matrix_value] - n += tn - elsif cell[:confusion_matrix_predicted] == "false" and cell[:confusion_matrix_actual] == "true" - fn = cell[:confusion_matrix_value] - n += fn - elsif cell[:confusion_matrix_predicted] == "true" and cell[:confusion_matrix_actual] == "false" - fp = cell[:confusion_matrix_value] - n += fp + + # loads metadata via yaml from validation object + # fields (like for example the validated model) can be acces via validation.metadata[OT.model] + def load_metadata( subjectid=nil ) + @metadata = YAML.load(OpenTox::RestClientWrapper.get(uri,{:subjectid => subjectid, :accept => "application/x-yaml"})) + end + + # PENDING: creates summary as used for ToxCreate + def summary + if @metadata[OT.classificationStatistics] + res = { + :nr_predictions => @metadata[OT.numInstances] - @metadata[OT.numUnpredicted], + :correct_predictions => @metadata[OT.classificationStatistics][OT.percentCorrect], + :weighted_area_under_roc => @metadata[OT.classificationStatistics][OT.weightedAreaUnderRoc], + } + @metadata[OT.classificationStatistics][OT.classValueStatistics].each do |s| + if s[OT.classValue].to_s=="true" + res[:true_positives] = s[OT.numTruePositives] + res[:false_positives] = s[OT.numFalsePositives] + res[:true_negatives] = s[OT.numTrueNegatives] + res[:false_negatives] = s[OT.numFalseNegatives] + res[:sensitivity] = s[OT.truePositiveRate] + res[:specificity] = s[OT.falsePositiveRate] + break end end + res + elsif @metadata[OT.regressionStatistics] { - :nr_predictions => n, - :true_positives => tp, - :false_positives => fp, - :true_negatives => tn, - :false_negatives => fn, - :correct_predictions => 100*(tp+tn).to_f/n, - :weighted_area_under_roc => v[:classification_statistics][:weighted_area_under_roc].to_f, - :sensitivity => tp.to_f/(tp+fn), - :specificity => tn.to_f/(tn+fp), - } - when "regression" - { - :nr_predictions => v[:num_instances] - v[:num_unpredicted], - :r_square => v[:regression_statistics][:r_square], - :root_mean_squared_error => v[:regression_statistics][:root_mean_squared_error], - :mean_absolute_error => v[:regression_statistics][:mean_absolute_error], + :nr_predictions => @metadata[OT.numInstances] - @metadata[OT.numUnpredicted], + :r_square => @metadata[OT.regressionStatistics][OT.rSquare], + :root_mean_squared_error => @metadata[OT.regressionStatistics][OT.rootMeanSquaredError], + :mean_absolute_error => @metadata[OT.regressionStatistics][OT.meanAbsoluteError], } end end + end + + class Crossvalidation + include OpenTox + + attr_reader :report + + # find crossvalidation, raises error if not found + # @param [String] uri + # @param [String,optional] subjectid + # @return [OpenTox::Crossvalidation] + def self.find( uri, subjectid=nil ) + cv = Crossvalidation.new(uri) + cv.load_metadata( subjectid ) + cv + end + + # creates a crossvalidations, waits until it finishes, may take some time + # @param [Hash] params (required:algorithm_uri,dataset_uri,prediction_feature, optional:algorithm_params,num_folds(10),random_seed(1),stratified(false)) + # @param [String,optional] subjectid + # @param [OpenTox::Task,optional] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly + # @return [OpenTox::Crossvalidation] + def self.create( params, subjectid=nil, waiting_task=nil ) + params[:subjectid] = subjectid if subjectid + uri = OpenTox::RestClientWrapper.post( File.join(CONFIG[:services]["opentox-validation"],"crossvalidation"), + params,{:content_type => "text/uri-list"},waiting_task ) + Crossvalidation.new(uri) + end - end + # looks for report for this crossvalidation, creates a report if no report is found + # @param [String,optional] subjectid + # @param [OpenTox::Task,optional] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly + # @return [OpenTox::CrossvalidationReport] + def find_or_create_report( subjectid=nil, waiting_task=nil ) + @report = CrossvalidationReport.find_for_crossvalidation(@uri, subjectid) unless @report + @report = CrossvalidationReport.create(@uri, subjectid, waiting_task) unless @report + @report.uri + end + + # loads metadata via yaml from crossvalidation object + # fields (like for example the validations) can be acces via validation.metadata[OT.validation] + def load_metadata( subjectid=nil ) + @metadata = YAML.load(OpenTox::RestClientWrapper.get(uri,{:subjectid => subjectid, :accept => "application/x-yaml"})) + end + + # PENDING: creates summary as used for ToxCreate + def summary( subjectid=nil ) + Validation.from_cv_statistics( @uri, subjectid ).summary + end + end + + class ValidationReport + include OpenTox + + # finds ValidationReport for a particular validation + # @param [String] crossvalidation uri + # @param [String,optional] subjectid + # @return [OpenTox::ValidationReport] nil if no report found + def self.find_for_validation( validation_uri, subjectid=nil ) + uris = RestClientWrapper.get(File.join(CONFIG[:services]["opentox-validation"], + "/report/validation?validation="+validation_uri), {:subjectid => subjectid}).chomp.split("\n") + uris.size==0 ? nil : ValidationReport.new(uris[-1]) + end + + end + + class CrossvalidationReport + include OpenTox + + # finds CrossvalidationReport via uri, raises error if not found + # @param [String] uri + # @param [String,optional] subjectid + # @return [OpenTox::CrossvalidationReport] + def self.find( uri, subjectid=nil ) + # PENDING load report data? + OpenTox::RestClientWrapper.get(uri,{:subjectid => subjectid}) + CrossvalidationReport.new(uri) + end + + # finds CrossvalidationReport for a particular crossvalidation + # @param [String] crossvalidation uri + # @param [String,optional] subjectid + # @return [OpenTox::CrossvalidationReport] nil if no report found + def self.find_for_crossvalidation( crossvalidation_uri, subjectid=nil ) + uris = RestClientWrapper.get(File.join(CONFIG[:services]["opentox-validation"], + "/report/crossvalidation?crossvalidation="+crossvalidation_uri), {:subjectid => subjectid}).chomp.split("\n") + uris.size==0 ? nil : CrossvalidationReport.new(uris[-1]) + end + + # creates a crossvalidation report via crossvalidation + # @param [String] crossvalidation uri + # @param [String,optional] subjectid + # @param [OpenTox::Task,optional] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly + # @return [OpenTox::CrossvalidationReport] + def self.create( crossvalidation_uri, subjectid=nil, waiting_task=nil ) + uri = RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/report/crossvalidation"), + { :validation_uris => crossvalidation_uri, :subjectid => subjectid }, {}, waiting_task ) + CrossvalidationReport.new(uri) + end + end + + class QMRFReport + include OpenTox + + # finds QMRFReport, raises Error if not found + # @param [String] uri + # @param [String,optional] subjectid + # @return [OpenTox::QMRFReport] + def self.find( uri, subjectid=nil ) + # PENDING load crossvalidation data? + OpenTox::RestClientWrapper.get(uri,{:subjectid => subjectid}) + QMRFReport.new(uri) + end + + # finds QMRF report for a particular model + # @param [String] model_uri + # @param [String,optional] subjectid + # @return [OpenTox::QMRFReport] nil if no report found + def self.find_for_model( model_uri, subjectid=nil ) + uris = RestClientWrapper.get(File.join(CONFIG[:services]["opentox-validation"], + "/reach_report/qmrf?model="+model_uri), {:subjectid => subjectid}).chomp.split("\n") + uris.size==0 ? nil : QMRFReport.new(uris[-1]) + end + + # creates a qmrf report via model + # @param [String] model_uri + # @param [String,optional] subjectid + # @param [OpenTox::Task,optional] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly + # @return [OpenTox::QMRFReport] + def self.create( model_uri, subjectid=nil, waiting_task=nil ) + uri = RestClientWrapper.post(File.join(CONFIG[:services]["opentox-validation"],"/reach_report/qmrf"), + { :model_uri => model_uri, :subjectid => subjectid }, {}, waiting_task ) + QMRFReport.new(uri) + end + end + end -- cgit v1.2.3 From f907690dc7d3f82c75a51718a3abfa7750dedaa5 Mon Sep 17 00:00:00 2001 From: mguetlein Date: Tue, 8 Feb 2011 16:58:32 +0100 Subject: set waiting_for in task --- lib/task.rb | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/task.rb b/lib/task.rb index ca18d7b..742afb4 100644 --- a/lib/task.rb +++ b/lib/task.rb @@ -139,7 +139,7 @@ module OpenTox end def cancel - RestClientWrapper.put(File.join(@uri,'Cancelled')) + RestClientWrapper.put(File.join(@uri,'Cancelled'),{:cannot_be => "empty"}) load_metadata end @@ -237,6 +237,7 @@ module OpenTox # @param [optional,Numeric] dur seconds pausing before cheking again for completion def wait_for_completion( waiting_task=nil, dur=0.3) + waiting_task.waiting_for(self.uri) if waiting_task due_to_time = Time.new + DEFAULT_TASK_MAX_DURATION LOGGER.debug "start waiting for task "+@uri.to_s+" at: "+Time.new.to_s+", waiting at least until "+due_to_time.to_s @@ -252,7 +253,7 @@ module OpenTox raise "max wait time exceeded ("+DEFAULT_TASK_MAX_DURATION.to_s+"sec), task: '"+@uri.to_s+"'" end end - + waiting_task.waiting_for(nil) if waiting_task LOGGER.debug "Task '"+@metadata[OT.hasStatus].to_s+"': "+@uri.to_s+", Result: "+@metadata[OT.resultURI].to_s end @@ -268,6 +269,10 @@ module OpenTox end end + def waiting_for(task_uri) + RestClientWrapper.put(File.join(@uri,'Running'),{:waiting_for => task_uri}) + end + private def check_state begin @@ -321,6 +326,10 @@ module OpenTox end end + def waiting_for(task_uri) + @task.waiting_for(task_uri) + end + def progress(pct) raise "no numeric >= 0 and <= 100 : '"+pct.to_s+"'" unless pct.is_a?(Numeric) and pct>=0 and pct<=100 #puts "subtask := "+pct.to_s+" -> task := "+(@min + @delta * pct.to_f * 0.01).to_s -- cgit v1.2.3 From 9a523f0fb2d5ee0058af5b5b82e01f39549f68fb Mon Sep 17 00:00:00 2001 From: mguetlein Date: Wed, 9 Feb 2011 14:06:12 +0100 Subject: fix rdf parsing to work with ambit dataset service with a&a, minor changes --- lib/error.rb | 2 +- lib/model.rb | 9 +++++---- lib/parser.rb | 41 +++++++++++++++++++++++++++-------------- lib/rest_client_wrapper.rb | 13 ++++++++----- lib/task.rb | 26 +++++++++----------------- 5 files changed, 50 insertions(+), 41 deletions(-) diff --git a/lib/error.rb b/lib/error.rb index 49756d5..7ca9767 100644 --- a/lib/error.rb +++ b/lib/error.rb @@ -55,7 +55,7 @@ module OpenTox end def self.from_rdf(rdf) - metadata = OpenTox::Parser::Owl.metadata_from_rdf( rdf, OT.ErrorReport ) + metadata = OpenTox::Parser::Owl.from_rdf( rdf, OT.ErrorReport ).metadata ErrorReport.new(metadata[OT.statusCode], metadata[OT.errorCode], metadata[OT.message], metadata[OT.actor], metadata[OT.errorCause]) end diff --git a/lib/model.rb b/lib/model.rb index 64d178f..9622d65 100644 --- a/lib/model.rb +++ b/lib/model.rb @@ -43,9 +43,11 @@ module OpenTox @algorithm = OpenTox::Algorithm::Generic.find(@metadata[OT.algorithm], subjectid) unless @algorithm algorithm_title = @algorithm ? @algorithm.metadata[DC.title] : nil + algorithm_type = @algorithm ? @algorithm.metadata[OT.isA] : nil @dependentVariable = OpenTox::Feature.find( @metadata[OT.dependentVariables],subjectid ) unless @dependentVariable - - [@dependentVariable.feature_type, @metadata[OT.isA], @metadata[DC.title], @uri, algorithm_title].each do |type| + type_indicators = [@dependentVariable.feature_type, @metadata[OT.isA], @metadata[DC.title], + @uri, algorithm_type, algorithm_title] + type_indicators.each do |type| case type when /(?i)classification/ return "classification" @@ -53,8 +55,7 @@ module OpenTox return "regression" end end - raise "unknown model "+[@dependentVariable.feature_type, @metadata[OT.isA], - @metadata[DC.title], @uri, algorithm_title].inspect + raise "unknown model "+type_indicators.inspect end end diff --git a/lib/parser.rb b/lib/parser.rb index 27dfeee..2f59d15 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -41,17 +41,17 @@ module OpenTox ##uri += "?subjectid=#{CGI.escape(subjectid)}" if subjectid ## `rapper -i rdfxml -o ntriples #{uri} 2>/dev/null`.each_line do |line| file = Tempfile.new("ot-rdfxml") - file.puts OpenTox::RestClientWrapper.get @uri,{:subjectid => subjectid,:accept => "application/rdf+xml"} + file.puts OpenTox::RestClientWrapper.get @uri,{:subjectid => subjectid,:accept => "application/rdf+xml"},nil,false file.close - file = "file://"+file.path statements = [] parameter_ids = [] - `rapper -i rdfxml -o ntriples #{file} 2>/dev/null`.each_line do |line| + `rapper -i rdfxml -o ntriples file://#{file.path} 2>/dev/null`.each_line do |line| triple = line.to_triple @metadata[triple[1]] = triple[2].split('^^').first if triple[0] == @uri and triple[1] != RDF['type'] statements << triple parameter_ids << triple[2] if triple[1] == OT.parameters end + File.delete(file.path) unless parameter_ids.empty? @metadata[OT.parameters] = [] parameter_ids.each do |p| @@ -63,18 +63,17 @@ module OpenTox @metadata end - # loads metadata from rdf-data + # creates owl object from rdf-data # @param [String] rdf # @param [String] type of the info (e.g. OT.Task, OT.ErrorReport) needed to get the subject-uri - # @return [Hash] metadata - def self.metadata_from_rdf( rdf, type ) + # @return [Owl] with uri and metadata set + def self.from_rdf( rdf, type ) # write to file and read convert with rapper into tripples file = Tempfile.new("ot-rdfxml") file.puts rdf file.close - file = "file://"+file.path #puts "cmd: rapper -i rdfxml -o ntriples #{file} 2>/dev/null" - triples = `rapper -i rdfxml -o ntriples #{file} 2>/dev/null` + triples = `rapper -i rdfxml -o ntriples file://#{file.path} 2>/dev/null` # load uri via type uri = nil @@ -85,19 +84,23 @@ module OpenTox uri = triple[0] end end - + File.delete(file) # load metadata metadata = {} triples.each_line do |line| triple = line.to_triple metadata[triple[1]] = triple[2].split('^^').first if triple[0] == uri and triple[1] != RDF['type'] end - metadata + owl = Owl::Generic.new(uri) + owl.metadata = metadata + owl end - + # Generic parser for all OpenTox classes class Generic include Owl + + attr_accessor :uri, :metadata end # OWL-DL parser for datasets @@ -128,12 +131,21 @@ module OpenTox # @return [Hash] Internal dataset representation def load_uri(subjectid=nil) uri = @uri - uri += "?subjectid=#{CGI.escape(subjectid)}" if subjectid + + # avoid using rapper directly because of 2 reasons: + # * http errors wont be noticed + # * subjectid cannot be sent as header + ##uri += "?subjectid=#{CGI.escape(subjectid)}" if subjectid + ##`rapper -i rdfxml -o ntriples #{file} 2>/dev/null`.each_line do |line| + file = Tempfile.new("ot-rdfxml") + file.puts OpenTox::RestClientWrapper.get @uri,{:subjectid => subjectid,:accept => "application/rdf+xml"},nil,false + file.close + data = {} feature_values = {} feature = {} other_statements = {} - `rapper -i rdfxml -o ntriples #{uri} 2>/dev/null`.each_line do |line| + `rapper -i rdfxml -o ntriples file://#{file.path} 2>/dev/null`.each_line do |line| triple = line.chomp.split(' ',3) triple = triple[0..2].collect{|i| i.sub(/\s+.$/,'').gsub(/[<>"]/,'')} case triple[1] @@ -150,6 +162,7 @@ module OpenTox else end end + File.delete(file.path) data.each do |id,entry| entry[:values].each do |value_id| value = feature_values[value_id].split(/\^\^/).first # remove XSD.type @@ -157,7 +170,7 @@ module OpenTox end end load_features - @dataset.metadata = load_metadata + @dataset.metadata = load_metadata(subjectid) @dataset end diff --git a/lib/rest_client_wrapper.rb b/lib/rest_client_wrapper.rb index 658f111..fcc0d08 100644 --- a/lib/rest_client_wrapper.rb +++ b/lib/rest_client_wrapper.rb @@ -61,9 +61,8 @@ module OpenTox raise "__waiting_task__ must be 'nil' or '(sub)task', is "+waiting_task.class.to_s if waiting_task!=nil and !(waiting_task.is_a?(Task) || waiting_task.is_a?(SubTask)) headers.each{ |k,v| headers.delete(k) if v==nil } if headers #remove keys with empty values, as this can cause problems - - # PENDING needed for NUTA, until we finally agree on how to send subjectid - headers[:subjectid] = payload.delete(:subjectid) if uri=~/ntua/ and payload and payload.is_a?(Hash) and payload.has_key?(:subjectid) + ## PENDING partner services accept subjectid only in header + headers[:subjectid] = payload.delete(:subjectid) if payload and payload.is_a?(Hash) and payload.has_key?(:subjectid) begin #LOGGER.debug "RestCall: "+rest_call.to_s+" "+uri.to_s+" "+headers.inspect+" "+payload.inspect @@ -94,6 +93,8 @@ module OpenTox rescue RestClient::RequestTimeout => ex received_error ex.message, 408, nil, {:rest_uri => uri, :headers => headers, :payload => payload} + rescue Errno::ECONNREFUSED => ex + received_error ex.message, 500, nil, {:rest_uri => uri, :headers => headers, :payload => payload} rescue RestClient::ExceptionWithResponse => ex # error comming from a different webservice, received_error ex.http_body, ex.http_code, ex.response.net_http_res.content_type, {:rest_uri => uri, :headers => headers, :payload => payload} @@ -107,7 +108,9 @@ module OpenTox end def self.wait_for_task( res, base_uri, waiting_task=nil ) - + #TODO remove TUM hack + content_type = "text/uri-list" if base_uri =~/tu-muenchen/ and res.content_type == "application/x-www-form-urlencoded;charset=UTF-8" +s task = nil case res.content_type when /application\/rdf\+xml/ @@ -118,7 +121,7 @@ module OpenTox raise "uri list has more than one entry, should be a task" if res.content_type=~/text\/uri-list/ and res.split("\n").size > 1 #if uri list contains more then one uri, its not a task task = OpenTox::Task.find(res.to_s.chomp) if res.to_s.uri? else - raise "unknown content-type for task: '"+res.content_type.to_s+"'" #+"' content: "+res[0..200].to_s + raise "unknown content-type for task : '"+res.content_type.to_s+"'"+" content: "+res[0..200].to_s end LOGGER.debug "result is a task '"+task.uri.to_s+"', wait for completion" diff --git a/lib/task.rb b/lib/task.rb index 742afb4..27dc1c2 100644 --- a/lib/task.rb +++ b/lib/task.rb @@ -94,23 +94,11 @@ module OpenTox def self.from_yaml(yaml) @metadata = YAML.load(yaml) end - def self.from_rdfxml(rdfxml) - file = Tempfile.new("ot-rdfxml") - file.puts rdfxml - file.close - file = "file://"+file.path - - # PENDING - raise "Parse from file not working: what is the base-object-uri??? (omitted in triples)" - - parser = Parser::Owl::Generic.new file - metadata = parser.load_metadata - puts metadata.inspect - - task = Task.new(uri) - task.add_metadata(metadata) + owl = OpenTox::Parser::Owl.from_rdf(rdfxml, OT.Task) + task = Task.new(owl.uri) + task.add_metadata(owl.metadata) task end @@ -176,7 +164,7 @@ module OpenTox end def load_metadata - if (CONFIG[:yaml_hosts].include?(URI.parse(uri).host)) + if (CONFIG[:yaml_hosts].include?(URI.parse(@uri).host)) result = RestClientWrapper.get(@uri, {:accept => 'application/x-yaml'}, nil, false) @metadata = YAML.load result.to_s @http_code = result.code @@ -184,6 +172,7 @@ module OpenTox @metadata = Parser::Owl::Generic.new(@uri).load_metadata @http_code = RestClientWrapper.get(uri, {:accept => 'application/rdf+xml'}, nil, false).code end + raise "could not load task metadata for task "+@uri.to_s if @metadata==nil || @metadata.size==0 end # create is private now, use OpenTox::Task.as_task @@ -274,11 +263,14 @@ module OpenTox end private + VALID_TASK_STATES = ["Cancelled", "Completed", "Running", "Error"] + def check_state begin + raise "illegal task state, invalid status: '"+@metadata[OT.hasStatus].to_s+"'" unless + @metadata[OT.hasStatus] unless VALID_TASK_STATES.include?(@metadata[OT.hasStatus]) raise "illegal task state, task is completed, resultURI is no URI: '"+@metadata[OT.resultURI].to_s+ "'" unless @metadata[OT.resultURI] and @metadata[OT.resultURI].to_s.uri? if completed? - if @http_code == 202 raise "#{@uri}: illegal task state, code is 202, but hasStatus is not Running: '"+@metadata[OT.hasStatus]+"'" unless running? elsif @http_code == 201 -- cgit v1.2.3 From d83015ff26c43ecbb06553403589b35388b3e3e9 Mon Sep 17 00:00:00 2001 From: mguetlein Date: Wed, 9 Feb 2011 14:11:41 +0100 Subject: remove letter --- lib/rest_client_wrapper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rest_client_wrapper.rb b/lib/rest_client_wrapper.rb index fcc0d08..d0e6727 100644 --- a/lib/rest_client_wrapper.rb +++ b/lib/rest_client_wrapper.rb @@ -110,7 +110,7 @@ module OpenTox def self.wait_for_task( res, base_uri, waiting_task=nil ) #TODO remove TUM hack content_type = "text/uri-list" if base_uri =~/tu-muenchen/ and res.content_type == "application/x-www-form-urlencoded;charset=UTF-8" -s + task = nil case res.content_type when /application\/rdf\+xml/ -- cgit v1.2.3 From e8b8f16cffc0401f4f51c7b5c68198dbe2b89ad2 Mon Sep 17 00:00:00 2001 From: mguetlein Date: Wed, 9 Feb 2011 14:43:39 +0100 Subject: fix tum hack --- lib/rest_client_wrapper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rest_client_wrapper.rb b/lib/rest_client_wrapper.rb index d0e6727..626f94f 100644 --- a/lib/rest_client_wrapper.rb +++ b/lib/rest_client_wrapper.rb @@ -109,7 +109,7 @@ module OpenTox def self.wait_for_task( res, base_uri, waiting_task=nil ) #TODO remove TUM hack - content_type = "text/uri-list" if base_uri =~/tu-muenchen/ and res.content_type == "application/x-www-form-urlencoded;charset=UTF-8" + res.content_type = "text/uri-list" if base_uri =~/tu-muenchen/ and res.content_type == "application/x-www-form-urlencoded;charset=UTF-8" task = nil case res.content_type @@ -121,7 +121,7 @@ module OpenTox raise "uri list has more than one entry, should be a task" if res.content_type=~/text\/uri-list/ and res.split("\n").size > 1 #if uri list contains more then one uri, its not a task task = OpenTox::Task.find(res.to_s.chomp) if res.to_s.uri? else - raise "unknown content-type for task : '"+res.content_type.to_s+"'"+" content: "+res[0..200].to_s + raise "unknown content-type for task : '"+res.content_type.to_s+"'"+" base-uri: "+base_uri.to_s+" content: "+res[0..200].to_s end LOGGER.debug "result is a task '"+task.uri.to_s+"', wait for completion" -- cgit v1.2.3 From 4109a5256e953a9962a6e46acb074b2d7d8d2bd9 Mon Sep 17 00:00:00 2001 From: mr Date: Wed, 9 Feb 2011 15:53:46 +0100 Subject: minor fix --- lib/authorization.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/authorization.rb b/lib/authorization.rb index 16f1ee4..b647bca 100644 --- a/lib/authorization.rb +++ b/lib/authorization.rb @@ -58,7 +58,7 @@ module OpenTox # @param [String, String]Username,Password # @return [String, nil] gives subjectid or nil def self.authenticate(user, pw) - return true if !AA_SERVER + return nil if !AA_SERVER begin resource = RestClient::Resource.new("#{AA_SERVER}/auth/authenticate") out = resource.post(:username=>user, :password => pw).sub("token.id=","").sub("\n","") -- cgit v1.2.3 From 87fa8c2c74484d85da3f581929a603f31ecc92d0 Mon Sep 17 00:00:00 2001 From: mr Date: Wed, 9 Feb 2011 17:12:51 +0100 Subject: task with subtask from mguetlein --- lib/task.rb | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/lib/task.rb b/lib/task.rb index 1a684df..7aa3dd5 100644 --- a/lib/task.rb +++ b/lib/task.rb @@ -107,20 +107,9 @@ module OpenTox def self.from_rdfxml(rdfxml) - file = Tempfile.new("ot-rdfxml") - file.puts rdfxml - file.close - file = "file://"+file.path - - # PENDING - raise "Parse from file not working: what is the base-object-uri??? (omitted in triples)" - - parser = Parser::Owl::Generic.new file - metadata = parser.load_metadata - puts metadata.inspect - - task = Task.new(uri) - task.add_metadata(metadata) + owl = OpenTox::Parser::Owl.from_rdf(rdfxml, OT.Task) + task = Task.new(owl.uri) + task.add_metadata(owl.metadata) task end @@ -149,7 +138,7 @@ module OpenTox end def cancel - RestClientWrapper.put(File.join(@uri,'Cancelled')) + RestClientWrapper.put(File.join(@uri,'Cancelled'),{:cannot_be => "empty"}) load_metadata end @@ -247,6 +236,7 @@ module OpenTox # @param [optional,Numeric] dur seconds pausing before cheking again for completion def wait_for_completion( waiting_task=nil, dur=0.3) + waiting_task.waiting_for(self.uri) if waiting_task due_to_time = Time.new + DEFAULT_TASK_MAX_DURATION LOGGER.debug "start waiting for task "+@uri.to_s+" at: "+Time.new.to_s+", waiting at least until "+due_to_time.to_s @@ -262,7 +252,7 @@ module OpenTox raise "max wait time exceeded ("+DEFAULT_TASK_MAX_DURATION.to_s+"sec), task: '"+@uri.to_s+"'" end end - + waiting_task.waiting_for(nil) if waiting_task LOGGER.debug "Task '"+@metadata[OT.hasStatus].to_s+"': "+@uri.to_s+", Result: "+@metadata[OT.resultURI].to_s end @@ -278,6 +268,10 @@ module OpenTox end end + def waiting_for(task_uri) + RestClientWrapper.put(File.join(@uri,'Running'),{:waiting_for => task_uri}) + end + private def check_state begin @@ -332,6 +326,10 @@ module OpenTox end end + def waiting_for(task_uri) + @task.waiting_for(task_uri) + end + def progress(pct) raise "no numeric >= 0 and <= 100 : '"+pct.to_s+"'" unless pct.is_a?(Numeric) and pct>=0 and pct<=100 #puts "subtask := "+pct.to_s+" -> task := "+(@min + @delta * pct.to_f * 0.01).to_s -- cgit v1.2.3 From 766d0eccb1a5d9f3016d9d85469dc68641b779ff Mon Sep 17 00:00:00 2001 From: mr Date: Thu, 10 Feb 2011 08:51:15 +0100 Subject: config.yaml with new settings for A&A --- lib/templates/config.yaml | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/lib/templates/config.yaml b/lib/templates/config.yaml index 116f462..8a5e460 100644 --- a/lib/templates/config.yaml +++ b/lib/templates/config.yaml @@ -39,16 +39,48 @@ # Uncomment for verbose logging # :logger: debug - +# :backtrace: 1 + + # OpenSSO Authorization # set ":server: " to disable A&A :authorization: :server: "https://opensso.in-silico.ch" - :free_request: #not controlled by A&A - - "GET" + :free_request: #request-method not controlled by A&A + - "GET" :authenticate_request: #only for authenticated user - "POST" :authorize_request: #only for authenticated and authorizeduser - "DELETE" - "PUT" + # Exceptions: + :free_uris: #request-method for uri not controlled by A&A + ? - :GET + : - !ruby/regexp /localhost\/algorithm/ + - "http://localhost/dataset" + - "http://localhost/model" + - "http://localhost/validation" + - "http://localhost/validation/crossvalidation" + - "http://localhost/validation/reach_report" + - "http://localhost/validation/reach_report/crossvalidation" + - "http://localhost/validation/report" + - "http://localhost/validation/report/crossvalidation" + - "http://localhost/validation/reach_report/qmrf" + ? - :GET + - :POST + : - !ruby/regexp /localhost\/toxcreate/ + - !ruby/regexp /localhost\/task/ + - !ruby/regexp /localhost\/compound/ + ? - :PUT + : - !ruby/regexp /localhost\/task/ + + :authorize_exceptions: #request-method for uri only authenticated, no authorization + ? - :POST + : - !ruby/regexp /localhost\/algorithm/ + - "http://localhost/dataset" + - "http://localhost/model" + - "http://localhost/validation" + - !ruby/regexp /localhost\/validation\/[a-z,A-Z,\/,_\-]*$/ + + \ No newline at end of file -- cgit v1.2.3 From 03795f02e06b3a9c635abf92c112a9dec142364b Mon Sep 17 00:00:00 2001 From: mr Date: Thu, 10 Feb 2011 09:20:20 +0100 Subject: A&A fixes --- lib/helper.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/helper.rb b/lib/helper.rb index 6ca3901..415ca11 100644 --- a/lib/helper.rb +++ b/lib/helper.rb @@ -9,7 +9,7 @@ helpers do end elsif !env["session"] && subjectid unless authorized?(subjectid) - LOGGER.debug "URI not authorized: #{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']} with request: #{request.env['REQUEST_METHOD']}" + LOGGER.debug "URI not authorized: clean: " + clean_uri("#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}").to_s + " full: #{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']} with request: #{request.env['REQUEST_METHOD']}" raise OpenTox::NotAuthorizedError.new "Not authorized" end else @@ -28,9 +28,11 @@ helpers do #cleans URI from querystring and file-extension. Sets port 80 to emptystring # @param [String] uri def clean_uri(uri) - uri = uri.sub(" ", "%20") + uri = uri.sub(" ", "%20") #dirty hacks => to fix + uri = uri[0,uri.index("InChI=")] + out = URI.parse(uri) - out.path = out.path[0, out.path.index(/[0-9]/)] if out.path.index(/[0-9]/) #cuts after id for a&a + out.path = out.path[0, out.path.length - (out.path.reverse.rindex(/\/{1}\d+\/{1}/))] if out.path.index(/\/{1}\d+\/{1}/) #cuts after /id/ for a&a "#{out.scheme}:" + (out.port != 80 ? out.port : "") + "//#{out.host}#{out.path.chomp('/')}" end -- cgit v1.2.3 From 65f6d64e57c81ae7b6dd72209fbeffee2d60da71 Mon Sep 17 00:00:00 2001 From: mr Date: Thu, 10 Feb 2011 09:22:13 +0100 Subject: A&A fixes --- lib/helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/helper.rb b/lib/helper.rb index 415ca11..5a2436f 100644 --- a/lib/helper.rb +++ b/lib/helper.rb @@ -29,7 +29,7 @@ helpers do # @param [String] uri def clean_uri(uri) uri = uri.sub(" ", "%20") #dirty hacks => to fix - uri = uri[0,uri.index("InChI=")] + uri = uri[0,uri.index("InChI=")] if uri.index("InChI=") out = URI.parse(uri) out.path = out.path[0, out.path.length - (out.path.reverse.rindex(/\/{1}\d+\/{1}/))] if out.path.index(/\/{1}\d+\/{1}/) #cuts after /id/ for a&a -- cgit v1.2.3 From 1f6625cffbb49ec93f6b4647ca1d11d01c5d11ea Mon Sep 17 00:00:00 2001 From: mguetlein Date: Thu, 10 Feb 2011 15:25:48 +0100 Subject: A&A hack for report svgs --- lib/helper.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/helper.rb b/lib/helper.rb index 5a2436f..191b932 100644 --- a/lib/helper.rb +++ b/lib/helper.rb @@ -19,6 +19,8 @@ helpers do #Check Authorization for URI with method and subjectid. def authorized?(subjectid) + # hack for reports, address problem as soon as subjectid is not longer allowed as param + return true if request.env['REQUEST_URI'] =~ /validation\/report\/.*svg$/ request_method = request.env['REQUEST_METHOD'] uri = clean_uri("#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}") request_method = "GET" if request_method == "POST" && uri =~ /\/model\/\d+\/?$/ -- cgit v1.2.3 From 616bb4b7aefed40cda0e09c88dc35ce1a82f8106 Mon Sep 17 00:00:00 2001 From: mguetlein Date: Thu, 10 Feb 2011 15:49:04 +0100 Subject: small fixes --- lib/parser.rb | 2 +- lib/rest_client_wrapper.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/parser.rb b/lib/parser.rb index 2f59d15..a2a96be 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -84,7 +84,7 @@ module OpenTox uri = triple[0] end end - File.delete(file) + File.delete(file.path) # load metadata metadata = {} triples.each_line do |line| diff --git a/lib/rest_client_wrapper.rb b/lib/rest_client_wrapper.rb index 7a6ed2a..d3136c7 100644 --- a/lib/rest_client_wrapper.rb +++ b/lib/rest_client_wrapper.rb @@ -62,6 +62,7 @@ module OpenTox waiting_task!=nil and !(waiting_task.is_a?(Task) || waiting_task.is_a?(SubTask)) headers.each{ |k,v| headers.delete(k) if v==nil } if headers #remove keys with empty values, as this can cause problems ## PENDING partner services accept subjectid only in header + headers = {} unless headers headers[:subjectid] = payload.delete(:subjectid) if payload and payload.is_a?(Hash) and payload.has_key?(:subjectid) # PENDING needed for NUTA, until we finally agree on how to send subjectid -- cgit v1.2.3 From 85096197c1618cea45d7a1a8d5e4810ce1166083 Mon Sep 17 00:00:00 2001 From: mguetlein Date: Thu, 10 Feb 2011 15:55:30 +0100 Subject: render https as link --- lib/to-html.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/to-html.rb b/lib/to-html.rb index e9764ef..4de5ee6 100755 --- a/lib/to-html.rb +++ b/lib/to-html.rb @@ -7,7 +7,7 @@ class String # encloses URI in text with with link tag # @return [String] new text with marked links def link_urls - self.gsub(/(?i)http:\/\/[^\r\n\s']*/, '\0') + self.gsub(/(?i)http(s?):\/\/[^\r\n\s']*/, '\0') end end -- cgit v1.2.3 From 1e2d28baa88f3a021604787c1356d6be1077be7e Mon Sep 17 00:00:00 2001 From: mguetlein Date: Thu, 10 Feb 2011 17:39:17 +0100 Subject: adjust rdf dataset parsing: take XSD type into account --- lib/parser.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/parser.rb b/lib/parser.rb index a2a96be..c8a573f 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -165,7 +165,13 @@ module OpenTox File.delete(file.path) data.each do |id,entry| entry[:values].each do |value_id| - value = feature_values[value_id].split(/\^\^/).first # remove XSD.type + split = feature_values[value_id].split(/\^\^/) + case split[-1] + when XSD.double + value = split.first.to_f + else + value = split.first + end @dataset.add entry[:compound],feature[value_id],value end end -- cgit v1.2.3 From 3c7e4de0e3f4c9bbf8df55c88f155b40f575b3ab Mon Sep 17 00:00:00 2001 From: mguetlein Date: Fri, 11 Feb 2011 10:51:01 +0100 Subject: fix read from rdf file --- lib/dataset.rb | 7 ++++++- lib/parser.rb | 39 ++++++++++++++++++++++----------------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/lib/dataset.rb b/lib/dataset.rb index 02b89cb..a843cea 100644 --- a/lib/dataset.rb +++ b/lib/dataset.rb @@ -85,7 +85,12 @@ module OpenTox end def load_rdfxml(rdfxml) - load_rdfxml_file Tempfile.open("ot-rdfxml"){|f| f.write(rdfxml)}.path + raise "rdfxml data is empty" if rdfxml.to_s.size==0 + file = Tempfile.new("ot-rdfxml") + file.puts rdfxml + file.close + load_rdfxml_file file + file.delete end # Load RDF/XML representation from a file diff --git a/lib/parser.rb b/lib/parser.rb index c8a573f..f79b5e2 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -30,28 +30,29 @@ module OpenTox # Read metadata from opentox service # @return [Hash] Object metadata def load_metadata(subjectid=nil) - if @dataset - uri = File.join(@uri,"metadata") - else - uri = @uri - end # avoid using rapper directly because of 2 reasons: # * http errors wont be noticed # * subjectid cannot be sent as header ##uri += "?subjectid=#{CGI.escape(subjectid)}" if subjectid ## `rapper -i rdfxml -o ntriples #{uri} 2>/dev/null`.each_line do |line| - file = Tempfile.new("ot-rdfxml") - file.puts OpenTox::RestClientWrapper.get @uri,{:subjectid => subjectid,:accept => "application/rdf+xml"},nil,false - file.close + if File.exist?(@uri) + file = File.new(@uri) + else + file = Tempfile.new("ot-rdfxml") + uri = @dataset ? File.join(@uri,"metadata") : @uri + file.puts OpenTox::RestClientWrapper.get uri,{:subjectid => subjectid,:accept => "application/rdf+xml"},nil,false + file.close + to_delete = file.path + end statements = [] parameter_ids = [] - `rapper -i rdfxml -o ntriples file://#{file.path} 2>/dev/null`.each_line do |line| + `rapper -i rdfxml -o ntriples #{file.path} 2>/dev/null`.each_line do |line| triple = line.to_triple @metadata[triple[1]] = triple[2].split('^^').first if triple[0] == @uri and triple[1] != RDF['type'] statements << triple parameter_ids << triple[2] if triple[1] == OT.parameters end - File.delete(file.path) + File.delete(to_delete) if to_delete unless parameter_ids.empty? @metadata[OT.parameters] = [] parameter_ids.each do |p| @@ -73,7 +74,7 @@ module OpenTox file.puts rdf file.close #puts "cmd: rapper -i rdfxml -o ntriples #{file} 2>/dev/null" - triples = `rapper -i rdfxml -o ntriples file://#{file.path} 2>/dev/null` + triples = `rapper -i rdfxml -o ntriples #{file.path} 2>/dev/null` # load uri via type uri = nil @@ -130,22 +131,26 @@ module OpenTox # dataset.save # @return [Hash] Internal dataset representation def load_uri(subjectid=nil) - uri = @uri # avoid using rapper directly because of 2 reasons: # * http errors wont be noticed # * subjectid cannot be sent as header ##uri += "?subjectid=#{CGI.escape(subjectid)}" if subjectid ##`rapper -i rdfxml -o ntriples #{file} 2>/dev/null`.each_line do |line| - file = Tempfile.new("ot-rdfxml") - file.puts OpenTox::RestClientWrapper.get @uri,{:subjectid => subjectid,:accept => "application/rdf+xml"},nil,false - file.close + if File.exist?(@uri) + file = File.new(@uri) + else + file = Tempfile.new("ot-rdfxml") + file.puts OpenTox::RestClientWrapper.get @uri,{:subjectid => subjectid,:accept => "application/rdf+xml"},nil,false + file.close + to_delete = file.path + end data = {} feature_values = {} feature = {} other_statements = {} - `rapper -i rdfxml -o ntriples file://#{file.path} 2>/dev/null`.each_line do |line| + `rapper -i rdfxml -o ntriples #{file.path} 2>/dev/null`.each_line do |line| triple = line.chomp.split(' ',3) triple = triple[0..2].collect{|i| i.sub(/\s+.$/,'').gsub(/[<>"]/,'')} case triple[1] @@ -162,7 +167,7 @@ module OpenTox else end end - File.delete(file.path) + File.delete(to_delete) if to_delete data.each do |id,entry| entry[:values].each do |value_id| split = feature_values[value_id].split(/\^\^/) -- cgit v1.2.3 From 1898a5353d790a17c3065e4349435642e1b7f701 Mon Sep 17 00:00:00 2001 From: mguetlein Date: Fri, 11 Feb 2011 16:34:41 +0100 Subject: adjust parser that dataset = daset.to_rdf.from rdf --- lib/parser.rb | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/parser.rb b/lib/parser.rb index f79b5e2..d2beeac 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -172,8 +172,10 @@ module OpenTox entry[:values].each do |value_id| split = feature_values[value_id].split(/\^\^/) case split[-1] - when XSD.double + when XSD.double, XSD.float value = split.first.to_f + when XSD.boolean + value = split.first=~/(?i)true/ ? true : false else value = split.first end @@ -188,15 +190,23 @@ module OpenTox # Read only features from a dataset service. # @return [Hash] Internal features representation def load_features(subjectid=nil) - uri = File.join(@uri,"features") - uri += "?subjectid=#{CGI.escape(subjectid)}" if subjectid + if File.exist?(@uri) + file = File.new(@uri) + else + file = Tempfile.new("ot-rdfxml") + uri = File.join(@uri,"features") + file.puts OpenTox::RestClientWrapper.get uri,{:subjectid => subjectid,:accept => "application/rdf+xml"},nil,false + file.close + to_delete = file.path + end statements = [] features = Set.new - `rapper -i rdfxml -o ntriples #{uri} 2>/dev/null`.each_line do |line| + `rapper -i rdfxml -o ntriples #{file.path} 2>/dev/null`.each_line do |line| triple = line.chomp.split('> ').collect{|i| i.sub(/\s+.$/,'').gsub(/[<>"]/,'')}[0..2] statements << triple - features << triple[0] if triple[1] == RDF['type'] and triple[2] == OT.Feature + features << triple[0] if triple[1] == RDF['type'] and (triple[2] == OT.Feature || triple[2] == OT.NumericFeature) end + File.delete(to_delete) if to_delete statements.each do |triple| if features.include? triple[0] @dataset.features[triple[0]] = {} unless @dataset.features[triple[0]] -- cgit v1.2.3 From 7dd4c74bf118285d567b0b221d091511b6a77b2f Mon Sep 17 00:00:00 2001 From: mguetlein Date: Sun, 13 Feb 2011 09:55:38 +0100 Subject: more debug out when authentication fails --- lib/authorization.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/authorization.rb b/lib/authorization.rb index 1942e95..eab20df 100644 --- a/lib/authorization.rb +++ b/lib/authorization.rb @@ -334,15 +334,15 @@ module OpenTox true elsif CONFIG[:authorization][:authenticate_request].include?(request_method) ret = OpenTox::Authorization.is_token_valid(subjectid) - #LOGGER.debug "authorized? >>#{ret}<< (token is in/valid), method: #{request_method}, URI: #{uri}, subjectid: #{subjectid}" + LOGGER.debug "authorized? >>#{ret}<< (token is in/valid), method: #{request_method}, URI: #{uri}, subjectid: #{subjectid}" unless ret ret elsif OpenTox::Authorization.authorize_exception?(uri, request_method) ret = OpenTox::Authorization.is_token_valid(subjectid) - #LOGGER.debug "authorized? >>#{ret}<< (uris is authorize exception, token is in/valid), method: #{request_method}, URI: #{uri}, subjectid: #{subjectid}" + LOGGER.debug "authorized? >>#{ret}<< (uris is authorize exception, token is in/valid), method: #{request_method}, URI: #{uri}, subjectid: #{subjectid}" unless ret ret elsif CONFIG[:authorization][:authorize_request].include?(request_method) ret = OpenTox::Authorization.authorize(uri, request_method, subjectid) - LOGGER.debug "authorized? >>#{ret}<< (uri (not) authorized), method: #{request_method}, URI: #{uri}, subjectid: #{subjectid}" + LOGGER.debug "authorized? >>#{ret}<< (uri (not) authorized), method: #{request_method}, URI: #{uri}, subjectid: #{subjectid}" unless ret ret else LOGGER.error "invalid request/uri method: #{request_method}, URI: #{uri}, subjectid: #{subjectid}" -- cgit v1.2.3 From d4eb231a35c23a5fdb36fd6220b5ab706e7528ba Mon Sep 17 00:00:00 2001 From: mguetlein Date: Mon, 14 Feb 2011 17:48:26 +0100 Subject: read from subjectcookie, fix read feature_type --- lib/feature.rb | 10 ++++++++-- lib/helper.rb | 3 +-- lib/model.rb | 22 +++++++++++++--------- lib/overwrite.rb | 2 +- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/lib/feature.rb b/lib/feature.rb index be063dd..c0729a7 100644 --- a/lib/feature.rb +++ b/lib/feature.rb @@ -16,8 +16,14 @@ module OpenTox # provides domain (possible target values) of classification feature # @return [Array] list with possible target values def domain - #TODO derieve from metadata / ontology - return [true, false] + if metadata[OT.acceptValue] + raise "accept value found, remove hack and implement correctly" + else + if @uri=~/feature\/26221/ || @uri=~/feature\/221726/ + return ["mutagen" , "nonmutagen"] + end + return [true, false] + end end # provides feature type, possible types are "regression" or "classification" diff --git a/lib/helper.rb b/lib/helper.rb index 191b932..a1590d7 100644 --- a/lib/helper.rb +++ b/lib/helper.rb @@ -19,8 +19,6 @@ helpers do #Check Authorization for URI with method and subjectid. def authorized?(subjectid) - # hack for reports, address problem as soon as subjectid is not longer allowed as param - return true if request.env['REQUEST_URI'] =~ /validation\/report\/.*svg$/ request_method = request.env['REQUEST_METHOD'] uri = clean_uri("#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}") request_method = "GET" if request_method == "POST" && uri =~ /\/model\/\d+\/?$/ @@ -52,6 +50,7 @@ before do subjectid = session[:subjectid] if session[:subjectid] subjectid = params[:subjectid] if params[:subjectid] and !subjectid subjectid = request.env['HTTP_SUBJECTID'] if request.env['HTTP_SUBJECTID'] and !subjectid + subjectid = request.cookies["subjectid"] unless subjectid # see http://rack.rubyforge.org/doc/SPEC.html subjectid = CGI.unescape(subjectid) if subjectid.include?("%23") @subjectid = subjectid diff --git a/lib/model.rb b/lib/model.rb index 9622d65..74408d8 100644 --- a/lib/model.rb +++ b/lib/model.rb @@ -38,24 +38,28 @@ module OpenTox # provides feature type, possible types are "regression" or "classification" # @return [String] feature type, "unknown" if type could not be estimated def feature_type(subjectid=nil) + return @feature_type if @feature_type + # dynamically perform restcalls if necessary load_metadata(subjectid) if @metadata==nil or @metadata.size==0 or (@metadata.size==1 && @metadata.values[0]==@uri) - - @algorithm = OpenTox::Algorithm::Generic.find(@metadata[OT.algorithm], subjectid) unless @algorithm - algorithm_title = @algorithm ? @algorithm.metadata[DC.title] : nil - algorithm_type = @algorithm ? @algorithm.metadata[OT.isA] : nil - @dependentVariable = OpenTox::Feature.find( @metadata[OT.dependentVariables],subjectid ) unless @dependentVariable - type_indicators = [@dependentVariable.feature_type, @metadata[OT.isA], @metadata[DC.title], + algorithm = OpenTox::Algorithm::Generic.find(@metadata[OT.algorithm], subjectid) + algorithm_title = algorithm ? algorithm.metadata[DC.title] : nil + algorithm_type = algorithm ? algorithm.metadata[OT.isA] : nil + dependent_variable = OpenTox::Feature.find( @metadata[OT.dependentVariables],subjectid ) + dependent_variable_type = dependent_variable ? dependent_variable.feature_type : nil + type_indicators = [dependent_variable_type, @metadata[OT.isA], @metadata[DC.title], @uri, algorithm_type, algorithm_title] type_indicators.each do |type| case type when /(?i)classification/ - return "classification" + @feature_type = "classification" + break when /(?i)regression/ - return "regression" + @feature_type = "regression" end end - raise "unknown model "+type_indicators.inspect + raise "unknown model "+type_indicators.inspect unless @feature_type + @feature_type end end diff --git a/lib/overwrite.rb b/lib/overwrite.rb index 7b53122..29a2860 100644 --- a/lib/overwrite.rb +++ b/lib/overwrite.rb @@ -45,7 +45,7 @@ class Sinatra::Base response['Content-Type'] = "application/rdf+xml" halt code,task.to_rdfxml when /yaml/ - response['Content-Type'] = "application/rdf+xml" + response['Content-Type'] = "application/x-yaml" halt code,task.to_yaml # PENDING differs from task-webservice when /html/ response['Content-Type'] = "text/html" -- cgit v1.2.3 From 8921d20b9d399274b0674794301ff3567ac7c816 Mon Sep 17 00:00:00 2001 From: mguetlein Date: Mon, 14 Feb 2011 18:01:42 +0100 Subject: handle nil values in split --- lib/dataset.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/dataset.rb b/lib/dataset.rb index a843cea..a0f99b1 100644 --- a/lib/dataset.rb +++ b/lib/dataset.rb @@ -237,7 +237,7 @@ module OpenTox @features[feature] = {} unless @features[feature] @data_entries[compound] = {} unless @data_entries[compound] @data_entries[compound][feature] = [] unless @data_entries[compound][feature] - @data_entries[compound][feature] << value + @data_entries[compound][feature] << value unless value end # Add/modify metadata, existing entries will be overwritten @@ -283,8 +283,12 @@ module OpenTox else compounds.each do |c| features.each do |f| - @data_entries[c][f].each do |v| - dataset.add(c,f,v) + unless @data_entries[c][f] + dataset.add(c,f,nil) + else + @data_entries[c][f].each do |v| + dataset.add(c,f,v) + end end end end -- cgit v1.2.3 From 267b691017202c2fccf69dbeecfd4ed524a73fc2 Mon Sep 17 00:00:00 2001 From: mguetlein Date: Mon, 14 Feb 2011 18:28:12 +0100 Subject: fix: handle nil values in split --- lib/dataset.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dataset.rb b/lib/dataset.rb index a0f99b1..efab0a3 100644 --- a/lib/dataset.rb +++ b/lib/dataset.rb @@ -237,7 +237,7 @@ module OpenTox @features[feature] = {} unless @features[feature] @data_entries[compound] = {} unless @data_entries[compound] @data_entries[compound][feature] = [] unless @data_entries[compound][feature] - @data_entries[compound][feature] << value unless value + @data_entries[compound][feature] << value if value end # Add/modify metadata, existing entries will be overwritten -- cgit v1.2.3 From 1bac8bd64f9f703d7e20a65da0ffb05cb150e90f Mon Sep 17 00:00:00 2001 From: mguetlein Date: Sat, 19 Feb 2011 11:26:24 +0100 Subject: add login functionality to webservices --- lib/overwrite.rb | 4 +-- lib/to-html.rb | 99 ++++++++++++++++++++++++++++++++++++-------------------- 2 files changed, 66 insertions(+), 37 deletions(-) diff --git a/lib/overwrite.rb b/lib/overwrite.rb index 29a2860..b5c3942 100644 --- a/lib/overwrite.rb +++ b/lib/overwrite.rb @@ -29,7 +29,7 @@ error Exception do halt error.http_code,rep.to_rdfxml when /html/ content_type 'text/html' - halt error.http_code,(OpenTox.text_to_html rep.to_yaml) + halt error.http_code,(OpenTox.text_to_html rep.to_yaml, @subjectid) else content_type 'application/x-yaml' halt error.http_code,rep.to_yaml @@ -49,7 +49,7 @@ class Sinatra::Base halt code,task.to_yaml # PENDING differs from task-webservice when /html/ response['Content-Type'] = "text/html" - halt code,OpenTox.text_to_html(task.to_yaml) + halt code,OpenTox.text_to_html(task.to_yaml, @subjectid) else # default /uri-list/ response['Content-Type'] = "text/uri-list" halt code,task.uri+"\n" diff --git a/lib/to-html.rb b/lib/to-html.rb index 4de5ee6..1f30ca1 100755 --- a/lib/to-html.rb +++ b/lib/to-html.rb @@ -1,7 +1,6 @@ OT_LOGO = "http://opentox.informatik.uni-freiburg.de/ot-logo.png" - class String # encloses URI in text with with link tag @@ -25,29 +24,30 @@ module OpenTox # @param [optional,String] description general info # @param [optional,Array] post_params, array of arrays containing info on POST operation, see example # @return [String] html page - def self.text_to_html( text, related_links=nil, description=nil, post_params=nil ) + def self.text_to_html( text, subjectid=nil, related_links=nil, description=nil, post_params=nil ) # TODO add title as parameter title = nil #$sinatra.url_for($sinatra.request.env['PATH_INFO'], :full) if $sinatra - - html = < -EOF - html.chomp! + html = "" html += ""+title+"" if title - html += < - -EOF - html.chomp! - html += "

Description

"+description.link_urls+"

" if description - html += "

Related links

"+related_links.link_urls+"

" if related_links - if post_params + html += "" + + if AA_SERVER + user = OpenTox::Authorization.get_user(subjectid) if subjectid + html += "

" + unless user + html += "You are currently not logged in to "+$url_provider.url_for("",:full)+ + ", login" + else + html += "You are logged in as '#{user}' to "+$url_provider.url_for("",:full)+ + ", logout" + end + html += "

" + end + + html += "

Description

"+description.link_urls+"

" if description + html += "

Related links

"+related_links.link_urls+"

" if related_links + if post_params html += "

POST parameters

" count = 0 post_params.each do |p| @@ -59,23 +59,52 @@ EOF html += "

" count += 1 end - end - html += "

Content

" if description || related_links - html += < -

-EOF - html.chomp! - html += text.link_urls - html += < - - - -EOF + end + html += "

Content

" if description || related_links + html += "

" + html += text.link_urls + html += "

" html end + def self.login( msg=nil ) + html = "Login" + html += "
" + html += "

" + html += msg+"\n\n" if msg + html += "Please login to "+$url_provider.url_for("",:full)+"\n\n" + html += "" + html += ""+ + ""+ + #""+ + "" + html += "
user:
password:

" + html + end +end + +get '/logout/?' do + response.set_cookie("subjectid",{:value=>nil}) + content_type "text/html" + content = "Sucessfully logged out from "+$url_provider.url_for("",:full) + OpenTox.text_to_html(content) +end + +get '/login/?' do + content_type "text/html" + OpenTox.login +end + +post '/login/?' do + subjectid = OpenTox::Authorization.authenticate(params[:user], params[:password]) + if (subjectid) + response.set_cookie("subjectid",{:value=>subjectid}) + content_type "text/html" + content = "Sucessfully logged in as '"+params[:user]+"' to "+$url_provider.url_for("",:full) + OpenTox.text_to_html(content,subjectid) + else + content_type "text/html" + OpenTox.login("Login failed, please try again") + end end -#puts OpenTox.text_to_html("bla") \ No newline at end of file -- cgit v1.2.3 From 80d49f60ac55cc2fb1c7974752e1e947fa3f3f70 Mon Sep 17 00:00:00 2001 From: mguetlein Date: Mon, 21 Feb 2011 13:52:00 +0100 Subject: clean uri now works for https, rdf parsing issues: /features and ?feature_uris, missing subjectid --- lib/dataset.rb | 2 ++ lib/helper.rb | 5 +++-- lib/parser.rb | 43 +++++++++++++++++++++++++++++-------------- lib/rest_client_wrapper.rb | 3 ++- 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/lib/dataset.rb b/lib/dataset.rb index efab0a3..3f530e6 100644 --- a/lib/dataset.rb +++ b/lib/dataset.rb @@ -141,8 +141,10 @@ module OpenTox if (CONFIG[:yaml_hosts].include?(URI.parse(@uri).host)) copy YAML.load(RestClientWrapper.get(@uri, {:accept => "application/x-yaml", :subjectid => subjectid})) else + puts "loading all.." parser = Parser::Owl::Dataset.new(@uri, subjectid) copy parser.load_uri(subjectid) + puts "..done" end end diff --git a/lib/helper.rb b/lib/helper.rb index a1590d7..b30908c 100644 --- a/lib/helper.rb +++ b/lib/helper.rb @@ -32,8 +32,9 @@ helpers do uri = uri[0,uri.index("InChI=")] if uri.index("InChI=") out = URI.parse(uri) - out.path = out.path[0, out.path.length - (out.path.reverse.rindex(/\/{1}\d+\/{1}/))] if out.path.index(/\/{1}\d+\/{1}/) #cuts after /id/ for a&a - "#{out.scheme}:" + (out.port != 80 ? out.port : "") + "//#{out.host}#{out.path.chomp('/')}" + out.path = out.path[0, out.path.length - (out.path.reverse.rindex(/\/{1}\d+\/{1}/))] if out.path.index(/\/{1}\d+\/{1}/) #cuts after /id/ for a&a + port = (out.scheme=="http" && out.port==80)||(out.scheme=="https" && out.port==443) ? "" : ":#{out.port.to_s}" + "#{out.scheme}://#{out.host}#{port}#{out.path.chomp("/")}" #" end #unprotected uri for login diff --git a/lib/parser.rb b/lib/parser.rb index d2beeac..f1249ad 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -39,7 +39,14 @@ module OpenTox file = File.new(@uri) else file = Tempfile.new("ot-rdfxml") - uri = @dataset ? File.join(@uri,"metadata") : @uri + if @dataset + # do not concat /metadata to uri string, this would not work for dataset/R401577?max=3 + uri = URI::parse(@uri) + uri.path = File.join(uri.path,"metadata") + uri = uri.to_s + else + uri = @uri + end file.puts OpenTox::RestClientWrapper.get uri,{:subjectid => subjectid,:accept => "application/rdf+xml"},nil,false file.close to_delete = file.path @@ -163,26 +170,31 @@ module OpenTox data[triple[0]] = {:compound => "", :values => []} unless data[triple[0]] data[triple[0]][:compound] = triple[2] when /#{OT.feature}/i - feature[triple[0]] = triple[2] + feature[triple[0]] = triple[2] else end end File.delete(to_delete) if to_delete data.each do |id,entry| - entry[:values].each do |value_id| - split = feature_values[value_id].split(/\^\^/) - case split[-1] - when XSD.double, XSD.float - value = split.first.to_f - when XSD.boolean - value = split.first=~/(?i)true/ ? true : false - else - value = split.first + if entry[:values].size==0 + # no feature values add plain compounds + @dataset.add_compound(entry[:compound]) + else + entry[:values].each do |value_id| + split = feature_values[value_id].split(/\^\^/) + case split[-1] + when XSD.double, XSD.float + value = split.first.to_f + when XSD.boolean + value = split.first=~/(?i)true/ ? true : false + else + value = split.first + end + @dataset.add entry[:compound],feature[value_id],value end - @dataset.add entry[:compound],feature[value_id],value end end - load_features + load_features subjectid @dataset.metadata = load_metadata(subjectid) @dataset end @@ -194,7 +206,10 @@ module OpenTox file = File.new(@uri) else file = Tempfile.new("ot-rdfxml") - uri = File.join(@uri,"features") + # do not concat /features to uri string, this would not work for dataset/R401577?max=3 + uri = URI::parse(@uri) + uri.path = File.join(uri.path,"features") + uri = uri.to_s file.puts OpenTox::RestClientWrapper.get uri,{:subjectid => subjectid,:accept => "application/rdf+xml"},nil,false file.close to_delete = file.path diff --git a/lib/rest_client_wrapper.rb b/lib/rest_client_wrapper.rb index d3136c7..dac24dc 100644 --- a/lib/rest_client_wrapper.rb +++ b/lib/rest_client_wrapper.rb @@ -76,7 +76,8 @@ module OpenTox else result = resource.send(rest_call, headers) end - + #LOGGER.debug "result body size: #{result.body.size}" + # PENDING NTUA does return errors with 200 raise RestClient::ExceptionWithResponse.new(result) if uri=~/ntua/ and result.body =~ /about.*http:\/\/anonymous.org\/error/ -- cgit v1.2.3 From 53dec3e3b1a59760ac9440749d159edc7ac09359 Mon Sep 17 00:00:00 2001 From: mguetlein Date: Mon, 21 Feb 2011 14:10:44 +0100 Subject: removing debug msg --- lib/dataset.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/dataset.rb b/lib/dataset.rb index 3f530e6..efab0a3 100644 --- a/lib/dataset.rb +++ b/lib/dataset.rb @@ -141,10 +141,8 @@ module OpenTox if (CONFIG[:yaml_hosts].include?(URI.parse(@uri).host)) copy YAML.load(RestClientWrapper.get(@uri, {:accept => "application/x-yaml", :subjectid => subjectid})) else - puts "loading all.." parser = Parser::Owl::Dataset.new(@uri, subjectid) copy parser.load_uri(subjectid) - puts "..done" end end -- cgit v1.2.3 From 9bc9d1c5c11aa64d410200cc21d07acc39cc3019 Mon Sep 17 00:00:00 2001 From: mguetlein Date: Mon, 21 Feb 2011 15:44:24 +0100 Subject: fix for Datset#add false values, fix for parsing compounds without values --- lib/dataset.rb | 2 +- lib/parser.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/dataset.rb b/lib/dataset.rb index efab0a3..2c47502 100644 --- a/lib/dataset.rb +++ b/lib/dataset.rb @@ -237,7 +237,7 @@ module OpenTox @features[feature] = {} unless @features[feature] @data_entries[compound] = {} unless @data_entries[compound] @data_entries[compound][feature] = [] unless @data_entries[compound][feature] - @data_entries[compound][feature] << value if value + @data_entries[compound][feature] << value if value!=nil end # Add/modify metadata, existing entries will be overwritten diff --git a/lib/parser.rb b/lib/parser.rb index f1249ad..f33017d 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -171,6 +171,10 @@ module OpenTox data[triple[0]][:compound] = triple[2] when /#{OT.feature}/i feature[triple[0]] = triple[2] + when /#{RDF.type}/i + if triple[2]=~/#{OT.Compound}/i and !data[triple[0]] + data[triple[0]] = {:compound => triple[0], :values => []} + end else end end -- cgit v1.2.3 From 2b61ab65d17b6c80b0afdee33956c263cd7f9c21 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 24 Feb 2011 11:59:56 +0000 Subject: set :lock, true in config_ru.rb, detect accept header in before filter --- lib/config/config_ru.rb | 1 + lib/helper.rb | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/lib/config/config_ru.rb b/lib/config/config_ru.rb index 3d8dce2..93df867 100644 --- a/lib/config/config_ru.rb +++ b/lib/config/config_ru.rb @@ -12,6 +12,7 @@ $stdout.sync = true $stderr.sync = true set :logging, false set :raise_errors, true +set :lock, true ['public','tmp'].each do |dir| FileUtils.mkdir_p dir unless File.exists?(dir) diff --git a/lib/helper.rb b/lib/helper.rb index b30908c..37238bd 100644 --- a/lib/helper.rb +++ b/lib/helper.rb @@ -61,6 +61,26 @@ before do end @subjectid = subjectid protected!(subjectid) + + extension = File.extname(request.path_info) # params[:id] is not yet available + unless extension.empty? + #request.path_info.sub!(/\.#{extension}$/,'') + case extension + when "html" + @accept = 'text/html' + when "yaml" + @accept = 'application/x-yaml' + when "csv" + @accept = 'text/csv' + when "rdfxml" + @accept = 'application/rdf+xml' + when "xls" + @accept = 'application/ms-excel' + else + halt 404, "File format #{extension} not supported." + end + end + end end -- cgit v1.2.3 From 96e7db0a280ed3f28266a117a27cc69cb800063f Mon Sep 17 00:00:00 2001 From: root Date: Fri, 25 Feb 2011 11:45:05 +0000 Subject: experiments with db adapters --- lib/environment.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/environment.rb b/lib/environment.rb index b30b3f3..5b16e4a 100644 --- a/lib/environment.rb +++ b/lib/environment.rb @@ -25,11 +25,17 @@ end # database if CONFIG[:database] ['dm-core', 'dm-serializer', 'dm-timestamps', 'dm-types', 'dm-migrations', 'dm-validations' ].each{|lib| require lib } +=begin +=end case CONFIG[:database][:adapter] when /sqlite/i db_dir = File.join(basedir, "db") FileUtils.mkdir_p db_dir DataMapper::setup(:default, "sqlite3://#{db_dir}/opentox.sqlite3") + #when /yaml/i + #db_dir = File.join(basedir, "db") + #FileUtils.mkdir_p db_dir + #DataMapper::setup(:default, {:adapter => "yaml", :directory => 'db'}) else DataMapper.setup(:default, { :adapter => CONFIG[:database][:adapter], @@ -38,6 +44,14 @@ if CONFIG[:database] :password => CONFIG[:database][:password], :host => CONFIG[:database][:host]}) end + #db_dir = File.join(basedir, "db") + #FileUtils.mkdir_p db_dir + #DataMapper::setup(:in_memory, "in_memory") + #require 'redis' + + #DataMapper.setup(:default, {:adapter => "redis"}) + + #DataMapper::Model.raise_on_save_failure = true end # load mail settings for error messages -- cgit v1.2.3 From d3dfccb8e46e1d5677877b833fa81acad4d026d4 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 25 Feb 2011 17:54:39 +0000 Subject: ohm/redis backend --- Rakefile | 1 + lib/environment.rb | 2 ++ lib/opentox-ruby.rb | 2 +- lib/overwrite.rb | 6 ++++++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 2578bb4..e49efc3 100644 --- a/Rakefile +++ b/Rakefile @@ -27,6 +27,7 @@ begin "yajl-ruby", "tmail", "rinruby", + "ohm", "rjb" ].each { |dep| gem.add_dependency dep } [ "dm-core", diff --git a/lib/environment.rb b/lib/environment.rb index 5b16e4a..73df64e 100644 --- a/lib/environment.rb +++ b/lib/environment.rb @@ -52,6 +52,8 @@ if CONFIG[:database] #DataMapper.setup(:default, {:adapter => "redis"}) #DataMapper::Model.raise_on_save_failure = true + require 'ohm' + Ohm.connect :thread_safe => true end # load mail settings for error messages diff --git a/lib/opentox-ruby.rb b/lib/opentox-ruby.rb index 735b845..ab8d824 100644 --- a/lib/opentox-ruby.rb +++ b/lib/opentox-ruby.rb @@ -1,4 +1,4 @@ -['rubygems', 'sinatra', 'sinatra/url_for', 'rest_client', 'yaml', 'cgi', 'spork', 'error', 'overwrite', 'environment'].each do |lib| +['rubygems', 'sinatra', 'sinatra/url_for', 'ohm', 'rest_client', 'yaml', 'cgi', 'spork', 'error', 'overwrite', 'environment'].each do |lib| require lib end diff --git a/lib/overwrite.rb b/lib/overwrite.rb index b5c3942..fbe775d 100644 --- a/lib/overwrite.rb +++ b/lib/overwrite.rb @@ -137,3 +137,9 @@ class OTLogger < Logger end +# make migration from datamapper more straightforward +class Ohm::Model + def self.get(id) + self[id] + end +end -- cgit v1.2.3 From 9f91e5438a32f0b3b5b3755adfa20d4e0d36566b Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Sun, 27 Feb 2011 09:32:57 +0100 Subject: code cleanup --- Rakefile | 2 ++ lib/environment.rb | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Rakefile b/Rakefile index e49efc3..eb0b23a 100644 --- a/Rakefile +++ b/Rakefile @@ -30,6 +30,7 @@ begin "ohm", "rjb" ].each { |dep| gem.add_dependency dep } +=begin [ "dm-core", 'dm-serializer', 'dm-timestamps', @@ -38,6 +39,7 @@ begin "dm-mysql-adapter", "dm-validations", ].each {|dep| gem.add_dependency dep, ">= 1" } +=end gem.add_dependency "haml", ">=3" ['jeweler'].each { |dep| gem.add_development_dependency dep } gem.files = FileList["[A-Z]*", "{bin,generators,lib,test}/**/*", 'lib/jeweler/templates/.gitignore'] diff --git a/lib/environment.rb b/lib/environment.rb index 73df64e..4f04e48 100644 --- a/lib/environment.rb +++ b/lib/environment.rb @@ -23,10 +23,9 @@ else end # database +=begin if CONFIG[:database] ['dm-core', 'dm-serializer', 'dm-timestamps', 'dm-types', 'dm-migrations', 'dm-validations' ].each{|lib| require lib } -=begin -=end case CONFIG[:database][:adapter] when /sqlite/i db_dir = File.join(basedir, "db") @@ -52,9 +51,10 @@ if CONFIG[:database] #DataMapper.setup(:default, {:adapter => "redis"}) #DataMapper::Model.raise_on_save_failure = true - require 'ohm' - Ohm.connect :thread_safe => true end +=end +require 'ohm' +Ohm.connect :thread_safe => true # load mail settings for error messages load File.join config_dir,"mail.rb" if File.exists?(File.join config_dir,"mail.rb") -- cgit v1.2.3 From 2b0ad7c00ec9559d05e07e78cee5f7846d51ee5f Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Thu, 3 Mar 2011 11:35:54 +0100 Subject: login methods in to-html.rb removed, interferes with ToxCreate login --- Rakefile | 1 + lib/to-html.rb | 2 ++ 2 files changed, 3 insertions(+) mode change 100755 => 100644 lib/to-html.rb diff --git a/Rakefile b/Rakefile index eb0b23a..b496cc2 100644 --- a/Rakefile +++ b/Rakefile @@ -28,6 +28,7 @@ begin "tmail", "rinruby", "ohm", + "SystemTimer", "rjb" ].each { |dep| gem.add_dependency dep } =begin diff --git a/lib/to-html.rb b/lib/to-html.rb old mode 100755 new mode 100644 index 1f30ca1..6785974 --- a/lib/to-html.rb +++ b/lib/to-html.rb @@ -83,6 +83,7 @@ module OpenTox end end +=begin get '/logout/?' do response.set_cookie("subjectid",{:value=>nil}) content_type "text/html" @@ -107,4 +108,5 @@ post '/login/?' do OpenTox.login("Login failed, please try again") end end +=end -- cgit v1.2.3 From 1ffb44de021b276d3ae25fc9d9b09ec1f0f9aa16 Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Wed, 9 Mar 2011 10:24:49 +0100 Subject: old database config removed --- lib/environment.rb | 32 +------------------------------- lib/helper.rb | 13 +++++++++++++ 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/lib/environment.rb b/lib/environment.rb index 4f04e48..59578c1 100644 --- a/lib/environment.rb +++ b/lib/environment.rb @@ -23,37 +23,7 @@ else end # database -=begin -if CONFIG[:database] - ['dm-core', 'dm-serializer', 'dm-timestamps', 'dm-types', 'dm-migrations', 'dm-validations' ].each{|lib| require lib } - case CONFIG[:database][:adapter] - when /sqlite/i - db_dir = File.join(basedir, "db") - FileUtils.mkdir_p db_dir - DataMapper::setup(:default, "sqlite3://#{db_dir}/opentox.sqlite3") - #when /yaml/i - #db_dir = File.join(basedir, "db") - #FileUtils.mkdir_p db_dir - #DataMapper::setup(:default, {:adapter => "yaml", :directory => 'db'}) - else - DataMapper.setup(:default, { - :adapter => CONFIG[:database][:adapter], - :database => CONFIG[:database][:database], - :username => CONFIG[:database][:username], - :password => CONFIG[:database][:password], - :host => CONFIG[:database][:host]}) - end - #db_dir = File.join(basedir, "db") - #FileUtils.mkdir_p db_dir - #DataMapper::setup(:in_memory, "in_memory") - #require 'redis' - - #DataMapper.setup(:default, {:adapter => "redis"}) - - #DataMapper::Model.raise_on_save_failure = true -end -=end -require 'ohm' +`redis-server /opt/redis/redis.conf` unless File.exists? "/var/run/redis.pid" Ohm.connect :thread_safe => true # load mail settings for error messages diff --git a/lib/helper.rb b/lib/helper.rb index 37238bd..3031b74 100644 --- a/lib/helper.rb +++ b/lib/helper.rb @@ -42,6 +42,19 @@ helpers do return env['REQUEST_URI'] =~ /\/login$/ end + def uri_available?(urlStr) + url = URI.parse(urlStr) + unless @subjectid + Net::HTTP.start(url.host, url.port) do |http| + return http.head(url.request_uri).code == "200" + end + else + Net::HTTP.start(url.host, url.port) do |http| + return http.post(url.request_uri, "subjectid=#{@subjectid}").code == "202" + end + end + end + end before do -- cgit v1.2.3 From 521c615c5aae4b2aaf28cdbcf5d6d37e85362f14 Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Wed, 9 Mar 2011 10:32:51 +0100 Subject: Version bump to 1.0.0 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 4e379d2..afaf360 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.2 +1.0.0 \ No newline at end of file -- cgit v1.2.3 From d1f555bdb0368fc82a2bb379ab7857bae727072a Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Wed, 9 Mar 2011 11:37:57 +0100 Subject: README updated and converted to markdown --- README.markdown | 43 +++++++++++++++++++++++++++++++++++++++++++ README.rdoc | 23 ----------------------- 2 files changed, 43 insertions(+), 23 deletions(-) create mode 100644 README.markdown delete mode 100644 README.rdoc diff --git a/README.markdown b/README.markdown new file mode 100644 index 0000000..8f18598 --- /dev/null +++ b/README.markdown @@ -0,0 +1,43 @@ +opentox-ruby +============ + +Ruby wrapper for the OpenTox REST API (http://www.opentox.org) + +Installation +------------ + +opentox-ruby depends on many third party programs and libraries, which makes the setup complicated and error prone. For this reason we recommend to use the installer from (opentox-install)[http://github.com/opentox/opentox-install]. If you want to install manually you can find the necessary steps in the installation scripts. + +Quickstart +---------- + +This example shows how to create a lazar model and predict a compound, it assumes that you have access to a working installation of OpenTox services with corresponding settings in $HOME/.opentox/config. Run the following code in irb or from a ruby script: + + require 'rubygems' + require 'opentox-ruby' + + # Authenticate + subjectid = OpenTox::Authorization.authenticate(USER,PASSWORD) + + # Upload a dataset + training_dataset = OpenTox::Dataset.create_from_csv_file(TRAINING_DATASET, subjectid) + + # Create a prediction model + model_uri = OpenTox::Algorithm::Lazar.new.run({:dataset_uri => training_dataset.uri, :subjectid => subjectid}).to_s + lazar = OpenTox::Model::Lazar.find model_uri, subjectid + + # Predict a compound + compound = OpenTox::Compound.from_smiles("c1ccccc1NN") + prediction_uri = lazar.run(:compound_uri => compound.uri, :subjectid => subjectid) + prediction = OpenTox::LazarPrediction.find(prediction_uri, subjectid) + puts prediction.to_yaml + +API documentation +----------------- + +http://rdoc.info/gems/opentox-ruby/0.0.2/frames + +Copyright +-------- + +Copyright (c) 2009-2011 Christoph Helma. See LICENSE for details. diff --git a/README.rdoc b/README.rdoc deleted file mode 100644 index 45cc5f6..0000000 --- a/README.rdoc +++ /dev/null @@ -1,23 +0,0 @@ -= opentox-ruby - -Ruby wrapper for the OpenTox REST API (http://www.opentox.org) - -== Installation - -Run the following if you haven't already: - - gem sources -a http://gems.github.com - -Install the gem: - - sudo gem install helma-opentox-ruby - -== Usage - -- adjust the settings in $HOME/.opentox/config -- require 'opentox-ruby' in your ruby application -- consult the rdoc API documentation for details - -== Copyright - -Copyright (c) 2009-2010 Christoph Helma. See LICENSE for details. -- cgit v1.2.3 From ef5c37ed8cb76b476133d3f7e013059b9bb13fee Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Wed, 9 Mar 2011 12:16:07 +0100 Subject: README fixed --- README.markdown | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/README.markdown b/README.markdown index 8f18598..4254748 100644 --- a/README.markdown +++ b/README.markdown @@ -1,43 +1,41 @@ opentox-ruby ============ -Ruby wrapper for the OpenTox REST API (http://www.opentox.org) +Ruby wrapper for the [OpenTox](http://www.opentox.org) REST API Installation ------------ -opentox-ruby depends on many third party programs and libraries, which makes the setup complicated and error prone. For this reason we recommend to use the installer from (opentox-install)[http://github.com/opentox/opentox-install]. If you want to install manually you can find the necessary steps in the installation scripts. +opentox-ruby depends on many third party programs and libraries, which makes the setup complicated and error prone. For this reason we recommend to use the installer from [opentox-install](http://github.com/opentox/opentox-install). If you want to install manually you can find the necessary steps in the installation scripts. Quickstart ---------- This example shows how to create a lazar model and predict a compound, it assumes that you have access to a working installation of OpenTox services with corresponding settings in $HOME/.opentox/config. Run the following code in irb or from a ruby script: - require 'rubygems' - require 'opentox-ruby' + require 'rubygems' + require 'opentox-ruby' - # Authenticate - subjectid = OpenTox::Authorization.authenticate(USER,PASSWORD) + # Authenticate + subjectid = OpenTox::Authorization.authenticate(USER,PASSWORD) - # Upload a dataset - training_dataset = OpenTox::Dataset.create_from_csv_file(TRAINING_DATASET, subjectid) + # Upload a dataset + training_dataset = OpenTox::Dataset.create_from_csv_file(TRAINING_DATASET, subjectid) - # Create a prediction model - model_uri = OpenTox::Algorithm::Lazar.new.run({:dataset_uri => training_dataset.uri, :subjectid => subjectid}).to_s - lazar = OpenTox::Model::Lazar.find model_uri, subjectid - - # Predict a compound - compound = OpenTox::Compound.from_smiles("c1ccccc1NN") - prediction_uri = lazar.run(:compound_uri => compound.uri, :subjectid => subjectid) - prediction = OpenTox::LazarPrediction.find(prediction_uri, subjectid) - puts prediction.to_yaml + # Create a prediction model + model_uri = OpenTox::Algorithm::Lazar.new.run({:dataset_uri => training_dataset.uri, :subjectid => subjectid}).to_s + lazar = OpenTox::Model::Lazar.find model_uri, subjectid + + # Predict a compound + compound = OpenTox::Compound.from_smiles("c1ccccc1NN") + prediction_uri = lazar.run(:compound_uri => compound.uri, :subjectid => subjectid) + prediction = OpenTox::LazarPrediction.find(prediction_uri, subjectid) + puts prediction.to_yaml -API documentation ------------------ - -http://rdoc.info/gems/opentox-ruby/0.0.2/frames +[API documentation](http://rdoc.info/gems/opentox-ruby/0.0.2/frames) +------------------------------------------------------------------- Copyright --------- +--------- -Copyright (c) 2009-2011 Christoph Helma. See LICENSE for details. +Copyright (c) 2009-2011 Christoph Helma, Martin Guetlein, Micha Rautenberg, Andreas Maunz, David Vorgrimmler, Denis Gebele. See LICENSE for details. -- cgit v1.2.3 From 66f2ee967317954568562510111b0d832881547d Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Wed, 9 Mar 2011 12:21:36 +0100 Subject: API Doc link fixed --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 4254748..79bdab2 100644 --- a/README.markdown +++ b/README.markdown @@ -32,7 +32,7 @@ This example shows how to create a lazar model and predict a compound, it assume prediction = OpenTox::LazarPrediction.find(prediction_uri, subjectid) puts prediction.to_yaml -[API documentation](http://rdoc.info/gems/opentox-ruby/0.0.2/frames) +[API documentation](http://rdoc.info/gems/opentox-ruby/1.0.0/frames) ------------------------------------------------------------------- Copyright -- cgit v1.2.3