summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristoph Helma <helma@in-silico.ch>2011-02-11 16:56:57 +0100
committerChristoph Helma <helma@in-silico.ch>2011-02-11 16:56:57 +0100
commit63ba9ff4305e71ac9f91299624f1acbee26ff327 (patch)
tree44b1a235dbe7daeab94657ff82620ae6edc30a69
parent55f81bb50e76e99f370516ec8625a5aae902e898 (diff)
parent1898a5353d790a17c3065e4349435642e1b7f701 (diff)
Merge remote branch 'mguetlein/development' into development
Conflicts: lib/validation.rb
-rw-r--r--lib/algorithm.rb15
-rw-r--r--lib/authorization.rb87
-rw-r--r--lib/dataset.rb22
-rw-r--r--lib/environment.rb4
-rw-r--r--lib/error.rb41
-rw-r--r--lib/helper.rb29
-rw-r--r--lib/model.rb26
-rw-r--r--lib/opentox.rb5
-rw-r--r--lib/overwrite.rb28
-rw-r--r--lib/parser.rb105
-rw-r--r--lib/rest_client_wrapper.rb55
-rw-r--r--lib/task.rb62
-rw-r--r--lib/templates/config.yaml38
-rwxr-xr-xlib/to-html.rb2
-rw-r--r--lib/validation.rb240
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