From fa9069e13fb6b1c8bb4ebcdf82f1cf1c04ad71ca Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Thu, 23 Feb 2012 17:56:46 +0000 Subject: (partially) switched back to RestClientWrapper --- lib/compound.rb | 8 +- lib/opentox-client.rb | 2 + lib/opentox.rb | 67 ++++------------- lib/overwrite.rb | 39 ++++++++++ lib/rest_client_wrapper.rb | 182 +++++++++++++++++++++++++++++++++++++++++++++ lib/task.rb | 10 +-- test/rest.rb | 23 ------ test/ruby-api.rb | 34 --------- 8 files changed, 245 insertions(+), 120 deletions(-) create mode 100644 lib/overwrite.rb create mode 100644 lib/rest_client_wrapper.rb delete mode 100644 test/rest.rb delete mode 100644 test/ruby-api.rb diff --git a/lib/compound.rb b/lib/compound.rb index 8761d50..ce0fdbf 100644 --- a/lib/compound.rb +++ b/lib/compound.rb @@ -11,21 +11,21 @@ module OpenTox # @param [String] smiles Smiles string # @return [OpenTox::Compound] Compound def self.from_smiles service_uri, smiles, subjectid=nil - Compound.new RestClient.post(service_uri, smiles, {:content_type => 'chemical/x-daylight-smiles', :subjectid => subjectid}) + Compound.new RestClientWrapper.post(service_uri, smiles, {:content_type => 'chemical/x-daylight-smiles', :subjectid => subjectid}) end # Create a compound from inchi string # @param [String] smiles InChI string # @return [OpenTox::Compound] Compound def self.from_inchi service_uri, inchi, subjectid=nil - Compound.new RestClient.post(service_uri, inchi, {:content_type => 'chemical/x-inchi', :subjectid => subjectid}) + Compound.new RestClientWrapper.post(service_uri, inchi, {:content_type => 'chemical/x-inchi', :subjectid => subjectid}) end # Create a compound from sdf string # @param [String] smiles SDF string # @return [OpenTox::Compound] Compound def self.from_sdf service_uri, sdf, subjectid=nil - Compound.new RestClient.post(service_uri, sdf, {:content_type => 'chemical/x-mdl-sdfile', :subjectid => subjectid}) + Compound.new RestClientWrapper.post(service_uri, sdf, {:content_type => 'chemical/x-mdl-sdfile', :subjectid => subjectid}) end # Create a compound from name. Relies on an external service for name lookups. @@ -34,7 +34,7 @@ module OpenTox # @param [String] name name can be also an InChI/InChiKey, CAS number, etc # @return [OpenTox::Compound] Compound def self.from_name service_uri, name, subjectid=nil - Compound.new RestClient.post(service_uri, name, {:content_type => 'text/plain', :subjectid => subjectid}) + Compound.new RestClientWrapper.post(service_uri, name, {:content_type => 'text/plain', :subjectid => subjectid}) end # Get InChI diff --git a/lib/opentox-client.rb b/lib/opentox-client.rb index fc6cbd1..1a5e7c3 100644 --- a/lib/opentox-client.rb +++ b/lib/opentox-client.rb @@ -6,7 +6,9 @@ require "rest-client" require 'uri' require 'yaml' require 'logger' +require File.join(File.dirname(__FILE__),"overwrite.rb") require File.join(File.dirname(__FILE__),"error.rb") +require File.join(File.dirname(__FILE__),"rest_client_wrapper.rb") require File.join(File.dirname(__FILE__),"otlogger.rb") # avoid require conflicts with logger require File.join(File.dirname(__FILE__),"opentox.rb") require File.join(File.dirname(__FILE__),"task.rb") diff --git a/lib/opentox.rb b/lib/opentox.rb index 2fbf9dc..f81ae10 100644 --- a/lib/opentox.rb +++ b/lib/opentox.rb @@ -4,45 +4,6 @@ RDF::OT1 = RDF::Vocabulary.new 'http://www.opentox.org/api/1.1#' RDF::OTA = RDF::Vocabulary.new 'http://www.opentox.org/algorithmTypes.owl#' SERVICES = ["Compound", "Feature", "Dataset", "Algorithm", "Model", "Validation", "Task", "Investigation"] -class String - def underscore - self.gsub(/::/, '/'). - gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). - gsub(/([a-z\d])([A-Z])/,'\1_\2'). - tr("-", "_"). - downcase - end -end - -module URI - - def self.task? uri - uri =~ /task/ and URI.valid? uri - end - - def self.dataset? uri, subjectid=nil - uri =~ /dataset/ and URI.accessible? uri, subjectid=nil - end - - def self.model? uri, subjectid=nil - uri =~ /model/ and URI.accessible? uri, subjectid=nil - end - - def self.accessible? uri, subjectid=nil - Net::HTTP.get_response(URI.parse(uri)) - true - rescue - false - end - - def self.valid? uri - u = URI::parse(uri) - u.scheme!=nil and u.host!=nil - rescue URI::InvalidURIError - false - end -end - # defaults to stderr, may be changed to file output $logger = OTLogger.new(STDERR) # no rotation $logger.level = Logger::DEBUG @@ -59,13 +20,15 @@ module OpenTox # Ruby interface - # override to read all error codes def metadata reload=true - if reload + if reload or @metadata.empty? @metadata = {} # ignore error codes from Task services (may contain eg 500 which causes exceptions in RestClient and RDF::Reader - RestClient.get(@uri) do |response, request, result, &block| + # TODO: convert to RestClientWrapper + kind_of?(OpenTox::Dataset) ? uri = File.join(@uri,"metadata") : uri = @uri + RestClient.get(uri) do |response, request, result| + #response = RestClientWrapper.get(@uri) #do |response, request, result| $logger.warn "#{@uri} returned #{result}" unless response.code == 200 or response.code == 202 or URI.task? @uri RDF::Reader.for(:rdfxml).new(response) do |reader| reader.each_statement do |statement| @@ -74,6 +37,7 @@ module OpenTox end end end + #puts @metadata.inspect @metadata end @@ -88,46 +52,41 @@ module OpenTox def get params={} params[:subjectid] ||= @subjectid params[:accept] ||= 'application/rdf+xml' - @response = RestClient.get @uri, params + @response = RestClientWrapper.get @uri, params end def post payload={}, params={} params[:subjectid] ||= @subjectid params[:accept] ||= 'application/rdf+xml' - @response = RestClient.post(@uri.to_s, payload, params) - begin - @response.to_s.to_object - rescue - @response - end + @response = RestClientWrapper.post(@uri.to_s, payload, params) end def put payload={}, params={} params[:subjectid] ||= @subjectid params[:accept] ||= 'application/rdf+xml' - @response = RestClient.put(@uri.to_s, payload, params) + @response = RestClientWrapper.put(@uri.to_s, payload, params) end def delete params={} params[:subjectid] ||= @subjectid params[:accept] ||= 'application/rdf+xml' - @response = RestClient.delete(@uri.to_s,:subjectid => @subjectid) + @response = RestClientWrapper.delete(@uri.to_s,:subjectid => @subjectid) end # class methods module ClassMethods def create service_uri, subjectid=nil - uri = RestClient.post(service_uri, {}, :subjectid => subjectid).chomp + uri = RestClientWrapper.post(service_uri, {}, :subjectid => subjectid).chomp subjectid ? eval("#{self}.new(\"#{uri}\", #{subjectid})") : eval("#{self}.new(\"#{uri}\")") end def from_file service_uri, file, subjectid=nil - RestClient.post(service_uri, :file => File.new(file), :subjectid => subjectid).chomp.to_object + RestClientWrapper.post(service_uri, :file => File.new(file), :subjectid => subjectid).chomp.to_object end def all service_uri, subjectid=nil - uris = RestClient.get(service_uri, {:accept => 'text/uri-list'}).split("\n").compact + uris = RestClientWrapper.get(service_uri, {:accept => 'text/uri-list'}).split("\n").compact uris.collect{|uri| subjectid ? eval("#{self}.new(\"#{uri}\", #{subjectid})") : eval("#{self}.new(\"#{uri}\")")} end diff --git a/lib/overwrite.rb b/lib/overwrite.rb new file mode 100644 index 0000000..e883d45 --- /dev/null +++ b/lib/overwrite.rb @@ -0,0 +1,39 @@ +class String + def underscore + self.gsub(/::/, '/'). + gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). + gsub(/([a-z\d])([A-Z])/,'\1_\2'). + tr("-", "_"). + downcase + end +end + +module URI + + def self.task? uri + uri =~ /task/ and URI.valid? uri + end + + def self.dataset? uri, subjectid=nil + uri =~ /dataset/ and URI.accessible? uri, subjectid=nil + end + + def self.model? uri, subjectid=nil + uri =~ /model/ and URI.accessible? uri, subjectid=nil + end + + def self.accessible? uri, subjectid=nil + Net::HTTP.get_response(URI.parse(uri)) + true + rescue + false + end + + def self.valid? uri + u = URI::parse(uri) + u.scheme!=nil and u.host!=nil + rescue URI::InvalidURIError + false + end +end + diff --git a/lib/rest_client_wrapper.rb b/lib/rest_client_wrapper.rb new file mode 100644 index 0000000..89de277 --- /dev/null +++ b/lib/rest_client_wrapper.rb @@ -0,0 +1,182 @@ +module OpenTox + + class WrapperResult < String + attr_accessor :content_type, :code + end + + class RestClientWrapper + + # performs a GET REST call + # raises OpenTox::Error if call fails (rescued in overwrite.rb -> halt 502) + # per default: waits for Task to finish and returns result URI of Task + # @param [String] uri destination URI + # @param [optional,Hash] headers contains params like accept-header + # @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly + # @param [wait,Boolean] wait set to false to NOT wait for task if result is task + # @return [OpenTox::WrapperResult] a String containing the result-body of the REST call + def self.get(uri, headers={}, waiting_task=nil, wait=true ) + execute( "get", uri, nil, headers, waiting_task, wait) + end + + # performs a POST REST call + # raises OpenTox::Error if call fails (rescued in overwrite.rb -> halt 502) + # per default: waits for Task to finish and returns result URI of Task + # @param [String] uri destination URI + # @param [optional,String] payload data posted to the service + # @param [optional,Hash] headers contains params like accept-header + # @param [optional,OpenTox::Task] waiting_task (can be a OpenTox::Subtask as well), progress is updated accordingly + # @param [wait,Boolean] wait set to false to NOT wait for task if result is task + # @return [OpenTox::WrapperResult] a String containing the result-body of the REST call + def self.post(uri, payload=nil, headers={}, waiting_task=nil, wait=true ) + execute( "post", uri, payload, headers, waiting_task, wait ) + end + + # performs a PUT REST call + # raises OpenTox::Error if call fails (rescued in overwrite.rb -> halt 502) + # @param [String] uri destination URI + # @param [optional,Hash] headers contains params like accept-header + # @param [optional,String] payload data put to the service + # @return [OpenTox::WrapperResult] a String containing the result-body of the REST call + def self.put(uri, payload=nil, headers={} ) + execute( "put", uri, payload, headers ) + end + + # performs a DELETE REST call + # raises OpenTox::Error if call fails (rescued in overwrite.rb -> halt 502) + # @param [String] uri destination URI + # @param [optional,Hash] headers contains params like accept-header + # @return [OpenTox::WrapperResult] a String containing the result-body of the REST call + def self.delete(uri, headers=nil ) + execute( "delete", uri, nil, headers) + end + + private + def self.execute( rest_call, uri, payload=nil, headers={}, waiting_task=nil, wait=true ) + + raise OpenTox::BadRequestError.new "uri is null" unless uri + raise OpenTox::BadRequestError.new "not a uri: "+uri.to_s unless URI.valid? uri.to_s + raise "headers are no hash: "+headers.inspect unless headers==nil or headers.is_a?(Hash) + raise OpenTox::BadRequestError.new "accept should go into the headers" if payload and payload.is_a?(Hash) and payload[:accept] + raise OpenTox::BadRequestError.new "content_type should go into the headers" if payload and payload.is_a?(Hash) and payload[:content_type] + raise "__waiting_task__ must be 'nil' or '(sub)task', is "+waiting_task.class.to_s if + waiting_task!=nil and !(waiting_task.is_a?(Task) || waiting_task.is_a?(SubTask)) + headers.each{ |k,v| headers.delete(k) if v==nil } if headers #remove keys with empty values, as this can cause problems + ## PENDING partner services accept subjectid only in header + headers = {} unless headers + headers[:subjectid] = payload.delete(:subjectid) if payload and payload.is_a?(Hash) and payload.has_key?(:subjectid) + + # PENDING needed for NUTA, until we finally agree on how to send subjectid + headers[:subjectid] = payload.delete(:subjectid) if uri=~/ntua/ and payload and payload.is_a?(Hash) and payload.has_key?(:subjectid) + + begin + #LOGGER.debug "RestCall: "+rest_call.to_s+" "+uri.to_s+" "+headers.inspect+" "+payload.inspect + resource = RestClient::Resource.new(uri,{:timeout => 600}) + if rest_call=="post" || rest_call=="put" + result = resource.send(rest_call, payload, headers){|response, request, result| response } + else + result = resource.send(rest_call, headers){|response, request, result| response } + end + #LOGGER.debug "result body size: #{result.body.size}" + + # PENDING NTUA does return errors with 200 + raise RestClient::ExceptionWithResponse.new(result) if uri=~/ntua/ and result.body =~ /about.*http:\/\/anonymous.org\/error/ + + # result is a string, with the additional fields content_type and code + res = WrapperResult.new(result.body) + res.content_type = result.headers[:content_type] + raise "content-type not set" unless res.content_type + res.code = result.code + + # TODO: Ambit returns task representation with 200 instead of result URI + return res if res.code==200 || !wait + + while (res.code==201 || res.code==202) + res = wait_for_task(res, uri, waiting_task) + end + raise "illegal status code: '"+res.code.to_s+"'" unless res.code==200 + return res + + rescue RestClient::RequestTimeout => ex + received_error ex.message, 408, nil, {:rest_uri => uri, :headers => headers, :payload => payload} + rescue Errno::ETIMEDOUT => ex + received_error ex.message, 408, nil, {:rest_uri => uri, :headers => headers, :payload => payload} + rescue Errno::ECONNREFUSED => ex + received_error ex.message, 500, nil, {:rest_uri => uri, :headers => headers, :payload => payload} + rescue RestClient::ExceptionWithResponse => ex + # error comming from a different webservice, + received_error ex.http_body, ex.http_code, ex.response.net_http_res.content_type, {:rest_uri => uri, :headers => headers, :payload => payload} + rescue OpenTox::RestCallError => ex + # already a rest-error, probably comes from wait_for_task, just pass through + raise ex + rescue => ex + # some internal error occuring in rest_client_wrapper, just pass through + raise ex + end + end + + def self.wait_for_task( res, base_uri, waiting_task=nil ) + #TODO remove TUM hack + res.content_type = "text/uri-list" if base_uri =~/tu-muenchen/ and res.content_type == "application/x-www-form-urlencoded;charset=UTF-8" + + task = nil + case res.content_type + when /application\/rdf\+xml/ + task = OpenTox::Task.from_rdfxml(res) + when /yaml/ + task = OpenTox::Task.from_yaml(res) + when /text\// + raise "uri list has more than one entry, should be a task" if res.content_type=~/text\/uri-list/ and res.split("\n").size > 1 #if uri list contains more then one uri, its not a task + task = OpenTox::Task.find(res.to_s.chomp) if res.to_s.uri? + else + raise "unknown content-type for task : '"+res.content_type.to_s+"'"+" base-uri: "+base_uri.to_s+" content: "+res[0..200].to_s + end + + #LOGGER.debug "result is a task '"+task.uri.to_s+"', wait for completion" + task.wait_for_completion waiting_task + unless task.completed? # maybe task was cancelled / error + if task.errorReport + received_error task.errorReport, task.http_code, nil, {:rest_uri => task.uri, :rest_code => task.http_code} + else + raise "status of task '"+task.uri.to_s+"' is no longer running (hasStatus is '"+task.status+ + "'), but it is neither completed nor has an errorReport" + end + end + + res = WrapperResult.new task.result_uri + res.code = task.http_code + res.content_type = "text/uri-list" + return res + end + + def self.received_error( body, code, content_type=nil, params=nil ) + + # try to parse body + report = nil + if body.is_a?(OpenTox::ErrorReport) + report = body + else + case content_type + when /yaml/ + report = YAML.load(body) + when /rdf/ + report = OpenTox::ErrorReport.from_rdf(body) + end + end + + unless report + # parsing was not successfull + # raise 'plain' RestCallError + err = OpenTox::RestCallError.new("REST call returned error: '"+body.to_s+"'") + err.rest_params = params + raise err + else + # parsing sucessfull + # raise RestCallError with parsed report as error cause + err = OpenTox::RestCallError.new("REST call subsequent error") + err.errorCause = report + err.rest_params = params + raise err + end + end + end +end diff --git a/lib/task.rb b/lib/task.rb index 9aaee04..286e998 100644 --- a/lib/task.rb +++ b/lib/task.rb @@ -7,7 +7,7 @@ module OpenTox attr_accessor :pid, :observer_pid def self.create service_uri, params={} - task = Task.new RestClient.post(service_uri,params).chomp + task = Task.new RestClientWrapper.post(service_uri,params).chomp pid = fork do begin result_uri = yield @@ -53,20 +53,20 @@ module OpenTox def cancel kill - RestClient.put(File.join(@uri,'Cancelled'),{}) + RestClientWrapper.put(File.join(@uri,'Cancelled'),{}) end def completed(uri) #TODO: subjectid? #TODO: error code raise "\"#{uri}\" does not exist." unless URI.accessible? uri - RestClient.put(File.join(@uri,'Completed'),{:resultURI => uri}) + RestClientWrapper.put(File.join(@uri,'Completed'),{:resultURI => uri}) end def error error $logger.error self if $logger report = ErrorReport.create(error,"http://localhost") - RestClient.put(File.join(@uri,'Error'),{:errorReport => report}) + RestClientWrapper.put(File.join(@uri,'Error'),{:errorReport => report}) kill end @@ -104,7 +104,7 @@ module OpenTox begin case method when /=/ - res = RestClient.put(File.join(@uri,method.sub(/=/,'')),{}) + res = RestClientWrapper.put(File.join(@uri,method.sub(/=/,'')),{}) super unless res.code == 200 #when /\?/ #return hasStatus == method.sub(/\?/,'').capitalize diff --git a/test/rest.rb b/test/rest.rb deleted file mode 100644 index 412e265..0000000 --- a/test/rest.rb +++ /dev/null @@ -1,23 +0,0 @@ -require 'test/unit' -$LOAD_PATH << File.join(File.dirname(__FILE__),'..','lib') -require File.join File.dirname(__FILE__),'..','lib','opentox-client.rb' - -class RestTest < Test::Unit::TestCase - - def test_post_get_delete - service_uri = "http://ot-dev.in-silico.ch/dataset" - dataset = OpenTox::Dataset.create service_uri - assert_match /#{service_uri}/, dataset.uri.to_s - puts dataset.uri - puts dataset.class - puts dataset.to_yaml - metadata = dataset.metadata - puts dataset.class -=begin - assert_equal RDF::OT.Dataset, metadata[RDF.type] - assert_equal dataset.uri, metadata[RDF::XSD.anyURI] -=end - dataset.delete - end - -end diff --git a/test/ruby-api.rb b/test/ruby-api.rb deleted file mode 100644 index 0c9386b..0000000 --- a/test/ruby-api.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'test/unit' -$LOAD_PATH << File.join(File.dirname(__FILE__),'..','lib') -require File.join File.dirname(__FILE__),'..','lib','opentox-client.rb' - -class RubyAPITest < Test::Unit::TestCase - - def test_all - datasets = OpenTox::Dataset.all "http://ot-dev.in-silico.ch/dataset" - assert_equal OpenTox::Dataset, datasets.first.class - assert_equal RDF::OT.Dataset, datasets.last.metadata[RDF.type] - end -=begin - - def test_create - d = OpenTox::Dataset.create "http://ot-dev.in-silico.ch/dataset" - puts d.inspect - assert_equal OpenTox::Dataset, d.class - assert_equal RDF::OT.Dataset, d.metadata[RDF.type] - d.delete - end - - def test_save - d = OpenTox::Dataset.create "http://ot-dev.in-silico.ch/dataset" - d.metadata - d.metadata[RDF::DC.title] = "test" - d.save - # TODO: save does not work with datasets - #puts d.response.code.inspect - #assert_equal "test", d.metadata[RDF::DC.title] # should reload metadata - d.delete - end -=end - -end -- cgit v1.2.3