opentox-ruby 0.0.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,32 +1,4 @@
1
1
  module OpenTox
2
-
3
- #PENDING: implement ot error api, move to own file
4
- class Error
5
-
6
- attr_accessor :code, :body, :uri, :payload, :headers
7
-
8
- def initialize(code, body, uri, payload, headers)
9
- self.code = code
10
- self.body = body.to_s[0..1000]
11
- self.uri = uri
12
- self.payload = payload
13
- self.headers = headers
14
- end
15
-
16
- def self.parse(error_array_string)
17
- begin
18
- err = YAML.load(error_array_string)
19
- if err and err.is_a?(Array) and err.size>0 and err[0].is_a?(Error)
20
- return err
21
- else
22
- return nil
23
- end
24
- rescue
25
- return nil
26
- end
27
- end
28
-
29
- end
30
2
 
31
3
  class WrapperResult < String
32
4
  attr_accessor :content_type, :code
@@ -34,45 +6,80 @@ module OpenTox
34
6
 
35
7
  class RestClientWrapper
36
8
 
37
- def self.get(uri, headers=nil, wait=true)
38
- execute( "get", uri, headers, nil, wait)
9
+ # performs a GET REST call
10
+ # raises OpenTox::Error if call fails (rescued in overwrite.rb -> halt 502)
11
+ # per default: waits for Task to finish and returns result URI of Task
12
+ # @param [String] uri destination URI
13
+ # @param [optional,Hash] headers contains params like accept-header
14
+ # @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly
15
+ # @param [wait,Boolean] wait set to false to NOT wait for task if result is task
16
+ # @return [OpenTox::WrapperResult] a String containing the result-body of the REST call
17
+ def self.get(uri, headers={}, waiting_task=nil, wait=true )
18
+ execute( "get", uri, nil, headers, waiting_task, wait)
39
19
  end
40
20
 
41
- def self.post(uri, headers, payload=nil, wait=true)
42
- execute( "post", uri, headers, payload, wait )
21
+ # performs a POST REST call
22
+ # raises OpenTox::Error if call fails (rescued in overwrite.rb -> halt 502)
23
+ # per default: waits for Task to finish and returns result URI of Task
24
+ # @param [String] uri destination URI
25
+ # @param [optional,String] payload data posted to the service
26
+ # @param [optional,Hash] headers contains params like accept-header
27
+ # @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly
28
+ # @param [wait,Boolean] wait set to false to NOT wait for task if result is task
29
+ # @return [OpenTox::WrapperResult] a String containing the result-body of the REST call
30
+ def self.post(uri, payload=nil, headers={}, waiting_task=nil, wait=true )
31
+ execute( "post", uri, payload, headers, waiting_task, wait )
43
32
  end
44
33
 
45
- def self.put(uri, headers, payload=nil )
46
- execute( "put", uri, headers, payload )
34
+ # performs a PUT REST call
35
+ # raises OpenTox::Error if call fails (rescued in overwrite.rb -> halt 502)
36
+ # @param [String] uri destination URI
37
+ # @param [optional,Hash] headers contains params like accept-header
38
+ # @param [optional,String] payload data put to the service
39
+ # @return [OpenTox::WrapperResult] a String containing the result-body of the REST call
40
+ def self.put(uri, payload=nil, headers={} )
41
+ execute( "put", uri, payload, headers )
47
42
  end
48
43
 
49
- def self.delete(uri, headers=nil)
50
- execute( "delete", uri, headers, nil)
44
+ # performs a DELETE REST call
45
+ # raises OpenTox::Error if call fails (rescued in overwrite.rb -> halt 502)
46
+ # @param [String] uri destination URI
47
+ # @param [optional,Hash] headers contains params like accept-header
48
+ # @return [OpenTox::WrapperResult] a String containing the result-body of the REST call
49
+ def self.delete(uri, headers=nil )
50
+ execute( "delete", uri, nil, headers)
51
51
  end
52
52
 
53
- def self.raise_uri_error(error_msg, uri, headers=nil, payload=nil)
54
- do_halt( "-", error_msg, uri, headers, payload )
55
- end
56
-
57
53
  private
58
- def self.execute( rest_call, uri, headers, payload=nil, wait=true )
54
+ def self.execute( rest_call, uri, payload=nil, headers={}, waiting_task=nil, wait=true )
59
55
 
60
- do_halt 400,"uri is null",uri,headers,payload unless uri
61
- do_halt 400,"not a uri",uri,headers,payload unless uri.to_s.uri?
62
- do_halt 400,"headers are no hash",uri,headers,payload unless headers==nil or headers.is_a?(Hash)
63
- do_halt 400,"nil headers for post not allowed, use {}",uri,headers,payload if rest_call=="post" and headers==nil
56
+ raise OpenTox::BadRequestError.new "uri is null" unless uri
57
+ raise OpenTox::BadRequestError.new "not a uri: "+uri.to_s unless uri.to_s.uri?
58
+ raise "headers are no hash: "+headers.inspect unless headers==nil or headers.is_a?(Hash)
59
+ raise OpenTox::BadRequestError.new "accept should go into the headers" if payload and payload.is_a?(Hash) and payload[:accept]
60
+ raise OpenTox::BadRequestError.new "content_type should go into the headers" if payload and payload.is_a?(Hash) and payload[:content_type]
61
+ raise "__waiting_task__ must be 'nil' or '(sub)task', is "+waiting_task.class.to_s if
62
+ waiting_task!=nil and !(waiting_task.is_a?(Task) || waiting_task.is_a?(SubTask))
64
63
  headers.each{ |k,v| headers.delete(k) if v==nil } if headers #remove keys with empty values, as this can cause problems
64
+ ## PENDING partner services accept subjectid only in header
65
+ headers = {} unless headers
66
+ headers[:subjectid] = payload.delete(:subjectid) if payload and payload.is_a?(Hash) and payload.has_key?(:subjectid)
67
+
68
+ # PENDING needed for NUTA, until we finally agree on how to send subjectid
69
+ headers[:subjectid] = payload.delete(:subjectid) if uri=~/ntua/ and payload and payload.is_a?(Hash) and payload.has_key?(:subjectid)
65
70
 
66
71
  begin
67
- #LOGGER.debug "RestCall: "+rest_call.to_s+" "+uri.to_s+" "+headers.inspect
68
- resource = RestClient::Resource.new(uri,{:timeout => 60})
69
- if payload
72
+ #LOGGER.debug "RestCall: "+rest_call.to_s+" "+uri.to_s+" "+headers.inspect+" "+payload.inspect
73
+ resource = RestClient::Resource.new(uri,{:timeout => 60})
74
+ if rest_call=="post" || rest_call=="put"
70
75
  result = resource.send(rest_call, payload, headers)
71
- elsif headers
72
- result = resource.send(rest_call, headers)
73
76
  else
74
- result = resource.send(rest_call)
77
+ result = resource.send(rest_call, headers)
75
78
  end
79
+ #LOGGER.debug "result body size: #{result.body.size}"
80
+
81
+ # PENDING NTUA does return errors with 200
82
+ raise RestClient::ExceptionWithResponse.new(result) if uri=~/ntua/ and result.body =~ /about.*http:\/\/anonymous.org\/error/
76
83
 
77
84
  # result is a string, with the additional fields content_type and code
78
85
  res = WrapperResult.new(result.body)
@@ -84,29 +91,31 @@ module OpenTox
84
91
  return res if res.code==200 || !wait
85
92
 
86
93
  while (res.code==201 || res.code==202)
87
- res = wait_for_task(res, uri)
94
+ res = wait_for_task(res, uri, waiting_task)
88
95
  end
89
96
  raise "illegal status code: '"+res.code.to_s+"'" unless res.code==200
90
97
  return res
91
98
 
92
99
  rescue RestClient::RequestTimeout => ex
93
- do_halt 408,ex.message,uri,headers,payload
100
+ received_error ex.message, 408, nil, {:rest_uri => uri, :headers => headers, :payload => payload}
101
+ rescue Errno::ECONNREFUSED => ex
102
+ received_error ex.message, 500, nil, {:rest_uri => uri, :headers => headers, :payload => payload}
103
+ rescue RestClient::ExceptionWithResponse => ex
104
+ # error comming from a different webservice,
105
+ received_error ex.http_body, ex.http_code, ex.response.net_http_res.content_type, {:rest_uri => uri, :headers => headers, :payload => payload}
106
+ rescue OpenTox::RestCallError => ex
107
+ # already a rest-error, probably comes from wait_for_task, just pass through
108
+ raise ex
94
109
  rescue => ex
95
- #raise ex
96
- #raise "'"+ex.message+"' uri: "+uri.to_s
97
- begin
98
- code = ex.http_code
99
- msg = ex.http_body
100
- rescue
101
- code = 500
102
- msg = ex.to_s
103
- end
104
- do_halt code,msg,uri,headers,payload
110
+ # some internal error occuring in rest_client_wrapper, just pass through
111
+ raise ex
105
112
  end
106
113
  end
107
114
 
108
- def self.wait_for_task( res, base_uri )
109
-
115
+ def self.wait_for_task( res, base_uri, waiting_task=nil )
116
+ #TODO remove TUM hack
117
+ res.content_type = "text/uri-list" if base_uri =~/tu-muenchen/ and res.content_type == "application/x-www-form-urlencoded;charset=UTF-8"
118
+
110
119
  task = nil
111
120
  case res.content_type
112
121
  when /application\/rdf\+xml/
@@ -115,53 +124,55 @@ module OpenTox
115
124
  task = OpenTox::Task.from_yaml(res)
116
125
  when /text\//
117
126
  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
118
- task = OpenTox::Task.find(res.to_s) if res.to_s.uri?
127
+ task = OpenTox::Task.find(res.to_s.chomp) if res.to_s.uri?
119
128
  else
120
- raise "unknown content-type for task: '"+res.content_type.to_s+"'" #+"' content: "+res[0..200].to_s
129
+ raise "unknown content-type for task : '"+res.content_type.to_s+"'"+" base-uri: "+base_uri.to_s+" content: "+res[0..200].to_s
121
130
  end
122
131
 
123
132
  LOGGER.debug "result is a task '"+task.uri.to_s+"', wait for completion"
124
- task.wait_for_completion
125
- raise task.description unless task.completed? # maybe task was cancelled / error
126
-
133
+ task.wait_for_completion waiting_task
134
+ unless task.completed? # maybe task was cancelled / error
135
+ if task.errorReport
136
+ received_error task.errorReport, task.http_code, nil, {:rest_uri => task.uri, :rest_code => task.http_code}
137
+ else
138
+ raise "task status: '"+task.status.to_s+"' but errorReport nil"
139
+ end
140
+ end
141
+
127
142
  res = WrapperResult.new task.result_uri
128
143
  res.code = task.http_code
129
144
  res.content_type = "text/uri-list"
130
145
  return res
131
146
  end
132
147
 
133
- def self.do_halt( code, body, uri, headers, payload=nil )
134
-
135
- #build error
136
- causing_errors = Error.parse(body)
137
- if causing_errors
138
- error = causing_errors + [Error.new(code, "subsequent error", uri, payload, headers)]
148
+ def self.received_error( body, code, content_type=nil, params=nil )
149
+
150
+ # try to parse body
151
+ report = nil
152
+ if body.is_a?(OpenTox::ErrorReport)
153
+ report = body
139
154
  else
140
- error = [Error.new(code, body, uri, payload, headers)]
155
+ case content_type
156
+ when /yaml/
157
+ report = YAML.load(body)
158
+ when /rdf/
159
+ report = OpenTox::ErrorReport.from_rdf(body)
160
+ end
141
161
  end
142
162
 
143
- #debug utility: write error to file
144
- error_dir = "/tmp/ot_errors"
145
- FileUtils.mkdir(error_dir) unless File.exist?(error_dir)
146
- raise "could not create error dir" unless File.exist?(error_dir) and File.directory?(error_dir)
147
- file_name = "error"
148
- time=Time.now.strftime("%m.%d.%Y-%H:%M:%S")
149
- count = 1
150
- count+=1 while File.exist?(File.join(error_dir,file_name+"_"+time+"_"+count.to_s))
151
- File.new(File.join(error_dir,file_name+"_"+time+"_"+count.to_s),"w").puts(body)
152
-
153
- # handle error
154
- # we are either in a task, or in sinatra
155
- # PENDING: always return yaml for now
156
-
157
- if $self_task #this global var in Task.create to mark that the current process is running in a task
158
- raise error.to_yaml # the error is caught, logged, and task state is set to error in Task.create
159
- #elsif $sinatra #else halt sinatra
160
- #$sinatra.halt(502,error.to_yaml)
161
- elsif defined?(halt)
162
- halt(502,error.to_yaml)
163
- else #for testing purposes (if classes used directly)
164
- raise error.to_yaml
163
+ unless report
164
+ # parsing was not successfull
165
+ # raise 'plain' RestCallError
166
+ err = OpenTox::RestCallError.new("REST call returned error: '"+body.to_s+"'")
167
+ err.rest_params = params
168
+ raise err
169
+ else
170
+ # parsing sucessfull
171
+ # raise RestCallError with parsed report as error cause
172
+ err = OpenTox::RestCallError.new("REST call subsequent error")
173
+ err.errorCause = report
174
+ err.rest_params = params
175
+ raise err
165
176
  end
166
177
  end
167
178
  end
data/lib/serializer.rb CHANGED
@@ -14,7 +14,7 @@ module OpenTox
14
14
  def initialize
15
15
 
16
16
  @object = {
17
- # this should come from opntox.owl
17
+ # this should come from opentox.owl
18
18
  OT.Compound => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
19
19
  OT.Feature => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
20
20
  OT.NominalFeature => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
@@ -26,6 +26,16 @@ module OpenTox
26
26
  OT.Algorithm => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
27
27
  OT.Parameter => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
28
28
  OT.Task => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
29
+ #classes for validation
30
+ OT.Validation => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
31
+ OT.ClassificationStatistics => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
32
+ OT.ConfusionMatrix => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
33
+ OT.ConfusionMatrixCell => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
34
+ OT.ClassValueStatistics => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
35
+ OT.RegressionStatistics => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
36
+ OT.Crossvalidation => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
37
+ OT.CrossvalidationInfo => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
38
+ OT.ErrorReport => { RDF["type"] => [{ "type" => "uri", "value" => OWL['Class'] }] } ,
29
39
 
30
40
  OT.compound => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
31
41
  OT.feature => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
@@ -34,6 +44,22 @@ module OpenTox
34
44
  OT.values => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
35
45
  OT.algorithm => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
36
46
  OT.parameters => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
47
+ #object props for validation#
48
+ OT.model => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
49
+ OT.trainingDataset => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
50
+ OT.predictionFeature => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
51
+ OT.predictionDataset => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
52
+ OT.crossvalidation => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
53
+ OT.testTargetDataset => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
54
+ OT.testDataset => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
55
+ OT.classificationStatistics => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
56
+ OT.confusionMatrix => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
57
+ OT.confusionMatrixCell => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
58
+ OT.classValueStatistics => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
59
+ OT.regressionStatistics => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
60
+ OT.validation => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
61
+ OT.crossvalidationInfo => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
62
+ OT.dataset => { RDF["type"] => [{ "type" => "uri", "value" => OWL.ObjectProperty }] } ,
37
63
 
38
64
  DC.title => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
39
65
  DC.identifier => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
@@ -47,6 +73,51 @@ module OpenTox
47
73
  OT.hasStatus => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
48
74
  OT.resultURI => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
49
75
  OT.percentageCompleted => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
76
+ # annotation props for validation
77
+ OT.numUnpredicted => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
78
+ OT.crossvalidationFold => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
79
+ OT.numInstances => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
80
+ OT.numWithoutClass => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
81
+ OT.percentWithoutClass => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
82
+ OT.percentUnpredicted => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
83
+ OT.confusionMatrixActual => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
84
+ OT.confusionMatrixPredicted => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
85
+ OT.confusionMatrixValue => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
86
+ OT.numIncorrect => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
87
+ OT.percentCorrect => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
88
+ OT.numCorrect => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
89
+ OT.accuracy => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
90
+ OT.trueNegativeRate => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
91
+ OT.truePositiveRate => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
92
+ OT.falseNegativeRate => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
93
+ OT.falsePositiveRate => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
94
+ OT.numTrueNegatives => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
95
+ OT.numTruePositives => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
96
+ OT.numFalseNegatives => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
97
+ OT.numFalsePositives => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
98
+ OT.classValue => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
99
+ OT.precision => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
100
+ OT.areaUnderRoc => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
101
+ OT.weightedAreaUnderRoc => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
102
+ OT.fMeasure => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
103
+ OT.percentIncorrect => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
104
+ OT.validationType => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
105
+ OT.realRuntime => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
106
+ OT.sampleCorrelationCoefficient => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
107
+ OT.targetVarianceActual => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
108
+ OT.targetVariancePredicted => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
109
+ OT.meanAbsoluteError => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
110
+ OT.sumSquaredError => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
111
+ OT.rootMeanSquaredError => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
112
+ OT.rSquare => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
113
+ OT.stratified => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
114
+ OT.numFolds => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
115
+ OT.randomSeed => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
116
+ OT.reportType => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
117
+ OT.message => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
118
+ OT.statusCode => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
119
+ OT.actor => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
120
+ OT.errorCode => { RDF["type"] => [{ "type" => "uri", "value" => OWL.AnnotationProperty }] } ,
50
121
 
51
122
  OT.hasSource => { RDF["type"] => [{ "type" => "uri", "value" => OWL.DatatypeProperty }] } ,
52
123
  OT.value => { RDF["type"] => [{ "type" => "uri", "value" => OWL.DatatypeProperty }] } ,
@@ -121,6 +192,64 @@ module OpenTox
121
192
  @object[uri] = { RDF["type"] => [{ "type" => "uri", "value" => OT.Task }] }
122
193
  add_metadata uri, metadata
123
194
  end
195
+
196
+ # Add a resource defined by resource_class and content
197
+ # (see documentation of add_content for example)
198
+ # @param [String] uri of resource
199
+ # @param [String] resource class, e.g. OT.Validation
200
+ # @param [Hash] content as hash
201
+ def add_resource(uri, resource_class, content)
202
+ @object[uri] = { RDF["type"] => [{ "type" => "uri", "value" => resource_class }] }
203
+ @@content_id = 1
204
+ add_content uri, content
205
+ end
206
+
207
+ private
208
+ @@content_id = 1
209
+
210
+ # Recursiv function to add content
211
+ # @example
212
+ # { DC.description => "bla",
213
+ # OT.similar_resources => [ "http://uri1", "http://uri2" ],
214
+ # OT.matrixCells =>
215
+ # [ { RDF.type => OT.MatrixCell, OT.cellIndex=1 OT.cellValue => "xy" },
216
+ # { RDF.type => OT.MatrixCell, OT.cellIndex=2 OT.cellValue => "z" } ],
217
+ # OT.info => { RDF.type => OT.ImportantInfo,
218
+ # DC.description => "blub" }
219
+ # }
220
+ # @param [String] uri
221
+ # @param [Hash] content as hash, uri must already have been added to @object
222
+ def add_content(uri, hash)
223
+ raise "content is no hash: "+hash.class.to_s unless hash.is_a?(Hash)
224
+ hash.each do |u,v|
225
+ if v.is_a? Hash
226
+ # value is again a hash, i.e. a new owl class is added
227
+ # first make sure type (==class) is set
228
+ type = v[RDF.type]
229
+ raise "type missing for "+u.to_s+" content:\n"+v.inspect unless type
230
+ raise "class unknown "+type.to_s+" (for "+u.to_s+")" unless @object.has_key?(type)
231
+ # create new node and add to current uri
232
+ genid = "_:#{type.split('#')[-1]}#{@@content_id}"
233
+ @@content_id += 1
234
+ @object[uri] = {} unless @object[uri]
235
+ @object[uri][u] = [{ "type" => "bnode", "value" => genid }]
236
+ # add content to new class
237
+ add_content(genid,v)
238
+ elsif v.is_a? Array
239
+ # value is an array, i.e. a list of values with property is added
240
+ v.each{ |vv| add_content( uri, { u => vv } ) }
241
+ else # v.is_a? String
242
+ # simple string value
243
+ @object[uri] = {} unless @object[uri]
244
+ @object[uri][u] = [] unless @object[uri][u]
245
+ raise "property unknown "+u.to_s if !@object.has_key?(u) and u!=RDF.type
246
+ # use << to allow different values for one property
247
+ @object[uri][u] << {"type" => type(v), "value" => v }
248
+ end
249
+ end
250
+ end
251
+
252
+ public
124
253
 
125
254
  # Add metadata
126
255
  # @param [Hash] metadata
data/lib/task.rb CHANGED
@@ -1,4 +1,3 @@
1
- $self_task=nil
2
1
 
3
2
  module OpenTox
4
3
 
@@ -13,7 +12,7 @@ module OpenTox
13
12
  DC.title => "",
14
13
  DC.date => "",
15
14
  OT.hasStatus => "Running",
16
- OT.percentageCompleted => "0",
15
+ OT.percentageCompleted => 0.0,
17
16
  OT.resultURI => "",
18
17
  DC.creator => "", # not mandatory according to API
19
18
  DC.description => "", # not mandatory according to API
@@ -34,7 +33,7 @@ module OpenTox
34
33
  def self.create( title=nil, creator=nil, max_duration=DEFAULT_TASK_MAX_DURATION, description=nil )
35
34
 
36
35
  params = {:title=>title, :creator=>creator, :max_duration=>max_duration, :description=>description }
37
- task_uri = RestClientWrapper.post(CONFIG[:services]["opentox-task"], params, nil, false).to_s
36
+ task_uri = RestClientWrapper.post(CONFIG[:services]["opentox-task"], params, {}, nil, false).to_s
38
37
  task = Task.new(task_uri.chomp)
39
38
 
40
39
  # measure current memory consumption
@@ -49,34 +48,24 @@ module OpenTox
49
48
 
50
49
  cpu_load = `cat /proc/loadavg`.split(/\s+/)[0..2].collect{|c| c.to_f}
51
50
  nr_cpu_cores = `cat /proc/cpuinfo |grep "cpu cores"|cut -d ":" -f2|tr -d " "`.split("\n").collect{|c| c.to_i}.inject{|sum,n| sum+n}
52
- if cpu_load[0] > nr_cpu_cores and cpu_load[0] > cpu_load[1] and cpu_load[1] > cpu_load[2] # average CPU load of the last minute is high and CPU load is increasing
53
- LOGGER.warn "Cannot start task - CPU load too high (#{cpu_load.join(", ")})"
54
- task.cancel
55
- return task
56
- #raise "Server too busy to start a new task"
57
- end
58
-
51
+ nr_cpu_cores = 1 if !nr_cpu_cores
52
+ #if cpu_load[0] > nr_cpu_cores and cpu_load[0] > cpu_load[1] and cpu_load[1] > cpu_load[2] # average CPU load of the last minute is high and CPU load is increasing
53
+ # LOGGER.warn "Cannot start task - CPU load too high (#{cpu_load.join(", ")})"
54
+ # task.cancel
55
+ # return task
56
+ # #raise "Server too busy to start a new task"
57
+ #end
59
58
 
60
59
  task_pid = Spork.spork(:logger => LOGGER) do
61
60
  LOGGER.debug "Task #{task.uri} started #{Time.now}"
62
- $self_task = task
63
-
64
61
  begin
65
- result = catch(:halt) do
66
- yield task
67
- end
68
- # catching halt, set task state to error
69
- if result && result.is_a?(Array) && result.size==2 && result[0]>202
70
- LOGGER.error "task was halted: "+result.inspect
71
- task.error(result[1])
72
- return
73
- end
62
+ result = yield task
74
63
  LOGGER.debug "Task #{task.uri} done #{Time.now} -> "+result.to_s
75
64
  task.completed(result)
76
- rescue => ex
77
- LOGGER.error "task failed: "+ex.message
78
- LOGGER.error ": "+ex.backtrace.join("\n")
79
- task.error(ex.message)
65
+ rescue => error
66
+ LOGGER.error "task failed: "+error.class.to_s+": "+error.message
67
+ LOGGER.error ":\n"+error.backtrace.join("\n")
68
+ task.error(OpenTox::ErrorReport.create(error, creator))
80
69
  end
81
70
  end
82
71
  task.pid = task_pid
@@ -88,11 +77,23 @@ module OpenTox
88
77
  # @param [String] uri Task URI
89
78
  # @return [OpenTox::Task] Task object
90
79
  def self.find(uri)
80
+ return nil unless uri
91
81
  task = Task.new(uri)
92
82
  task.load_metadata
83
+ raise "could not load task metadata" if task.metadata==nil or task.metadata.size==0
93
84
  task
94
85
  end
95
86
 
87
+ # Find a task for querying, status changes
88
+ # @param [String] uri Task URI
89
+ # @return [OpenTox::Task] Task object
90
+ def self.exist?(uri)
91
+ begin
92
+ return find(uri)
93
+ rescue
94
+ end
95
+ end
96
+
96
97
  # Get a list of all tasks
97
98
  # @param [optional, String] uri URI of task service
98
99
  # @return [text/uri-list] Task URIs
@@ -103,16 +104,19 @@ module OpenTox
103
104
  def self.from_yaml(yaml)
104
105
  @metadata = YAML.load(yaml)
105
106
  end
106
-
107
+
107
108
  def self.from_rdfxml(rdfxml)
108
- file = Tempfile.open("ot-rdfxml"){|f| f.write(rdfxml)}.path
109
- parser = Parser::Owl::Generic.new file
110
- @metadata = parser.load_metadata
109
+ owl = OpenTox::Parser::Owl.from_rdf(rdfxml, OT.Task)
110
+ task = Task.new(owl.uri)
111
+ task.add_metadata(owl.metadata)
112
+ task
111
113
  end
112
114
 
113
115
  def to_rdfxml
114
116
  s = Serializer::Owl.new
117
+ @metadata[OT.errorReport] = @uri+"/ErrorReport/tmpId" if @error_report
115
118
  s.add_task(@uri,@metadata)
119
+ s.add_resource(@uri+"/ErrorReport/tmpId", OT.errorReport, @error_report.rdf_content) if @error_report
116
120
  s.to_rdfxml
117
121
  end
118
122
 
@@ -128,8 +132,12 @@ module OpenTox
128
132
  @metadata[DC.description]
129
133
  end
130
134
 
135
+ def errorReport
136
+ @metadata[OT.errorReport]
137
+ end
138
+
131
139
  def cancel
132
- RestClientWrapper.put(File.join(@uri,'Cancelled'))
140
+ RestClientWrapper.put(File.join(@uri,'Cancelled'),{:cannot_be => "empty"})
133
141
  load_metadata
134
142
  end
135
143
 
@@ -138,11 +146,17 @@ module OpenTox
138
146
  load_metadata
139
147
  end
140
148
 
141
- def error(description)
142
- RestClientWrapper.put(File.join(@uri,'Error'),{:description => description.to_s[0..2000]})
149
+ def error(error_report)
150
+ raise "no error report" unless error_report.is_a?(OpenTox::ErrorReport)
151
+ RestClientWrapper.put(File.join(@uri,'Error'),{:errorReport => error_report.to_yaml})
143
152
  load_metadata
144
153
  end
145
154
 
155
+ # not stored just for to_rdf
156
+ def add_error_report( error_report )
157
+ @error_report = error_report
158
+ end
159
+
146
160
  def pid=(pid)
147
161
  RestClientWrapper.put(File.join(@uri,'pid'), {:pid => pid})
148
162
  end
@@ -160,19 +174,20 @@ module OpenTox
160
174
  end
161
175
 
162
176
  def load_metadata
163
- if (CONFIG[:yaml_hosts].include?(URI.parse(uri).host))
164
- result = RestClientWrapper.get(@uri, {:accept => 'application/x-yaml'}, false)
177
+ if (CONFIG[:yaml_hosts].include?(URI.parse(@uri).host))
178
+ result = RestClientWrapper.get(@uri, {:accept => 'application/x-yaml'}, nil, false)
165
179
  @metadata = YAML.load result.to_s
166
180
  @http_code = result.code
167
181
  else
168
182
  @metadata = Parser::Owl::Generic.new(@uri).load_metadata
169
- @http_code = RestClientWrapper.get(uri, {:accept => 'application/rdf+xml'}, false).code
183
+ @http_code = RestClientWrapper.get(uri, {:accept => 'application/rdf+xml'}, nil, false).code
170
184
  end
185
+ raise "could not load task metadata for task "+@uri.to_s if @metadata==nil || @metadata.size==0
171
186
  end
172
187
 
173
188
  # create is private now, use OpenTox::Task.as_task
174
189
  #def self.create( params )
175
- #task_uri = RestClientWrapper.post(CONFIG[:services]["opentox-task"], params, nil, false).to_s
190
+ #task_uri = RestClientWrapper.post(CONFIG[:services]["opentox-task"], params, {}, false).to_s
176
191
  #Task.find(task_uri.chomp)
177
192
  #end
178
193
 
@@ -217,8 +232,11 @@ module OpenTox
217
232
  =end
218
233
 
219
234
  # waits for a task, unless time exceeds or state is no longer running
220
- def wait_for_completion(dur=0.3)
235
+ # @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly
236
+ # @param [optional,Numeric] dur seconds pausing before cheking again for completion
237
+ def wait_for_completion( waiting_task=nil, dur=0.3)
221
238
 
239
+ waiting_task.waiting_for(self.uri) if waiting_task
222
240
  due_to_time = Time.new + DEFAULT_TASK_MAX_DURATION
223
241
  LOGGER.debug "start waiting for task "+@uri.to_s+" at: "+Time.new.to_s+", waiting at least until "+due_to_time.to_s
224
242
 
@@ -227,21 +245,42 @@ module OpenTox
227
245
  while self.running?
228
246
  sleep dur
229
247
  load_metadata
248
+ # if another (sub)task is waiting for self, set progress accordingly
249
+ waiting_task.progress(@metadata[OT.percentageCompleted].to_f) if waiting_task
230
250
  check_state
231
251
  if (Time.new > due_to_time)
232
252
  raise "max wait time exceeded ("+DEFAULT_TASK_MAX_DURATION.to_s+"sec), task: '"+@uri.to_s+"'"
233
253
  end
234
254
  end
235
-
236
- LOGGER.debug "Task '"+@metadata[OT.hasStatus]+"': "+@uri.to_s+", Result: "+@metadata[OT.resultURI].to_s
255
+ waiting_task.waiting_for(nil) if waiting_task
256
+ LOGGER.debug "Task '"+@metadata[OT.hasStatus].to_s+"': "+@uri.to_s+", Result: "+@metadata[OT.resultURI].to_s
237
257
  end
238
-
258
+
259
+ # updates percentageCompleted value (can only be increased)
260
+ # task has to be running
261
+ # @param [Numeric] pct value between 0 and 100
262
+ def progress(pct)
263
+ #puts "task := "+pct.to_s
264
+ raise "no numeric >= 0 and <= 100 : '"+pct.to_s+"'" unless pct.is_a?(Numeric) and pct>=0 and pct<=100
265
+ if (pct > @metadata[OT.percentageCompleted] + 0.0001)
266
+ RestClientWrapper.put(File.join(@uri,'Running'),{:percentageCompleted => pct})
267
+ load_metadata
268
+ end
269
+ end
270
+
271
+ def waiting_for(task_uri)
272
+ RestClientWrapper.put(File.join(@uri,'Running'),{:waiting_for => task_uri})
273
+ end
274
+
239
275
  private
276
+ VALID_TASK_STATES = ["Cancelled", "Completed", "Running", "Error"]
277
+
240
278
  def check_state
241
279
  begin
280
+ raise "illegal task state, invalid status: '"+@metadata[OT.hasStatus].to_s+"'" unless
281
+ @metadata[OT.hasStatus] unless VALID_TASK_STATES.include?(@metadata[OT.hasStatus])
242
282
  raise "illegal task state, task is completed, resultURI is no URI: '"+@metadata[OT.resultURI].to_s+
243
283
  "'" unless @metadata[OT.resultURI] and @metadata[OT.resultURI].to_s.uri? if completed?
244
-
245
284
  if @http_code == 202
246
285
  raise "#{@uri}: illegal task state, code is 202, but hasStatus is not Running: '"+@metadata[OT.hasStatus]+"'" unless running?
247
286
  elsif @http_code == 201
@@ -250,10 +289,108 @@ module OpenTox
250
289
  "'" unless @metadata[OT.resultURI] and @metadata[OT.resultURI].to_s.uri?
251
290
  end
252
291
  rescue => ex
253
- RestClientWrapper.raise_uri_error(ex.message, @uri)
292
+ raise OpenTox::BadRequestError.new ex.message+" (task-uri:"+@uri+")"
254
293
  end
255
294
  end
295
+ end
296
+
297
+ # Convenience class to split a (sub)task into subtasks
298
+ #
299
+ # example:
300
+ # a crossvalidation is split into creating datasets and performing the validations
301
+ # creating the dataset is 1/3 of the work, perform the validations is 2/3:
302
+ # Task.as_task do |task|
303
+ # create_datasets( SubTask.new(task, 0, 33) )
304
+ # perfom_validations( SubTask.new(task, 33, 100) )
305
+ # end
306
+ # inside the create_datasets / perform_validations you can use subtask.progress(<val>)
307
+ # with vals from 0-100
308
+ #
309
+ # note that you can split a subtask into further subtasks
310
+ class SubTask
311
+
312
+ def initialize(task, min, max)
313
+ raise "not a task or subtask" if task!=nil and !(task.is_a?(Task) or task.is_a?(SubTask))
314
+ raise "invalid max ("+max.to_s+"), min ("+min.to_s+") params" unless
315
+ min.is_a?(Numeric) and max.is_a?(Numeric) and min >= 0 and max <= 100 and max > min
316
+ @task = task
317
+ @min = min
318
+ @max = max
319
+ @delta = max - min
320
+ end
321
+
322
+ # convenience method to handle null tasks
323
+ def self.create(task, min, max)
324
+ if task
325
+ SubTask.new(task, min, max)
326
+ else
327
+ nil
328
+ end
329
+ end
330
+
331
+ def waiting_for(task_uri)
332
+ @task.waiting_for(task_uri)
333
+ end
334
+
335
+ def progress(pct)
336
+ raise "no numeric >= 0 and <= 100 : '"+pct.to_s+"'" unless pct.is_a?(Numeric) and pct>=0 and pct<=100
337
+ #puts "subtask := "+pct.to_s+" -> task := "+(@min + @delta * pct.to_f * 0.01).to_s
338
+ @task.progress( @min + @delta * pct.to_f * 0.01 )
339
+ end
340
+
341
+ def running?()
342
+ @task.running?
343
+ end
344
+ end
256
345
 
346
+
347
+ # The David Gallagher feature:
348
+ # a fake sub task to keep the progress bar movin for external jobs
349
+ # note: param could be a subtask
350
+ #
351
+ # usage (for a call that is normally finished in under 60 seconds):
352
+ # fsk = FakeSubTask.new(task, 60)
353
+ # external_lib_call.start
354
+ # external_lib_call.wait_until_finished
355
+ # fsk.finished
356
+ #
357
+ # what happens:
358
+ # the FakeSubTask updates the task.progress each second until
359
+ # runtime is up or the finished mehtod is called
360
+ #
361
+ # example if the param runtime is too low:
362
+ # 25% .. 50% .. 75% .. 100% .. 100% .. 100% .. 100% .. 100%
363
+ # example if the param runtime is too high:
364
+ # 5% .. 10% .. 15% .. 20% .. 25% .. 30% .. 35% .. 100%
365
+ # the latter example is better (keep the bar movin!)
366
+ # -> better make a conservative runtime estimate
367
+ class FakeSubTask
368
+
369
+ def initialize(task, runtime)
370
+ @task = task
371
+ @thread = Thread.new do
372
+ timeleft = runtime
373
+ while (timeleft > 0 and @task.running?)
374
+ sleep 1
375
+ timeleft -= 1
376
+ @task.progress( (runtime - timeleft) / runtime.to_f * 100 )
377
+ end
378
+ end
379
+ end
380
+
381
+ # convenience method to handle null tasks
382
+ def self.create(task, runtime)
383
+ if task
384
+ FakeSubTask.new(task, runtime)
385
+ else
386
+ nil
387
+ end
388
+ end
389
+
390
+ def finished
391
+ @thread.exit
392
+ @task.progress(100) if @task.running?
393
+ end
257
394
  end
258
395
 
259
396
  end