myexperiment-hackers
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[myexperiment-hackers] [3209] branches/wf4ever/vendor/plugins/rosrs: upd


From: noreply
Subject: [myexperiment-hackers] [3209] branches/wf4ever/vendor/plugins/rosrs: updated rosrs client to fc038a81
Date: Tue, 27 Nov 2012 12:54:32 +0000 (UTC)

Revision
3209
Author
dgc
Date
2012-11-27 12:54:32 +0000 (Tue, 27 Nov 2012)

Log Message

updated rosrs client to fc038a81

Modified Paths

Diff

Modified: branches/wf4ever/vendor/plugins/rosrs/README.md (3208 => 3209)


--- branches/wf4ever/vendor/plugins/rosrs/README.md	2012-11-26 16:59:57 UTC (rev 3208)
+++ branches/wf4ever/vendor/plugins/rosrs/README.md	2012-11-27 12:54:32 UTC (rev 3209)
@@ -82,8 +82,7 @@
         "47d5423c-b507-4e1c-8")
 
     # Create a new RO
-    code, reason, rouri, manifest = @rosrs.create_research_object("Test-RO-name",
-        "Test RO for ROSRSSession", "TestROSRS_Session.py", "2012-09-28")
+    code, reason, rouri, manifest = @rosrs.create_research_object("Test-RO-name")
     if code != 201
         raise "Failed to create new RO: "+reason
     end

Modified: branches/wf4ever/vendor/plugins/rosrs/lib/wf4ever/rosrs/annotation.rb (3208 => 3209)


--- branches/wf4ever/vendor/plugins/rosrs/lib/wf4ever/rosrs/annotation.rb	2012-11-26 16:59:57 UTC (rev 3208)
+++ branches/wf4ever/vendor/plugins/rosrs/lib/wf4ever/rosrs/annotation.rb	2012-11-27 12:54:32 UTC (rev 3209)
@@ -3,67 +3,74 @@
 
     attr_reader :uri, :body_uri, :resource_uri, :created_at, :created_by, :research_object
 
-    def initialize(research_object, uri, body_uri, resource_uri, created_at = nil, created_by = nil, options = {})
+    def initialize(research_object, uri, body_uri, resource_uri, options = {})
       @research_object = research_object
       @session = @research_object.session
       @uri = uri
       @body_uri = body_uri
       @resource_uri = resource_uri
-      @created_at = created_at
-      @created_by = created_by
+      @created_at = options[:created_at]
+      @created_by = options[:created_by]
       @loaded = false
       if options[:body]
         @body = options[:body]
         @loaded = true
       end
-      load! if options[:load]
+      load if options[:load]
     end
 
+    ##
+    # The resource which this annotation relates to
+    def resource
+      @resource ||= @research_object.resources(@resource_uri) ||
+                    @research_object.folders(@resource_uri) ||
+                    ROSRS::Resource.new(@research_object, @resource_uri)
+    end
+
     def loaded?
       @loaded
     end
 
-    def load!
-      @body = @session.get_annotation(body_uri || uri)
+    def load
+      c,r,u,@body = @session.get_annotation(body_uri)
       @loaded = true
     end
 
     def body
-      load! unless loaded?
+      load unless loaded?
       @body
     end
 
-    def delete!
-      @session.remove_annotation(uri)
-      true
+    def delete
+      code = @session.remove_annotation(uri)
+      @loaded = false
+      @research_object.remove_annotation(self)
+      code == 204
     end
 
-    def self.create(ro, resource_uri, annotation_graph)
-      code, reason, annotation_uri, body_uri = ro.session.create_internal_annotation(ro.uri, resource_uri, annotation_graph)
-      self.new(ro, annotation_uri, body_uri, resource_uri, :body => annotation_graph)
+    def self.create(ro, resource_uri, annotation)
+      if ROSRS::Helper.is_uri?(annotation)
+        code, reason, annotation_uri = ro.session.create_annotation_stub(ro.uri, resource_uri, annotation)
+        self.new(ro, annotation_uri, annotation, resource_uri)
+      else
+        code, reason, annotation_uri, body_uri = ro.session.create_internal_annotation(ro.uri, resource_uri, annotation)
+        self.new(ro, annotation_uri, body_uri, resource_uri, :body => annotation)
+      end
     end
 
-    def self.create_remote(ro, resource_uri, body_uri)
-      code, reason, annotation_uri = ro.session.create_annotation_stub(ro.uri, resource_uri, body_uri)
-      self.new(ro, annotation_uri, body_uri, resource_uri)
-    end
-
-    def update!(resource_uri, annotation_graph)
-      code, reason, body_uri = @session.update_internal_annotation(@research_object.uri, @uri, resource_uri, annotation_graph)
+    def update(resource_uri, annotation)
+      if ROSRS::Helper.is_uri?(annotation)
+        code, reason = @session.update_annotation_stub(@research_object.uri, @uri, resource_uri, body_uri)
+        @loaded = false
+      else
+        code, reason, body_uri = @session.update_internal_annotation(@research_object.uri, @uri, resource_uri, annotation)
+        @loaded = true
+        @body = annotation
+      end
       @resource_uri = resource_uri
       @body_uri = body_uri
-      @body = annotation_graph
-      @loaded = true
       self
     end
 
-    def update_remote!(resource_uri, body_uri)
-      code, reason = @session.update_annotation_stub(@research_object.uri, @uri, resource_uri, body_uri)
-      @resource_uri = resource_uri
-      @body_uri = body_uri
-      @loaded = false
-      self
-    end
-
   end
 end
\ No newline at end of file

Modified: branches/wf4ever/vendor/plugins/rosrs/lib/wf4ever/rosrs/exceptions.rb (3208 => 3209)


--- branches/wf4ever/vendor/plugins/rosrs/lib/wf4ever/rosrs/exceptions.rb	2012-11-26 16:59:57 UTC (rev 3208)
+++ branches/wf4ever/vendor/plugins/rosrs/lib/wf4ever/rosrs/exceptions.rb	2012-11-27 12:54:32 UTC (rev 3209)
@@ -4,4 +4,16 @@
     class Exception < Exception
     end
 
+    class NotFoundException < Exception
+    end
+
+    class ForbiddenException < Exception
+    end
+
+    class UnauthorizedException < Exception
+    end
+
+    class ConflictException < Exception
+    end
+
   end
\ No newline at end of file

Modified: branches/wf4ever/vendor/plugins/rosrs/lib/wf4ever/rosrs/folder.rb (3208 => 3209)


--- branches/wf4ever/vendor/plugins/rosrs/lib/wf4ever/rosrs/folder.rb	2012-11-26 16:59:57 UTC (rev 3208)
+++ branches/wf4ever/vendor/plugins/rosrs/lib/wf4ever/rosrs/folder.rb	2012-11-27 12:54:32 UTC (rev 3209)
@@ -1,25 +1,26 @@
 module ROSRS
 
   # A representation of a folder in a Research Object.
+  class Folder < Resource
 
-  class Folder
+    attr_reader :name
 
-    attr_reader :research_object, :name, :uri
-
     ##
     # +research_object+:: The Wf4Ever::ResearchObject that aggregates this folder.
-    # +name+::            The display name of the Folder.
     # +uri+::             The URI for the resource referred to by the Folder.
     # +options+::         A hash of options:
-    # [:eager_load]       Whether or not to eagerly load the entire Folder hierarchy within in this Folder.
-    def initialize(research_object, name, uri, options = {})
-      @name = name
-      @uri = uri
-      @research_object = research_object
+    # [:contents_graph]   An RDFGraph of the folder contents, if on hand (to save having to make a request).
+    # [:root_folder]      A boolean flag to say if this folder is the root folder of the RO.
+    def initialize(research_object, uri, proxy_uri = nil, options = {})
+      super(research_object, uri, proxy_uri)
+      @name = uri.to_s.split('/').last
       @session = research_object.session
       @loaded = false
-      @contents = []
-      load! if (@eager_load = options[:eager_load])
+      if options[:contents_graph]
+        @contents = parse_folder_description(options[:contents_graph])
+        @loaded = true
+      end
+      @root_folder = options[:root_folder]
     end
 
     ##
@@ -35,85 +36,91 @@
     end
 
     ##
-    # Returns an array of FolderEntry and Folder objects.
-    def contents
-      load! unless loaded?
-      @contents
+    # Returns whether or not this folder is the root folder of the RO
+    def root?
+      @root_folder
     end
 
     ##
-    # Returns the number of entries within the folder.
-    def size
-      contents.size
+    # Returns an array of FolderEntry objects
+    def contents
+      load unless loaded?
+      @contents
     end
 
     ##
     # Fetch and parse the Folder's description to get the Folder's contents.
-    def load!
-      fetch_folder_contents!
+    def load
+      @contents = fetch_folder_contents
       @loaded = true
     end
 
     ##
-    # Manually set the Folder's contents.
-    #
-    # Saves making an HTTP request if you already have the folder description.
-    def set_contents!(contents)
-      @contents = contents
-      @loaded = true
-    end
-
-    ##
     # Delete this folder from the RO. Contents will be preserved.
     # Also deletes any entries in other folders pointing to this one.
-    def delete!
-      @session.delete_folder(@uri)
-      true
+    def delete
+      code = @session.delete_folder(@uri)[0]
+      @loaded = false
+      @research_object.remove_folder(self)
+      code == 204
     end
 
     ##
-    # Add a resource to this folder. The resource must already be present in the RO.
-    def add(resource_uri, resource_name = nil)
-      @session.add_folder_entry(@uri, resource_uri, resource_name, :parent => self)
+    # Add an entry to the folder. +resource+ can be a ROSRS::Resource object or a URI
+    def add(resource, entry_name = nil)
+      if resource.instance_of?(ROSRS::Resource)
+        contents << ROSRS::FolderEntry.create(self, entry_name, resource.uri)
+      else
+        contents << ROSRS::FolderEntry.create(self, entry_name, resource)
+      end
+
     end
 
+    ##
+    # Remove an entry from the folder.
     def remove(entry)
+      entry.delete
+      contents.delete(entry)
+    end
 
+    ##
+    # Create a folder in the RO and add it to this folder as a subfolder
+    def create_folder(name)
+      # Makes folder name parent/child instead of just child
+      folder_name = (URI(uri) + URI(name) - URI(@research_object.uri)).to_s
+      folder = @research_object.create_folder(folder_name)
+      add(folder.uri, name)
+      folder
     end
 
-    def self.create(ro, name, contents)
-      @session.create_folder(ro, name, contents)
+    def self.create(ro, name, contents = [])
+      code, reason, uri, proxy_uri, folder_description = ro.session.create_folder(ro.uri, name, contents)
+      self.new(ro, uri, proxy_uri, :contents_graph => folder_description)
     end
 
     private
 
-    # Get folder contents from resource map
-    def fetch_folder_contents!
-      code, reason, headers, uripath, graph = @session.do_request_rdf("GET", uri,
-                                                                      :accept => 'application/vnd.wf4ever.folder')
-      set_contents!(graph)
+    # Get folder contents from remote resource map file
+    def fetch_folder_contents
+      code, reason, headers, uripath, graph = @session.get_folder(uri)
+      parse_folder_description(graph)
     end
 
     def parse_folder_description(graph)
       contents = []
 
       query = RDF::Query.new do
-        pattern [:folder_entry, RDF.type,  RDF::RO.FolderEntry]
+        pattern [:folder_entry, RDF.type, RDF::RO.FolderEntry]
         pattern [:folder_entry, RDF::RO.entryName, :name]
         pattern [:folder_entry, RDF::ORE.proxyFor, :target]
-        pattern [:target, RDF.type, RDF::RO.Resource]
-        # The pattern below is treated as mandatory - Bug in RDF library! :( Maybe not needed?
-        # pattern [:target, RDF::ORE.isDescribedBy, :target_resource_map], :optional => true
       end
 
       # Create instances for each item.
       graph.query(query).each do |result|
-        if result.respond_to? :target_resource_map
-          contents << ROSRS::Folder.new(@research_object, result.name.to_s, result.target.to_s, :eager_load => @eager_load)
-        else
-          contents << ROSRS::FolderEntry.new(self, result.name.to_s, result.target.to_s, result.entry_uri.to_s)
-        end
+        contents << ROSRS::FolderEntry.new(self, result.name.to_s, result.folder_entry.to_s, result.target.to_s)
       end
+
+      contents
     end
 
   end

Modified: branches/wf4ever/vendor/plugins/rosrs/lib/wf4ever/rosrs/folder_entry.rb (3208 => 3209)


--- branches/wf4ever/vendor/plugins/rosrs/lib/wf4ever/rosrs/folder_entry.rb	2012-11-26 16:59:57 UTC (rev 3208)
+++ branches/wf4ever/vendor/plugins/rosrs/lib/wf4ever/rosrs/folder_entry.rb	2012-11-27 12:54:32 UTC (rev 3209)
@@ -2,31 +2,43 @@
 
   # An item within a folder.
 
-  class FolderEntry < Resource
+  class FolderEntry
 
-    attr_reader :parent_folder, :name, :uri, :resource_uri
+    attr_reader :parent_folder, :name, :uri
 
     ##
     # +parent_folder+:: The ROSRS::Folder object in which this entry resides..
     # +name+::          The display name of the ROSRS::FolderEntry.
     # +uri+::           The URI of this ROSRS::FolderEntry
     # +resource_uri+::  The URI for the resource referred to by this ROSRS::FolderEntry.
-    # +folder+::        (Optional) The ROSRS::Folder that this entry points to, if applicable.
-    def initialize(parent_folder, name, uri, resource_uri, folder = nil)
-      super(folder.research_object, uri, resource_uri)
+    def initialize(parent_folder, name, uri, resource_uri)
+      @uri = uri
       @name = name
-      @folder = folder
-      @is_folder = !options[:folder].nil?
-      @resource = options[:folder]
-      @session = @folder.research_object.session
+      @resource_uri = resource_uri
+      @parent_folder = parent_folder
+      @session = @parent_folder.research_object.session
     end
 
+    # The resource that this entry points to. Lazily loaded.
+    def resource
+      @resource ||= @parent_folder.research_object.resources(@resource_uri) ||
+                    @parent_folder.research_object.folders(@resource_uri) ||
+                    ROSRS::Resource.new(@parent_folder.research_object, @resource_uri)
+    end
+
     ##
-    # Returns boolean stating whether or not this entry points to a folder
-    def folder?
-      @is_folder
+    # Removes this folder entry from the folder. Does not delete the resource.
+    def delete
+      code = @session.delete_resource(@uri)[0]
+      @loaded = false
+      code == 204
     end
 
+    def self.create(parent_folder, name, resource_uri)
+      code, reason, uri = parent_folder.research_object.session.add_folder_entry(parent_folder.uri, resource_uri, name)
+      self.new(parent_folder, name, uri, resource_uri)
+    end
+
   end
 
 end
\ No newline at end of file

Modified: branches/wf4ever/vendor/plugins/rosrs/lib/wf4ever/rosrs/research_object.rb (3208 => 3209)


--- branches/wf4ever/vendor/plugins/rosrs/lib/wf4ever/rosrs/research_object.rb	2012-11-26 16:59:57 UTC (rev 3208)
+++ branches/wf4ever/vendor/plugins/rosrs/lib/wf4ever/rosrs/research_object.rb	2012-11-27 12:54:32 UTC (rev 3209)
@@ -6,71 +6,147 @@
 
     def initialize(rosrs_session, uri)
       @session = rosrs_session
+      if URI(uri).relative?
+        uri = (rosrs_session.uri + URI(uri)).to_s
+      end
       @uri = uri
+      @uri << '/' unless uri[-1] == '/'
       @loaded = false
     end
 
+    def self.create(session, name)
+      c,r,u,m = session.create_research_object(name)
+      self.new(session, u)
+    end
+
+    ##
+    # Has this RO's manifest been fetched and parsed?
     def loaded?
       @loaded
     end
 
-    def load!
+    ##
+    # Fetch and parse the RO manifest
+    def load
       manifest_uri, @manifest = @session.get_manifest(uri)
+      @folders = extract_folders
+      @resources = extract_resources
       @annotations = extract_annotations
-      @root_folder = extract_root_folder
       @loaded = true
     end
 
     def manifest
-      load! unless loaded?
+      load unless loaded?
       @manifest
     end
 
+    ##
+    # Get Annotations in this RO for the given resource_uri.
+    # If resource_uri is nil, get the Annotations on the RO itself.
     def annotations(resource_uri = nil)
-      load! unless loaded?
+      load unless loaded?
       if resource_uri.nil?
-        @annotations
+        @address@hidden
       else
-        @annotations.select {|a| a.resource_uri == resource_uri}
+        @annotations[resource_uri] || []
       end
     end
 
+    ##
+    # Return the Resource object for the given resource_uri, if it exists.
+    # If resource_uri is nil, return all Resources in the RO.
+    def resources(resource_uri = nil)
+      load unless loaded?
+      if resource_uri.nil?
+        @resources.values
+      else
+        @resources[resource_uri]
+      end
+    end
+
+    ##
+    # Return the Folder object for the given resource_uri, if it exists.
+    # If resource_uri is nil, return all Folder in the RO.
+    def folders(resource_uri = nil)
+      load unless loaded?
+      if resource_uri.nil?
+        @folders.values
+      else
+        @folders[resource_uri]
+      end
+    end
+
+    ##
+    # Return the root folder of the RO.
     def root_folder
-      load! unless loaded?
+      load unless loaded?
       @root_folder
     end
 
-    def delete!
-      @session.delete_research_object(uri)
-      true
+    ##
+    # Delete this RO from the repository
+    def delete
+      code = @session.delete_research_object(@uri)[0]
+      @loaded = false
+      code == 204
     end
 
     ##
     # Create an annotation for a given resource_uri, using the supplied annotation body.
-    def create_annotation(resource_uri, annotation_body)
-      annotation = ROSRS::Annotation.create(self, resource_uri, annotation_body)
-      annotations << annotation
+    def create_annotation(resource_uri, annotation)
+      annotation = ROSRS::Annotation.create(self, resource_uri, annotation)
+      load unless loaded?
+      @annotations[resource_uri] ||= []
+      @annotations[resource_uri] << annotation
       annotation
     end
 
     ##
-    # Create an annotation for a given resource_uri, using a remote body pointed to by body_uri.
-    def create_remote_annotation(resource_uri, body_uri)
-      annotation = ROSRS::Annotation.create_remote(self, resource_uri, body_uri)
-      annotations << annotation
-      annotation
+    # Create a folder in the research object.
+    def create_folder(name)
+      folder = ROSRS::Folder.create(self, name)
+      load unless loaded?
+      @folders[folder.uri] = folder
+      folder
     end
 
     ##
-    # Create a root folder in the research object if one doesn't already exist.
-    def create_folder(name)
-      @session.create_folder(@uri, name)
+    # Aggregate a resource
+    # If a body (and optional content type) is given, it will create an internal resource with that body, plus the proxy
+    # that points to it. If only the first argument is given, an external resource will be created.
+    def aggregate(uri, body = nil, content_type = 'text/plain')
+      resource = ROSRS::Resource.create(self, uri, body, content_type)
+      load unless loaded?
+      @resources[resource.uri] = resource
     end
 
+    ##
+    # Remove the chosen resource from the RO
+    def remove(resource)
+      resource.delete
+    end
+
+    def remove_resource(resource)
+      @resources.delete(resource.uri)
+      @annotations.delete(resource.uri)
+      resource
+    end
+
+    def remove_folder(folder)
+      @folders.delete(folder.uri)
+      @annotations.delete(folder.uri)
+      folder
+    end
+
+    def remove_annotation(annotation)
+      @annotations[annotation.resource_uri].delete(annotation)
+      annotation
+    end
+
     private
 
     def extract_annotations
-      annotations = []
+      annotations = {}
       queries = [RDF::RO.annotatesAggregatedResource, RDF::AO.annotatesResource].collect do |predicate|
         RDF::Query.new do
           pattern [:annotation_uri, RDF.type, RDF::RO.AggregatedAnnotation]
@@ -83,33 +159,75 @@
 
       queries.each do |query|
         @manifest.query(query) do |result|
-            annotations << ROSRS::Annotation.new(self,
-                                                   result.annotation_uri.to_s,
-                                                   result.body_uri.to_s,
-                                                   result.resource_uri.to_s,
-                                                   result.created_at.to_s,
-                                                   result.created_by.to_s)
+          annotations[result.resource_uri.to_s] ||= []
+          annotations[result.resource_uri.to_s] << ROSRS::Annotation.new(self,
+                                                                         result.annotation_uri.to_s,
+                                                                         result.body_uri.to_s,
+                                                                         result.resource_uri.to_s,
+                                                                         :created_at => result.created_at.to_s,
+                                                                         :created_by => result.created_by.to_s)
         end
       end
 
       annotations
     end
 
+    def extract_folders
+      folders = {}
+
+      query = RDF::Query.new do
+        pattern [:research_object, RDF::ORE.aggregates, :folder]
+        pattern [:folder, RDF.type, RDF::RO.Folder]
+        pattern [:proxy_uri, RDF::ORE.proxyFor, :folder]
+      end
+
+      @manifest.query(query).each do |result|
+        folder_uri = result.folder.to_s
+        folders[folder_uri] = ROSRS::Folder.new(self, folder_uri, result.proxy_uri.to_s)
+      end
+
+      @root_folder = folders[extract_root_folder]
+
+      folders
+    end
+
     def extract_root_folder
       query = RDF::Query.new do
-        pattern [:research_object, RDF::RO.rootFolder,  :folder]
-        pattern [:folder, RDF::ORE.isDescribedBy, :folder_resource_map]
+        pattern [:research_object, RDF::ORE.aggregates, :folder]
+        pattern [:research_object, RDF::RO.rootFolder, :folder]
+        pattern [:folder, RDF.type, RDF::RO.Folder]
+        pattern [:proxy_uri, RDF::ORE.proxyFor, :folder]
       end
 
       result = @manifest.query(query).first
       if result
-        folder_uri = result.folder.to_s
-        folder_name = folder_uri.to_s.split('/').last
-        ROSRS::Folder.new(self, folder_name, folder_uri)
+        result.folder.to_s
       else
         nil
       end
     end
 
+    def extract_resources
+      resources = {}
+      folder_resources = @folders.values.map {|f| f.uri}
+
+      query = RDF::Query.new do
+        pattern [:research_object, RDF::ORE.aggregates, :resource]
+        pattern [:resource, RDF.type, RDF::RO.Resource]
+        #pattern [:resource, RDF::RO.name, :name]
+        pattern [:proxy_uri, RDF::ORE.proxyFor, :resource]
+        pattern [:resource, RDF::DC.creator, :created_by]
+        pattern [:resource, RDF::DC.created, :created_at]
+      end
+
+      @manifest.query(query).each do |result|
+        unless folder_resources.include?(result.resource.to_s) # We only want non-folder resources
+          resources[result.resource.to_s] = ROSRS::Resource.new(self, result.resource.to_s, result.proxy_uri.to_s)
+        end
+      end
+
+      resources
+    end
+
   end
 end
\ No newline at end of file

Modified: branches/wf4ever/vendor/plugins/rosrs/lib/wf4ever/rosrs/resource.rb (3208 => 3209)


--- branches/wf4ever/vendor/plugins/rosrs/lib/wf4ever/rosrs/resource.rb	2012-11-26 16:59:57 UTC (rev 3208)
+++ branches/wf4ever/vendor/plugins/rosrs/lib/wf4ever/rosrs/resource.rb	2012-11-27 12:54:32 UTC (rev 3209)
@@ -1,34 +1,59 @@
 module ROSRS
 
   class Resource
-    attr_reader :uri, :proxy_uri
+    attr_reader :research_object, :uri, :proxy_uri, :created_at, :created_by
 
-    def initialize(research_object, uri, proxy_uri, external = false)
+    def initialize(research_object, uri, proxy_uri = nil, options = {})
       @research_object = research_object
       @uri = uri
       @proxy_uri = proxy_uri
       @session = @research_object.session
-      @external = external
+      @created_at = options[:created_at]
+      @created_by = options[:created_by]
     end
 
     ##
+    # Get all the annotations on this resource
+    def annotations
+      @research_object.annotations(@uri)
+    end
+
+    ##
+    # Add an annotation to this resource
+    def annotate(annotation)
+      @research_object.create_annotation(@uri, annotation)
+    end
+
+    ##
     # Removes this resource from the Research Object.
-    def delete!
-      if internal?
-        @session.remove_resource(@uri)
-      elsif external?
-        @session.remove_resource(@proxy_uri)
-      end
-      true
+    def delete
+      code = @session.delete_resource(@proxy_uri)[0]
+      @loaded = false
+      @research_object.remove_resource(self)
+      code == 204
     end
 
     def internal?
-      !external
+      @uri.include?(@research_object.uri)
     end
 
     def external?
-      external
+      !internal?
     end
 
+    def self.create(research_object, uri, body = nil, content_type = 'text/plain')
+      if body.nil?
+        code, reason, proxy_uri, resource_uri = research_object.session.aggregate_external_resource(research_object.uri, uri)
+        self.new(research_object, resource_uri, proxy_uri)
+      else
+        code, reason, proxy_uri, resource_uri = research_object.session.aggregate_internal_resource(research_object.uri,
+                                                                                                   uri,
+                                                                                                   :body => body,
+                                                                                                   :ctype => content_type)
+        self.new(research_object, resource_uri, proxy_uri)
+      end
+
+    end
+
   end
 end
\ No newline at end of file

Modified: branches/wf4ever/vendor/plugins/rosrs/lib/wf4ever/rosrs/session.rb (3208 => 3209)


--- branches/wf4ever/vendor/plugins/rosrs/lib/wf4ever/rosrs/session.rb	2012-11-26 16:59:57 UTC (rev 3208)
+++ branches/wf4ever/vendor/plugins/rosrs/lib/wf4ever/rosrs/session.rb	2012-11-27 12:54:32 UTC (rev 3209)
@@ -2,6 +2,8 @@
 module ROSRS
   class Session
 
+    attr_reader :uri
+
     ANNOTATION_CONTENT_TYPES =
       { "application/rdf+xml" => :xml,
         "text/turtle"         => :turtle,
@@ -11,6 +13,10 @@
         #"application/xhtml"   => :rdfa,
       }
 
+    PARSEABLE_CONTENT_TYPES = ['application/vnd.wf4ever.folderentry',
+                               'application/vnd.wf4ever.folder',
+                               'application/rdf+xml']
+
     # -------------
     # General setup
     # -------------
@@ -28,12 +34,24 @@
       end
     end
 
-    def error(msg, value=nil)
+    def error(code, msg, value=nil)
       # Raise exception with supplied message and optional value
       if value
         msg += " (#{value})"
       end
-      raise ROSRS::Exception.new("Session::Exception on address@hidden #{msg}")
+      msg = "Exception on address@hidden #{msg}"
+      case code
+        when 401
+          raise ROSRS::UnauthorizedException.new(msg)
+        when 403
+          raise ROSRS::ForbiddenException.new(msg)
+        when 404
+          raise ROSRS::NotFoundException.new(msg)
+        when 409
+          raise ROSRS::ConflictException.new(msg)
+        else
+          raise ROSRS::Exception.new(msg)
+      end
     end
 
     # -------
@@ -52,7 +70,7 @@
         matches = link.strip.match(/<([^>]*)>\s*;.*rel\s*=\s*"?([^;"]*)"?/)
         if matches
           links[matches[2]] ||= []
-          links[matches[2]] << URI(matches[1])
+          links[matches[2]] << matches[1]
         end
       end
       links
@@ -66,11 +84,11 @@
     def get_request_path(uripath)
       uripath = URI(uripath.to_s)
       if uripath.scheme && (uripath.scheme != @uri.scheme)
-        error("Request URI scheme does not match session: #{uripath}")
+        error(nil, "Request URI scheme does not match session: #{uripath}")
       end
       if (uripath.host && uripath.host != @uri.host) ||
          (uripath.port && uripath.port != @uri.port)
-        error("Request URI host or port does not match session: #{uripath}")
+        error(nil, "Request URI host or port does not match session: #{uripath}")
       end
       requri = URI.join(@uri.to_s, uripath.path).path
       if uripath.query
@@ -132,7 +150,7 @@
       when 'DELETE'
         req = Net::HTTP::Delete.new(get_request_path(uripath))
       else
-        error("Unrecognized HTTP method #{method}")
+        error(nil, "Unrecognized HTTP method #{method}")
       end
 
       if options[:body]
@@ -170,16 +188,11 @@
       options[:accept] ||= "application/rdf+xml"
       code, reason, headers, uripath, data = "" uripath, options)
       if code >= 200 and code < 300
-        if headers["content-type"].downcase == options[:accept]
-          begin
-            data = "" => data, :format => :xml)
-          rescue Exception => e
-            code = 902
-            reason = "RDF parse failure (#{e.message})"
-          end
-        else
-          code = 901
-          reason = "Non-RDF content-type returned (#{headers["content-type"]})"
+        begin
+          data = "" => data, :format => :xml)
+        rescue Exception => e
+          code = 902
+          reason = "RDF parse failure (#{e.message})"
         end
       end
       [code, reason, headers, uripath, data]
@@ -191,26 +204,13 @@
 
     ##
     # Returns [copde, reason, uri, manifest]
-    def create_research_object(name, title, creator, date)
-      reqheaders   = {
-          "slug"    => name
-          }
-      roinfo = {
-          "id"      => name,
-          "title"   => title,
-          "creator" => creator,
-          "date"    => date
-          }
-      roinfotext = roinfo.to_json
+    def create_research_object(name)
       code, reason, headers, uripath, data = "" "",
-          :body       => roinfotext,
-          :headers    => reqheaders)
+          :headers    => {'slug' => name})
       if code == 201
         [code, reason, headers["location"], data]
-      elsif code == 409
-        [code, reason, nil, data]
       else
-        error("Error creating RO: #{code} #{reason}")
+        error(code, "Error creating RO: #{code} #{reason}")
       end
     end
 
@@ -221,7 +221,7 @@
       if [204, 404].include?(code)
         [code, reason]
       else
-        error("Error deleting RO #{ro_uri}: #{code} #{reason}")
+        error(code, "Error deleting RO #{ro_uri}: #{code} #{reason}")
       end
     end
 
@@ -241,41 +241,47 @@
     # Returns: [code, reason, proxyuri, resource_uri], where code is 200 or 201
 
     def aggregate_internal_resource(ro_uri, respath=nil, options={})
-        # POST (empty) proxy value to RO ...
-      reqheaders = options[:headers] || {}
       if respath
-        reqheaders['slug'] = respath
+        options[:headers] ||= {}
+        options[:headers]['slug'] = respath
       end
-      proxydata = %q(
+      # POST resource content to indicated URI
+      code, reason, headers = do_request("POST", ro_uri, options)
+      unless [200,201].include?(code)
+        error(code, "Error creating aggregated resource content",
+                "#{code}, #{reason}, #{respath}")
+      end
+      proxyuri = headers["location"]
+      resource_uri = parse_links(headers)[RDF::ORE.proxyFor.to_s].first
+      [code, reason, proxyuri, resource_uri]
+    end
+
+
+    ##
+    # Aggregate external resource
+    #
+    # Returns: [code, reason, proxyuri, resource_uri], where code is 200 or 201
+    def aggregate_external_resource(ro_uri, resource_uri=nil)
+      proxydata = %(
         <rdf:RDF
           xmlns:ore="http://www.openarchives.org/ore/terms/"
           xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" >
           <ore:Proxy>
+            <ore:proxyFor rdf:resource="#{resource_uri}"/>
           </ore:Proxy>
         </rdf:RDF>
         )
       code, reason, headers = do_request("POST", ro_uri,
         :ctype    => "application/vnd.wf4ever.proxy",
-        :headers  => reqheaders,
         :body     => proxydata)
       if code != 201
-        error("Error creating aggregation proxy",
-              "#{code} #{reason} #{respath}")
+        error(code, "Error creating aggregation proxy",
+              "#{code} #{reason} #{resource_uri}")
+      else
+        proxyuri = headers["location"]
+        resuri = parse_links(headers)[RDF::ORE.proxyFor.to_s].first
+        [code, reason, proxyuri, resuri]
       end
-      proxyuri = URI(headers["location"])
-      links    = parse_links(headers)
-      resource_uri = links[RDF::ORE.proxyFor.to_s].first
-      unless resource_uri
-        error("No ore:proxyFor link in create proxy response",
-              "Proxy URI #{proxyuri}")
-      end
-      # PUT resource content to indicated URI
-      code, reason = do_request("PUT", resource_uri, options)
-      unless [200,201].include?(code)
-          error("Error creating aggregated resource content",
-                "#{code}, #{reason}, #{respath}")
-      end
-      [code, reason, proxyuri, resource_uri]
     end
 
     # -----------------------
@@ -297,8 +303,11 @@
         resuriref = URI.join(ro_uri.to_s, resuriref.to_s)
       end
       code, reason, headers, uri, data = "" resuriref, options)
+      if parseable?(headers["content-type"])
+        data = "" => data, :format => :xml)
+      end
       unless [200,404].include?(code)
-        error("Error retrieving RO resource: #{code}, #{reason}, #{resuriref}")
+        error(code, "Error retrieving RO resource: #{code}, #{reason}, #{resuriref}")
       end
       [code, reason, headers, uri, data]
     end
@@ -321,7 +330,7 @@
       end
       code, reason, headers, uri, data = "" resource_uri, options)
       unless [200,404].include?(code)
-        error("Error retrieving RO resource: #{code}, #{reason}, #{resource_uri}")
+        error(code, "Error retrieving RO resource: #{code}, #{reason}, #{resource_uri}")
       end
       [code, reason, headers, uri, data]
     end
@@ -333,7 +342,7 @@
     def get_manifest(ro_uri)
       code, reason, headers, uri, data = "" ro_uri)
       if code != 200
-        error("Error retrieving RO manifest: #{code} #{reason}")
+        error(code, "Error retrieving RO manifest: #{code} #{reason}")
       end
       [uri, data]
     end
@@ -351,7 +360,7 @@
         :ctype => "application/rdf+xml",
         :body  => annotation_graph.serialize(format=:xml))
       if code != 201
-        error("Error creating annotation body resource",
+        error(code, "Error creating annotation body resource",
               "#{code}, #{reason}, #{ro_uri}")
       end
       [code, reason, body_uri]
@@ -391,9 +400,9 @@
           :ctype => "application/vnd.wf4ever.annotation",
           :body  => annotation)
       if code != 201
-          error("Error creating annotation #{code}, #{reason}, #{resource_uri}")
+        error(code, "Error creating annotation #{code}, #{reason}, #{resource_uri}")
       end
-      [code, reason, URI(headers["location"])]
+      [code, reason, headers["location"]]
     end
 
     ##
@@ -406,11 +415,9 @@
           :body  => annotation_graph.serialize(format=:xml),
           :link => "<#{resource_uri}>; rel=\"#{RDF::AO.annotatesResource}\"")
       if code != 201
-        error("Error creating annotation #{code}, #{reason}, #{resource_uri}")
+        error(code, "Error creating annotation #{code}, #{reason}, #{resource_uri}")
       end
-      puts parse_links(headers).inspect
-puts "      parse_links(headers) = #{      parse_links(headers).inspect}"
-      [code, reason, URI(headers["location"]), parse_links(headers)[RDF::AO.body.to_s].first]
+      [code, reason, headers["location"], parse_links(headers)[RDF::AO.body.to_s].first]
     end
 
     ##
@@ -431,7 +438,7 @@
           :ctype => "application/vnd.wf4ever.annotation",
           :body  => annotation)
       if code != 200
-          error("Error updating annotation #{code}, #{reason}, #{resource_uri}")
+        error(code, "Error updating annotation #{code}, #{reason}, #{resource_uri}")
       end
       [code, reason]
     end
@@ -443,7 +450,7 @@
     def update_internal_annotation(ro_uri, stuburi, resource_uri, annotation_graph)
       code, reason, body_uri = create_annotation_body(ro_uri, annotation_graph)
       if code != 201
-          error("Error creating annotation #{code}, #{reason}, #{resource_uri}")
+        error(code, "Error creating annotation #{code}, #{reason}, #{resource_uri}")
       end
       code, reason = update_annotation_stub(ro_uri, stuburi, resource_uri, body_uri)
       [code, reason, body_uri]
@@ -542,12 +549,12 @@
             warn("Warning: #{buri} has unrecognized content-type: #{content_type}")
           end
         else
-          error("Failed to GET #{buri}: #{code} #{reason}")
+          error(code, "Failed to GET #{buri}: #{code} #{reason}")
         end
       end
       annotation_graphs
     end
-  
+
     ##
     # Build RDF graph of all annnotations associated with a resource
     # (or all annotations for an RO)
@@ -566,7 +573,7 @@
             warn("Warning: #{buri} has unrecognized content-type: #{content_type}")
           end
         else
-          error("Failed to GET #{buri}: #{code} #{reason}")
+          error(code, "Failed to GET #{buri}: #{code} #{reason}")
         end
       end
       annotation_graph
@@ -575,10 +582,10 @@
     ##
     # Retrieve annotation for given annotation URI
     #
-    # Returns: annotation_graph
+    # Returns: [code, reason, uri, annotation_graph]
     def get_annotation(annotation_uri)
-      code, reason, headers, uri, annotation_graph = get_resource_rdf(annotation_uri)
-      annotation_graph
+      code, reason, headers, uri, annotation_graph = get_resource(annotation_uri)
+      [code, reason, uri, annotation_graph]
     end
 
     ##
@@ -590,65 +597,36 @@
       if code == 204
         [code, reason]
       else
-        error("Failed to DELETE annotation #{annotation_uri}: #{code} #{reason}")
+        error(code, "Failed to DELETE annotation #{annotation_uri}: #{code} #{reason}")
       end
     end
 
     # -----------------------
-    # Folder manipulation
+    # Folders
     # -----------------------
 
     ##
-    # Returns an array of the given research object's root folders, as Folder objects.
-    def get_root_folder(ro_uri, options = {})
-      uri, data = ""
-      query = RDF::Query.new do
-        pattern [:research_object, RDF::RO.rootFolder,  :folder]
-        pattern [:folder, RDF::ORE.isDescribedBy, :folder_resource_map]
+    # Returns [code, reason, headers, uri, folder_contents]
+    def get_folder(folder_uri)
+      code, reason, headers, uri, folder_contents = do_request_rdf("GET", folder_uri,
+                                                                   :accept => 'application/vnd.wf4ever.folder')
+      if code != 200
+        error(code, reason)
       end
 
-      result = data.query(query).first
-
-      get_folder(result.folder_resource_map.to_s, options.merge({:name => result.folder.to_s}))
+      [code, reason, headers, uri, folder_contents]
     end
 
     ##
-    # Returns an RO::Folder object from the given resource map URI.
-    def get_folder(folder_uri, options = {})
-      folder_name = options[:name] || folder_uri.to_s.split('/').last
-      ROSRS::Folder.new(self, folder_name, folder_uri, :eager_load => options[:eager_load])
-    end
-
-    ##
-    # Returns an array of the given research object's root folders, as RO::Folder objects.
-    # These folders have their contents pre-loaded
-    # and the full hierarchy can be traversed without making further requests
-    def get_folder_hierarchy(ro_uri, options = {})
-      options[:eager_load] = true
-      get_root_folder(ro_uri, options)
-    end
-
-    ##
-    # Takes a folder URI and returns a it's description in RDF
-    def get_folder_description(folder_uri)
-      code, reason, headers, uripath, graph = do_request_rdf("GET", folder_uri,
-                                                             :accept => 'application/vnd.wf4ever.folder')
-      if code == 201
-        parse_folder_description(graph)
-      else
-        error("Error getting folder description: #{code} #{reason}")
-      end
-    end
-
-    ##
     # +contents+ is an Array containing Hash elements, which must consist of a :uri and an optional :name.
     # Example:
     #   folder_contents = [{:name => 'test_data.txt', :uri => 'http://www.example.com/ro/file1.txt'},
     #                      {:uri => 'http://www.myexperiment.org/workflows/7'}]
     #   create_folder('ros/new_ro/', 'example_data', folder_contents)
     #
-    # Returns the created folder as an RO::Folder object
+    # Returns [code, reason, uri, proxy_uri, folder_description_graph]
     def create_folder(ro_uri, name, contents = [])
+      name << "/" unless name[-1] == "/" # Need trailing slash on folders...
       code, reason, headers, uripath, folder_description = do_request_rdf("POST", ro_uri,
           :body       => create_folder_description(contents),
           :headers    => {"Slug" => name,
@@ -657,53 +635,37 @@
 
       if code == 201
         uri = parse_links(headers)[RDF::ORE.proxyFor.to_s].first
-        folder = ROSRS::Folder.new(self, uri.to_s.split('/').last, uri)
-
-        # Parse folder contents from response
-        query = RDF::Query.new do
-          pattern [:folder_entry, RDF.type, RDF.Description]
-          pattern [:folder_entry, RDF::RO.entryName, :name]
-          pattern [:folder_entry, RDF::ORE.proxyFor, :target]
-          #pattern [:folder_entry, SOMETHING, :entry_uri]
-        end
-
-        folder_contents = folder_description.query(query).collect do |e|
-          ROSRS::FolderEntry.new(self, e.name.to_s, e.target.to_s, e.entry_uri.to_s, folder)
-        end
-
-        folder.set_contents!(folder_contents)
-        folder
+        [code, reason, uri, headers["location"], folder_description]
       else
-        error("Error creating folder: #{code} #{reason}")
+        error(code, "Error creating folder: #{code} #{reason}")
       end
     end
 
-    def delete_folder(folder_uri)
-      code, reason = do_request("DELETE", folder_uri)
-      error("Error deleting folder #{folder_uri}: #{code} #{reason}") unless [204, 404].include?(code)
-      [code, reason]
-    end
-
-    def add_folder_entry(folder_uri, resource_uri, resource_name = nil, options = {})
+    def add_folder_entry(folder_uri, resource_uri, resource_name = nil)
       code, reason, headers, body= do_request("POST", folder_uri,
           :body       => create_folder_entry_description(resource_uri, resource_name),
-          :headers    => {"Content-Type" => 'application/vnd.wf4ever.proxy',})
+          :headers    => {"Content-Type" => 'application/vnd.wf4ever.folderentry',})
       if code == 201
-        ROSRS::FolderEntry.new(self, resource_name, parse_links(headers)[RDF::ORE.proxyFor.to_s].first,
-                            headers["Location"], options[:folder])
+        [code, reason, headers["Location"], parse_links(headers)[RDF::ORE.proxyFor.to_s].first]
       else
-        error("Error adding resource to folder: #{code} #{reason}")
+        error(code, "Error adding resource to folder: #{code} #{reason}")
       end
     end
 
+    #--------
+
     def delete_resource(resource_uri)
-      code, reason = do_request("DELETE", resource_uri)
-      error("Error deleting resource #{resource_uri}: #{code} #{reason}") unless code == 204
+      code, reason = do_request_follow_redirect("DELETE", resource_uri)
+      error(code, "Error deleting resource #{resource_uri}: #{code} #{reason}") unless [204,404].include?(code)
       [code, reason]
     end
 
     private
 
+    def parseable?(content_type)
+      PARSEABLE_CONTENT_TYPES.include?(content_type.downcase)
+    end
+
     ##
     # Takes +contents+, an Array containing Hash elements, which must consist of a :uri and an optional :name,
     # and returns an RDF description of the folder contents.

Modified: branches/wf4ever/vendor/plugins/rosrs/lib/wf4ever/rosrs_client.rb (3208 => 3209)


--- branches/wf4ever/vendor/plugins/rosrs/lib/wf4ever/rosrs_client.rb	2012-11-26 16:59:57 UTC (rev 3208)
+++ branches/wf4ever/vendor/plugins/rosrs/lib/wf4ever/rosrs_client.rb	2012-11-27 12:54:32 UTC (rev 3209)
@@ -9,10 +9,11 @@
 require File.expand_path(File.join(File.dirname(__FILE__), 'rosrs', 'namespaces'))
 require File.expand_path(File.join(File.dirname(__FILE__), 'rosrs', 'rdf_graph'))
 require File.expand_path(File.join(File.dirname(__FILE__), 'rosrs', 'session'))
+require File.expand_path(File.join(File.dirname(__FILE__), 'rosrs', 'helper'))
 require File.expand_path(File.join(File.dirname(__FILE__), 'rosrs', 'exceptions'))
 require File.expand_path(File.join(File.dirname(__FILE__), 'rosrs', 'research_object'))
 require File.expand_path(File.join(File.dirname(__FILE__), 'rosrs', 'annotation'))
-require File.expand_path(File.join(File.dirname(__FILE__), 'rosrs',  'resource'))
+require File.expand_path(File.join(File.dirname(__FILE__), 'rosrs', 'resource'))
 require File.expand_path(File.join(File.dirname(__FILE__), 'rosrs', 'folder'))
 require File.expand_path(File.join(File.dirname(__FILE__), 'rosrs', 'folder_entry'))
 

Modified: branches/wf4ever/vendor/plugins/rosrs/test/test_rosrs_session.rb (3208 => 3209)


--- branches/wf4ever/vendor/plugins/rosrs/test/test_rosrs_session.rb	2012-11-26 16:59:57 UTC (rev 3208)
+++ branches/wf4ever/vendor/plugins/rosrs/test/test_rosrs_session.rb	2012-11-27 12:54:32 UTC (rev 3209)
@@ -10,7 +10,7 @@
   #
   class Config
     def self.rosrs_api_uri; "http://sandbox.wf4ever-project.org/rodl/ROs/"; end
-    def self.authorization; "b1b286f1-24cf-4a86-97b1-208513d403ee"; end
+    def self.authorization; "32801fc0-1df1-4e34-b"; end
     def self.test_ro_name;  "TestSessionRO_ruby"; end
     def self.test_ro_path; test_ro_name+"/"; end
     def self.test_ro_uri;   rosrs_api_uri+test_ro_path; end
@@ -24,13 +24,15 @@
     @rouri = nil
     @rosrs = ROSRS::Session.new(Config.rosrs_api_uri, Config.authorization)
 
-    StringIO.class_eval do
-      def readpartial(*args)
-        result = read(*args)
-        if result.nil?
-          raise EOFError
-        else
-          result
+    if VERSION < "1.9.1"
+      StringIO.class_eval do
+        def readpartial(*args)
+          result = read(*args)
+          if result.nil?
+            raise EOFError
+          else
+            result
+          end
         end
       end
     end
@@ -73,8 +75,7 @@
 
   def create_test_research_object
     c, r = @rosrs.delete_research_object(Config.test_ro_uri)
-    c,r,u,m = @rosrs.create_research_object(Config.test_ro_name,
-        "Test RO for Session", "TestROSRS_Session.py", "2012-09-28")
+    c,r,u,m = @rosrs.create_research_object(Config.test_ro_name)
     assert_equal(c, 201)
     @rouri = u
     [c,r,u,m]
@@ -308,7 +309,7 @@
     buris1 = @rosrs.get_annotation_body_uris(@rouri, @res_txt)
     assert_includes(uri(bodyuri1), buris1)
     # Retrieve annotation
-    c,r,auri1,agr1a = @rosrs.get_annotation_body(annuri)
+    c,r,auri1,agr1a = @rosrs.get_annotation(annuri)
     assert_equal(200, c)
     assert_equal("OK", r)
     # The following test fails, due to a temp[orary redirect from the annotation
@@ -347,7 +348,7 @@
     buris2 = @rosrs.get_annotation_body_uris(@rouri, @res_txt)
     assert_includes(uri(bodyuri2), buris2)
     # Retrieve annotation
-    c,r,auri2,agr2a = @rosrs.get_annotation_body(annuri)
+    c,r,auri2,agr2a = @rosrs.get_annotation(annuri)
     assert_equal(c, 200)
     assert_equal(r, "OK")
     s2a = [uri(@res_txt), RDF::DC.title, lit("Title 2")]
@@ -372,7 +373,7 @@
     populate_test_research_object
 
     # Create external annotation on @res_txt
-    c,r,annuri,bodyuri1 = @rosrs.create_external_annotation(@rouri, @res_txt, "http://www.example.com/something")
+    c,r,annuri = @rosrs.create_external_annotation(@rouri, @res_txt, "http://www.example.com/something")
     assert_equal(201, c)
     assert_equal("Created", r)
 
@@ -380,10 +381,10 @@
     auris1 = @rosrs.get_annotation_stub_uris(@rouri, @res_txt)
     assert_includes(uri(annuri), auris1)
     buris1 = @rosrs.get_annotation_body_uris(@rouri, @res_txt)
-    assert_includes(uri(bodyuri1), buris1)
+    assert_includes("http://www.example.com/something", buris1)
 
     # Update external annotation
-    c,r,bodyuri2 = @rosrs.update_external_annotation(@rouri, annuri, @res_txt, "http://www.example.com/other")
+    c,r = @rosrs.update_external_annotation(@rouri, annuri, @res_txt, "http://www.example.com/other")
     assert_equal(c, 200)
     assert_equal(r, "OK")
 
@@ -391,7 +392,7 @@
     auris2 = @rosrs.get_annotation_stub_uris(@rouri, @res_txt)
     assert_includes(uri(annuri), auris2)
     buris2 = @rosrs.get_annotation_body_uris(@rouri, @res_txt)
-    assert_includes(uri(bodyuri2), buris2)
+    assert_includes("http://www.example.com/other", buris2)
 
     # Remove annotation
     code, reason = @rosrs.remove_annotation(annuri)

reply via email to

[Prev in Thread] Current Thread [Next in Thread]