Diff
Modified: branches/ro/Gemfile (3784 => 3785)
--- branches/ro/Gemfile 2013-11-13 12:10:29 UTC (rev 3784)
+++ branches/ro/Gemfile 2013-11-13 13:33:26 UTC (rev 3785)
@@ -18,6 +18,7 @@
gem "taverna-t2flow", "~> 0.4.0"
gem "workflow-to-galaxy", "~> 0.3.5"
gem "rdf", "~> 1.0.6"
+gem "rdf-raptor", "~> 1.0.1"
gem "rdf-n3", "~> 1.0.0"
gem "rdf-turtle", "~> 1.0.6"
gem "recaptcha", "~> 0.3.4"
@@ -30,4 +31,5 @@
gem "simple-rss", "~> 1.2.3"
gem "net-http-persistent", "~> 2.8"
gem "delayed_job", "~>2.0.4"
+gem "sanitize", "~> 2.0.6"
Modified: branches/ro/Rakefile (3784 => 3785)
--- branches/ro/Rakefile 2013-11-13 12:10:29 UTC (rev 3784)
+++ branches/ro/Rakefile 2013-11-13 13:33:26 UTC (rev 3785)
@@ -398,3 +398,35 @@
port = YAML.load(File.read("config/sunspot.yml"))[Rails.env]["solr"]["port"]
`sunspot-solr stop -p #{port}`
end
+
+desc 'Clear RDF cache for research objects'
+task "myexp:ro:clean" do
+ require File.dirname(__FILE__) + '/config/environment'
+
+ Resource.all.each do |resource|
+ unless resource.is_resource
+ if resource.content_blob
+ resource.content_blob.destroy
+ resource.update_attribute(:content_blob, nil)
+ end
+ end
+ end
+
+end
+
+desc 'Ensure all RO enabled models have research objects'
+task "myexp:ro:addmissing" do
+ require File.dirname(__FILE__) + '/config/environment'
+
+ [Workflow, Blob, Pack].each do |model|
+ model.all.each do |record|
+ if record.research_object.nil?
+ ResearchObject.create(
+ :context => record,
+ :slug => "#{Conf.to_visible(model.name)}#{record.id}",
+ :user => record.contributor)
+ end
+ end
+ end
+end
+
Modified: branches/ro/app/controllers/application_controller.rb (3784 => 3785)
--- branches/ro/app/controllers/application_controller.rb 2013-11-13 12:10:29 UTC (rev 3784)
+++ branches/ro/app/controllers/application_controller.rb 2013-11-13 13:33:26 UTC (rev 3785)
@@ -546,6 +546,8 @@
render_401
elsif status_code == :not_found
render_404
+ elsif status_code == :unprocessable_entity
+ render_422
elsif status_code == :internal_server_error
render_500
else
@@ -573,6 +575,14 @@
end
end
+ def render_422(message = nil)
+ @message = message
+ respond_to do |format|
+ format.html { render :template => "errors/422", :status => 422 }
+ format.all { render :nothing => true, :status => 422 }
+ end
+ end
+
def render_500(message = nil)
@message = message
respond_to do |format|
Copied: branches/ro/app/controllers/resources_controller.rb (from rev 3780, branches/packs/app/controllers/resources_controller.rb) (0 => 3785)
--- branches/ro/app/controllers/resources_controller.rb (rev 0)
+++ branches/ro/app/controllers/resources_controller.rb 2013-11-13 13:33:26 UTC (rev 3785)
@@ -0,0 +1,181 @@
+# myExperiment: app/controllers/resources_controller.rb
+#
+# Copyright (c) 2007-2013 The University of Manchester, the University of
+# Oxford, and the University of Southampton. See license.txt for details.
+
+require 'securerandom'
+
+class ResourcesController < ApplicationController
+
+ include ResearchObjectsHelper
+
+# before_filter :dump_request_to_log
+# after_filter :dump_response_to_log
+
+ def dump_to_log(thing, type)
+ # Dump headers
+
+ logger.info "---- #{type} headers ----------------------------------"
+ thing.headers.each do |header, value|
+ if header.starts_with?("HTTP_")
+ logger.info sprintf("%s: %s\n", header.sub(/^HTTP_/, "").downcase, value)
+ end
+ end
+
+ logger.info "Content-Type: " + thing.content_type.to_s
+ if thing.body
+ logger.info "---- #{type} body ------------------------------------"
+ if thing.body.kind_of?(String)
+ logger.info thing.body
+ else
+ logger.info thing.body.read
+ thing.body.rewind
+ end
+ end
+ logger.info "---- #{type} end ------------------------------------"
+
+ end
+
+ def dump_request_to_log
+ dump_to_log(request, 'Request')
+ end
+
+ def dump_response_to_log
+ dump_to_log(response, 'Response')
+ end
+
+ def show
+
+ ro = ResearchObject.find_by_slug_and_version(params[:research_object_id], nil)
+
+ unless ro
+ render :text => "Research Object not found", :status => :not_found
+ return
+ end
+
+ unless Authorization.check('view', ro, current_user)
+ render_401("You are unauthorized to view this research object.")
+ return
+ end
+
+ resource = ro.resources.find_by_path(params[:id])
+
+ unless resource
+ render :text => "Resource not found", :status => :not_found
+ return
+ end
+
+ # FIXME: This needs to support 406
+
+ unless Authorization.check('view', resource, current_user)
+ render_401("You are unauthorized to view this resource.")
+ return
+ end
+
+ # FIXME: This needs to support 401/403
+
+ if resource.is_proxy
+ if resource.proxy_for
+ redirect_to resource.proxy_for.uri.to_s, :status => 303
+ else
+ redirect_to resource.proxy_for_path, :status => 303
+ end
+ else
+
+ # Generate RDF on demand if required.
+
+ if resource.content_blob.nil?
+ resource.generate_graph!
+ resource.reload
+ end
+
+ send_data(resource.content_blob.data, :type => resource.content_type)
+ end
+ end
+
+ def post
+
+ research_object = ResearchObject.find_by_slug_and_version(params[:research_object_id], nil)
+
+ unless research_object
+ render :text => "Research Object not found", :status => :not_found
+ return
+ end
+
+ slug = request.headers["Slug"].gsub(" ", "%20") if request.headers["Slug"]
+
+ status, reason, location, links, filename, changes = research_object.new_or_update_resource(
+ :slug => slug,
+ :path => params[:path],
+ :content_type => request.content_type.to_s,
+ :user_uri => user_url(current_user),
+ :data ="" request.body.read,
+ :links => parse_links(request.headers))
+
+ research_object.update_manifest! if status == :created
+
+ response.headers["Location"] = location if location.kind_of?(String)
+ response.headers["Location"] = location.to_s if location.kind_of?(RDF::URI)
+
+ if links.length > 0
+ response.headers['Link'] = links.map do |link|
+ "<#{link[:link].kind_of?(RDF::URI) ? link[:link].to_s : link[:link]}>; " +
+ "rel=\"#{link[:rel].kind_of?(RDF::URI) ? link[:rel].to_s : link[:rel]}\""
+ end
+ end
+
+ if status == :created
+
+ graph = RDF::Graph.new
+
+ changes.each do |change|
+ graph << change.description
+ end
+
+ body = pretty_rdf_xml(render_rdf(graph))
+
+ send_data body, :type => 'application/rdf+xml', :filename => filename, :status => :created
+ else
+ render :status => status, :text => reason
+ end
+ end
+
+ def delete
+
+ ro = ResearchObject.find_by_slug_and_version(params[:research_object_id], nil)
+
+ unless ro
+ render :text => "Research Object not found", :status => :not_found
+ return
+ end
+
+ path = params[:id]
+
+ if path == ResearchObject::MANIFEST_PATH
+ render :text => "Cannot delete the manifest", :status => :forbidden
+ return
+ end
+
+ resource = ro.resources.find_by_path(path)
+
+ if resource.nil?
+ render :text => "Resource not found", :status => :not_found
+ return
+ end
+
+ # Delete the proxy if one exists for this resource.
+
+ if resource.content_type == 'application/vnd.wf4ever.proxy'
+ proxy = resource
+ resource = Resource.find_by_path(resource.proxy_for_path)
+ else
+ proxy = Resource.find_by_proxy_for_path(resource.path)
+ end
+
+ proxy.destroy if proxy
+ resource.destroy if resource
+
+ render :nothing => true, :status => :no_content
+ end
+
+end
Copied: branches/ro/app/helpers/research_objects_helper.rb (from rev 3780, branches/packs/app/helpers/research_objects_helper.rb) (0 => 3785)
--- branches/ro/app/helpers/research_objects_helper.rb (rev 0)
+++ branches/ro/app/helpers/research_objects_helper.rb 2013-11-13 13:33:26 UTC (rev 3785)
@@ -0,0 +1,415 @@
+# myExperiment: app/helpers/research_objects_helper.rb
+#
+# Copyright (c) 2007-2013 The University of Manchester, the University of
+# Oxford, and the University of Southampton. See license.txt for details.
+
+require 'securerandom'
+require 'xml/libxml'
+
+module ResearchObjectsHelper
+
+ NAMESPACES = {
+ "http://www.w3.org/1999/02/22-rdf-syntax-ns#" => "rdf",
+ "http://www.w3.org/2000/01/rdf-schema#" => "rdfs",
+ "http://purl.org/dc/terms/" => "dct",
+ "http://www.openarchives.org/ore/terms/" => "ore",
+ "http://purl.org/ao/" => "ao",
+ "http://purl.org/wf4ever/ro#" => "ro",
+ "http://www.w3.org/ns/prov#" => "prov",
+ "http://xmlns.com/foaf/0.1/" => "foaf",
+ "http://www.w3.org/ns/oa#" => "oa",
+ "http://purl.org/pav/" => "pav",
+ "http://purl.org/wf4ever/bundle#" => "bundle",
+ "http://purl.org/dc/elements/1.1/" => "dce",
+ "http://purl.org/wf4ever/roterms#" => "roterms",
+ "http://purl.org/wf4ever/wfprov#" => "wfprov",
+ "http://purl.org/wf4ever/wfdesc#" => "wfdesc",
+ "http://purl.org/wf4ever/wf4ever#" => "wf4ever",
+ "http://ns.taverna.org.uk/2012/tavernaprov/" => "tavernaprov",
+ "http://www.w3.org/2011/content#" => "content",
+ "http://www.w3.org/2002/07/owl#" => "owl"
+
+ }
+
+ private
+
+ def shorten_uri(uri)
+ uri = uri.to_s
+
+ NAMESPACES.each do |namespace, prefix|
+ if uri.starts_with?(namespace)
+ return "#{prefix}:#{uri[namespace.length..-1]}"
+ end
+ end
+
+ uri
+ end
+
+ def pretty_rdf_xml(text)
+
+ descriptions = { }
+
+ doc = LibXML::XML::Parser.string(text).parse
+
+ # Merge descriptions where the subject is the same.
+
+ doc.root.find("/rdf:RDF/rdf:Description").each do |description|
+
+ # FIXME: The following attribute access is case sensitive.
+
+ if description.attributes["about"]
+ key = "about #{description.attributes["about"]}"
+ else
+ key = "nodeID #{description.attributes["nodeID"]}"
+ end
+
+ if descriptions[key]
+
+ if descriptions[key].children.last.to_s == "\n "
+ descriptions[key].children.last.remove!
+ descriptions[key].children << "\n"
+ end
+
+ description.each do |object|
+ if object.element?
+ descriptions[key] << "\n "
+ descriptions[key] << object
+ descriptions[key] << "\n "
+ end
+ end
+
+ description.prev.remove!
+ description.remove!
+ else
+ descriptions[key] = description
+
+ description.prev = XML::Node.new_text("\n ")
+ end
+ end
+
+ doc.root << XML::Node.new_text("\n")
+
+ # Adjust namespaces to human readable names
+
+ namespaces = { "rdf" => "http://www.w3.org/1999/02/22-rdf-syntax-ns#" }
+
+ doc.root.find("/rdf:RDF/rdf:Description/*").each do |object|
+
+ next unless object.element?
+ next unless object.namespaces.namespace.prefix == "ns0"
+
+ href = ""
+ prefix = NAMESPACES[href]
+
+ namespaces[prefix] = href unless namespaces[prefix]
+
+ if prefix
+ el = XML::Node.new(prefix + ":" + object.name)
+
+ # Copy attributes
+
+ object.attributes.each do |attr|
+ if attr.ns?
+ el.attributes[attr.ns.prefix + ":" + attr.name] = attr.value
+ else
+ el.attributes[attr.name] = attr.value
+ end
+ end
+
+ # Move children
+
+ object.children.each do |child|
+ el << child.remove!
+ end
+
+ object.next = el
+ object.remove!
+ end
+ end
+
+ # Add namespaces
+
+ namespaces.delete("rdf")
+
+ namespaces.each do |prefix, href|
+ next if prefix == "rdf"
+ LibXML::XML::Namespace.new(doc.root, prefix, href)
+ end
+
+ text = doc.to_s
+
+ text.gsub!(/" xmlns:/, "\"\n xmlns:") # FIXME - do this without affecting the entire document
+
+ text
+ end
+
+ def relative_uri(uri, context)
+
+ uri = uri.to_s
+ context = context.to_s
+
+ if (uri == context)
+ candidate = "."
+ elsif uri.starts_with?(context)
+ candidate = uri[context.length..-1]
+ end
+
+ return uri if candidate.nil?
+ return uri if URI(context).merge(candidate).to_s != uri
+
+ candidate
+ end
+
+ def merge_graphs_aux(node, bnodes)
+ if node.class == RDF::Node
+ if bnodes[node]
+ bnodes[node]
+ else
+ bnodes[node] = RDF::Node.new
+ end
+ else
+ node
+ end
+ end
+
+ def merge_graphs(graphs)
+
+ result = RDF::Graph.new
+
+ graphs.each do |graph|
+
+ bnodes = {}
+
+ graph.statements.each do |subject, predicate, object|
+ result << [merge_graphs_aux(subject, bnodes),
+ merge_graphs_aux(predicate, bnodes),
+ merge_graphs_aux(object, bnodes)]
+ end
+ end
+
+ result
+ end
+
+ def parse_links(headers)
+
+ links = {}
+
+ link_headers = headers["Link"]
+
+ if link_headers
+ link_headers.split(",").each do |link|
+ matches = link.strip.match(/<([^>]*)>\s*;.*rel\s*=\s*"?([^;"]*)"?/)
+ if matches
+ links[matches[2]] ||= []
+ links[matches[2]] << matches[1]
+ end
+ end
+ end
+
+ links
+ end
+
+ def calculate_path(path, content_type, links = {})
+
+ return path if path
+
+ case content_type
+ when "application/vnd.wf4ever.proxy"
+ ".ro/proxies/#{SecureRandom.uuid}"
+ when "application/vnd.wf4ever.annotation"
+ ".ro/annotations/#{SecureRandom.uuid}"
+ when "application/vnd.wf4ever.folder"
+ ".ro/folders/#{SecureRandom.uuid}"
+ when "application/vnd.wf4ever.folderentry"
+ ".ro/entries/#{SecureRandom.uuid}"
+ when "application/vnd.wf4ever.folder"
+ ".ro/resource_maps/#{SecureRandom.uuid}"
+ else
+ SecureRandom.uuid
+ end
+ end
+
+ def load_graph(content, opts = {})
+
+ content_type = opts[:content_type] || "application/rdf+xml"
+ base_uri = opts[:base_uri]
+
+ case content_type
+ when "application/rdf+xml"
+ format = :rdfxml
+ when "text/turtle", "application/x-turtle"
+ format = :turtle
+ end
+
+ graph = RDF::Graph.new
+ graph << RDF::Reader.for(format).new(content, :base_uri => base_uri) if content
+ graph
+ end
+
+ RDF_NS = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'
+
+ def render_rdf_xml(graph, opts = {})
+
+ document = LibXML::XML::Document.new
+
+ document.root = LibXML::XML::Node.new('rdf:RDF')
+ LibXML::XML::Namespace.new(document.root, 'rdf', RDF_NS)
+
+ graph.each do |subject, predicate, object|
+
+ description = LibXML::XML::Node.new('rdf:Description')
+
+ if subject.anonymous?
+ description["rdf:nodeID"] = subject.id
+ else
+ description["rdf:about"] = relative_uri(subject.to_s, opts[:base_uri])
+ end
+
+ # Split the predicate URI into a namespace and a term.
+
+ match = predicate.to_s.match(/^(.*[\/#])([^\/#]+)$/)
+
+ namespace = match[1]
+ term = match[2]
+
+ if namespace == RDF_NS
+ statement = LibXML::XML::Node.new("rdf:#{term}")
+ else
+ statement = LibXML::XML::Node.new("ns0:#{term}")
+ LibXML::XML::Namespace.new(statement, 'ns0', namespace)
+ end
+
+ if object.literal?
+
+ statement['rdf:datatype'] = object.datatype.to_s if object.has_datatype?
+ statement['rdf:language'] = object.language.to_s if object.has_language?
+
+ statement << object.to_s
+
+ elsif object.resource?
+
+ if object.anonymous?
+ statement['rdf:nodeID'] = object.id
+ else
+ statement['rdf:resource'] = relative_uri(object.to_s, opts[:base_uri])
+ end
+
+ end
+
+ description << statement
+ document.root << description
+ end
+
+ document.to_s
+ end
+
+ def render_rdf(graph, opts = {})
+ if opts[:format] == :rdfxml || opts[:format].nil?
+ render_rdf_xml(graph, opts)
+ else
+ RDF::Writer.for(opts[:format]).buffer(:base_uri => opts[:base_uri]) { |writer| writer << graph }
+ end
+ end
+
+ def create_rdf_xml(&blk)
+ graph = RDF::Graph.new
+ yield(graph)
+ pretty_rdf_xml(render_rdf(graph))
+ end
+
+ def resource_path_fixed(context, resource)
+
+ resources_path = polymorphic_path([context, :items])
+
+ ore_path = resource.ore_path
+
+ if resource.is_root_folder?
+ resources_path
+ elsif ore_path
+ "#{resources_path}/#{ore_path}"
+ else
+ throw "No ORE path to this resource"
+ end
+ end
+
+ def parent_folders(resource)
+
+ folders = []
+
+ return [] if resource.is_root_folder
+
+ while resource.folder_entry.proxy_in.is_root_folder == false
+ resource = resource.folder_entry.proxy_in
+ folders << resource
+ end
+
+ folders.reverse
+ end
+
+ def item_uri(item)
+ polymorphic_url([item.research_object.context, :items]) + "/" + item.ore_path
+ end
+
+ def item_uri_with_resource(item, resource)
+ item_uri(item) + "?" + { :resource => resource }.to_query
+ end
+
+ def resource_label(resource, statements)
+
+ label = statements.query([resource, RDF::RDFS.label, nil]).first_literal
+
+ return label if label
+
+ resource
+ end
+
+ def item_link(item, resource, statements)
+ link_to(h(resource_label(resource, statements)), item_uri_with_resource(item, resource))
+ end
+
+ def annotate_resources(research_object, resource_uris, body_graph, content_type = 'application/rdf+xml')
+ research_object.create_annotation(
+ :body_graph => body_graph,
+ :content_type => content_type,
+ :resources => resource_uris,
+ :creator_uri => "/users/#{current_user.id}")
+ end
+
+ def post_process_file(research_object, data, resource_uri)
+
+ # Process robundle
+
+ if data[0..3] == "PK\x03\x04"
+
+ begin
+ zip_file = Tempfile.new('workflow_run.zip.')
+ zip_file.binmode
+ zip_file.write(data)
+ zip_file.close
+
+ Zip::ZipFile.open(zip_file.path) { |zip|
+
+ wfdesc = zip.get_entry(".ro/annotations/workflow.wfdesc.ttl").get_input_stream.read
+ wfprov = zip.get_entry("workflowrun.prov.ttl").get_input_stream.read
+
+ annotate_resources(research_object, [resource_uri], wfdesc, 'text/turtle')
+ annotate_resources(research_object, [resource_uri], wfprov, 'text/turtle')
+ }
+
+ rescue
+ raise unless Rails.env == "production"
+ end
+ end
+ end
+
+ def transform_wf(research_object, resource_uri)
+ format = "application/vnd.taverna.t2flow+xml"
+ token = Conf.wf_ro_service_bearer_token
+ uri = Wf4Ever::TransformationClient.create_job(Conf.wf_ro_service_uri, resource_uri.to_s, format, research_object.uri, token)
+puts " [Conf.wf_ro_service_uri, resource_uri, format, @pack.research_object.uri, token] = #{ [Conf.wf_ro_service_uri, resource_uri, format, research_object.uri, token].inspect}"
+ puts "################## Transforming at " + uri
+
+ uri
+ end
+end
+
Copied: branches/ro/app/models/annotation_resource.rb (from rev 3780, branches/packs/app/models/annotation_resource.rb) (0 => 3785)
--- branches/ro/app/models/annotation_resource.rb (rev 0)
+++ branches/ro/app/models/annotation_resource.rb 2013-11-13 13:33:26 UTC (rev 3785)
@@ -0,0 +1,6 @@
+
+class AnnotationResource < ActiveRecord::Base
+ belongs_to :annotation, :class_name => "Resource"
+ belongs_to :research_object
+end
+
Modified: branches/ro/app/models/blob.rb (3784 => 3785)
--- branches/ro/app/models/blob.rb 2013-11-13 12:10:29 UTC (rev 3784)
+++ branches/ro/app/models/blob.rb 2013-11-13 13:33:26 UTC (rev 3785)
@@ -10,8 +10,12 @@
require 'acts_as_attributable'
require 'sunspot_rails'
+require 'has_research_object'
+
class Blob < ActiveRecord::Base
+ include ResearchObjectsHelper
+
acts_as_site_entity :owner_text => 'Uploader'
acts_as_contributable
@@ -67,6 +71,10 @@
end
end
+ has_research_object
+
+ after_create :create_research_object
+
format_attribute :body
def type
@@ -106,4 +114,21 @@
content_type.mime_type == 'application/vnd.taverna.component-profile+xml'
end
+ def create_research_object
+
+ user_path = "/users/#{contributor_id}"
+
+ slug = "File#{self.id}"
+ slug = SecureRandom.uuid if ResearchObject.find_by_slug_and_version(slug, nil)
+
+ ro = build_research_object(:slug => slug, :user => self.contributor)
+ ro.save
+
+ file_resource = ro.create_aggregated_resource(
+ :user_uri => user_path,
+ :path => local_name, # FIXME - where should these be URL encoded?
+ :data ="" content_blob.data,
+ :context => self,
+ :content_type => content_type.mime_type)
+ end
end
Modified: branches/ro/app/models/content_blob.rb (3784 => 3785)
--- branches/ro/app/models/content_blob.rb 2013-11-13 12:10:29 UTC (rev 3784)
+++ branches/ro/app/models/content_blob.rb 2013-11-13 13:33:26 UTC (rev 3785)
@@ -1,26 +1,41 @@
# myExperiment: app/models/content_blob.rb
#
-# Copyright (c) 2009 University of Manchester and the University of Southampton.
-# See license.txt for details.
+# Copyright (c) 2007-2013 The University of Manchester, the University of
+# Oxford, and the University of Southampton. See license.txt for details.
require 'digest/md5'
require 'digest/sha1'
class ContentBlob < ActiveRecord::Base
+ before_save :update_metadata
+
validate do |record|
- if record.data.nil? || record.data.length == 0
- record.errors.add(:data, 'cannot be empty.')
+ if record.data.nil?
+ record.errors.add(:data, 'cannot be undefined.')
end
end
- before_save do |blob|
- blob.update_checksums
+ def update_metadata
+ calc_sha1
+ calc_md5
+ calc_size
end
- def update_checksums
- self.md5 = Digest::MD5.hexdigest(data)
+ def calc_sha1
self.sha1 = Digest::SHA1.hexdigest(data)
end
+ def calc_md5
+ self.md5 = Digest::MD5.hexdigest(data)
+ end
+
+ def calc_size
+ case self.data
+ when StringIO
+ self.size = self.data.size
+ when String
+ self.size = self.data.bytesize
+ end
+ end
end
Modified: branches/ro/app/models/pack.rb (3784 => 3785)
--- branches/ro/app/models/pack.rb 2013-11-13 12:10:29 UTC (rev 3784)
+++ branches/ro/app/models/pack.rb 2013-11-13 13:33:26 UTC (rev 3785)
@@ -10,9 +10,14 @@
require 'tempfile'
require 'cgi'
require 'sunspot_rails'
+require 'has_research_object'
class Pack < ActiveRecord::Base
+ include ResearchObjectsHelper
+
+ after_create :create_research_object
+
acts_as_site_entity :owner_text => 'Creator'
acts_as_contributable
@@ -28,6 +33,8 @@
belongs_to :license
+ has_research_object
+
def find_version(version)
match = versions.find(:first, :conditions => ["version = ?", version])
return match if match
@@ -662,7 +669,10 @@
inhibit_timestamps do
- version = versions.build(
+ pack_entry_map = {}
+ resource_map = {}
+
+ new_pack_version = versions.build(
:version => current_version,
:contributor => contributor,
:title => title,
@@ -671,7 +681,7 @@
contributable_entries.each do |entry|
- version.contributable_entries.build(
+ pack_entry_map[entry] = new_pack_version.contributable_entries.build(
:pack => self,
:contributable_id => entry.contributable_id,
:contributable_type => entry.contributable_type,
@@ -685,7 +695,7 @@
remote_entries.each do |entry|
- tt = version.remote_entries.build(
+ pack_entry_map[entry] = new_pack_version.remote_entries.build(
:pack => self,
:title => entry.title,
:uri => entry.uri,
@@ -696,9 +706,62 @@
:created_at => entry.created_at,
:updated_at => entry.updated_at)
end
+
+ # Calculate new research object version index
+
+ new_research_object = new_pack_version.build_research_object(
+ :slug => "#{research_object.slug}v#{current_version}",
+ :version => current_version,
+ :user => contributor)
+
+ research_object.resources.each do |resource|
+
+ new_resource = new_research_object.resources.build(
+ :context => resource.context,
+ :sha1 => resource.sha1,
+ :size => resource.size,
+ :content_type => resource.content_type,
+ :path => resource.path,
+ :entry_name => resource.entry_name,
+ :creator_uri => resource.creator_uri,
+ :proxy_in_path => resource.proxy_in_path,
+ :proxy_for_path => resource.proxy_for_path,
+ :ao_body_path => resource.ao_body_path,
+ :resource_map_path => resource.resource_map_path,
+ :aggregated_by_path => resource.aggregated_by_path,
+ :is_resource => resource.is_resource,
+ :is_aggregated => resource.is_aggregated,
+ :is_proxy => resource.is_proxy,
+ :is_annotation => resource.is_annotation,
+ :is_resource_map => resource.is_resource_map,
+ :is_folder => resource.is_folder,
+ :is_folder_entry => resource.is_folder_entry,
+ :is_root_folder => resource.is_root_folder,
+ :created_at => resource.created_at,
+ :updated_at => resource.updated_at,
+ :uuid => resource.uuid,
+ :title => resource.title)
+
+ resource_map[resource] = new_resource
+
+ if resource.content_blob
+ new_resource.build_content_blob(:data ="" resource.content_blob.data)
+ end
+
+ end
+
+ research_object.annotation_resources.each do |annotation_resource|
+
+ new_research_object.annotation_resources.build(
+ :annotation => resource_map[annotation_resource.annotation],
+ :resource_path => annotation_resource.resource_path)
+
+ end
+
end
save
+
end
def describe_version(version_number)
@@ -1029,4 +1092,12 @@
boost
end
+ def create_research_object
+
+ slug = "Pack#{self.id}"
+ slug = SecureRandom.uuid if ResearchObject.find_by_slug_and_version(slug, nil)
+
+ ro = build_research_object(:slug => slug, :user => self.contributor)
+ ro.save
+ end
end
Modified: branches/ro/app/models/pack_contributable_entry.rb (3784 => 3785)
--- branches/ro/app/models/pack_contributable_entry.rb 2013-11-13 12:10:29 UTC (rev 3784)
+++ branches/ro/app/models/pack_contributable_entry.rb 2013-11-13 13:33:26 UTC (rev 3785)
@@ -3,6 +3,8 @@
# Copyright (c) 2008 University of Manchester and the University of Southampton.
# See license.txt for details.
+require 'has_research_object'
+
class PackContributableEntry < ActiveRecord::Base
belongs_to :pack
validates_presence_of :pack
@@ -20,6 +22,10 @@
after_save :touch_pack
after_destroy :touch_pack
+ after_save :synchronize_research_object
+
+ has_resource
+
def check_unique
conditions = ["pack_id = ?", "version = ?", "contributable_type = ?", "contributable_id = ?"]
@@ -91,4 +97,32 @@
def touch_pack
pack.touch unless (pack.destroyed? || pack.contribution.nil?)
end
+
+ def synchronize_research_object
+
+ ro = pack.research_object
+
+ user_path = "/users/#{user_id}"
+
+ if ro && resource.nil?
+
+ case contributable
+ when Workflow
+ data = ""
+ path = contributable.unique_name + (contributable.file_ext ? ".#{contributable.file_ext}" : "")
+ when Blob
+ data = ""
+ path = contributable.local_name
+ end
+
+ resource = ro.create_aggregated_resource(
+ :user_uri => user_path,
+ :path => CGI::escape(path),
+ :data ="" data,
+ :context => self,
+ :content_type => contributable.content_type.mime_type)
+
+ ro.update_manifest!
+ end
+ end
end
Modified: branches/ro/app/models/pack_remote_entry.rb (3784 => 3785)
--- branches/ro/app/models/pack_remote_entry.rb 2013-11-13 12:10:29 UTC (rev 3784)
+++ branches/ro/app/models/pack_remote_entry.rb 2013-11-13 13:33:26 UTC (rev 3785)
@@ -3,6 +3,8 @@
# Copyright (c) 2008 University of Manchester and the University of Southampton.
# See license.txt for details.
+require 'has_research_object'
+
class PackRemoteEntry < ActiveRecord::Base
belongs_to :pack
validates_presence_of :pack
@@ -18,6 +20,10 @@
after_save :touch_pack
after_destroy :touch_pack
+ after_save :synchronize_research_object
+
+ has_resource
+
def check_unique
if PackRemoteEntry.find(:first, :conditions => ["pack_id = ? AND version = ? AND uri = ?", self.pack_id, self.version, self.uri])
errors.add_to_base("This external link already exists in the pack")
@@ -34,4 +40,22 @@
def available?
true
end
+
+ def synchronize_research_object
+
+ ro = pack.research_object
+
+ user_path = "/users/#{user_id}"
+
+ if ro && resource.nil?
+
+ resource = ro.create_proxy(
+ :proxy_for_path => uri,
+ :proxy_in_path => ".",
+ :context => self,
+ :user_uri => user_path)
+
+ ro.update_manifest!
+ end
+ end
end
Modified: branches/ro/app/models/pack_version.rb (3784 => 3785)
--- branches/ro/app/models/pack_version.rb 2013-11-13 12:10:29 UTC (rev 3784)
+++ branches/ro/app/models/pack_version.rb 2013-11-13 13:33:26 UTC (rev 3785)
@@ -3,6 +3,8 @@
# Copyright (c) 2012 University of Manchester and the University of Southampton.
# See license.txt for details.
+require 'has_research_object'
+
class PackVersion < ActiveRecord::Base
validates_presence_of :title
@@ -30,6 +32,8 @@
WHERE pack_id = #{pack_id} AND version = #{version}
ORDER BY created_at DESC'
+ has_research_object
+
def items_count
contributable_entries.count + remote_entries.count
end
Copied: branches/ro/app/models/research_object.rb (from rev 3780, branches/packs/app/models/research_object.rb) (0 => 3785)
--- branches/ro/app/models/research_object.rb (rev 0)
+++ branches/ro/app/models/research_object.rb 2013-11-13 13:33:26 UTC (rev 3785)
@@ -0,0 +1,993 @@
+# myExperiment: app/models/research_object.rb
+#
+# Copyright (c) 2007-2013 The University of Manchester, the University of
+# Oxford, and the University of Southampton. See license.txt for details.
+
+require 'rdf'
+require 'rdf/raptor'
+require 'yaml'
+require 'zip/zip'
+
+class ResearchObject < ActiveRecord::Base
+
+ MANIFEST_PATH = ".ro/manifest.rdf"
+
+ include ResearchObjectsHelper
+
+ after_create :create_manifest
+
+ belongs_to :user
+
+ has_many :resources, :dependent => :destroy
+
+ has_many :proxies, :class_name => 'Resource', :conditions => { :is_proxy => true }
+
+ has_many :annotation_resources
+
+ belongs_to :context, :polymorphic => true
+
+ has_many :checklists, :dependent => :destroy
+
+ validates_presence_of :slug
+
+ def uri
+ Conf.base_uri + "/rodl/ROs/#{slug}/"
+ end
+
+ def creator_uri
+ Conf.base_uri + "/users/#{user_id}"
+ end
+
+ def description
+
+ ro_uri = RDF::URI(uri)
+
+ graph = RDF::Graph.new
+
+ graph << [ro_uri, RDF.type, RO.ResearchObject]
+ graph << [ro_uri, RDF.type, ORE.Aggregation]
+ graph << [ro_uri, ORE.isDescribedBy, ro_uri + MANIFEST_PATH]
+ graph << [ro_uri, RDF::DC.created, created_at.to_datetime]
+ graph << [ro_uri, RDF::DC.creator, RDF::URI(creator_uri)]
+
+ graph << [RDF::URI(creator_uri), RDF.type, RDF::FOAF.Agent]
+
+ if root_folder
+ graph << [ro_uri, RO.rootFolder, RDF::URI(root_folder.uri)]
+ end
+
+ resources.each do |resource|
+
+ if resource.is_aggregated
+ graph << [ro_uri, ORE.aggregates, RDF::URI(resource.resource_uri)]
+ end
+
+ graph << resource.description
+ end
+
+ graph
+ end
+
+ def update_manifest!
+ manifest_resource.update_graph! if manifest_resource
+ end
+
+ def manifest_resource
+ resources.find(:first, :conditions => { :path => MANIFEST_PATH })
+ end
+
+ def root_folder
+ resources.find(:first, :conditions => { :is_root_folder => true } )
+ end
+
+ def annotations
+ annotation_resources.find(:all,
+ :conditions => { :resource_path => '.' }).map { |ar| ar.annotation }
+ end
+
+ def new_or_update_resource(opts = {})
+
+ changed = []
+ links = []
+ location = nil
+
+ content_type = opts[:content_type]
+ slug = opts[:slug]
+ path = opts[:path]
+ user_uri = opts[:user_uri]
+ data = ""
+ request_links = opts[:links] || {}
+
+ if slug == ResearchObject::MANIFEST_PATH
+
+ return [:forbidden, "Cannot overwrite the manifest", nil, []] unless opts[:force_write]
+
+ manifest = create_resource(
+ :path => calculate_path(slug, content_type, request_links),
+ :content_blob => ContentBlob.new(:data ="" data),
+ :creator_uri => user_uri,
+ :content_type => content_type,
+ :is_resource => false,
+ :is_aggregated => false,
+ :is_proxy => false,
+ :is_annotation => false,
+ :is_folder => false)
+
+ changed << manifest
+
+ elsif content_type == "application/vnd.wf4ever.proxy"
+
+ graph = load_graph(data)
+
+ node = graph.query([nil, RDF.type, ORE.proxy]).first_subject
+ proxy_for_uri = graph.query([node, ORE.proxyFor, nil ]).first_object
+
+ proxy = create_proxy(
+ :path => calculate_path(slug, content_type, request_links),
+ :proxy_for_path => relative_uri(proxy_for_uri, uri),
+ :proxy_in_path => ".",
+ :user_uri => user_uri)
+
+ proxy.update_graph!
+
+ location = proxy.uri
+ links << { :link => proxy_for_uri, :rel => ORE.proxyFor }
+ changed << proxy
+
+ elsif content_type == "application/vnd.wf4ever.annotation"
+
+ # Get information.
+
+ graph = load_graph(data)
+
+ aggregated_annotations = graph.query([nil, RDF.type, RO.AggregatedAnnotation])
+
+ if aggregated_annotations.count != 1 # FIXME - add test case
+ return [:unprocessable_entity, "The annotation must contain exactly one aggregated annotation", nil, []]
+ end
+
+ aggregated_annotation = aggregated_annotations.first_subject
+
+ ao_body_statements = graph.query([aggregated_annotation, AO.body, nil])
+
+ if ao_body_statements.count != 1 # FIXME - add test case
+ return [:unprocessable_entity, "Annotations must contain exactly one annotation body", nil, []]
+ end
+
+ annotated_resources_statements = graph.query([aggregated_annotation, AO.annotatesResource, nil])
+
+ if annotated_resources_statements.count == 0 # FIXME - add test case
+ return [:unprocessable_entity, "Annotations must annotate one or more resources", nil, []]
+ end
+
+ ao_body_uri = ao_body_statements.first_object.to_s
+
+ stub = create_annotation_stub(
+ :user_uri => user_uri,
+ :ao_body_path => relative_uri(ao_body_uri, uri),
+ :resource_paths => annotated_resources_statements.map { |a| relative_uri(a.object, uri) } )
+
+ annotated_resources_statements.each do |annotated_resource|
+ links << { :link => annotated_resource.object, :rel => AO.annotatesResource }
+ end
+
+ stub.update_graph!
+
+ links << { :link => ao_body_uri, :rel => AO.body }
+ changed << stub
+
+ elsif content_type == "application/vnd.wf4ever.folder"
+
+ folder = create_folder(slug, opts[:user_uri])
+
+ location = folder.proxy.uri
+
+ links << { :link => folder.resource_map.uri, :rel => ORE.isDescribedBy }
+ links << { :link => folder.uri, :rel => ORE.proxyFor }
+
+ changed << folder.proxy
+ changed << folder.resource_map
+ changed << folder
+
+ elsif content_type == "application/vnd.wf4ever.folderentry"
+
+ # Obtain information to create the folder entry.
+
+ graph = load_graph(data)
+
+ node = graph.query([nil, RDF.type, RO.FolderEntry]).first_subject
+ proxy_for_uri = graph.query([node, ORE.proxyFor, nil ]).first_object
+
+ # FIXME - need to check if proxy_in and proxy_for actually exists and error if not
+
+ proxy_for_path = relative_uri(proxy_for_uri, uri)
+ proxy_in_path = opts[:path]
+
+ # Create the folder entry
+
+ folder_entry = create_folder_entry(proxy_for_path, proxy_in_path, user_uri)
+
+ location = folder_entry.uri
+
+ links << { :link => proxy_for_uri, :rel => ORE.proxyFor }
+
+ changed << folder_entry
+
+ elsif request_links[AO.annotatesResource.to_s]
+
+ path = calculate_path(nil, content_type)
+ ro_uri = RDF::URI(uri)
+ annotation_uri = ro_uri + path
+
+ # Create an annotation body using the provided graph
+
+ ao_body = create_aggregated_resource(
+ :path => calculate_path(slug, content_type, request_links),
+ :data ="" data,
+ :content_type => content_type,
+ :user_uri => user_uri)
+
+ stub = create_annotation_stub(
+ :user_uri => user_uri,
+ :ao_body_path => ao_body.path,
+ :resource_paths => request_links[AO.annotatesResource.to_s].map { |resource| relative_uri(resource, uri) } )
+
+ stub.update_graph!
+
+ request_links[AO.annotatesResource.to_s].each do |annotated_resource_uri|
+ links << { :link => annotated_resource_uri, :rel => AO.annotatesResource }
+ end
+
+ changed << stub
+ changed << ao_body
+ changed << ao_body.proxy
+
+ links << { :link => stub.uri, :rel => AO.body }
+
+ location = stub.uri
+
+ else
+
+ resource = create_aggregated_resource(
+ :path => calculate_path(slug, content_type, request_links),
+ :data ="" data,
+ :content_type => content_type,
+ :user_uri => user_uri)
+
+ changed << resource
+ end
+
+ if resource && content_type != "application/vnd.wf4ever.proxy" && !resource.is_manifest? && !request_links[AO.annotatesResource.to_s]
+
+ proxy = resources.find(:first,
+ :conditions => { :content_type => 'application/vnd.wf4ever.proxy',
+ :proxy_in_path => '.',
+ :proxy_for_path => resource.path })
+
+ # Create a proxy for this resource if it doesn't exist.
+
+ unless proxy
+ proxy = create_proxy(
+ :proxy_for_path => resource.path,
+ :proxy_in_path => ".",
+ :user_uri => user_uri)
+
+ proxy.update_graph!
+ end
+
+ links << { :link => resource.uri, :rel => ORE.proxyFor }
+ location = proxy.uri
+
+ changed << proxy
+ end
+
+ location ||= resource.uri if resource
+
+ [:created, nil, location, links, path, changed]
+ end
+
+ # opts[:path] - optional path to use for the proxy
+ # opts[:proxy_for_path] - required
+ # opts[:proxy_in_path] - required
+ # opts[:user_uri] - optional
+
+ def create_proxy(opts)
+
+ # FIXME - these should be validations on the resource model
+ throw "proxy_for_path required" unless opts[:proxy_for_path]
+ throw "proxy_in_path required" unless opts[:proxy_in_path]
+
+ create_resource(
+ :context => opts[:context],
+ :path => opts[:path] || calculate_path(nil, 'application/vnd.wf4ever.proxy'),
+ :is_proxy => true,
+ :proxy_for_path => opts[:proxy_for_path],
+ :proxy_in_path => opts[:proxy_in_path],
+ :creator_uri => opts[:user_uri],
+ :content_type => "application/vnd.wf4ever.proxy")
+ end
+
+ def create_annotation_stub(opts)
+
+ # FIXME - these should be validations on the resource model
+ throw "ao_body_path required" unless opts[:ao_body_path]
+ throw "resource_paths required" unless opts[:resource_paths]
+
+ stub = create_resource(
+ :path => opts[:path] || calculate_path(nil, 'application/vnd.wf4ever.annotation'),
+ :creator_uri => opts[:user_uri],
+ :content_type => 'application/rdf+xml',
+ :is_aggregated => true,
+ :is_annotation => true,
+ :ao_body_path => opts[:ao_body_path])
+
+ opts[:resource_paths].map do |resource_path|
+ stub.annotation_resources.create(:resource_path => resource_path, :research_object => self)
+ end
+
+ stub
+ end
+
+ # opts = :slug, :body_graph, :creator_uri, :resources
+ #
+ def create_annotation(opts = {})
+
+ # FIXME - these should be validations on the resource model
+ throw "body_graph required" unless opts[:body_graph]
+ throw "content_type required" unless opts[:content_type]
+ throw "resources required" unless opts[:resources]
+ throw "creator_uri required" unless opts[:creator_uri]
+
+ resources = opts[:resources]
+ resources = [resources] unless resources.kind_of?(Array)
+
+ if opts[:body_graph].kind_of?(RDF::Graph)
+ data = "" { |graph| graph << opts[:body_graph] }
+ else
+ data = ""
+ end
+
+ # Create an annotation body using the provided graph
+
+ ao_body = create_aggregated_resource(
+ :path => calculate_path(opts[:slug], opts[:content_type]),
+ :data ="" data,
+ :content_type => opts[:content_type],
+ :user_uri => opts[:creator_uri])
+
+ stub = create_annotation_stub(
+ :user_uri => opts[:creator_uri],
+ :ao_body_path => ao_body.path,
+ :resource_paths => resources.map { |resource| relative_uri(resource, uri) } )
+
+ stub.update_graph!
+
+ update_manifest!
+
+ stub
+
+ end
+
+ def create_aggregated_resource(opts = {})
+
+ throw "user_uri required" unless opts[:user_uri]
+ throw "data required" unless opts[:data]
+ throw "content_type required" unless opts[:content_type]
+
+ path = calculate_path(opts[:path], opts[:content_type])
+
+ # Create a proxy for this resource if it doesn't exist.
+
+ unless proxies.exists?(:proxy_for_path => path)
+ proxy = create_proxy(
+ :proxy_for_path => path,
+ :proxy_in_path => ".",
+ :user_uri => opts[:user_uri])
+
+ proxy.update_graph!
+ end
+
+ # Create the resource.
+
+ create_resource(
+ :context => opts[:context],
+ :path => path,
+ :content_blob => ContentBlob.new(:data ="" opts[:data]),
+ :creator_uri => opts[:user_uri],
+ :content_type => opts[:content_type],
+ :is_resource => true,
+ :is_aggregated => true)
+ end
+
+ def create_resource_map(opts)
+
+ create_resource(
+ :path => calculate_path(opts[:path], "application/vnd.wf4ever.folder"),
+ :creator_uri => opts[:user_uri],
+ :content_type => 'application/rdf+xml',
+ :is_resource_map => true)
+
+ end
+
+ def create_folder_resource(opts)
+
+ # Create a resource map for this folder
+
+ resource_map = create_resource_map(:user_uri => opts[:user_uri])
+
+ # Create the folder entry
+
+ folder = create_resource(
+ :path => opts[:path],
+ :creator_uri => opts[:user_uri],
+ :content_type => "application/vnd.wf4ever.folder",
+ :resource_map_path => resource_map.path,
+ :is_aggregated => true,
+ :is_root_folder => root_folder.nil?,
+ :is_folder => true)
+
+ folder.update_graph!
+ resource_map.update_graph!
+
+ folder
+ end
+
+ def create_resource(attributes)
+
+ resource = resources.find_by_path(attributes[:path]) || resources.new
+
+ # FIXME - We need to know when we should be allowed to overwrite a
+ # resource. The RO structure needs to remain consistent.
+
+ resource.attributes = attributes
+ resource.save
+ resource
+ end
+
+ def create_folder(path, user_uri)
+
+ folder = create_folder_resource(
+ :path => path,
+ :user_uri => user_uri)
+
+ proxy = create_proxy(
+ :proxy_for_path => folder.path,
+ :proxy_in_path => ".",
+ :user_uri => user_uri)
+
+ folder
+ end
+
+ def create_folder_entry(path, parent_path, user_uri)
+
+ folder_entry = create_resource(
+ :path => calculate_path(nil, 'application/vnd.wf4ever.folderentry'),
+ :entry_name => URI(path).path.split("/").last,
+ :is_folder_entry => true,
+ :proxy_in_path => parent_path,
+ :proxy_for_path => path,
+ :content_type => 'application/vnd.wf4ever.folderentry',
+ :creator_uri => user_uri)
+
+ if folder_entry.proxy_for
+ folder_entry.proxy_for.update_attribute(:aggregated_by_path, parent_path)
+ end
+
+ folder_entry.update_graph!
+
+ if folder_entry.proxy_for
+ folder_entry.proxy_for.update_graph!
+ end
+
+ folder_entry
+ end
+
+ def find_using_path(path)
+ bits = path.split("/")
+
+ object = root_folder
+
+ while (bit = bits.shift)
+ folder_entries = object.proxies.select { |p| p.entry_name == bit }
+ return nil if folder_entries.empty?
+ object = folder_entries.first.proxy_for
+ end
+
+ object
+ end
+
+ def merged_annotation_graphs
+
+ graphs = resources.all(:conditions => { :is_annotation => true }).map do |annotation|
+ ao_body = annotation.ao_body
+ load_graph(ao_body.content_blob.data, :content_type => ao_body.content_type)
+ end
+
+ merge_graphs(graphs)
+ end
+
+ def ore_structure_aux(entry, all_entries) #:nodoc:
+
+ if entry.proxy_for.nil?
+ {
+ :name => entry.entry_name,
+ :uri => entry.proxy_for_path,
+ :type => :remote
+ }
+ elsif entry.proxy_for.is_folder
+
+ sub_entries = all_entries.select { |fe| fe.proxy_in_path == entry.proxy_for_path }
+
+ {
+ :name => entry.entry_name,
+ :type => :folder,
+ :entries => sub_entries.map { |sub_entry| ore_structure_aux(sub_entry, all_entries) }
+ }
+ else
+ {
+ :name => entry.entry_name,
+ :type => :file,
+ :path => entry.proxy_for_path
+ }
+ end
+ end
+
+ def ore_structure
+
+ return [] if root_folder.nil?
+
+ all_entries = resources.find(:all, :conditions => { :is_folder_entry => true } )
+
+ all_entries.select { |entry| entry.proxy_in_path == root_folder.path }.map do |entry|
+ ore_structure_aux(entry, all_entries)
+ end
+ end
+
+ def ore_directories_aux(prefix, structure)
+ results = []
+
+ structure.each do |entry|
+ if entry[:type] == :folder
+ results << "#{prefix}#{entry[:name]}"
+ results += ore_directories_aux("#{prefix}#{entry[:name]}/", entry[:entries])
+ end
+ end
+
+ results
+ end
+
+ def ore_directories
+ ore_directories_aux('', ore_structure).sort
+ end
+
+ def ore_resources_aux(structure, prefix)
+ results = []
+
+ structure.each do |entry|
+
+ case entry[:type]
+ when :file
+ results << { :name => entry[:name], :type => entry[:type], :ore_path => "#{prefix}#{entry[:path]}" }
+ when :folder
+ results += ore_resources_aux(entry[:entries], "#{prefix}#{entry[:name]}/")
+ end
+ end
+
+ results
+ end
+
+ def ore_resources
+ ore_resources_aux(ore_structure, '')
+ end
+
+ def find_template_from_graph(graph, templates)
+
+ templates.each do |name, template|
+ parameters = match_ro_template(graph, template)
+ return [template, parameters] if parameters
+ end
+
+ nil
+ end
+
+ def create_graph_using_ro_template(parameters, template)
+
+ graph = RDF::Graph.new
+
+ # Create the B-nodes.
+
+ if template["bnodes"]
+ template["bnodes"].each do |bnode|
+ parameters[bnode] = RDF::Node.new(bnode)
+ end
+ end
+
+ template["required_statements"].each do |statement|
+
+ node_template = statement["template"]
+ depends = statement["depends"]
+
+ if depends
+
+ all_present = true
+
+ depends.each do |dependent|
+ all_present = false if parameters[dependent].to_s.empty?
+ end
+
+ next unless all_present
+ end
+
+ graph << [prepare_ro_template_value(node_template[0], parameters),
+ prepare_ro_template_value(node_template[1], parameters),
+ prepare_ro_template_value(node_template[2], parameters)]
+ end
+
+ graph
+ end
+
+ def annotation_with_template(annotation)
+
+ graph = load_graph(annotation.ao_body.content_blob.data, :content_type => annotation.ao_body.content_type)
+
+ template, parameters = find_template_from_graph(graph, Conf.ro_templates)
+
+ {
+ :annotation => annotation,
+ :graph => graph,
+ :template => template,
+ :parameters => parameters
+ }
+ end
+
+ def annotations_with_templates_aux(annotations)
+
+ annotations.uniq.map do |annotation|
+ annotation_with_template(annotation)
+ end
+ end
+
+ def all_annotations_with_templates
+ return @all_annotations_with_templates if @all_annotations_with_templates
+
+ all_annotations = annotation_resources.map { |ar| ar.annotation }
+
+ @all_annotations_with_templates = annotations_with_templates_aux(all_annotations)
+ end
+
+ def annotations_with_templates
+ return @annotations_with_templates if @annotations_with_templates
+
+ @annotations_with_templates = annotations_with_templates_aux(annotations)
+ end
+
+ def annotations_of_type(type)
+ annotations_with_templates.select do |annotation|
+ annotation[:template] && annotation[:template]["label"] == type
+ end
+ end
+
+ def import_bundle(user)
+
+ ro_uri = RDF::URI('file://zip/')
+
+ user_uri = "/users/#{user.id}"
+
+ resources.each do |resource|
+ resource.destroy
+ end
+
+ ore_aggregates = RDF::URI("http://www.openarchives.org/ore/terms/aggregates")
+ ore_is_described_by = RDF::URI("http://www.openarchives.org/ore/terms/isDescribedBy")
+ ore_proxy = RDF::URI("http://www.openarchives.org/ore/terms/Proxy")
+ ore_proxy_for = RDF::URI("http://www.openarchives.org/ore/terms/proxyFor")
+ ore_proxy_in = RDF::URI("http://www.openarchives.org/ore/terms/proxyIn")
+ rdf_type = RDF.type
+ ro_folder = RDF::URI("http://purl.org/wf4ever/ro#Folder")
+ ro_folder_entry = RDF::URI("http://purl.org/wf4ever/ro#FolderEntry")
+ ro_resource = RDF::URI("http://purl.org/wf4ever/ro#Resource")
+
+ content = File.read("test_ro.zip") # FIXME - this obviously needs to fetch
+
+ begin
+ zip_file = Tempfile.new('ro.zip.')
+ zip_file.binmode
+ zip_file.write(content)
+ zip_file.close
+
+ Zip::ZipFile.open(zip_file.path) { |zip|
+
+ # Extract and load the remote manifest and folders.
+
+ graph = load_graph(zip.get_entry(ResearchObject::MANIFEST_PATH).get_input_stream.read,
+ :base_uri => ro_uri + ResearchObject::MANIFEST_PATH)
+
+ aggregates = graph.query([ro_uri, ore_aggregates, nil]).objects
+ proxies = graph.query([nil, rdf_type, ore_proxy]).subjects
+ folders = graph.query([nil, rdf_type, ro_folder]).subjects
+ annotations = graph.query([nil, RDF.type, RO.AggregatedAnnotation]).subjects
+
+ folders.each do |folder|
+ folder_rdf = graph.query([folder, ore_is_described_by, nil]).first_object
+
+ folder_path = relative_uri(folder_rdf, ro_uri)
+
+ folder_graph = load_graph(zip.get_entry(folder_path).get_input_stream.read,
+ :base_uri => ro_uri + folder_path)
+
+ graph << folder_graph
+ end
+
+ proxies.each do |proxy|
+
+ proxy_for = relative_uri(graph.query([proxy, ore_proxy_for, nil]).first_object, ro_uri)
+ proxy_in = relative_uri(graph.query([proxy, ore_proxy_in, nil]).first_object, ro_uri)
+
+ target = ro_uri + proxy_for
+
+ next if folders.include?(target)
+ next if annotations.include?(target)
+
+ create_proxy(
+ :path => calculate_path(nil, 'application/vnd.wf4ever.proxy'),
+ :proxy_for_path => proxy_for,
+ :proxy_in_path => proxy_in,
+ :user_uri => user_uri)
+
+ end
+
+ aggregates.each do |aggregate|
+
+ next if folders.include?(aggregate)
+ next if annotations.include?(aggregate)
+
+ path = relative_uri(aggregate, ro_uri)
+
+ data = ""
+
+ create_aggregated_resource(
+ :path => path,
+ :data ="" data,
+ :content_type => 'application/rdf+xml', # FIXME - I can't get this information without fetching the resources :(
+ :user_uri => user_uri)
+
+ end
+
+ # Annotations
+
+ annotations.each do |annotation|
+
+ body_uri = graph.query([annotation, AO.body, nil]).first_object
+ annotated_resources = graph.query([annotation, RO.annotatesAggregatedResource, nil]).objects
+
+ annotated_resources.each do |ar|
+
+ stub = create_annotation_stub(
+ :user_uri => user_uri,
+ :ao_body_path => relative_uri(body_uri, ro_uri),
+ :resource_paths => annotated_resources.map { |a| relative_uri(a, ro_uri) } )
+ end
+ end
+ }
+
+ rescue
+ raise #unless Rails.env == "production"
+ end
+
+ nil
+ end
+
+ def generate_zip!
+
+ zip_file_name = "tmp/zipped_ro.#{$$}.zip"
+
+ FileUtils.rm_rf(zip_file_name)
+
+ zip_file = Zip::ZipFile.open(zip_file_name, Zip::ZipFile::CREATE)
+
+ resources.each do |resource|
+
+ if resource.content_blob.nil?
+ resource.generate_graph!
+ resource.reload
+ end
+
+ next unless resource.content_blob
+
+ next if resource.is_folder
+
+ zip_file.get_output_stream(resource.path) do |stream|
+ data = ""
+ data.force_encoding(Encoding::ASCII_8BIT)
+ stream.write(data)
+ end
+ end
+
+ zip_file.close
+
+ zip_file_name
+ end
+
+ def run_checklist!(slug)
+
+ checklist = checklists.find_by_slug(slug)
+
+ entry = Conf.research_object_checklists[slug]
+
+ if checklist.nil?
+ checklist = checklists.new(
+ :slug => slug,
+ :label => entry["label"],
+ :minim_url => entry["minim"],
+ :purpose => entry["purpose"])
+ end
+
+ checklist.run_checklist!
+
+ checklist.save
+
+ checklist
+ end
+
+private
+
+ def create_manifest #:nodoc:
+
+ resources.create(:path => ResearchObject::MANIFEST_PATH,
+ :content_type => 'application/rdf+xml')
+
+ update_manifest!
+ end
+
+ def verify_select_value(symbol, node, template)
+
+ # We are only interested in parameterized nodes.
+
+ return true unless symbol.kind_of?(Symbol)
+
+ # Ignore blank nodes.
+
+ return true if template["bnodes"] && template["bnodes"].include?(symbol)
+
+ # Find parameter.
+
+ parameter = template["parameters"].find { |p| p["symbol"] == symbol }
+
+ throw "No template match for #{symbol.inspect}" if parameter.nil?
+
+ # No problem if the parameter type isn't a select.
+ return true unless parameter["type"] == "select"
+
+ # Verify that the node type is correct.
+
+ case parameter["node_type"]
+ when "literal"
+ return false unless node.kind_of?(RDF::Literal)
+ when "resource"
+ return false unless node.kind_of?(RDF::URI)
+ else
+ throw "Unknown node_type (#{parameter["node_type"]})"
+ end
+
+ # Verify that the node value is valid.
+
+ if parameter["options"]
+ options = parameter["options"]
+ elsif parameter["special"] == "wfprov_workflow_run"
+ options = wfprov_workflow_run(self)
+ elsif parameter["special"] == "wfdesc_workflow"
+ options = wfprov_workflow_run(self)
+ end
+
+ return false unless options.find { |o| o[1] == node.to_s }
+
+ # A valid select match as it passed the tests.
+
+ return true
+ end
+
+ def match_ro_template(graph, template)
+
+ parameters = {}
+
+ # Work on a copy of the graph
+
+ graph_copy = RDF::Graph.new
+
+ graph.each do |statement|
+ graph_copy << statement
+ end
+
+ found = []
+ missing = []
+
+ template["required_statements"].each do |statement|
+
+ node_template = statement["template"]
+ depends = statement["depends"]
+
+ # Find a statement that matches the current statement in the template.
+
+ target = [prepare_ro_template_value(node_template[0], parameters),
+ prepare_ro_template_value(node_template[1], parameters),
+ prepare_ro_template_value(node_template[2], parameters)]
+
+ match = graph_copy.query(target).first
+
+ if match
+ return nil unless verify_select_value(node_template[0], match.subject, template)
+ return nil unless verify_select_value(node_template[1], match.predicate, template)
+ return nil unless verify_select_value(node_template[2], match.object, template)
+ end
+
+ if depends
+ if match
+ found += depends
+ else
+ missing += depends
+ end
+ end
+
+ if match.nil?
+ if depends
+ next
+ else
+ return nil
+ end
+ end
+
+ # Verify that there are no mismatches between existing parameters and found
+ # parameters; Then fill in newly defined parameters.
+
+ return nil unless process_ro_template_parameter(node_template[0], match[0], parameters)
+ return nil unless process_ro_template_parameter(node_template[1], match[1], parameters)
+ return nil unless process_ro_template_parameter(node_template[2], match[2], parameters)
+
+ # Remove the current statement from the graph copy
+
+ graph_copy.delete(match)
+ end
+
+ # Verify that all statements were consumed in processing the template.
+
+ return nil unless graph_copy.empty?
+
+ # Verify that no dependencies were missing in some optional statements and
+ # present in other optional statements.
+
+ return nil unless (found & missing).empty?
+
+ parameters
+ end
+
+ def process_ro_template_parameter(name, value, parameters)
+
+ # Terms in the template can be one of three things:
+ #
+ # 1. A parameter, denoted by a symbol
+ # 2. A URI, denoted by a string enclosed by angle brackets
+ # 3. A literal, denoted by a string enclosed with double quote marks
+
+ return true unless name.class == Symbol
+
+ if parameters[name].nil?
+ parameters[name] = value
+ true
+ else
+ value == parameters[name]
+ end
+ end
+
+ def prepare_ro_template_value(value, parameters)
+ if value.class == Symbol
+ parameters[value]
+ elsif (value[0..0] == '<') && (value[-1..-1] == '>')
+ RDF::URI.parse(value[1..-2])
+ elsif (value[0..0] == '"') && (value[-1..-1] == '"')
+ RDF::Literal.new(value[1..-2])
+ else
+ raise "Unknown template value: #{value}"
+ end
+ end
+
+end
Copied: branches/ro/app/models/resource.rb (from rev 3780, branches/packs/app/models/resource.rb) (0 => 3785)
--- branches/ro/app/models/resource.rb (rev 0)
+++ branches/ro/app/models/resource.rb 2013-11-13 13:33:26 UTC (rev 3785)
@@ -0,0 +1,350 @@
+# myExperiment: app/models/resource.rb
+#
+# Copyright (c) 2007-2013 The University of Manchester, the University of
+# Oxford, and the University of Southampton. See license.txt for details.
+
+require 'securerandom'
+
+class Resource < ActiveRecord::Base
+
+ include ResearchObjectsHelper
+
+ before_save :set_uuid
+
+ before_save :copy_metadata
+
+ after_save :touch_research_object
+ after_destroy :touch_research_object
+
+ after_create :generate_create_activity
+
+ belongs_to :research_object
+
+ belongs_to :content_blob, :dependent => :destroy
+
+ belongs_to :context, :polymorphic => true
+
+ def proxy_for
+ research_object.resources.find(:first,
+ :conditions => { :path => proxy_for_path } )
+ end
+
+ def proxy
+ research_object.resources.find(:first,
+ :conditions => { :proxy_for_path => path, :is_folder_entry => false } )
+ end
+
+ def folder_entry
+ research_object.resources.find(:first,
+ :conditions => { :proxy_for_path => path, :is_folder_entry => true } )
+ # FIXME: dependent => destroy
+ end
+
+ def proxy_in
+ research_object.resources.find(:first,
+ :conditions => { :path => proxy_in_path } )
+ end
+
+ def proxies
+ research_object.resources.find(:all,
+ :conditions => { :proxy_in_path => path } )
+ end
+
+ def aggregated_by
+ research_object.resources.find(:first,
+ :conditions => { :path => aggregated_by_path } )
+ end
+
+ def aggregates
+ research_object.resources.find(:all,
+ :conditions => { :aggregated_by_path => path } )
+ end
+
+ def ao_body
+ research_object.resources.find(:first,
+ :conditions => { :path => ao_body_path } )
+ end
+
+ def resource_map
+ research_object.resources.find(:first,
+ :conditions => { :path => resource_map_path } )
+ end
+
+ def is_resource_map_to
+ research_object.resources.find(:first,
+ :conditions => { :resource_map_path => path } )
+ end
+
+ has_many :annotation_resources, :foreign_key => 'annotation_id', :dependent => :destroy
+
+ validates_uniqueness_of :path, :scope => :research_object_id
+ validates_presence_of :content_type
+ validates_presence_of :path
+
+ after_destroy :destroy_related_resources
+ after_destroy :update_manifest!
+
+ def is_manifest?
+ path == ResearchObject::MANIFEST_PATH
+ end
+
+ def description
+
+ graph = RDF::Graph.new
+
+ ro_uri = RDF::URI(research_object.uri)
+ uri = resource_uri
+
+ if is_manifest?
+ graph << [uri, RDF.type, RO.Manifest]
+ graph << [uri, RDF.type, ORE.ResourceMap]
+ graph << [uri, ORE.describes, ro_uri]
+ end
+
+ if is_resource
+ graph << [uri, RDF.type, RO.Resource]
+ end
+
+ if is_aggregated
+ graph << [uri, RDF.type, ORE.AggregatedResource]
+ end
+
+ if is_proxy
+ graph << [uri, RDF.type, ORE.Proxy]
+ graph << [uri, ORE.proxyIn, ro_uri.join(proxy_in_path)]
+ graph << [uri, ORE.proxyFor, ro_uri.join(proxy_for_path)]
+ end
+
+ if is_annotation
+ graph << [uri, RDF.type, RO.AggregatedAnnotation]
+ graph << [uri, RDF.type, AO.Annotation]
+ graph << [uri, AO.body, ro_uri.join(ao_body_path)]
+
+ annotation_resources.each do |resource|
+ graph << [uri, RO.annotatesAggregatedResource, ro_uri.join(resource.resource_path)]
+ end
+ end
+
+ if is_resource_map
+
+ folder = is_resource_map_to
+
+ folder_uri = RDF::URI(folder.uri)
+
+ graph << [uri, RDF.type, ORE.ResourceMap]
+ graph << [uri, ORE.describes, folder_uri]
+
+ graph << [folder_uri, RDF.type, RO.Folder]
+ graph << [folder_uri, RDF.type, ORE.Aggregation]
+ graph << [folder_uri, ORE.isDescribedBy, uri]
+ graph << [folder_uri, ORE.isAggregatedBy, RDF::URI(folder.aggregated_by.uri)] if folder.aggregated_by_path
+
+ folder.aggregates.each do |aggregate|
+ graph << [folder_uri, ORE.aggregates, RDF::URI(aggregate.uri)]
+ graph << [RDF::URI(aggregate.uri), ORE.isAggregatedBy, folder_uri]
+ end
+ end
+
+ if is_folder
+ graph << [uri, RDF.type, RO.Resource]
+ graph << [uri, RDF.type, RO.Folder]
+ graph << [uri, RDF.type, ORE.AggregatedResource]
+ graph << [uri, RDF.type, ORE.Aggregation]
+
+ resource_map = Resource.find(:first, :conditions => { :path => resource_map_path })
+
+ graph << [uri, ORE.isDescribedBy, ro_uri.join(resource_map_path)]
+ end
+
+ if is_folder_entry
+ graph << [uri, RDF.type, ORE.Proxy]
+ graph << [uri, RDF.type, RO.FolderEntry]
+ graph << [uri, ORE.proxyIn, ro_uri.join(proxy_in_path)]
+ graph << [uri, ORE.proxyFor, ro_uri.join(proxy_for_path)]
+ end
+
+ name = path.split("/").last if path
+
+ graph << [uri, RDF::DC.created, created_at.to_datetime] if created_at
+ graph << [uri, RDF::DC.creator, RDF::URI(Conf.base_uri).join(creator_uri)] if creator_uri
+ graph << [uri, RO["name"], name] if name
+
+ if content_blob && !is_manifest?
+ graph << [uri, RO.filesize, content_blob.size] if content_blob.size
+ graph << [uri, RO.checksum, RDF::URI("urn:MD5:#{content_blob.md5}")]
+ end
+
+ graph
+ end
+
+ def resource_uri
+ RDF::URI(research_object.uri) + path
+ end
+
+ def uri
+ RDF::URI(research_object.uri) + path
+ end
+
+ def name
+ URI(path).path.split("/").last
+ end
+
+ def ore_path
+
+ # Not all resources are listed in the ORE structure.
+ return nil if folder_entry.nil?
+
+ entry = folder_entry
+
+ bits = []
+
+ while entry
+ bits.unshift(entry.entry_name)
+ entry = entry.proxy_in.folder_entry
+ end
+
+ return bits.join("/")
+ end
+
+ def update_graph!
+ unless is_resource
+ if content_blob
+ content_blob.destroy
+ update_attribute(:content_blob, nil)
+ end
+ end
+ end
+
+ def generate_graph!
+
+ if path == ResearchObject::MANIFEST_PATH
+
+ # The manifest should contain the metadata for every resource, so we need
+ # to know the metrics of each. This means that we must ensure that all
+ # RDF is generated before we create the manifest.
+
+ research_object.resources(true).each do |resource|
+ unless resource.path == ResearchObject::MANIFEST_PATH
+ resource.generate_graph! if resource.content_blob.nil?
+ end
+ end
+
+ manifest_body = pretty_rdf_xml(render_rdf(research_object.description))
+
+ research_object.new_or_update_resource(
+ :slug => ResearchObject::MANIFEST_PATH,
+ :content_type => "application/rdf+xml",
+ :data ="" manifest_body,
+ :force_write => true)
+
+ else
+
+ unless is_resource
+
+ new_description = create_rdf_xml { |graph| graph << description }
+
+ content_blob.destroy if content_blob
+ update_attribute(:content_blob, ContentBlob.new(:data ="" new_description))
+ end
+ end
+ end
+
+ def annotations
+ research_object.annotation_resources.find(:all,
+ :conditions => { :resource_path => path }).map { |ar| ar.annotation }
+ end
+
+ def merged_annotation_graphs
+
+ result = RDF::Graph.new
+
+ annotations.each do |annotation|
+ ao_body = annotation.ao_body
+ result << load_graph(ao_body.content_blob.data, :content_type => ao_body.content_type)
+ end
+
+ result
+ end
+
+ def annotations_with_templates
+ annotations.map do |annotation|
+
+ graph = load_graph(annotation.ao_body.content_blob.data, :content_type => annotation.ao_body.content_type, :base_uri => annotation.ao_body.uri)
+
+ template, parameters = research_object.find_template_from_graph(graph, Conf.ro_templates)
+
+ {
+ :annotation => annotation,
+ :graph => graph,
+ :template => template,
+ :parameters => parameters
+ }
+ end
+ end
+
+ def copy_metadata
+ if content_blob
+ self.sha1 = content_blob.calc_sha1
+ self.size = content_blob.calc_size
+ else
+ self.sha1 = nil
+ self.size = nil
+ end
+ end
+
+ def set_uuid
+ self.uuid = SecureRandom.uuid
+ end
+
+ def update_manifest!
+ research_object.update_manifest!
+ end
+
+ def touch_research_object
+ research_object.touch
+ end
+
+ def destroy_related_resources
+
+ # Delete the proxy for this resource if it exists.
+ proxy.destroy if proxy
+
+ # Delete the folder entry for this resource if it exists.
+ folder_entry.destroy if folder_entry
+
+ # Remove this resource as target from annotations
+
+ annotation_resources = AnnotationResource.find(:all, :conditions => {
+ :research_object_id => research_object.id,
+ :resource_path => path })
+
+ annotation_resources.each do |annotation_resource|
+ annotation = annotation_resource.annotation
+ annotation_resource.destroy
+
+ if annotation.annotation_resources(true).empty?
+ # annotation.ao_body.destroy
+ annotation.destroy
+ end
+ end
+ end
+
+ def file_count
+ if is_folder
+ aggregates.map { |aggregate| aggregate.file_count }.sum
+ else
+ 1
+ end
+ end
+
+ def generate_create_activity
+
+ if context
+ if match = creator_uri.match("^\/users\/([0-9]+)$")
+ user = User.find(match[1])
+ end
+
+ Activity.create_activities(:subject => user, :action ="" 'create', :object => self)
+ end
+ end
+end
Modified: branches/ro/app/models/user.rb (3784 => 3785)
--- branches/ro/app/models/user.rb 2013-11-13 12:10:29 UTC (rev 3784)
+++ branches/ro/app/models/user.rb 2013-11-13 13:33:26 UTC (rev 3785)
@@ -661,6 +661,10 @@
all_networks.map {|n| n.policies}.flatten
end
+ def uri
+ "#{Conf.base_uri}/users/#{id}"
+ end
+
protected
# clean up emails and username before validation
Modified: branches/ro/app/models/workflow.rb (3784 => 3785)
--- branches/ro/app/models/workflow.rb 2013-11-13 12:10:29 UTC (rev 3784)
+++ branches/ro/app/models/workflow.rb 2013-11-13 13:33:26 UTC (rev 3785)
@@ -17,8 +17,12 @@
require 'scufl/model'
require 'scufl/parser'
+require 'has_research_object'
+
class Workflow < ActiveRecord::Base
+ include ResearchObjectsHelper
+
has_many :citations,
:order => "created_at DESC",
:dependent => :destroy
@@ -110,6 +114,10 @@
validates_presence_of :content_blob
validates_presence_of :content_type
+ has_research_object
+
+ after_create :create_research_object
+
def tag_list_comma
list = ''
tags.each do |t|
@@ -439,4 +447,21 @@
self.packs.select { |p| p.component_family? }
end
+ def create_research_object
+
+ user_path = "/users/#{contributor_id}"
+
+ slug = "Workflow#{self.id}"
+ slug = SecureRandom.uuid if ResearchObject.find_by_slug_and_version(slug, nil)
+
+ ro = build_research_object(:slug => slug, :user => self.contributor)
+ ro.save
+
+ workflow_resource = ro.create_aggregated_resource(
+ :user_uri => user_path,
+ :path => filename, # FIXME - where should these be URL encoded?
+ :data ="" content_blob.data,
+ :context => self,
+ :content_type => content_type.mime_type)
+ end
end
Modified: branches/ro/app/views/blobs/show.rhtml (3784 => 3785)
--- branches/ro/app/views/blobs/show.rhtml 2013-11-13 12:10:29 UTC (rev 3784)
+++ branches/ro/app/views/blobs/show.rhtml 2013-11-13 13:33:26 UTC (rev 3785)
@@ -49,22 +49,38 @@
<%= render(:partial => "contributions/version_selector", :locals => { :resource => @blob, :version => @version, :path => :blob_version_path }) %>
<div class="contribution_version_inner_box">
- <p>
- <b>Title:</b>
- <span class="title"><%=h @version.title %></span>
- </p>
+
+ <table class="metadata">
+
+ <tr>
+ <td>Title</td>
+ <td><%=h @version.title %></td>
+ </tr>
- <p>
- <b>File name:</b>
- <%=h @version.local_name %>
- </p>
+ <tr>
+ <td>File name</td>
+ <td><%=h @version.local_name %></td>
+ </tr>
- <% if @version.content_type %>
- <p>
- <b>File type:</b>
- <%=h @version.content_type.title %>
- </p>
- <% end %>
+ <% if Authorization.check('download', @blob, current_user) %>
+ <tr>
+ <td>File size</td>
+ <td class="size"><%=h @version.content_blob.size.to_s %></td>
+ </tr>
+ <tr>
+ <td>SHA1</td>
+ <td class="sha1"><%=h @version.content_blob.sha1 %></td>
+ </tr>
+ <% end %>
+
+ <% if @version.content_type %>
+ <tr>
+ <td>Content type</td>
+ <td class="content_type"><%=h @version.content_type.title %></td>
+ </tr>
+ <% end %>
+
+ </table>
<br/>
Copied: branches/ro/app/views/errors/422.html.erb (from rev 3780, branches/packs/app/views/errors/422.html.erb) (0 => 3785)
--- branches/ro/app/views/errors/422.html.erb (rev 0)
+++ branches/ro/app/views/errors/422.html.erb 2013-11-13 13:33:26 UTC (rev 3785)
@@ -0,0 +1,8 @@
+<div class="error">
+ <h1>422 - Unprocessable Entity</h1>
+ <% if @message %>
+ <%= @message %>
+ <% else %>
+ The request was well-formed but was unable to be followed due to semantic errors.
+ <% end %>
+</div>
Modified: branches/ro/app/views/errors/500.html.erb (3784 => 3785)
--- branches/ro/app/views/errors/500.html.erb 2013-11-13 12:10:29 UTC (rev 3784)
+++ branches/ro/app/views/errors/500.html.erb 2013-11-13 13:33:26 UTC (rev 3785)
@@ -1,5 +1,10 @@
<div class="error">
<h1>500 - Internal Server Error</h1>
- The server encountered an error whilst processing your request.<br/><br/>
- The administrators have been notified of this problem.
-</div>
\ No newline at end of file
+ <% if @message %>
+ <%= @message %>
+ <% else %>
+ The server encountered an error whilst processing your request.
+ <br/><br/>
+ The administrators have been notified of this problem.
+ <% end %>
+</div>
Modified: branches/ro/app/views/packs/index.rhtml (3784 => 3785)
--- branches/ro/app/views/packs/index.rhtml 2013-11-13 12:10:29 UTC (rev 3784)
+++ branches/ro/app/views/packs/index.rhtml 2013-11-13 13:33:26 UTC (rev 3785)
@@ -1,25 +1,3 @@
-<div class="box_standout" style="margin: 1.5em 3em; padding: 0.7em 1.5em;">
- <p>
- <%= image_tag 'manhattan_studio/folder-closed_24.png', :style => "vertical-align: middle;" -%>
- <span style="font-size: 108%; font-weight: bold; margin-left: 8px; vertical-align: middle;">
- <%= link_to_function "What are Packs?" + expand_image("0.3em"), visual_effect(:toggle_blind, "packs_info_more", :duration => 0.4) %>
- </span>
- </p>
- <div id="packs_info_more" style="margin-left: 36px; font-size: 93%; color: #333333; display: none;">
- <p>
- Packs allow you to <font style="color: black; font-weight: bolder;">collect different items</font> together,
- like you might with a "wish list" or "shopping basket".
- You do this by <font style="color: black; font-weight: bolder;">linking</font> to different things.
- </p>
- <p>
- You can link to internal things (such as workflows, files and even other packs) as well as link to things outside of <%= Conf.sitename %>.
- </p>
- <p>
- Your packs can then be shared, tagged, discovered and discussed easily on <%= Conf.sitename %>.
- </p>
- <center><%= image_tag 'packs.png' -%></center>
- </div>
-</div>
<h1><%= context_prefix(@context) -%> Packs</h1>
<%= render :partial => "content/index" -%>
Modified: branches/ro/config/environment.rb (3784 => 3785)
--- branches/ro/config/environment.rb 2013-11-13 12:10:29 UTC (rev 3784)
+++ branches/ro/config/environment.rb 2013-11-13 13:33:26 UTC (rev 3785)
@@ -66,6 +66,7 @@
Mime::Type.register "application/whip-archive", :whip
Mime::Type.register "application/rdf+xml", :rdf
+Mime::Type.register "application/zip", :zip
# Include your application configuration below
Copied: branches/ro/config/initializers/namespaces.rb (from rev 3780, branches/packs/config/initializers/namespaces.rb) (0 => 3785)
--- branches/ro/config/initializers/namespaces.rb (rev 0)
+++ branches/ro/config/initializers/namespaces.rb 2013-11-13 13:33:26 UTC (rev 3785)
@@ -0,0 +1,39 @@
+require 'rdf'
+
+class AO < RDF::Vocabulary("http://purl.org/ao/")
+ # Declaring these might not be necessary
+ property :Annotation
+ property :body
+ property :annotatesResource
+end
+
+class ORE < RDF::Vocabulary("http://www.openarchives.org/ore/terms/")
+ property :Aggregation
+ property :AggregatedResource
+ property :Proxy
+ property :aggregates
+ property :proxyFor
+ property :proxyIn
+ property :isDescribedBy
+end
+
+class RO < RDF::Vocabulary("http://purl.org/wf4ever/ro#")
+ property :ResearchObject
+ property :AggregatedAnnotation
+ property :annotatesAggregatedResource
+ property :FolderEntry
+ property :Folder
+ property :Resource
+ property :entryName
+end
+
+class ROEVO < RDF::Vocabulary("http://purl.org/wf4ever/roevo#")
+ property :LiveRO
+end
+
+class ROTERMS < RDF::Vocabulary("http://ro.example.org/ro/terms/")
+ property :note
+ property :resource
+ property :defaultBase
+end
+
Copied: branches/ro/db/migrate/20130520145852_add_size_to_content_blobs.rb (from rev 3780, branches/packs/db/migrate/20130520145852_add_size_to_content_blobs.rb) (0 => 3785)
--- branches/ro/db/migrate/20130520145852_add_size_to_content_blobs.rb (rev 0)
+++ branches/ro/db/migrate/20130520145852_add_size_to_content_blobs.rb 2013-11-13 13:33:26 UTC (rev 3785)
@@ -0,0 +1,10 @@
+class AddSizeToContentBlobs < ActiveRecord::Migration
+ def self.up
+ add_column :content_blobs, :size, :integer
+ execute "UPDATE content_blobs SET size = LENGTH(data);"
+ end
+
+ def self.down
+ drop_column :content_blobs, :size
+ end
+end
Copied: branches/ro/db/migrate/20130520145900_create_research_objects.rb (from rev 3780, branches/packs/db/migrate/20130520145900_create_research_objects.rb) (0 => 3785)
--- branches/ro/db/migrate/20130520145900_create_research_objects.rb (rev 0)
+++ branches/ro/db/migrate/20130520145900_create_research_objects.rb 2013-11-13 13:33:26 UTC (rev 3785)
@@ -0,0 +1,64 @@
+# myExperiment: db/migrate/20130520145900_create_research_objects.rb
+#
+# Copyright (c) 2007-2013 The University of Manchester, the University of
+# Oxford, and the University of Southampton. See license.txt for details.
+
+class CreateResearchObjects < ActiveRecord::Migration
+ def self.up
+
+ create_table "research_objects", :force => true do |t|
+ t.string "context_type"
+ t.integer "context_id"
+ t.string "slug"
+ t.integer "version"
+ t.string "version_type"
+ t.integer "user_id"
+
+ t.timestamps
+ end
+
+ create_table "resources" do |t|
+ t.integer "research_object_id"
+ t.string "context_type"
+ t.integer "context_id"
+ t.integer "content_blob_id"
+ t.string "sha1", :limit => 40
+ t.integer "size"
+ t.string "content_type"
+ t.text "path"
+ t.string "entry_name"
+ t.string "creator_uri"
+ t.string "uuid", :limit => 36
+ t.string "proxy_in_path"
+ t.string "proxy_for_path"
+ t.string "ao_body_path"
+ t.string "resource_map_path"
+ t.string "aggregated_by_path"
+
+ t.boolean "is_resource", :default => false
+ t.boolean "is_aggregated", :default => false
+ t.boolean "is_proxy", :default => false
+ t.boolean "is_annotation", :default => false
+ t.boolean "is_resource_map", :default => false
+ t.boolean "is_folder", :default => false
+ t.boolean "is_folder_entry", :default => false
+ t.boolean "is_root_folder", :default => false
+
+ t.timestamps
+ end
+
+ create_table "annotation_resources" do |t|
+ t.integer "research_object_id"
+ t.integer "annotation_id"
+ t.string "resource_path"
+ end
+
+ end
+
+ def self.down
+ drop_table :research_objects
+ drop_table :resources
+ drop_table :annotation_resources
+ end
+
+end
Modified: branches/ro/lib/authorization.rb (3784 => 3785)
--- branches/ro/lib/authorization.rb 2013-11-13 12:10:29 UTC (rev 3784)
+++ branches/ro/lib/authorization.rb 2013-11-13 13:33:26 UTC (rev 3785)
@@ -345,6 +345,81 @@
end
+ when "ResearchObject"
+
+ case action
+
+ when "create"
+
+ # Only authenticated users can create research objects
+
+ return !user.nil?
+
+ when "view"
+
+ # If the research object is connected to a contribution then defer
+ # authorization to it.
+
+ return Authorization.check('view', object.context, user) if object.context
+
+ # Since there is no context it is visible to all.
+
+ return true
+
+ when "edit"
+
+ # If the research object is connected to a contribution then defer
+ # authorization to it.
+
+ return Authorization.check('edit', object.context. user) if object.context
+
+ # Since there is no context, only the owner can edit it.
+
+ return object.user == user
+
+ when "destroy"
+
+ # If the research object is connected to a contribution then
+ # disallow deletion as this is only performed when the contribution
+ # is deleted.
+
+ return false if object.context
+
+ # Since there is no context, only the owner can delete it.
+
+ return object.user == user
+
+ end
+
+ when "Resource"
+
+ case action
+
+ when "create"
+
+ # Only users that can edit the context can create RO resources
+ return Authorization.check('edit', context, user)
+
+ when "view"
+
+ # You can only view a resource if you can view the context
+ return false unless Authorization.check('view', object.research_object.context, user)
+
+ # In addition to the above, you must be able to view the
+ # contributable if it is local to myExperiment
+
+ if object.context
+ return Authorization.check('view', object.context, user)
+ end
+
+ return true
+
+ when "destroy"
+
+ # Only users that can edit the context can delete RO resources
+ return Authorization.check('edit', object.research_object.context, user)
+ end
+
when "Message"
case action
when "view"
@@ -493,5 +568,17 @@
scope
end
+
+ # This function calculates which agents are authorized to perform a given
+ # action on a given object. E.g. "Who can edit this workflow"?
+
+ def self.authorized_for_object(action, object)
+ case action
+ when :view, :download, :edit
+ [object.contributor] + Permission.all(:conditions => { action ="" true, :policy_id => object.contribution.policy_id } ).map { |p| p.contributor }
+ else
+ raise "Unknown action: #{action}"
+ end
+ end
end
Copied: branches/ro/test/functional/research_objects_test.rb (from rev 3780, branches/packs/test/functional/research_objects_test.rb) (0 => 3785)
--- branches/ro/test/functional/research_objects_test.rb (rev 0)
+++ branches/ro/test/functional/research_objects_test.rb 2013-11-13 13:33:26 UTC (rev 3785)
@@ -0,0 +1,430 @@
+# myExperiment: test/functional/research_objects_test.rb
+#
+# Copyright (c) 2007-2013 The University of Manchester, the University of
+# Oxford, and the University of Southampton. See license.txt for details.
+
+require_relative '../test_helper'
+
+class ResearchObjectsTest < ActionController::IntegrationTest
+
+ include ResearchObjectsHelper
+
+ fixtures :all
+
+ def setup
+ host!("test.host")
+ end
+
+ test "creation and deletion of research objects" do
+
+ ros_uri = research_objects_url
+ ro_uri = research_object_url('test_ro') + "/"
+ manifest_uri = ro_uri + ResearchObject::MANIFEST_PATH
+
+ # Test that the index of research objects equals the test packs
+
+ get research_objects_path
+
+ fixture_ros =
+ "http://test.host/rodl/ROs/Pack1/\n" +
+ "http://test.host/rodl/ROs/Pack2/\n" +
+ "http://test.host/rodl/ROs/Pack3/\n" +
+ "http://test.host/rodl/ROs/Pack4/\n" +
+ "http://test.host/rodl/ROs/Pack5/\n"
+
+ assert_response :ok
+ assert_equal fixture_ros, @response.body
+ assert_equal "text/uri-list", @response.content_type
+
+ # Create a research object.
+
+ post research_objects_path, {}, { "HTTP_SLUG" => "test_ro" }
+
+ assert_response :created
+ assert_equal ro_uri, @response.headers["Location"]
+
+ # Test that you can't create another RO at the same location
+
+ post research_objects_path, {}, { "HTTP_SLUG" => "test_ro" }
+
+ assert_response :conflict
+
+ # Test that the index now contains the new research object.
+
+ get research_objects_path
+
+ assert_response :ok
+ assert_equal "#{fixture_ros}#{ro_uri}\n", @response.body
+ assert_equal "text/uri-list", @response.content_type
+
+ # Test the manifest redirection.
+
+ get ro_uri, {}, { "HTTP_ACCEPT" => "application/rdf+xml" }
+
+ assert_response :see_other
+ assert_equal manifest_uri, @response.headers["Location"]
+
+ # Get the manifest.
+
+ get manifest_uri
+
+ assert_response :ok
+
+ # Delete the test RO.
+
+ delete ro_uri
+
+ assert_response :no_content
+
+ # Try to get the deleted research object.
+
+ get ro_uri
+
+ assert_response :not_found
+
+ # Try to get the manifest of the deleted research object.
+
+ get manifest_uri
+
+ assert_response :not_found
+
+ # Delete the non-existent RO.
+
+ delete ro_uri
+
+ assert_response :not_found
+
+ end
+
+ test "manifest statements of a newly created research object" do
+
+ # Create a new research object and get the manifest
+
+ post research_objects_path, {}, { "HTTP_SLUG" => "test_ro" }
+
+ assert_response :created
+ assert_equal research_object_url('test_ro') + "/", @response.headers["Location"]
+
+ get research_object_resource_path('test_ro', '.ro/manifest.rdf')
+
+ assert_response :ok
+
+ # Parse the graph and ensure that it has statements
+
+ graph = RDF::Graph.new
+ graph << RDF::Reader.for(:rdfxml).new(@response.body)
+
+ ro_uri = RDF::URI(research_object_url('test_ro')) + "/"
+
+ assert_operator 0, :<, graph.count
+
+ # Test that the creator of the RO is correct
+
+ assert_equal user_url(users(:john)), graph.query([ro_uri, RDF::DC.creator, nil]).first_object
+
+ # Test that the date has the correct datatype and within reason
+
+ assert_equal RDF::XSD.dateTime, graph.query([ro_uri, RDF::DC.created, nil]).first_object.datatype
+
+ created = DateTime.parse(graph.query([ro_uri, RDF::DC.created, nil]).first_object.value)
+
+ assert_operator DateTime.parse("2000-01-01"), :<, created
+ assert_operator DateTime.parse("3000-01-01"), :>, created
+
+ end
+
+ test "automatic generation of proxies for generic resources" do
+
+ # Create the test RO.
+
+ post research_objects_path, {}, { "HTTP_SLUG" => "test_ro" }
+ assert_response :created
+
+ # Create the resource and check the response is the proxy
+
+ post research_object_path('test_ro'), "Hello World.\n", { "HTTP_SLUG" => "test_resource.txt", "CONTENT_TYPE" => "text/plain" }
+
+ assert_response :created
+ assert_equal "application/rdf+xml", @response.content_type.to_s
+
+ links = parse_links(@response.headers)
+
+ resource_uri = links["http://www.openarchives.org/ore/terms/proxyFor"].first
+
+ assert_equal research_object_resource_url("test_ro", "test_resource.txt"), resource_uri
+
+ graph = RDF::Graph.new
+ graph << RDF::Reader.for(:rdfxml).new(@response.body)
+
+ assert_equal 1, graph.query([nil, RDF.type, RDF::URI("http://www.openarchives.org/ore/terms/Proxy")]).count
+
+ proxy_uri = graph.query([nil, RDF::URI("http://www.openarchives.org/ore/terms/proxyFor"), RDF::URI(resource_uri)]).first_subject.to_s
+
+ # Confirm that the resource was created.
+
+ get resource_uri
+
+ assert_response :ok
+ assert_equal "Hello World.\n", @response.body
+ assert_equal "text/plain", @response.content_type.to_s
+
+ # Confirm that a proxy was created automatically and that the proxy graph is correct.
+
+ get proxy_uri
+
+ assert_response :see_other
+
+ graph2 = RDF::Graph.new
+ graph2 << RDF::Reader.for(:rdfxml).new(@response.body)
+
+ assert graph2.query([
+ RDF::URI(proxy_uri),
+ RDF::URI("http://www.openarchives.org/ore/terms/proxyFor"),
+ RDF::URI(resource_uri)
+ ])
+
+ assert graph2.query([
+ RDF::URI(proxy_uri),
+ RDF.type,
+ RDF::URI("http://www.openarchives.org/ore/terms/Proxy")
+ ])
+
+ assert graph2.query([
+ RDF::URI(proxy_uri),
+ RDF::URI("http://www.openarchives.org/ore/terms/proxyIn"),
+ RDF::URI(research_object_url("test_ro") + "/")
+ ])
+ end
+
+ test "disallow overwriting of the manifest" do
+
+ # Create the test RO.
+
+ post research_objects_path, {}, { "HTTP_SLUG" => "test_ro" }
+ assert_response :created
+
+ # Create a resource and try to overwrite the manifest
+
+ post research_object_path('test_ro'), "Hello World.\n", { "HTTP_SLUG" => ResearchObject::MANIFEST_PATH, "CONTENT_TYPE" => "text/plain" }
+ assert_response :forbidden
+ end
+
+ test "creation of a proxy for an external resource" do
+
+ # Create the test RO.
+
+ post research_objects_path, {}, { "HTTP_SLUG" => "test_ro" }
+
+ assert_response :created
+
+ ro_uri = @response.location
+
+ # Create a proxy by providing a proxy description with an ore:proxyFor term.
+
+ external_resource_uri = "http://elsewhere.example.com/external_resource.txt"
+
+ test_proxy_description = %Q(
+ <rdf:RDF
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:ore="http://www.openarchives.org/ore/terms/" >
+
+ <rdf:Description>
+ <ore:proxyFor rdf:resource="#{external_resource_uri}"/>
+ <rdf:type rdf:resource="http://www.openarchives.org/ore/terms/Proxy"/>
+ </rdf:Description>
+
+ </rdf:RDF>
+ )
+
+ post(research_object_path('test_ro'),
+ test_proxy_description,
+ { "CONTENT_TYPE" => "application/vnd.wf4ever.proxy" })
+
+ links = parse_links(@response.headers)
+
+ proxy_for_uri = links["http://www.openarchives.org/ore/terms/proxyFor"].first
+ proxy_uri = @response.location
+
+ graph = load_graph(@response.body)
+
+ # Check ore:proxyFor link
+
+ assert_equal(external_resource_uri, proxy_for_uri)
+
+ # Check the returned RDF for the appropriate proxy statements
+
+ assert_equal 1, graph.query([RDF::URI(proxy_uri), RDF.type, ORE.Proxy]).count
+ assert_equal 1, graph.query([RDF::URI(proxy_uri), ORE.proxyFor, RDF::URI(proxy_for_uri)]).count
+ assert_equal 1, graph.query([RDF::URI(proxy_uri), ORE.proxyIn, RDF::URI(ro_uri)]).count
+
+ # Check the manifest for the appropriate proxy statements
+
+ get ro_uri + ResearchObject::MANIFEST_PATH
+ assert_response :ok
+
+ manifest_graph = load_graph(@response.body)
+
+ assert_equal 1, manifest_graph.query([RDF::URI(proxy_uri), RDF.type, ORE.Proxy]).count
+ assert_equal 1, manifest_graph.query([RDF::URI(proxy_uri), ORE.proxyFor, RDF::URI(proxy_for_uri)]).count
+ assert_equal 1, manifest_graph.query([RDF::URI(proxy_uri), ORE.proxyIn, RDF::URI(ro_uri)]).count
+ end
+
+ test "creation of a folder" do
+
+ # Create the test RO.
+
+ post research_objects_path, {}, { "HTTP_SLUG" => "test_ro" }
+
+ assert_response :created
+
+ ro_uri = @response.location
+
+ # Create the folder
+
+ test_folder_description = %Q(
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" >
+ <rdf:Description>
+ <rdf:type rdf:resource="http://purl.org/wf4ever/ro#Folder"/>
+ </rdf:Description>
+ </rdf:RDF>
+ )
+
+ test_folder_path = "test_folder/"
+
+ test_folder_uri = RDF::URI(ro_uri) + test_folder_path
+
+ post(research_object_path('test_ro'),
+ test_folder_description,
+ { "CONTENT_TYPE" => "application/vnd.wf4ever.folder",
+ "SLUG" => test_folder_path })
+
+ assert_response :created
+
+ links = parse_links(@response.headers)
+
+ proxy_for_uri = links["http://www.openarchives.org/ore/terms/proxyFor"].first
+ is_described_by = links["http://www.openarchives.org/ore/terms/isDescribedBy"].first
+
+ proxy_uri = @response.location
+
+ graph = load_graph(@response.body)
+
+ resource_map_uri = graph.query([test_folder_uri, ORE.isDescribedBy, nil]).first_object
+
+ assert resource_map_uri
+
+ # Assert the link and location headers match up with the response RDF
+
+ assert_equal 1, graph.query([RDF::URI(proxy_uri), RDF.type, ORE.Proxy]).count
+ assert_equal 1, graph.query([RDF::URI(proxy_uri), ORE.proxyFor, test_folder_uri]).count
+ assert_equal 1, graph.query([resource_map_uri, ORE.describes, test_folder_uri]).count
+
+ # Assert the statements in the manifest.
+
+ get ro_uri + ResearchObject::MANIFEST_PATH
+ assert_response :ok
+
+ manifest_graph = load_graph(@response.body)
+
+ assert_equal 1, manifest_graph.query([RDF::URI(proxy_uri), RDF.type, ORE.Proxy]).count
+ assert_equal 1, manifest_graph.query([RDF::URI(proxy_uri), ORE.proxyFor, test_folder_uri]).count
+ assert_equal 1, manifest_graph.query([resource_map_uri, ORE.describes, test_folder_uri]).count
+ end
+
+ test "creation of a folder entry" do
+
+ # Create the test RO.
+
+ post research_objects_path, {}, { "HTTP_SLUG" => "test_ro" }
+
+ assert_response :created
+
+ ro_uri = @response.location
+
+ # Create the folder
+
+ test_folder_description = %Q(
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" >
+ <rdf:Description>
+ <rdf:type rdf:resource="http://purl.org/wf4ever/ro#Folder"/>
+ </rdf:Description>
+ </rdf:RDF>
+ )
+
+ test_folder_path = "test_folder/"
+
+ test_folder_uri = RDF::URI(ro_uri) + test_folder_path
+
+ post(research_object_path('test_ro'),
+ test_folder_description,
+ { "CONTENT_TYPE" => "application/vnd.wf4ever.folder",
+ "SLUG" => test_folder_path })
+
+ assert_response :created
+
+ # Create the folder entry
+
+ test_folder_description = %Q(
+ <rdf:RDF
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:ore="http://www.openarchives.org/ore/terms/" >
+
+ <rdf:Description>
+ <rdf:type rdf:resource="http://purl.org/wf4ever/ro#FolderEntry"/>
+ <ore:proxyFor rdf:resource="#{test_folder_uri}"/>
+ </rdf:Description>
+ </rdf:RDF>
+ )
+
+
+# proxy_for_uri = links["http://www.openarchives.org/ore/terms/proxyFor"].first
+# is_described_by = links["http://www.openarchives.org/ore/terms/isDescribedBy"].first
+
+# proxy_uri = @response.location
+
+# graph = load_graph(@response.body)
+
+# resource_map_uri = graph.query([test_folder_uri, ORE.isDescribedBy, nil]).first_object
+
+# assert resource_map_uri
+
+# # Assert the link and location headers match up with the response RDF
+
+# assert_equal 1, graph.query([RDF::URI(proxy_uri), RDF.type, ORE.Proxy]).count
+# assert_equal 1, graph.query([RDF::URI(proxy_uri), ORE.proxyFor, test_folder_uri]).count
+# assert_equal 1, graph.query([resource_map_uri, ORE.describes, test_folder_uri]).count
+
+# # Assert the statements in the manifest.
+
+# get ro_uri + ResearchObject::MANIFEST_PATH
+# assert_response :ok
+
+# manifest_graph = load_graph(@response.body)
+
+# assert_equal 1, manifest_graph.query([RDF::URI(proxy_uri), RDF.type, ORE.Proxy]).count
+# assert_equal 1, manifest_graph.query([RDF::URI(proxy_uri), ORE.proxyFor, test_folder_uri]).count
+# assert_equal 1, manifest_graph.query([resource_map_uri, ORE.describes, test_folder_uri]).count
+ end
+
+ def parse_links(headers)
+
+ links = {}
+
+ link_headers = headers["Link"]
+
+ return {} if link_headers.nil?
+
+ if link_headers
+ link_headers.each do |link|
+ matches = link.strip.match(/<([^>]*)>\s*;.*rel\s*=\s*"?([^;"]*)"?/)
+ if matches
+ links[matches[2]] ||= []
+ links[matches[2]] << matches[1]
+ end
+ end
+ end
+
+ links
+ end
+
+end