opentox-ruby 0.0.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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