diff options
author | Christoph Helma <helma@in-silico.ch> | 2011-02-11 16:56:57 +0100 |
---|---|---|
committer | Christoph Helma <helma@in-silico.ch> | 2011-02-11 16:56:57 +0100 |
commit | 63ba9ff4305e71ac9f91299624f1acbee26ff327 (patch) | |
tree | 44b1a235dbe7daeab94657ff82620ae6edc30a69 | |
parent | 55f81bb50e76e99f370516ec8625a5aae902e898 (diff) | |
parent | 1898a5353d790a17c3065e4349435642e1b7f701 (diff) |
Merge remote branch 'mguetlein/development' into development
Conflicts:
lib/validation.rb
-rw-r--r-- | lib/algorithm.rb | 15 | ||||
-rw-r--r-- | lib/authorization.rb | 87 | ||||
-rw-r--r-- | lib/dataset.rb | 22 | ||||
-rw-r--r-- | lib/environment.rb | 4 | ||||
-rw-r--r-- | lib/error.rb | 41 | ||||
-rw-r--r-- | lib/helper.rb | 29 | ||||
-rw-r--r-- | lib/model.rb | 26 | ||||
-rw-r--r-- | lib/opentox.rb | 5 | ||||
-rw-r--r-- | lib/overwrite.rb | 28 | ||||
-rw-r--r-- | lib/parser.rb | 105 | ||||
-rw-r--r-- | lib/rest_client_wrapper.rb | 55 | ||||
-rw-r--r-- | lib/task.rb | 62 | ||||
-rw-r--r-- | lib/templates/config.yaml | 38 | ||||
-rwxr-xr-x | lib/to-html.rb | 2 | ||||
-rw-r--r-- | lib/validation.rb | 240 |
15 files changed, 529 insertions, 230 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 b4c1ee5..1942e95 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","") @@ -192,10 +192,9 @@ 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") rescue return false end @@ -306,7 +305,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 @@ -328,57 +326,56 @@ 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| + 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 - - +end
\ No newline at end of file diff --git a/lib/dataset.rb b/lib/dataset.rb index 9c20968..a843cea 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 @@ -72,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 @@ -285,7 +303,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..7ca9767 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.from_rdf( rdf, OT.ErrorReport ).metadata + 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/helper.rb b/lib/helper.rb index afeeb43..191b932 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: 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 @@ -18,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+\/?$/ @@ -27,29 +30,23 @@ 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") #dirty hacks => to fix + uri = uri[0,uri.index("InChI=")] if uri.index("InChI=") + 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.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 - #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] @@ -59,7 +56,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 80d7ec4..9622d65 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,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" @@ -56,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 @@ -137,7 +135,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 @@ -303,7 +301,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..d2beeac 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -30,21 +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") + # 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| + if File.exist?(@uri) + file = File.new(@uri) else - uri = @uri + 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 - uri += "?subjectid=#{CGI.escape(subjectid)}" if subjectid statements = [] parameter_ids = [] - `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.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(to_delete) if to_delete unless parameter_ids.empty? @metadata[OT.parameters] = [] parameter_ids.each do |p| @@ -55,10 +63,45 @@ module OpenTox end @metadata end - + + # 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 [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 + #puts "cmd: rapper -i rdfxml -o ntriples #{file} 2>/dev/null" + triples = `rapper -i rdfxml -o ntriples #{file.path} 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 + File.delete(file.path) + # 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 + 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 @@ -88,13 +131,26 @@ module OpenTox # dataset.save # @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| + 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 #{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(' ',3) triple = triple[0..2].collect{|i| i.sub(/\s+.$/,'').gsub(/[<>"]/,'')} case triple[1] @@ -111,29 +167,46 @@ module OpenTox else end end + File.delete(to_delete) if to_delete 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, 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 end load_features - @dataset.metadata = load_metadata + @dataset.metadata = load_metadata(subjectid) @dataset end # 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]] diff --git a/lib/rest_client_wrapper.rb b/lib/rest_client_wrapper.rb index 7c2d719..d3136c7 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,45 @@ 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 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 + 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 +96,12 @@ 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 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} + 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 @@ -101,7 +112,9 @@ module OpenTox end def self.wait_for_task( res, base_uri, waiting_task=nil ) - + #TODO remove TUM hack + 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 when /application\/rdf\+xml/ @@ -112,7 +125,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" diff --git a/lib/task.rb b/lib/task.rb index 74940de..0ee3a11 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 @@ -94,23 +104,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 @@ -139,7 +137,7 @@ module OpenTox end def cancel - RestClientWrapper.put(File.join(@uri,'Cancelled')) + RestClientWrapper.put(File.join(@uri,'Cancelled'),{:cannot_be => "empty"}) load_metadata end @@ -176,7 +174,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,11 +182,12 @@ 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 #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 @@ -237,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 @@ -252,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 @@ -268,12 +268,19 @@ module OpenTox end end + def waiting_for(task_uri) + RestClientWrapper.put(File.join(@uri,'Running'),{:waiting_for => task_uri}) + 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 @@ -285,7 +292,6 @@ module OpenTox raise OpenTox::BadRequestError.new ex.message+" (task-uri:"+@uri+")" end end - end # Convenience class to split a (sub)task into subtasks @@ -304,7 +310,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 @@ -322,6 +328,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 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 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']*/, '<a href=\0>\0</a>') + self.gsub(/(?i)http(s?):\/\/[^\r\n\s']*/, '<a href=\0>\0</a>') end end diff --git a/lib/validation.rb b/lib/validation.rb index 23b246b..a47a554 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 [String] report uri + 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 |