opentox-ruby-api-wrapper 1.4.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,170 @@
1
+
2
+
3
+ module OpenTox
4
+
5
+ #PENDING: implement ot error api, move to own file
6
+ class Error
7
+
8
+ attr_accessor :code, :body, :uri, :payload, :headers
9
+
10
+ def initialize(code, body, uri, payload, headers)
11
+ self.code = code
12
+ self.body = body
13
+ self.uri = uri
14
+ self.payload = payload
15
+ self.headers = headers
16
+ end
17
+
18
+ def self.parse(error_array_string)
19
+ begin
20
+ err = YAML.load(error_array_string)
21
+ if err and err.is_a?(Array) and err.size>0 and err[0].is_a?(Error)
22
+ return err
23
+ else
24
+ return nil
25
+ end
26
+ rescue
27
+ return nil
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+ class WrapperResult < String
34
+ attr_accessor :content_type, :code
35
+ end
36
+
37
+ class RestClientWrapper
38
+
39
+ def self.get(uri, headers=nil, wait=true)
40
+ execute( "get", uri, headers, nil, wait)
41
+ end
42
+
43
+ def self.post(uri, headers, payload=nil, wait=true)
44
+ execute( "post", uri, headers, payload, wait )
45
+ end
46
+
47
+ def self.put(uri, headers, payload=nil )
48
+ execute( "put", uri, headers, payload )
49
+ end
50
+
51
+ def self.delete(uri, headers=nil)
52
+ execute( "delete", uri, headers, nil)
53
+ end
54
+
55
+ def self.raise_uri_error(error_msg, uri, headers=nil, payload=nil)
56
+ do_halt( "-", error_msg, uri, headers, payload )
57
+ end
58
+
59
+ private
60
+ def self.execute( rest_call, uri, headers, payload=nil, wait=true )
61
+
62
+ do_halt 400,"uri is null",uri,headers,payload unless uri
63
+ do_halt 400,"not a uri",uri,headers,payload unless Utils.is_uri?(uri)
64
+ do_halt 400,"headers are no hash",uri,headers,payload unless headers==nil or headers.is_a?(Hash)
65
+ do_halt 400,"nil headers for post not allowed, use {}",uri,headers,payload if rest_call=="post" and headers==nil
66
+ headers.each{ |k,v| headers.delete(k) if v==nil } if headers #remove keys with empty values, as this can cause problems
67
+
68
+ begin
69
+ #LOGGER.debug "RestCall: "+rest_call.to_s+" "+uri.to_s+" "+headers.inspect
70
+ resource = RestClient::Resource.new(uri,{:timeout => 60}) #, :user => @@users[:users].keys[0], :password => @@users[:users].values[0]})
71
+ if payload
72
+ result = resource.send(rest_call, payload, headers)
73
+ elsif headers
74
+ result = resource.send(rest_call, headers)
75
+ else
76
+ result = resource.send(rest_call)
77
+ end
78
+
79
+ # result is a string, with the additional fields content_type and code
80
+ res = WrapperResult.new(result.body)
81
+ res.content_type = result.headers[:content_type]
82
+ raise "content-type not set" unless res.content_type
83
+ res.code = result.code
84
+
85
+ return res if res.code==200 || !wait
86
+
87
+ while (res.code==201 || res.code==202)
88
+ res = wait_for_task(res, uri)
89
+ end
90
+ raise "illegal status code: '"+res.code.to_s+"'" unless res.code==200
91
+ return res
92
+
93
+ rescue RestClient::RequestFailed => ex
94
+ do_halt ex.http_code,ex.http_body,uri,headers,payload
95
+ rescue RestClient::RequestTimeout => ex
96
+ do_halt 408,ex.message,uri,headers,payload
97
+ rescue => ex
98
+ #raise ex
99
+ #raise "'"+ex.message+"' uri: "+uri.to_s
100
+ begin
101
+ code = ex.http_code
102
+ msg = ex.http_body
103
+ rescue
104
+ code = 500
105
+ msg = ex.to_s
106
+ end
107
+ do_halt code,msg,uri,headers,payload
108
+ end
109
+ end
110
+
111
+ def self.wait_for_task( res, base_uri )
112
+
113
+ task = nil
114
+ case res.content_type
115
+ when /application\/rdf\+xml|text\/x-yaml/
116
+ task = OpenTox::Task.from_data(res, res.content_type, res.code, base_uri)
117
+ when /text\//
118
+ raise "uri list has more than one entry, should be a task" if res.content_type=~/text\/uri-list/ and
119
+ res.split("\n").size > 1 #if uri list contains more then one uri, its not a task
120
+ task = OpenTox::Task.find(res.to_s) if Utils.task_uri?(res)
121
+ else
122
+ raise "unknown content-type for task: '"+res.content_type.to_s+"'" #+"' content: "+res[0..200].to_s
123
+ end
124
+
125
+ LOGGER.debug "result is a task '"+task.uri.to_s+"', wait for completion"
126
+ task.wait_for_completion
127
+ raise task.description unless task.completed? # maybe task was cancelled / error
128
+
129
+ res = WrapperResult.new task.resultURI
130
+ res.code = task.http_code
131
+ res.content_type = "text/uri-list"
132
+ return res
133
+ end
134
+
135
+ def self.do_halt( code, body, uri, headers, payload=nil )
136
+
137
+ #build error
138
+ causing_errors = Error.parse(body)
139
+ if causing_errors
140
+ error = causing_errors + [Error.new(code, "subsequent error", uri, payload, headers)]
141
+ else
142
+ error = [Error.new(code, body, uri, payload, headers)]
143
+ end
144
+
145
+ # #debug utility: write error to file
146
+ # error_dir = "/tmp/ot_errors"
147
+ # FileUtils.mkdir(error_dir) unless File.exist?(error_dir)
148
+ # raise "could not create error dir" unless File.exist?(error_dir) and File.directory?(error_dir)
149
+ # file_name = "error"
150
+ # time=Time.now.strftime("%m.%d.%Y-%H:%M:%S")
151
+ # count = 1
152
+ # count+=1 while File.exist?(File.join(error_dir,file_name+"_"+time+"_"+count.to_s))
153
+ # File.new(File.join(error_dir,file_name+"_"+time+"_"+count.to_s),"w").puts(body)
154
+
155
+ # handle error
156
+ # we are either in a task, or in sinatra
157
+ # PENDING: always return yaml for now
158
+
159
+ if $self_task #this global var in Task.as_task to mark that the current process is running in a task
160
+ raise error.to_yaml # the error is caught, logged, and task state is set to error in Task.as_task
161
+ #elsif $sinatra #else halt sinatra
162
+ #$sinatra.halt(502,error.to_yaml)
163
+ elsif defined?(halt)
164
+ halt(502,error.to_yaml)
165
+ else #for testing purposes (if classes used directly)
166
+ raise error.to_yaml
167
+ end
168
+ end
169
+ end
170
+ end
data/lib/spork.rb CHANGED
@@ -37,6 +37,7 @@ module Spork
37
37
  def self.spork(options={})
38
38
  logger = options[:logger]
39
39
  logger.debug "spork> parent PID = #{Process.pid}" if logger
40
+
40
41
  child = fork do
41
42
  begin
42
43
  start = Time.now
@@ -52,6 +53,7 @@ module Spork
52
53
  yield
53
54
 
54
55
  rescue => ex
56
+ #raise ex
55
57
  logger.error "spork> Exception in child[#{Process.pid}] - #{ex.class}: #{ex.message}" if logger
56
58
  ensure
57
59
  logger.info "spork> child[#{Process.pid}] took #{Time.now - start} sec" if logger
data/lib/task.rb CHANGED
@@ -1,97 +1,170 @@
1
1
  LOGGER.progname = File.expand_path(__FILE__)
2
2
 
3
+ $self_task=nil
4
+
3
5
  module OpenTox
4
6
 
5
7
  class Task
6
8
 
7
- attr_accessor :uri
8
-
9
- def initialize(uri)
10
- @uri = uri.chomp
11
- end
12
-
13
- def self.create
14
- resource = RestClient::Resource.new(@@config[:services]["opentox-task"], :user => @@users[:users].keys[0], :password => @@users[:users].values[0])
15
- uri = resource.post({}).chomp
16
- Task.new(uri.chomp)
17
- end
18
-
9
+ # due_to_time is only set in local tasks
10
+ TASK_ATTRIBS = [ :uri, :date, :title, :creator, :description, :hasStatus, :percentageCompleted, :resultURI, :due_to_time ]
11
+ TASK_ATTRIBS.each{ |a| attr_accessor(a) }
12
+ attr_accessor :http_code
13
+
14
+ private
15
+ def initialize(uri)
16
+ @uri = uri.to_s.strip
17
+ end
18
+
19
+ # create is private now, use OpenTox::Task.as_task
20
+ def self.create(max_duration)
21
+ task_uri = RestClientWrapper.post(@@config[:services]["opentox-task"], {:max_duration => max_duration}, nil, false).to_s
22
+ Task.find(task_uri.chomp)
23
+ end
24
+
25
+ public
19
26
  def self.find(uri)
20
- Task.new(uri)
21
- end
22
-
23
- def self.base_uri
24
- @@config[:services]["opentox-task"]
25
- end
26
-
27
- def self.all
28
- task_uris = RestClient.get(@@config[:services]["opentox-task"]).chomp.split(/\n/)
29
- task_uris.collect{|uri| Task.new(uri)}
30
- end
31
-
32
- def created_at
33
- RestClient.get File.join(@uri, 'created_at')
34
- end
35
-
36
- def finished_at
37
- RestClient.get File.join(@uri, 'finished_at')
38
- end
39
-
40
- def status
41
- RestClient.get File.join(@uri, 'status')
42
- end
43
-
44
- def resource
45
- RestClient.get File.join(@uri, 'resource')
46
- end
47
-
48
- def started
49
- LOGGER.info File.join(@uri,'started')
50
- resource = RestClient::Resource.new(File.join(@uri,'started'), :user => @@users[:users].keys[0], :password => @@users[:users].values[0])
51
- resource.put({})
52
- end
53
-
27
+ task = Task.new(uri)
28
+ task.reload
29
+ return task
30
+ end
31
+
32
+ def self.from_data(data, content_type, code, base_uri)
33
+ task = Task.new(nil)
34
+ task.http_code = code
35
+ task.reload_from_data(data, content_type, base_uri)
36
+ return task
37
+ end
38
+
39
+ def reload
40
+ result = RestClientWrapper.get(uri, {:accept => 'application/rdf+xml'}, false)#'application/x-yaml'})
41
+ @http_code = result.code
42
+ reload_from_data(result, result.content_type, uri)
43
+ end
44
+
45
+ def reload_from_data( data, content_type, base_uri )
46
+ case content_type
47
+ when /yaml/
48
+ task = YAML.load data
49
+ TASK_ATTRIBS.each do |a|
50
+ raise "task yaml data invalid, key missing: "+a.to_s unless task.has_key?(a)
51
+ send("#{a.to_s}=".to_sym,task[a])
52
+ end
53
+ when /application\/rdf\+xml/
54
+ owl = OpenTox::Owl.from_data(data,base_uri,"Task")
55
+ self.uri = owl.uri
56
+ (TASK_ATTRIBS-[:uri]).each{|a| self.send("#{a.to_s}=".to_sym, owl.get(a.to_s))}
57
+ else
58
+ raise "content type for tasks not supported: "+content_type.to_s
59
+ end
60
+ raise "uri is null after loading" unless @uri and @uri.to_s.strip.size>0
61
+ end
62
+
54
63
  def cancel
55
- resource = RestClient::Resource.new(File.join(@uri,'cancelled'), :user => @@users[:users].keys[0], :password => @@users[:users].values[0])
56
- resource.put({})
64
+ RestClientWrapper.put(File.join(@uri,'Cancelled'))
65
+ reload
57
66
  end
58
67
 
59
68
  def completed(uri)
60
- resource = RestClient::Resource.new(File.join(@uri,'completed'), :user => @@users[:users].keys[0], :password => @@users[:users].values[0])
61
- resource.put :resource => uri
69
+ RestClientWrapper.put(File.join(@uri,'Completed'),{:resultURI => uri})
70
+ reload
62
71
  end
63
72
 
64
- def failed
65
- resource = RestClient::Resource.new(File.join(@uri,'failed'), :user => @@users[:users].keys[0], :password => @@users[:users].values[0])
66
- resource.put({})
73
+ def error(description)
74
+ RestClientWrapper.put(File.join(@uri,'Error'),{:description => description})
75
+ reload
67
76
  end
77
+
78
+ def pid=(pid)
79
+ RestClientWrapper.put(File.join(@uri,'pid'), {:pid => pid})
80
+ end
68
81
 
69
- def parent=(task)
70
- #RestClient.put File.join(@uri,'parent'), {:uri => task.uri}
71
- resource = RestClient::Resource.new(File.join(@uri,'parent'), :user => @@users[:users].keys[0], :password => @@users[:users].values[0])
72
- LOGGER.debug "task.rb: #{resource}"
73
- resource.put :uri => task.uri
74
- end
75
-
76
- def pid=(pid)
77
- resource = RestClient::Resource.new(File.join(@uri,'pid'), :user => @@users[:users].keys[0], :password => @@users[:users].values[0])
78
- resource.put :pid => pid
79
- end
82
+ def running?
83
+ @hasStatus.to_s == 'Running'
84
+ end
80
85
 
81
86
  def completed?
82
- self.status.to_s == 'completed'
83
- end
84
-
85
- def failed?
86
- self.status.to_s == 'failed'
87
- end
88
-
89
- def wait_for_completion(dur=0.1)
90
- until self.completed? or self.failed?
87
+ @hasStatus.to_s == 'Completed'
88
+ end
89
+
90
+ def error?
91
+ @hasStatus.to_s == 'Error'
92
+ end
93
+
94
+ # waits for a task, unless time exceeds or state is no longer running
95
+ def wait_for_completion(dur=0.3)
96
+
97
+ if (@uri.match(@@config[:services]["opentox-task"]))
98
+ due_to_time = Time.parse(@due_to_time)
99
+ running_time = due_to_time - Time.parse(@date)
100
+ else
101
+ # the date of the external task cannot be trusted, offest to local time might be to big
102
+ due_to_time = Time.new + EXTERNAL_TASK_MAX_DURATION
103
+ running_time = EXTERNAL_TASK_MAX_DURATION
104
+ end
105
+ LOGGER.debug "start waiting for task "+@uri.to_s+" at: "+Time.new.to_s+", waiting at least until "+due_to_time.to_s
106
+
107
+ while self.running?
91
108
  sleep dur
109
+ reload
110
+ check_state
111
+ if (Time.new > due_to_time)
112
+ raise "max wait time exceeded ("+running_time.to_s+"sec), task: '"+@uri.to_s+"'"
113
+ end
92
114
  end
93
- end
94
-
115
+
116
+ LOGGER.debug "task no longer running: "+@uri.to_s+", result: "+@resultURI.to_s
117
+ end
118
+
119
+ def check_state
120
+ begin
121
+ raise "illegal task state, task is completed, resultURI is no URI: '"+@resultURI.to_s+
122
+ "'" unless @resultURI and Utils.is_uri?(@resultURI) if completed?
123
+
124
+ if @http_code == 202
125
+ raise "illegal task state, code is 202, but hasStatus is not Running: '"+@hasStatus+"'" unless running?
126
+ elsif @http_code == 201
127
+ raise "illegal task state, code is 201, but hasStatus is not Completed: '"+@hasStatus+"'" unless completed?
128
+ raise "illegal task state, code is 201, resultURI is no task-URI: '"+@resultURI.to_s+
129
+ "'" unless @resultURI and Utils.task_uri?(@resultURI)
130
+ end
131
+ rescue => ex
132
+ RestClientWrapper.raise_uri_error(ex.message, @uri)
133
+ end
134
+ end
135
+
136
+ # returns the task uri
137
+ # catches halts and exceptions, task state is set to error then
138
+ def self.as_task(max_duration=DEFAULT_TASK_MAX_DURATION)
139
+ #return yield nil
140
+
141
+ task = OpenTox::Task.create(max_duration)
142
+ task_pid = Spork.spork(:logger => LOGGER) do
143
+ LOGGER.debug "Task #{task.uri} started #{Time.now}"
144
+ $self_task = task
145
+
146
+ begin
147
+ result = catch(:halt) do
148
+ yield task
149
+ end
150
+ # catching halt, set task state to error
151
+ if result && result.is_a?(Array) && result.size==2 && result[0]>202
152
+ LOGGER.error "task was halted: "+result.inspect
153
+ task.error(result[1])
154
+ return
155
+ end
156
+ LOGGER.debug "Task #{task.uri} done #{Time.now} -> "+result.to_s
157
+ task.completed(result)
158
+ rescue => ex
159
+ LOGGER.error "task failed: "+ex.message
160
+ LOGGER.error ": "+ex.backtrace.join("\n")
161
+ task.error(ex.message)
162
+ end
163
+ end
164
+ task.pid = task_pid
165
+ LOGGER.debug "Started task: "+task.uri.to_s
166
+ task.uri
167
+ end
95
168
  end
96
169
 
97
170
  end
@@ -1,13 +1,25 @@
1
1
  # Example configuration for OpenTox, please adjust to your settings
2
2
  #
3
+ # Database setup:
4
+ #
5
+ # Example MySql:
6
+ #
7
+ # :database:
8
+ # :adapter: mysql
9
+ # :database: production
10
+ # :username: production
11
+ # :password: your_password
12
+ # :host: localhost
13
+ #
3
14
  # Example 1: Using external test services
4
15
  #
5
16
  # :services:
6
- # opentox-compound: "http://webservices.in-silico.ch/test/compound/"
7
- # opentox-dataset: "http://webservices.in-silico.ch/test/dataset/"
8
- # opentox-algorithm: "http://webservices.in-silico.ch/test/algorithm/"
9
- # opentox-model: "http://webservices.in-silico.ch/test/model/"
10
- # opentox-task: "http://webservices.in-silico.ch/test/task/"
17
+ # opentox-compound: "http://webservices.in-silico.ch/compound/"
18
+ # opentox-dataset: "http://webservices.in-silico.ch/dataset/"
19
+ # opentox-algorithm: "http://webservices.in-silico.ch/algorithm/"
20
+ # opentox-model: "http://webservices.in-silico.ch/model/"
21
+ # opentox-task: "http://webservices.in-silico.ch/task/"
22
+ # opentox-validation: "http://opentox.informatik.uni-freiburg.de/validation/"
11
23
  #
12
24
  # Example 2: Using local services
13
25
  # :base_dir: /home/ch/webservices
@@ -18,3 +30,39 @@
18
30
  # opentox-algorithm: "http://localhost:4002/"
19
31
  # opentox-model: "http://localhost:4003/"
20
32
  # opentox-task: "http://localhost:4004/"
33
+ # opentox-validation: "http://opentox.informatik.uni-freiburg.de/validation/"
34
+ #
35
+ # Accept headers:
36
+ #
37
+ # :accept_headers:
38
+ # opentox-compound:
39
+ # - "chemical/x-daylight-smiles"
40
+ # - "chemical/x-inchi"
41
+ # - "chemical/x-mdl-sdfile"
42
+ # - "image/gif"
43
+ # - "text/plain"
44
+ # opentox-dataset:
45
+ # - "application/x-yaml"
46
+ # - "text/x-yaml"
47
+ # - "application/rdf+xml"
48
+ # opentox-algorithm:
49
+ # - "application/x-yaml"
50
+ # - "text/x-yaml"
51
+ # - "application/rdf+xml"
52
+ # opentox-model:
53
+ # - "application/x-yaml"
54
+ # - "text/x-yaml"
55
+ # - "application/rdf+xml"
56
+ # opentox-task:
57
+ # - "application/x-yaml"
58
+ # - "text/x-yaml"
59
+ # - "application/rdf+xml"
60
+ # opentox-validation:
61
+ # - "application/x-yaml"
62
+ # - "text/x-yaml"
63
+ # - "application/rdf+xml"
64
+
65
+ # Timeouts:
66
+ #
67
+ # :default_task_max_duration: 3600
68
+ # :external_task_max_duration: 3600
data/lib/utils.rb CHANGED
@@ -4,6 +4,38 @@ module OpenTox
4
4
  def self.gauss(sim, sigma = 0.3)
5
5
  x = 1.0 - sim
6
6
  Math.exp(-(x*x)/(2*sigma*sigma))
7
- end
8
- end
7
+ end
8
+
9
+ def self.task_uri?(uri)
10
+ is_uri?(uri) && uri.to_s =~ /task/
11
+ end
12
+
13
+ def self.dataset_uri?(uri)
14
+ is_uri?(uri) && uri.to_s =~ /dataset/
15
+ end
16
+
17
+ def self.model_uri?(uri)
18
+ is_uri?(uri) && uri.to_s =~ /model/
19
+ end
20
+
21
+
22
+ def self.is_uri?(uri)
23
+ return false if uri==nil || uri.to_s.size==0
24
+ begin
25
+ u = URI::parse(uri)
26
+ return (u.scheme!=nil and u.host!=nil)
27
+ rescue URI::InvalidURIError
28
+ return false
29
+ end
30
+ end
31
+ end
32
+
33
+ # ['rubygems', 'rest_client'].each do |r|
34
+ # require r
35
+ # end
36
+ # ["bla", "google.de", "http://google.de"].each do |u|
37
+ # puts u+"? "+Utils.is_uri?(u).to_s
38
+ # end
39
+
9
40
  end
41
+
data/lib/validation.rb CHANGED
@@ -4,8 +4,16 @@ module OpenTox
4
4
  attr_accessor :uri
5
5
 
6
6
  def initialize(params)
7
- resource = RestClient::Resource.new(params[:uri], :user => @@users[:users].keys[0], :password => @@users[:users].values[0])
8
- @uri = resource.post(params).to_s
7
+ #resource = RestClient::Resource.new(params[:uri], :user => @@users[:users].keys[0], :password => @@users[:users].values[0])
8
+ #@uri = resource.post(params).body
9
+ #LOGGER.debug "VALIDATION URI: " + @uri.to_s
10
+ call = "curl -X POST "
11
+ params.each do |k,v|
12
+ call += " -d "+k.to_s+"=\""+URI.encode(v.to_s)+"\"" unless k == :uri
13
+ end
14
+ call += " "+params[:uri]
15
+ LOGGER.debug call
16
+ @uri = `#{call}`
9
17
  end
10
18
 
11
19
  def self.crossvalidation(params)