summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/algorithm.rb19
-rw-r--r--lib/authorization.rb43
-rw-r--r--lib/compound.rb6
-rw-r--r--lib/dataset.rb34
-rw-r--r--lib/error.rb75
-rw-r--r--lib/feature.rb23
-rw-r--r--lib/helper.rb10
-rw-r--r--lib/model.rb59
-rw-r--r--lib/opentox-ruby.rb5
-rw-r--r--lib/overwrite.rb91
-rw-r--r--lib/policy.rb10
-rw-r--r--lib/rest_client_wrapper.rb175
-rw-r--r--lib/serializer.rb131
-rw-r--r--lib/task.rb164
-rwxr-xr-xlib/to-html.rb81
15 files changed, 753 insertions, 173 deletions
diff --git a/lib/algorithm.rb b/lib/algorithm.rb
index a2f7786..58a2640 100644
--- a/lib/algorithm.rb
+++ b/lib/algorithm.rb
@@ -13,9 +13,10 @@ module OpenTox
# Execute algorithm with parameters, please consult the OpenTox API and the webservice documentation for acceptable parameters
# @param [optional,Hash] params Algorithm parameters
+ # @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly
# @return [String] URI of new resource (dataset, model, ...)
- def run(params=nil)
- RestClientWrapper.post(@uri, {:accept => 'text/uri-list'}, params).to_s
+ def run(params=nil, waiting_task=nil)
+ RestClientWrapper.post(@uri, {:accept => 'text/uri-list'}, params, waiting_task).to_s
end
# Get OWL-DL representation in RDF/XML format
@@ -29,6 +30,20 @@ module OpenTox
# Generic Algorithm class, should work with all OpenTox webservices
class Generic
include Algorithm
+
+ # Find Generic Opentox Algorithm via URI, and loads metadata
+ # @param [String] uri Algorithm URI
+ # @return [OpenTox::Algorithm::Generic] Algorithm instance, nil if alogrithm was not found
+ def self.find(uri)
+ alg = Generic.new(uri)
+ alg.load_metadata
+ if alg.metadata==nil or alg.metadata.size==0
+ nil
+ else
+ alg
+ end
+ end
+
end
# Fminer algorithms (https://github.com/amaunz/fminer2)
diff --git a/lib/authorization.rb b/lib/authorization.rb
index 7e898cc..5bc690a 100644
--- a/lib/authorization.rb
+++ b/lib/authorization.rb
@@ -134,6 +134,20 @@ module OpenTox
end
end
+ # Lists policies alongside with affected uris
+ # @param [String] subjectid
+ # @return [Hash] keys: all policies of the subjectid owner, values: uris affected by those policies
+ def self.list_policy_uris( subjectid )
+ names = list_policies(subjectid)
+ policies = {}
+ names.each do |n|
+ p = OpenTox::Policies.new
+ p.load_xml( list_policy(n, subjectid) )
+ policies[n] = p.uris
+ end
+ policies
+ end
+
#Returns the owner (who created the first policy) of an URI
# @param [String, String]uri,subjectid
# return [String, nil]owner,nil returns owner of the URI
@@ -273,15 +287,32 @@ module OpenTox
return true
end
- #Checks (if subjectid is valid) if a policy exist and create default policy if not
+ # Checks (if subjectid is valid) if a policy exist and create default policy if not
+ # @param [String] uri
+ # @param [String] subjectid
+ # @return [Boolean] true if policy checked/created successfully (or no uri/subjectid given), false else
def self.check_policy(uri, subjectid)
+ return true unless uri and subjectid
token_valid = OpenTox::Authorization.is_token_valid(subjectid)
LOGGER.debug "OpenTox::Authorization.check_policy with uri: #{uri}, subjectid: #{subjectid} is valid: #{token_valid}"
- if uri and token_valid
- if !uri_has_policy(uri, subjectid)
- return send_policy(uri, subjectid)
- else
- LOGGER.debug "OpenTox::Authorization.check_policy URI: #{uri} has already a Policy."
+ # check if subjectid is valid
+ unless token_valid
+ # abort if invalid
+ LOGGER.error "OpenTox::Authorization.check_policy, subjectid NOT valid: #{subjectid}"
+ return false
+ end
+
+ if !uri_has_policy(uri, subjectid)
+ # if no policy exists, create a policy, return result of send policy
+ send_policy(uri, subjectid)
+ else
+ LOGGER.debug "OpenTox::Authorization.check_policy URI: #{uri} has already a Policy."
+ # if policy exists check for POST rights
+ if authorize(uri, "POST", subjectid)
+ true
+ else
+ LOGGER.error "OpenTox::Authorization.check_policy, already exists, but no POST-authorization with subjectid: #{subjectid}"
+ false
end
end
true
diff --git a/lib/compound.rb b/lib/compound.rb
index 6834860..a85507b 100644
--- a/lib/compound.rb
+++ b/lib/compound.rb
@@ -68,6 +68,12 @@ module OpenTox
c
end
+ # Get InChI
+ # @return [String] InChI string
+ def to_inchi
+ @inchi
+ end
+
# Get (canonical) smiles
# @return [String] Smiles string
def to_smiles
diff --git a/lib/dataset.rb b/lib/dataset.rb
index a85c2b5..640e3da 100644
--- a/lib/dataset.rb
+++ b/lib/dataset.rb
@@ -46,7 +46,7 @@ module OpenTox
dataset.save(subjectid)
dataset
end
-
+
# Find a dataset and load all data. This can be time consuming, use Dataset.new together with one of the load_* methods for a fine grained control over data loading.
# @param [String] uri Dataset URI
# @return [OpenTox::Dataset] Dataset object with all data
@@ -242,6 +242,38 @@ module OpenTox
def add_feature_metadata(feature,metadata)
metadata.each { |k,v| @features[feature][k] = v }
end
+
+ # Add a new compound
+ # @param [String] compound Compound URI
+ def add_compound (compound)
+ @compounds << compound unless @compounds.include? compound
+ end
+
+ # Creates a new dataset, by splitting the current dataset, i.e. using only a subset of compounds and features
+ # @param [Array] compounds List of compound URIs
+ # @param [Array] features List of feature URIs
+ # @param [Hash] metadata Hash containing the metadata for the new dataset
+ # @param [String] subjectid
+ # @return [OpenTox::Dataset] newly created dataset, already saved
+ def split( compounds, features, metadata, subjectid=nil)
+ LOGGER.debug "split dataset using "+compounds.size.to_s+"/"+@compounds.size.to_s+" compounds"
+ raise "no new compounds selected" unless compounds and compounds.size>0
+ dataset = OpenTox::Dataset.create(CONFIG[:services]["opentox-dataset"],subjectid)
+ if features.size==0
+ compounds.each{ |c| dataset.add_compound(c) }
+ else
+ compounds.each do |c|
+ features.each do |f|
+ @data_entries[c][f].each do |v|
+ dataset.add(c,f,v)
+ end
+ end
+ end
+ end
+ dataset.add_metadata(metadata)
+ dataset.save(subjectid)
+ dataset
+ end
# Save dataset at the dataset service
# - creates a new dataset if uri is not set
diff --git a/lib/error.rb b/lib/error.rb
new file mode 100644
index 0000000..e5c460d
--- /dev/null
+++ b/lib/error.rb
@@ -0,0 +1,75 @@
+
+# adding additional fields to Exception class to format errors according to OT-API
+class Exception
+ attr_accessor :errorCause
+ def http_code; 500; end
+end
+
+module OpenTox
+
+ class BadRequestError < RuntimeError
+ def http_code; 400; end
+ end
+
+ class NotAuthorizedError < RuntimeError
+ def http_code; 401; end
+ end
+
+ class NotFoundError < RuntimeError
+ def http_code; 404; end
+ end
+
+ class RestCallError < RuntimeError
+ attr_accessor :rest_params
+ def http_code; 502; end
+ end
+
+ class ErrorReport
+
+ # TODO replace params with URIs (errorCause -> OT.errorCause)
+ attr_reader :message, :actor, :errorCause, :http_code, :errorDetails, :errorType
+
+ # creates a error report object, from an ruby-exception object
+ # @param [Exception] error
+ # @param [String] actor, URI of the call that cause the error
+ def initialize( error, actor )
+ @http_code = error.http_code
+ @errorType = error.class.to_s
+ @message = error.message
+ @actor = actor
+ @errorCause = error.errorCause if error.errorCause
+ @rest_params = error.rest_params if error.is_a?(OpenTox::RestCallError) and error.rest_params
+ @backtrace = error.backtrace.join("\n") if CONFIG[:backtrace]
+ end
+
+ # overwrite sorting to make easier readable
+ def to_yaml_properties
+ p = super
+ p = ( p - ["@backtrace"]) + ["@backtrace"] if @backtrace
+ p = ( p - ["@errorCause"]) + ["@errorCause"] if @errorCause
+ p
+ end
+
+ def rdf_content()
+ c = {
+ RDF.type => OT.ErrorReport,
+ OT.statusCode => @http_code,
+ OT.message => @message,
+ OT.actor => @actor,
+ OT.errorCode => @errorType,
+ }
+ c[OT.errorCause] = @errorCause.rdf_content if @errorCause
+ c
+ end
+
+ def self.from_rdf(rdf)
+ raise "not yet implemented"
+ end
+
+ def to_rdfxml
+ s = Serializer::Owl.new
+ s.add_resource(CONFIG[:services]["opentox-task"]+"/tmpId/ErrorReport/tmpId", OT.errorReport, rdf_content)
+ s.to_rdfxml
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/feature.rb b/lib/feature.rb
index 349f8ae..28ac0c5 100644
--- a/lib/feature.rb
+++ b/lib/feature.rb
@@ -3,7 +3,7 @@ module OpenTox
include OpenTox
def self.find(uri, subjectid=nil)
- feature = Feature.new uri
+ feature = Feature.new uri
if (CONFIG[:yaml_hosts].include?(URI.parse(uri).host))
feature.add_metadata YAML.load(RestClientWrapper.get(uri,{:accept => "application/x-yaml", :subjectid => subjectid}))
else
@@ -11,5 +11,26 @@ module OpenTox
end
feature
end
+
+ # provides domain (possible target values) of classification feature
+ # @return [Array] list with possible target values
+ def domain
+ #TODO derieve from metadata / ontology
+ return [true, false]
+ end
+
+ # provides feature type, possible types are "regression" or "classification"
+ # @return [String] feature type, unknown if OT.isA property is unknown/ not set
+ def feature_type
+ case metadata[OT.isA]
+ when /NominalFeature/
+ "classification"
+ when /NumericFeature/
+ "regression"
+ else
+ "unknown"
+ end
+ end
+
end
end
diff --git a/lib/helper.rb b/lib/helper.rb
index 5fe1857..e82c8fb 100644
--- a/lib/helper.rb
+++ b/lib/helper.rb
@@ -9,11 +9,10 @@ helpers do
end
elsif !env["session"] && subjectid
unless authorized?(subjectid)
- throw(:halt, [401, "Not authorized.\n"])
- redirect back
+ raise OpenTox::NotAuthorizedError.new "Not authorized"
end
else
- throw(:halt, [401, "Not authorized.\n"]) unless authorized?(subjectid)
+ raise OpenTox::NotAuthorizedError.new "Not authorized" unless authorized?(subjectid)
end
end
@@ -60,7 +59,7 @@ helpers do
end
before do
- unless unprotected_requests or CONFIG[:authorization][:free_request].include?(env['REQUEST_METHOD'])
+ unless !AA_SERVER or unprotected_requests or CONFIG[:authorization][:free_request].include?(env['REQUEST_METHOD'])
begin
subjectid = nil
subjectid = session[:subjectid] if session[:subjectid]
@@ -73,7 +72,8 @@ before do
LOGGER.debug "OpenTox ruby api wrapper: helper before filter: NO subjectid for URI: #{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}"
subjectid = ""
end
- protected!(subjectid) if AA_SERVER
+ @subjectid = subjectid
+ protected!(subjectid)
end
end
diff --git a/lib/model.rb b/lib/model.rb
index 6ef4af2..85be1b5 100644
--- a/lib/model.rb
+++ b/lib/model.rb
@@ -6,26 +6,54 @@ module OpenTox
# Run a model with parameters
# @param [Hash] params Parameters for OpenTox model
+ # @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly
# @return [text/uri-list] Task or resource URI
- def run(params)
+ def run( params, waiting_task=nil )
if CONFIG[:yaml_hosts].include?(URI.parse(@uri).host)
accept = 'application/x-yaml'
else
accept = 'application/rdf+xml'
end
- begin
- RestClientWrapper.post(@uri,{:accept => accept},params).to_s
- rescue => e
- LOGGER.error "Failed to run #{@uri} with #{params.inspect} (#{e.inspect})"
- raise "Failed to run #{@uri} with #{params.inspect}"
- end
+ RestClientWrapper.post(@uri,{:accept => accept},params,waiting_task).to_s
end
# Generic OpenTox model class for all API compliant services
class Generic
include Model
+
+ # Find Generic Opentox Model via URI, and loads metadata
+ # @param [String] uri Model URI
+ # @return [OpenTox::Model::Generic] Model instance, nil if model was not found
+ def self.find(uri)
+ model = Generic.new(uri)
+ model.load_metadata
+ if model.metadata==nil or model.metadata.size==0
+ nil
+ else
+ model
+ end
+ end
+
+ # provides feature type, possible types are "regression" or "classification"
+ # @return [String] feature type, "unknown" if type could not be estimated
+ def feature_type
+ # dynamically perform restcalls if necessary
+ load_metadata if @metadata==nil or @metadata.size==0 or (@metadata.size==1 && @metadata.values[0]==@uri)
+ @dependentVariable = OpenTox::Feature.find( @metadata[OT.dependentVariables] ) unless @dependentVariable
+
+ [@dependentVariable.feature_type, @metadata[OT.isA], @metadata[DC.title], @uri].each do |type|
+ case type
+ when /(?i)classification/
+ return "classification"
+ when /(?i)regression/
+ return "regression"
+ end
+ end
+ raise "unknown model "+[@dependentVariable.feature_type, @metadata[OT.isA], @metadata[DC.title], @uri].inspect
+ end
+
end
-
+
# Lazy Structure Activity Relationship class
class Lazar
@@ -89,8 +117,10 @@ module OpenTox
# Predict a dataset
# @param [String] dataset_uri Dataset URI
+ # @param [optional,subjectid]
+ # @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly
# @return [OpenTox::Dataset] Dataset with predictions
- def predict_dataset(dataset_uri, subjectid=nil)
+ def predict_dataset(dataset_uri, subjectid=nil, waiting_task=nil)
@prediction_dataset = Dataset.create(CONFIG[:services]["opentox-dataset"], subjectid)
@prediction_dataset.add_metadata({
OT.hasSource => @uri,
@@ -99,9 +129,16 @@ module OpenTox
OT.parameters => [{DC.title => "dataset_uri", OT.paramValue => dataset_uri}]
})
d = Dataset.new(dataset_uri,subjectid)
- d.load_compounds(subjectid)
+ d.load_compounds
+ count = 0
d.compounds.each do |compound_uri|
- predict(compound_uri,false,subjectid)
+ begin
+ predict(compound_uri,false,subjectid)
+ count += 1
+ waiting_task.progress( count/d.compounds.size.to_f*100.0 ) if waiting_task
+ rescue => ex
+ LOGGER.warn "prediction for compound "+compound_uri.to_s+" failed: "+ex.message
+ end
end
@prediction_dataset.save(subjectid)
@prediction_dataset
diff --git a/lib/opentox-ruby.rb b/lib/opentox-ruby.rb
index c0bff95..735b845 100644
--- a/lib/opentox-ruby.rb
+++ b/lib/opentox-ruby.rb
@@ -1,4 +1,4 @@
-['rubygems', 'sinatra', 'sinatra/url_for', 'rest_client', 'yaml', 'cgi', 'spork', 'overwrite', 'environment'].each do |lib|
+['rubygems', 'sinatra', 'sinatra/url_for', 'rest_client', 'yaml', 'cgi', 'spork', 'error', 'overwrite', 'environment'].each do |lib|
require lib
end
@@ -8,6 +8,7 @@ rescue LoadError
puts "Please install Openbabel with 'rake openbabel:install' in the compound component"
end
-['opentox', 'compound','dataset', 'parser','serializer', 'algorithm','model','task','validation','feature', 'rest_client_wrapper', 'authorization', 'policy', 'helper'].each do |lib|
+['opentox', 'compound','dataset', 'parser','serializer', 'algorithm','model','task','validation','feature',
+ 'rest_client_wrapper', 'authorization', 'policy', 'helper', 'to-html' ].each do |lib|
require lib
end
diff --git a/lib/overwrite.rb b/lib/overwrite.rb
index f39fec3..e52618c 100644
--- a/lib/overwrite.rb
+++ b/lib/overwrite.rb
@@ -1,38 +1,71 @@
# class overwrites aka monkey patches
-# hack: store sinatra in global var to make url_for and halt methods accessible
-before{ $sinatra = self unless $sinatra }
+# hack: store sinatra instance in global var $url_provider to make url_for and halt methods accessible
+before {
+ raise "should not happen, url provider already differently initialized "+
+ $url_provider.request.host.to_s+" != "+self.request.host.to_s if
+ $url_provider and $url_provider.request.host!=self.request.host and
+ $url_provider.request.script_name!=self.request.script_name
+ $url_provider = self
+ # stupid internet explorer does not ask for text/html, add this manually
+ request.env['HTTP_ACCEPT'] += ";text/html" if request.env["HTTP_USER_AGENT"]=~/MSIE/
+}
-class Sinatra::Base
- # overwriting halt to log halts (!= 202)
- def halt(*response)
- LOGGER.error "halt "+response.first.to_s+" "+(response.size>1 ? response[1].to_s : "") if response and response.first and response.first >= 300
- # orig sinatra code:
- response = response.first if response.length == 1
- throw :halt, response
+# Error handling
+# Errors are logged as error and formated according to acccept-header
+# Non OpenTox::Errors (defined in error.rb) are handled as internal error (500), stacktrace is logged
+# IMPT: set sinatra settings :show_exceptions + :raise_errors to false in config.ru, otherwise Rack::Showexceptions takes over
+error Exception do
+ error = request.env['sinatra.error']
+ # log error to logfile
+ LOGGER.error error.class.to_s+": "+error.message
+ # 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
+
+ actor = "#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['REQUEST_URI']}"
+ rep = OpenTox::ErrorReport.new(error, actor)
+
+ case request.env['HTTP_ACCEPT']
+ when /rdf/
+ content_type 'application/rdf+xml'
+ halt error.http_code,rep.to_rdfxml
+ when /html/
+ content_type 'text/html'
+ halt error.http_code,(OpenTox.text_to_html rep.to_yaml)
+ else
+ content_type 'application/x-yaml'
+ halt error.http_code,rep.to_yaml
end
end
class String
- def task_uri?
- self.uri? && !self.match(/task/).nil?
- end
-
- def dataset_uri?
- self.uri? && !self.match(/dataset/).nil?
- end
-
- def self.model_uri?
- self.uri? && !self.match(/model/).nil?
- end
+ def task_uri?
+ self.uri? && !self.match(/task/).nil?
+ end
+
+ def dataset_uri?
+ self.uri? && !self.match(/dataset/).nil?
+ end
+
+ def self.model_uri?
+ self.uri? && !self.match(/model/).nil?
+ end
- def uri?
- begin
- u = URI::parse(self)
- return (u.scheme!=nil and u.host!=nil)
- rescue URI::InvalidURIError
- return false
- end
+ def uri?
+ begin
+ u = URI::parse(self)
+ return (u.scheme!=nil and u.host!=nil)
+ rescue URI::InvalidURIError
+ return false
end
+ end
+
+ def underscore
+ self.gsub(/::/, '/').
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
+ tr("-", "_").
+ downcase
+ end
end
require 'logger'
@@ -52,7 +85,7 @@ class OTLogger < Logger
n = 2
line = lines[n]
- while (line =~ /spork.rb/ or line =~ /create/ or line =~ /ot-logger.rb/)
+ while (line =~ /spork.rb/ or line =~ /create/ or line =~ /overwrite.rb/)
n += 1
line = lines[n]
end
@@ -63,7 +96,7 @@ class OTLogger < Logger
end
def format(msg)
- pwd.ljust(18)+" :: "+msg.to_s+" :: "+trace+" :: "+($sinatra ? $sinatra.request.env['REMOTE_ADDR'] : nil).to_s
+ pwd.ljust(18)+" :: "+msg.to_s+" :: "+trace
end
def debug(msg)
diff --git a/lib/policy.rb b/lib/policy.rb
index 9c81fbd..8591d52 100644
--- a/lib/policy.rb
+++ b/lib/policy.rb
@@ -32,6 +32,11 @@ module OpenTox
end
return true
end
+
+ # @return [Array] set of arrays affected by policies
+ def uris
+ @policies.collect{ |k,v| v.uris }.flatten.uniq
+ end
#drop all policies in a policies instance
def names
@@ -199,6 +204,11 @@ module OpenTox
@subjects[name] = Subject.new(name, type, value)
end
+ # @return [Array] set of uris affected by policy
+ def uris
+ @rules.collect{ |k,v| v.uri }.uniq
+ end
+
#rule inside a policy
class Rule
diff --git a/lib/rest_client_wrapper.rb b/lib/rest_client_wrapper.rb
index 5f5273b..7c2d719 100644
--- a/lib/rest_client_wrapper.rb
+++ b/lib/rest_client_wrapper.rb
@@ -1,32 +1,4 @@
module OpenTox
-
- #PENDING: implement ot error api, move to own file
- class Error
-
- attr_accessor :code, :body, :uri, :payload, :headers
-
- def initialize(code, body, uri, payload, headers)
- self.code = code
- self.body = body.to_s[0..1000]
- self.uri = uri
- self.payload = payload
- self.headers = headers
- end
-
- def self.parse(error_array_string)
- begin
- err = YAML.load(error_array_string)
- if err and err.is_a?(Array) and err.size>0 and err[0].is_a?(Error)
- return err
- else
- return nil
- end
- rescue
- return nil
- end
- end
-
- end
class WrapperResult < String
attr_accessor :content_type, :code
@@ -34,37 +6,61 @@ module OpenTox
class RestClientWrapper
- def self.get(uri, headers=nil, wait=true)
- execute( "get", uri, headers, nil, wait)
+ # performs a GET REST call
+ # raises OpenTox::Error if call fails (rescued in overwrite.rb -> halt 502)
+ # per default: waits for Task to finish and returns result URI of Task
+ # @param [String] uri destination URI
+ # @param [optional,Hash] headers contains params like accept-header
+ # @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly
+ # @param [wait,Boolean] wait set to false to NOT wait for task if result is task
+ # @return [OpenTox::WrapperResult] a String containing the result-body of the REST call
+ def self.get(uri, headers=nil, waiting_task=nil, wait=true )
+ execute( "get", uri, headers, nil, waiting_task, wait)
end
- def self.post(uri, headers, payload=nil, wait=true)
- execute( "post", uri, headers, payload, wait )
+ # performs a POST REST call
+ # raises OpenTox::Error if call fails (rescued in overwrite.rb -> halt 502)
+ # per default: waits for Task to finish and returns result URI of Task
+ # @param [String] uri destination URI
+ # @param [optional,Hash] headers contains params like accept-header
+ # @param [optional,String] payload data posted to the service
+ # @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly
+ # @param [wait,Boolean] wait set to false to NOT wait for task if result is task
+ # @return [OpenTox::WrapperResult] a String containing the result-body of the REST call
+ def self.post(uri, headers, payload=nil, waiting_task=nil, wait=true )
+ execute( "post", uri, headers, payload, waiting_task, wait )
end
+ # performs a PUT REST call
+ # raises OpenTox::Error if call fails (rescued in overwrite.rb -> halt 502)
+ # @param [String] uri destination URI
+ # @param [optional,Hash] headers contains params like accept-header
+ # @param [optional,String] payload data put to the service
+ # @return [OpenTox::WrapperResult] a String containing the result-body of the REST call
def self.put(uri, headers, payload=nil )
execute( "put", uri, headers, payload )
end
- def self.delete(uri, headers=nil)
+ # performs a DELETE REST call
+ # raises OpenTox::Error if call fails (rescued in overwrite.rb -> halt 502)
+ # @param [String] uri destination URI
+ # @param [optional,Hash] headers contains params like accept-header
+ # @return [OpenTox::WrapperResult] a String containing the result-body of the REST call
+ def self.delete(uri, headers=nil )
execute( "delete", uri, headers, nil)
end
- def self.raise_uri_error(error_msg, uri, headers=nil, payload=nil)
- do_halt( "-", error_msg, uri, headers, payload )
- end
-
private
- def self.execute( rest_call, uri, headers, payload=nil, wait=true )
+ def self.execute( rest_call, uri, headers, payload=nil, waiting_task=nil, wait=true )
- do_halt 400,"uri is null",uri,headers,payload unless uri
- do_halt 400,"not a uri",uri,headers,payload unless uri.to_s.uri?
- do_halt 400,"headers are no hash",uri,headers,payload unless headers==nil or headers.is_a?(Hash)
- do_halt 400,"nil headers for post not allowed, use {}",uri,headers,payload if rest_call=="post" and headers==nil
+ raise OpenTox::BadRequestError.new "uri is null" unless uri
+ raise OpenTox::BadRequestError.new "not a uri: "+uri.to_s unless uri.to_s.uri?
+ raise OpenTox::BadRequestError.new "headers are no hash: "+headers.inspect unless headers==nil or headers.is_a?(Hash)
+ raise OpenTox::BadRequestError.new "nil headers for post not allowed, use {}" if rest_call=="post" and headers==nil
headers.each{ |k,v| headers.delete(k) if v==nil } if headers #remove keys with empty values, as this can cause problems
begin
- #LOGGER.debug "RestCall: "+rest_call.to_s+" "+uri.to_s+" "+headers.inspect
+ #LOGGER.debug "RestCall: "+rest_call.to_s+" "+uri.to_s+" "+headers.inspect+" "+payload.inspect
resource = RestClient::Resource.new(uri,{:timeout => 60})
if payload
result = resource.send(rest_call, payload, headers)
@@ -80,32 +76,31 @@ module OpenTox
raise "content-type not set" unless res.content_type
res.code = result.code
+ #LOGGER.debug "RestCall result: "+res.to_s+" "+res.code.to_s+" "+res.content_type.to_s
# TODO: Ambit returns task representation with 200 instead of result URI
return res if res.code==200 || !wait
while (res.code==201 || res.code==202)
- res = wait_for_task(res, uri)
+ res = wait_for_task(res, uri, waiting_task)
end
raise "illegal status code: '"+res.code.to_s+"'" unless res.code==200
return res
rescue RestClient::RequestTimeout => ex
- do_halt 408,ex.message,uri,headers,payload
+ received_error ex.message, 408, nil, {:rest_uri => uri, :headers => headers}
+ 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}
+ rescue OpenTox::RestCallError => ex
+ # already a rest-error, probably comes from wait_for_task, just pass through
+ raise ex
rescue => ex
- #raise ex
- #raise "'"+ex.message+"' uri: "+uri.to_s
- begin
- code = ex.http_code
- msg = ex.http_body
- rescue
- code = 500
- msg = ex.to_s
- end
- do_halt code,msg,uri,headers,payload
+ # some internal error occuring in rest_client_wrapper, just pass through
+ raise ex
end
end
- def self.wait_for_task( res, base_uri )
+ def self.wait_for_task( res, base_uri, waiting_task=nil )
task = nil
case res.content_type
@@ -115,53 +110,55 @@ module OpenTox
task = OpenTox::Task.from_yaml(res)
when /text\//
raise "uri list has more than one entry, should be a task" if res.content_type=~/text\/uri-list/ and res.split("\n").size > 1 #if uri list contains more then one uri, its not a task
- task = OpenTox::Task.find(res.to_s) if res.to_s.uri?
+ task = OpenTox::Task.find(res.to_s.chomp) if res.to_s.uri?
else
raise "unknown content-type for task: '"+res.content_type.to_s+"'" #+"' content: "+res[0..200].to_s
end
LOGGER.debug "result is a task '"+task.uri.to_s+"', wait for completion"
- task.wait_for_completion
- raise task.description unless task.completed? # maybe task was cancelled / error
-
+ task.wait_for_completion waiting_task
+ unless task.completed? # maybe task was cancelled / error
+ if task.errorReport
+ received_error task.errorReport, task.http_code, nil, {:rest_uri => task.uri, :rest_code => task.http_code}
+ else
+ raise "task status: '"+task.status.to_s+"' but errorReport nil"
+ end
+ end
+
res = WrapperResult.new task.result_uri
res.code = task.http_code
res.content_type = "text/uri-list"
return res
end
- def self.do_halt( code, body, uri, headers, payload=nil )
-
- #build error
- causing_errors = Error.parse(body)
- if causing_errors
- error = causing_errors + [Error.new(code, "subsequent error", uri, payload, headers)]
+ def self.received_error( body, code, content_type=nil, params=nil )
+
+ # try to parse body
+ report = nil
+ if body.is_a?(OpenTox::ErrorReport)
+ report = body
else
- error = [Error.new(code, body, uri, payload, headers)]
+ case content_type
+ when /yaml/
+ report = YAML.load(body)
+ when /rdf/
+ report = OpenTox::ErrorReport.from_rdf(body)
+ end
end
- #debug utility: write error to file
- error_dir = "/tmp/ot_errors"
- FileUtils.mkdir(error_dir) unless File.exist?(error_dir)
- raise "could not create error dir" unless File.exist?(error_dir) and File.directory?(error_dir)
- file_name = "error"
- time=Time.now.strftime("%m.%d.%Y-%H:%M:%S")
- count = 1
- count+=1 while File.exist?(File.join(error_dir,file_name+"_"+time+"_"+count.to_s))
- File.new(File.join(error_dir,file_name+"_"+time+"_"+count.to_s),"w").puts(body)
-
- # handle error
- # we are either in a task, or in sinatra
- # PENDING: always return yaml for now
-
- if $self_task #this global var in Task.create to mark that the current process is running in a task
- raise error.to_yaml # the error is caught, logged, and task state is set to error in Task.create
- #elsif $sinatra #else halt sinatra
- #$sinatra.halt(502,error.to_yaml)
- elsif defined?(halt)
- halt(502,error.to_yaml)
- else #for testing purposes (if classes used directly)
- raise error.to_yaml
+ unless report
+ # parsing was not successfull
+ # raise 'plain' RestCallError
+ err = OpenTox::RestCallError.new("REST call returned error: '"+body.to_s+"'")
+ err.rest_params = params
+ raise err
+ else
+ # parsing sucessfull
+ # raise RestCallError with parsed report as error cause
+ err = OpenTox::RestCallError.new("REST call subsequent error")
+ err.errorCause = report
+ err.rest_params = params
+ raise err
end
end
end
diff --git a/lib/serializer.rb b/lib/serializer.rb
index 495702a..44b4414 100644
--- a/lib/serializer.rb
+++ b/lib/serializer.rb
@@ -14,7 +14,7 @@ module OpenTox
def initialize
@object = {
- # this should come from opntox.owl
+ # this should come from opentox.owl
OT.Compound => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
OT.Feature => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
OT.NominalFeature => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
@@ -26,6 +26,16 @@ module OpenTox
OT.Algorithm => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
OT.Parameter => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
OT.Task => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
+ #classes for validation
+ OT.Validation => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
+ OT.ClassificationStatistics => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
+ OT.ConfusionMatrix => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
+ OT.ConfusionMatrixCell => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
+ OT.ClassValueStatistics => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
+ OT.RegressionStatistics => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
+ OT.Crossvalidation => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
+ OT.CrossvalidationInfo => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
+ OT.ErrorReport => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
OT.compound => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
OT.feature => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
@@ -34,6 +44,22 @@ module OpenTox
OT.values => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
OT.algorithm => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
OT.parameters => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
+ #object props for validation#
+ OT.model => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
+ OT.trainingDataset => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
+ OT.predictionFeature => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
+ OT.predictionDataset => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
+ OT.crossvalidation => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
+ OT.testTargetDataset => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
+ OT.testDataset => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
+ OT.classificationStatistics => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
+ OT.confusionMatrix => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
+ OT.confusionMatrixCell => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
+ OT.classValueStatistics => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
+ OT.regressionStatistics => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
+ OT.validation => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
+ OT.crossvalidationInfo => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
+ OT.dataset => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
DC.title => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
DC.identifier => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
@@ -47,6 +73,51 @@ module OpenTox
OT.hasStatus => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
OT.resultURI => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
OT.percentageCompleted => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ # annotation props for validation
+ OT.numUnpredicted => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.crossvalidationFold => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.numInstances => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.numWithoutClass => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.percentWithoutClass => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.percentUnpredicted => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.confusionMatrixActual => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.confusionMatrixPredicted => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.confusionMatrixValue => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.numIncorrect => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.percentCorrect => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.numCorrect => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.accuracy => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.trueNegativeRate => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.truePositiveRate => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.falseNegativeRate => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.falsePositiveRate => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.numTrueNegatives => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.numTruePositives => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.numFalseNegatives => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.numFalsePositives => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.classValue => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.precision => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.areaUnderRoc => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.weightedAreaUnderRoc => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.fMeasure => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.percentIncorrect => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.validationType => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.realRuntime => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.sampleCorrelationCoefficient => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.targetVarianceActual => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.targetVariancePredicted => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.meanAbsoluteError => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.sumSquaredError => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.rootMeanSquaredError => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.rSquare => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.stratified => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.numFolds => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.randomSeed => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.reportType => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.message => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.statusCode => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.actor => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
+ OT.errorCode => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
OT.hasSource => { RDF["type"] => [{ "type" => "uri", "value" => OWL.DatatypeProperty }] } ,
OT.value => { RDF["type"] => [{ "type" => "uri", "value" => OWL.DatatypeProperty }] } ,
@@ -121,6 +192,64 @@ module OpenTox
@object[uri] = { RDF["type"] => [{ "type" => "uri", "value" => OT.Task }] }
add_metadata uri, metadata
end
+
+ # Add a resource defined by resource_class and content
+ # (see documentation of add_content for example)
+ # @param [String] uri of resource
+ # @param [String] resource class, e.g. OT.Validation
+ # @param [Hash] content as hash
+ def add_resource(uri, resource_class, content)
+ @object[uri] = { RDF["type"] => [{ "type" => "uri", "value" => resource_class }] }
+ @@content_id = 1
+ add_content uri, content
+ end
+
+ private
+ @@content_id = 1
+
+ # Recursiv function to add content
+ # @example
+ # { DC.description => "bla",
+ # OT.similar_resources => [ "http://uri1", "http://uri2" ],
+ # OT.matrixCells =>
+ # [ { RDF.type => OT.MatrixCell, OT.cellIndex=1 OT.cellValue => "xy" },
+ # { RDF.type => OT.MatrixCell, OT.cellIndex=2 OT.cellValue => "z" } ],
+ # OT.info => { RDF.type => OT.ImportantInfo,
+ # DC.description => "blub" }
+ # }
+ # @param [String] uri
+ # @param [Hash] content as hash, uri must already have been added to @object
+ def add_content(uri, hash)
+ raise "content is no hash: "+hash.class.to_s unless hash.is_a?(Hash)
+ hash.each do |u,v|
+ if v.is_a? Hash
+ # value is again a hash, i.e. a new owl class is added
+ # first make sure type (==class) is set
+ type = v[RDF.type]
+ raise "type missing for "+u.to_s+" content:\n"+v.inspect unless type
+ raise "class unknown "+type.to_s+" (for "+u.to_s+")" unless @object.has_key?(type)
+ # create new node and add to current uri
+ genid = "_:#{type.split('#')[-1]}#{@@content_id}"
+ @@content_id += 1
+ @object[uri] = {} unless @object[uri]
+ @object[uri][u] = [{ "type" => "bnode", "value" => genid }]
+ # add content to new class
+ add_content(genid,v)
+ elsif v.is_a? Array
+ # value is an array, i.e. a list of values with property is added
+ v.each{ |vv| add_content( uri, { u => vv } ) }
+ else # v.is_a? String
+ # simple string value
+ @object[uri] = {} unless @object[uri]
+ @object[uri][u] = [] unless @object[uri][u]
+ raise "property unknown "+u.to_s if !@object.has_key?(u) and u!=RDF.type
+ # use << to allow different values for one property
+ @object[uri][u] << {"type" => type(v), "value" => v }
+ end
+ end
+ end
+
+ public
# Add metadata
# @param [Hash] metadata
diff --git a/lib/task.rb b/lib/task.rb
index 9cf909f..3c6aba5 100644
--- a/lib/task.rb
+++ b/lib/task.rb
@@ -1,4 +1,3 @@
-$self_task=nil
module OpenTox
@@ -13,7 +12,7 @@ module OpenTox
DC.title => "",
DC.date => "",
OT.hasStatus => "Running",
- OT.percentageCompleted => "0",
+ OT.percentageCompleted => 0.0,
OT.resultURI => "",
DC.creator => "", # not mandatory according to API
DC.description => "", # not mandatory according to API
@@ -57,27 +56,17 @@ module OpenTox
# #raise "Server too busy to start a new task"
#end
-
task_pid = Spork.spork(:logger => LOGGER) do
LOGGER.debug "Task #{task.uri} started #{Time.now}"
- $self_task = task
-
begin
- result = catch(:halt) do
- yield task
- end
- # catching halt, set task state to error
- if result && result.is_a?(Array) && result.size==2 && result[0]>202
- LOGGER.error "task was halted: "+result.inspect
- task.error(result[1])
- return
- end
+ result = yield task
LOGGER.debug "Task #{task.uri} done #{Time.now} -> "+result.to_s
task.completed(result)
- rescue => ex
- LOGGER.error "task failed: "+ex.message
- LOGGER.error ": "+ex.backtrace.join("\n")
- task.error(ex.message)
+ rescue => error
+ LOGGER.error "task failed: "+error.class.to_s+": "+error.message
+ # log backtrace only if code is 500 -> unwanted (Runtime)Exceptions and internal errors (see error.rb)
+ LOGGER.error ":\n"+error.backtrace.join("\n") if error.http_code==500
+ task.error(OpenTox::ErrorReport.new(error, creator))
end
end
task.pid = task_pid
@@ -113,7 +102,9 @@ module OpenTox
def to_rdfxml
s = Serializer::Owl.new
+ @metadata[OT.errorReport] = @uri+"/ErrorReport/tmpId" if @error_report
s.add_task(@uri,@metadata)
+ s.add_resource(@uri+"/ErrorReport/tmpId", OT.errorReport, @error_report.rdf_content) if @error_report
s.to_rdfxml
end
@@ -129,6 +120,10 @@ module OpenTox
@metadata[DC.description]
end
+ def errorReport
+ @metadata[OT.errorReport]
+ end
+
def cancel
RestClientWrapper.put(File.join(@uri,'Cancelled'))
load_metadata
@@ -139,11 +134,17 @@ module OpenTox
load_metadata
end
- def error(description)
- RestClientWrapper.put(File.join(@uri,'Error'),{:description => description.to_s[0..2000]})
+ def error(error_report)
+ raise "no error report" unless error_report.is_a?(OpenTox::ErrorReport)
+ RestClientWrapper.put(File.join(@uri,'Error'),{:errorReport => error_report.to_yaml})
load_metadata
end
+ # not stored just for to_rdf
+ def add_error_report( error_report )
+ @error_report = error_report
+ end
+
def pid=(pid)
RestClientWrapper.put(File.join(@uri,'pid'), {:pid => pid})
end
@@ -162,12 +163,12 @@ module OpenTox
def load_metadata
if (CONFIG[:yaml_hosts].include?(URI.parse(uri).host))
- result = RestClientWrapper.get(@uri, {:accept => 'application/x-yaml'}, false)
+ result = RestClientWrapper.get(@uri, {:accept => 'application/x-yaml'}, nil, false)
@metadata = YAML.load result.to_s
@http_code = result.code
else
@metadata = Parser::Owl::Generic.new(@uri).load_metadata
- @http_code = RestClientWrapper.get(uri, {:accept => 'application/rdf+xml'}, false).code
+ @http_code = RestClientWrapper.get(uri, {:accept => 'application/rdf+xml'}, nil, false).code
end
end
@@ -218,7 +219,9 @@ module OpenTox
=end
# waits for a task, unless time exceeds or state is no longer running
- def wait_for_completion(dur=0.3)
+ # @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly
+ # @param [optional,Numeric] dur seconds pausing before cheking again for completion
+ def wait_for_completion( waiting_task=nil, dur=0.3)
due_to_time = Time.new + DEFAULT_TASK_MAX_DURATION
LOGGER.debug "start waiting for task "+@uri.to_s+" at: "+Time.new.to_s+", waiting at least until "+due_to_time.to_s
@@ -228,15 +231,29 @@ module OpenTox
while self.running?
sleep dur
load_metadata
+ # if another (sub)task is waiting for self, set progress accordingly
+ waiting_task.progress(@metadata[OT.percentageCompleted]) if waiting_task
check_state
if (Time.new > due_to_time)
raise "max wait time exceeded ("+DEFAULT_TASK_MAX_DURATION.to_s+"sec), task: '"+@uri.to_s+"'"
end
end
- LOGGER.debug "Task '"+@metadata[OT.hasStatus]+"': "+@uri.to_s+", Result: "+@metadata[OT.resultURI].to_s
+ LOGGER.debug "Task '"+@metadata[OT.hasStatus].to_s+"': "+@uri.to_s+", Result: "+@metadata[OT.resultURI].to_s
end
-
+
+ # updates percentageCompleted value (can only be increased)
+ # task has to be running
+ # @param [Numeric] pct value between 0 and 100
+ def progress(pct)
+ #puts "task := "+pct.to_s
+ raise "no numeric >= 0 and <= 100 : '"+pct.to_s+"'" unless pct.is_a?(Numeric) and pct>=0 and pct<=100
+ if (pct > @metadata[OT.percentageCompleted] + 0.0001)
+ RestClientWrapper.put(File.join(@uri,'Running'),{:percentageCompleted => pct})
+ load_metadata
+ end
+ end
+
private
def check_state
begin
@@ -251,10 +268,105 @@ module OpenTox
"'" unless @metadata[OT.resultURI] and @metadata[OT.resultURI].to_s.uri?
end
rescue => ex
- RestClientWrapper.raise_uri_error(ex.message, @uri)
+ raise OpenTox::BadRequestError.new ex.message+" (task-uri:"+@uri+")"
+ end
+ end
+
+ end
+
+ # Convenience class to split a (sub)task into subtasks
+ #
+ # example:
+ # a crossvalidation is split into creating datasets and performing the validations
+ # creating the dataset is 1/3 of the work, perform the validations is 2/3:
+ # Task.as_task do |task|
+ # create_datasets( SubTask.new(task, 0, 33) )
+ # perfom_validations( SubTask.new(task, 33, 100) )
+ # end
+ # inside the create_datasets / perform_validations you can use subtask.progress(<val>)
+ # with vals from 0-100
+ #
+ # note that you can split a subtask into further subtasks
+ class SubTask
+
+ def initialize(task, min, max)
+ raise "not a task or subtask" unless task.is_a?(Task) or task.is_a?(SubTask)
+ raise "invalid max ("+max.to_s+"), min ("+min.to_s+") params" unless
+ min.is_a?(Numeric) and max.is_a?(Numeric) and min >= 0 and max <= 100 and max > min
+ @task = task
+ @min = min
+ @max = max
+ @delta = max - min
+ end
+
+ # convenience method to handle null tasks
+ def self.create(task, min, max)
+ if task
+ SubTask.new(task, min, max)
+ else
+ nil
end
end
+
+ def progress(pct)
+ raise "no numeric >= 0 and <= 100 : '"+pct.to_s+"'" unless pct.is_a?(Numeric) and pct>=0 and pct<=100
+ #puts "subtask := "+pct.to_s+" -> task := "+(@min + @delta * pct.to_f * 0.01).to_s
+ @task.progress( @min + @delta * pct.to_f * 0.01 )
+ end
+
+ def running?()
+ @task.running?
+ end
+ end
+
+ # The David Gallagher feature:
+ # a fake sub task to keep the progress bar movin for external jobs
+ # note: param could be a subtask
+ #
+ # usage (for a call that is normally finished in under 60 seconds):
+ # fsk = FakeSubTask.new(task, 60)
+ # external_lib_call.start
+ # external_lib_call.wait_until_finished
+ # fsk.finished
+ #
+ # what happens:
+ # the FakeSubTask updates the task.progress each second until
+ # runtime is up or the finished mehtod is called
+ #
+ # example if the param runtime is too low:
+ # 25% .. 50% .. 75% .. 100% .. 100% .. 100% .. 100% .. 100%
+ # example if the param runtime is too high:
+ # 5% .. 10% .. 15% .. 20% .. 25% .. 30% .. 35% .. 100%
+ # the latter example is better (keep the bar movin!)
+ # -> better make a conservative runtime estimate
+ class FakeSubTask
+
+ def initialize(task, runtime)
+ @task = task
+ @thread = Thread.new do
+ timeleft = runtime
+ while (timeleft > 0 and @task.running?)
+ sleep 1
+ timeleft -= 1
+ @task.progress( (runtime - timeleft) / runtime.to_f * 100 )
+ end
+ end
+ end
+
+ # convenience method to handle null tasks
+ def self.create(task, runtime)
+ if task
+ FakeSubTask.new(task, runtime)
+ else
+ nil
+ end
+ end
+
+ def finished
+ @thread.exit
+ @task.progress(100) if @task.running?
+ end
end
end
diff --git a/lib/to-html.rb b/lib/to-html.rb
new file mode 100755
index 0000000..e9764ef
--- /dev/null
+++ b/lib/to-html.rb
@@ -0,0 +1,81 @@
+
+OT_LOGO = "http://opentox.informatik.uni-freiburg.de/ot-logo.png"
+
+
+class String
+
+ # encloses URI in text with with link tag
+ # @return [String] new text with marked links
+ def link_urls
+ self.gsub(/(?i)http:\/\/[^\r\n\s']*/, '<a href=\0>\0</a>')
+ end
+end
+
+module OpenTox
+
+ # produces a html page for making web services browser friendly
+ # format of text (=string params) is preserved (e.g. line breaks)
+ # urls are marked as links
+ # @example post params:
+ # [ [ [:mandatory_param_1], [:mandatory_param_2], [:optional_param,"default_value"] ],
+ # [ [:alteranative_mandatory_param_1], [:alteranative_mandatory_param_2] ]
+ # ]
+ # @param [String] text this is the actual content,
+ # @param [optional,String] related_links info on related resources
+ # @param [optional,String] description general info
+ # @param [optional,Array] post_params, array of arrays containing info on POST operation, see example
+ # @return [String] html page
+ def self.text_to_html( text, related_links=nil, description=nil, post_params=nil )
+
+ # TODO add title as parameter
+ title = nil #$sinatra.url_for($sinatra.request.env['PATH_INFO'], :full) if $sinatra
+
+ html = <<EOF
+<html>
+EOF
+ html.chomp!
+ html += "<title>"+title+"</title>" if title
+ html += <<EOF
+<img src="
+EOF
+ html.chomp!
+ html += OT_LOGO
+ html += <<EOF
+">
+<body>
+EOF
+ html.chomp!
+ html += "<h3>Description</h3><pre><p>"+description.link_urls+"</p></pre>" if description
+ html += "<h3>Related links</h3><pre><p>"+related_links.link_urls+"</p></pre>" if related_links
+ if post_params
+ html += "<h3>POST parameters</h3>"
+ count = 0
+ post_params.each do |p|
+ html += "<pre><p>alternatively:</p></pre>" if count > 0
+ html += "<pre><p><table><thead><tr><th>param</th><th>default_value</th></tr></thead>"
+ p.each do |k,v|
+ html += "<tr><th>"+k.to_s+"</th><th>"+(v!=nil ? v.to_s : "<i>mandatory</i>")+"</th></tr>"
+ end
+ html += "</table></p></pre>"
+ count += 1
+ end
+ end
+ html += "<h3>Content</h3>" if description || related_links
+ html += <<EOF
+<pre>
+<p style="padding:15px; border:10px solid #5D308A">
+EOF
+ html.chomp!
+ html += text.link_urls
+ html += <<EOF
+</p>
+</pre>
+</body>
+<html>
+EOF
+ html
+ end
+
+end
+
+#puts OpenTox.text_to_html("bla") \ No newline at end of file