Diff
Added: branches/packs/app/controllers/research_objects_controller.rb (0 => 3559)
--- branches/packs/app/controllers/research_objects_controller.rb (rev 0)
+++ branches/packs/app/controllers/research_objects_controller.rb 2013-05-22 13:45:00 UTC (rev 3559)
@@ -0,0 +1,81 @@
+require 'securerandom'
+
+class ResearchObjectsController < ActionController::Base
+
+ # GET /rodl
+ def index
+
+ uri_list = ""
+
+ ResearchObject.all.each do |ro|
+ uri_list << "#{research_object_url(ro.slug)}/\n"
+ end
+
+ send_data(uri_list, :type => 'text/uri-list')
+ end
+
+ # GET /rodl/:id
+ def show
+
+ slug = params[:id]
+ slug = slug[0..-2] if slug.ends_with?("/")
+
+ ro = ResearchObject.find_by_slug_and_version(slug, nil)
+
+ if (ro)
+ redirect_to research_object_url(slug) + "/" + ResearchObject::MANIFEST_PATH, :status => 303
+ else
+ render :text => "Research Object not found", :status => 404
+ end
+ end
+
+ # POST /rodl
+ def create
+
+ current_user = User.find(1) # FIXME - hardcoded
+
+ slug = request.headers["Slug"]
+
+ # Remove trailing slash if given.
+
+ slug = slug[0..-2] if slug.ends_with?("/")
+
+ # If a research object exists with the slug then respond with 409 Conflict.
+
+ if ResearchObject.find_by_slug_and_version(slug, nil)
+ render :nothing => true, :status => 409
+ return
+ end
+
+ # Create the research object with a blank manifest. The blank manifest is
+ # created so that when the manifest is aggregated it contains the
+ # description of the manifest.
+
+ ro_uri = research_object_url(slug) + "/"
+
+ ro = ResearchObject.create(:slug => slug, :user => current_user)
+
+ response.headers["Location"] = ro_uri
+
+ send_data(ro.manifest_resource.data, :type => "application/rdf+xml", :filename => ResearchObject::MANIFEST_PATH, :status => 201)
+ end
+
+ # DELETE /rodl/:id
+ def destroy
+
+ ro = ResearchObject.find_by_slug_and_version(params[:id], nil)
+
+ if ro
+ ro.destroy
+ render :nothing => true, :status => 204
+ else
+ render :text => "Research Object not found", :status => 404
+ end
+ end
+
+ # PUT /rodl/:id
+ def update
+ end
+
+end
+
Added: branches/packs/app/controllers/resources_controller.rb (0 => 3559)
--- branches/packs/app/controllers/resources_controller.rb (rev 0)
+++ branches/packs/app/controllers/resources_controller.rb 2013-05-22 13:45:00 UTC (rev 3559)
@@ -0,0 +1,150 @@
+require 'securerandom'
+
+class ResourcesController < ActionController::Base
+
+ 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
+
+ resource = ro.resources.find_by_path(params[:id])
+
+ unless resource
+ render :text => "Resource not found", :status => :not_found
+ return
+ end
+
+ send_data(resource.data, :type => resource.content_type)
+ end
+
+ def post
+
+ current_user = User.find(1) # FIXME - hardcoded
+
+ 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 << research_object.resources.find_by_path(change).description
+ end
+
+ body = pretty_rdf_xml(RDF::Writer.for(:rdfxml).buffer { |writer| writer << graph } )
+
+ send_data body, :type => 'application/rdf+xml', :filename => filename, :status => :created
+ else
+ render :status => status, :text => reason
+ end
+ end
+
+ def delete
+
+ current_user = User.find(1) # FIXME - hardcoded
+
+ 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
Added: branches/packs/app/helpers/research_objects_helper.rb (0 => 3559)
--- branches/packs/app/helpers/research_objects_helper.rb (rev 0)
+++ branches/packs/app/helpers/research_objects_helper.rb 2013-05-22 13:45:00 UTC (rev 3559)
@@ -0,0 +1,172 @@
+
+require 'xml/libxml'
+
+module ResearchObjectsHelper
+
+ NAMESPACES = {
+ "http://purl.org/dc/terms/" => "dct",
+ "http://www.openarchives.org/ore/terms/" => "ore",
+ "http://purl.org/ao/" => "ao",
+ "http://purl.org/wf4ever/ro#" => "ro"
+ }
+
+ 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|
+
+ resource = description.attributes["about"]
+
+ if descriptions[resource]
+
+ if descriptions[resource].children.last.to_s == "\n "
+ descriptions[resource].children.last.remove!
+ descriptions[resource].children << "\n"
+ end
+
+ description.each do |object|
+ if object.element?
+ descriptions[resource] << "\n "
+ descriptions[resource] << object
+ descriptions[resource] << "\n "
+ end
+ end
+
+ description.prev.remove!
+ description.remove!
+ else
+ descriptions[resource] = 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 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}"
+ else
+ SecureRandom.uuid
+ end
+ end
+
+ def load_graph(content)
+ graph = RDF::Graph.new # FIXME - this should support more than just rdf+xml
+ graph << RDF::Reader.for(:rdfxml).new(content) if content
+ graph
+ end
+
+ def create_rdf_xml(&blk)
+ graph = RDF::Graph.new
+ yield(graph)
+ pretty_rdf_xml(RDF::Writer.for(:rdfxml).buffer { |writer| writer << graph })
+ end
+
+end
+
Added: branches/packs/app/models/annotation_resource.rb (0 => 3559)
--- branches/packs/app/models/annotation_resource.rb (rev 0)
+++ branches/packs/app/models/annotation_resource.rb 2013-05-22 13:45:00 UTC (rev 3559)
@@ -0,0 +1,5 @@
+
+class AnnotationResource < ActiveRecord::Base
+ belongs_to :annotation, :class_name => "Resource"
+end
+
Added: branches/packs/app/models/research_object.rb (0 => 3559)
--- branches/packs/app/models/research_object.rb (rev 0)
+++ branches/packs/app/models/research_object.rb 2013-05-22 13:45:00 UTC (rev 3559)
@@ -0,0 +1,469 @@
+require 'rdf'
+require 'rdf/raptor'
+
+class ResearchObject < ActiveRecord::Base
+
+ MANIFEST_PATH = ".ro/manifest.rdf"
+
+ include ResearchObjectsHelper
+
+ after_create :create_manifest
+
+ belongs_to :user
+
+ has_many :resources, :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, RDF::URI("http://purl.org/wf4ever/ro#ResearchObject")]
+ graph << [ro_uri, RDF.type, RDF::URI("http://www.openarchives.org/ore/terms/Aggregation")]
+ graph << [ro_uri, RDF::URI("http://www.openarchives.org/ore/terms/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]
+
+ resources.each do |resource|
+
+ if resource.is_aggregated
+ graph << [ro_uri, RDF::URI("http://www.openarchives.org/ore/terms/aggregates"), RDF::URI(resource.resource_uri)]
+ end
+
+ graph << resource.description
+ end
+
+ graph
+ end
+
+ def update_manifest!
+
+ resources.reload
+
+ manifest_body = pretty_rdf_xml(RDF::Writer.for(:rdfxml).buffer { |writer| writer << description })
+
+ new_or_update_resource(
+ :slug => MANIFEST_PATH,
+ :content_type => "application/rdf+xml",
+ :data ="" manifest_body,
+ :force_write => true)
+ end
+
+ def manifest_resource
+ resources.find(:first, :conditions => { :path => MANIFEST_PATH })
+ end
+
+ def new_or_update_resource(opts = {})
+
+ changed_descriptions = []
+ links = []
+ location = nil
+
+ content_type = opts[:content_type]
+ slug = opts[:slug]
+ path = opts[:slug]
+ user_uri = opts[:user_uri]
+ data = ""
+ request_links = opts[:links] || {}
+
+ if slug == ResearchObject::MANIFEST_PATH
+
+ path = calculate_path(slug, content_type, request_links)
+
+ return [:forbidden, "Cannot overwrite the manifest", nil, []] unless opts[:force_write]
+
+ resource = resources.find_by_path(path)
+ resource = resources.new(:path => path) unless resource
+
+ resource.content_blob.destroy if resource.content_blob
+
+ resource.data = "" # FIXME - to be removed
+ resource.content_blob = ContentBlob.new(:data ="" data)
+ resource.creator_uri = user_uri
+ resource.content_type = content_type
+ resource.name = path.split("/").last if path
+
+ resource.is_resource = false
+ resource.is_aggregated = false
+ resource.is_proxy = false
+ resource.is_annotation = false
+ resource.is_folder = false
+
+
+ resource.save
+
+ changed_descriptions << resource.path
+
+ elsif content_type == "application/vnd.wf4ever.proxy"
+
+ path = calculate_path(slug, content_type, request_links)
+
+ # Obtain information to create the 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
+
+ # Contruct the proxy.
+
+ ro_uri = RDF::URI(uri)
+ proxy_uri = ro_uri + path
+ proxy_in_uri = ro_uri
+ proxy_for_path = relative_uri(proxy_for_uri, uri)
+ proxy_in_path = relative_uri(proxy_in_uri, uri)
+
+ proxy_body = create_rdf_xml do |graph|
+ graph << [proxy_uri, RDF.type, ORE.Proxy]
+ graph << [proxy_uri, ORE.proxyIn, proxy_in_uri]
+ graph << [proxy_uri, ORE.proxyFor, proxy_for_uri]
+ end
+
+ proxy = resources.find_by_path(path)
+ proxy = resources.new(:path => path) unless proxy
+
+ proxy.content_blob.destroy if proxy.content_blob
+
+ proxy.is_proxy = true
+ proxy.proxy_for_path = proxy_for_path
+ proxy.proxy_in_path = proxy_in_path
+ proxy.data = "" # FIXME - to be removed
+ proxy.content_blob = ContentBlob.new(:data ="" proxy_body)
+ proxy.creator_uri = user_uri
+ proxy.content_type = content_type
+ proxy.name = path.split("/").last
+
+ proxy.save
+
+ location = proxy.uri
+ links << { :link => proxy_for_uri, :rel => ORE.proxyFor }
+
+ changed_descriptions << proxy.path
+
+ elsif content_type == "application/vnd.wf4ever.annotation"
+
+ path = calculate_path(slug, content_type, request_links)
+
+ resource = resources.find_by_path(path)
+ resource = resources.new(:path => path) unless resource
+
+ resource.content_blob.destroy if resource.content_blob
+
+ resource.data = "" # FIXME - to be removed
+ resource.content_blob = ContentBlob.new(:data ="" data)
+ resource.creator_uri = user_uri
+ resource.content_type = content_type
+ resource.name = path.split("/").last if path
+
+ # Creation of an annotation stub directly
+
+ resource.is_resource = false
+ resource.is_aggregated = true
+ resource.is_proxy = false
+ resource.is_annotation = true
+ resource.is_folder = false
+
+ graph = load_graph(resource.data)
+
+ aggregated_annotations = graph.query([nil, RDF.type, RDF::URI("http://purl.org/wf4ever/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, RDF::URI("http://purl.org/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
+
+ ao_body_uri = ao_body_statements.first_object.to_s
+
+ resource.ao_body_path = relative_uri(ao_body_uri, uri)
+
+ annotated_resources_statements = graph.query([aggregated_annotation, RDF::URI("http://purl.org/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
+
+ annotated_resources_statements.each do |annotated_resource|
+ resource.annotation_resources.build(:resource_uri => annotated_resource.object.to_s)
+ links << { :link => annotated_resource.object.to_s, :rel => "http://purl.org/ao/annotatesResource" }
+ end
+
+ links << { :link => ao_body_uri, :rel => "http://purl.org/ao/body" }
+
+
+ resource.save
+
+ changed_descriptions << resource.path
+
+ elsif content_type == "application/vnd.wf4ever.folder"
+
+ path = calculate_path(slug, content_type, request_links)
+
+ resource = resources.find_by_path(path)
+ resource = resources.new(:path => path) unless resource
+
+ resource.content_blob.destroy if resource.content_blob
+
+ resource.data = "" # FIXME - to be removed
+ resource.content_blob = ContentBlob.new(:data ="" data)
+ resource.creator_uri = user_uri
+ resource.content_type = content_type
+ resource.name = path.split("/").last if path
+
+
+ resource.is_resource = false
+ resource.is_aggregated = true
+ resource.is_proxy = false
+ resource.is_annotation = false
+ resource.is_folder = true
+
+ # Create a resource map for this folder
+
+ resource_uri = resource.resource_uri.to_s
+
+ resource_map_path = ".ro/resource_maps/#{SecureRandom.uuid}"
+ resource_map_uri = uri + resource_map_path
+
+ resource_map_graph = RDF::Graph.new
+ resource_map_graph << [RDF::URI(resource_map_uri), RDF.type, RDF::URI("http://www.openarchives.org/ore/terms/ResourceMap")]
+ resource_map_graph << [RDF::URI(resource_map_uri), RDF::URI("http://www.openarchives.org/ore/terms/describes"), RDF::URI(resource_uri)]
+
+ resource_map_body = pretty_rdf_xml(RDF::Writer.for(:rdfxml).buffer { |writer| writer << resource_map_graph } )
+
+ # FIXME - this should be a recursive call
+
+ resource_map_attributes = {
+ :data ="" resource_map_body,
+ :content_blob => ContentBlob.new(:data ="" resource_map_body),
+ :creator_uri => user_uri,
+ :content_type => 'application/vnd.wf4ever.folder',
+ :is_resource_map => true,
+ :path => resource_map_path
+ }
+
+ resources.create(resource_map_attributes)
+
+ resource.resource_map_path = resource_map_path
+
+ links << { :link => resource_map_uri, :rel => "http://www.openarchives.org/ore/terms/isDescribedBy" }
+ links << { :link => resource_uri, :rel => "http://www.openarchives.org/ore/terms/proxyFor" }
+
+ changed_descriptions << resource_map_path
+
+
+ resource.save
+
+ changed_descriptions << resource.path
+
+ elsif content_type == "application/vnd.wf4ever.folderentry"
+
+ path = calculate_path(nil, '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
+
+ proxy_for_path = relative_uri(proxy_for_uri, uri)
+ proxy_in_path = opts[:path]
+ proxy_in_uri = uri + proxy_in_path
+
+ # Create the folder entry
+
+ folder_entry = resources.new(:path => path)
+
+ folder_entry_uri = RDF::URI(folder_entry.uri)
+
+ folder_entry_body = create_rdf_xml do |graph|
+ graph << [folder_entry_uri, RDF.type, ORE.Proxy]
+ graph << [folder_entry_uri, RDF.type, RO.FolderEntry]
+ graph << [folder_entry_uri, ORE.proxyIn, RDF::URI(proxy_in_uri)]
+ graph << [folder_entry_uri, ORE.proxyFor, RDF::URI(proxy_for_uri)]
+ end
+
+ folder_entry.is_folder_entry = true
+ folder_entry.data = "" # FIXME - to be removed
+ folder_entry.content_blob = ContentBlob.new(:data ="" folder_entry_body)
+ folder_entry.proxy_in_path = proxy_in_path
+ folder_entry.proxy_for_path = proxy_for_path
+ folder_entry.content_type = content_type
+ folder_entry.creator_uri = user_uri
+ folder_entry.name = path.split("/").last if path
+
+ folder_entry.save
+
+ folder_entry.proxy_for.update_attribute(:aggregated_by_path, proxy_in_path)
+
+ location = folder_entry.uri
+
+ links << { :link => proxy_for_uri, :rel => "http://www.openarchives.org/ore/terms/proxyFor" }
+
+ elsif request_links["http://purl.org/ao/annotatesResource"]
+
+ path = calculate_path(nil, content_type)
+ ro_uri = RDF::URI(uri)
+ annotation_uri = ro_uri + path
+
+
+ # Create an annotation body using the provided graph
+
+ # Process ao:annotatesResource links by creating annotation stubs using the
+ # given resource as an ao:body.
+
+ ao_body_path = calculate_path(slug, content_type, request_links)
+
+ ao_body = resources.find_by_path(path)
+ ao_body = resources.new(:path => path) unless ao_body
+
+ ao_body.content_blob.destroy if ao_body.content_blob
+
+ ao_body.data = "" # FIXME - to be removed
+ ao_body.content_blob = ContentBlob.new(:data ="" data)
+ ao_body.creator_uri = user_uri
+ ao_body.content_type = content_type
+ ao_body.name = ao_body_path.split("/").last
+ ao_body.is_resource = true
+ ao_body.is_aggregated = true
+
+ ao_body.save
+ # FIXME - no proxy is created for this ao:body resource
+
+ changed_descriptions << ao_body.path
+
+ annotation_rdf = create_rdf_xml do |graph|
+ graph << [annotation_uri, RDF.type, RO.AggregatedAnnotation]
+ graph << [annotation_uri, RDF.type, AO.Annotation]
+ graph << [annotation_uri, AO.body, RDF::URI(ao_body.uri)]
+
+ request_links["http://purl.org/ao/annotatesResource"].each do |annotated_resource_uri|
+ graph << [annotation_uri, AO.annotatesResource, RDF::URI(annotated_resource_uri)]
+ end
+ end
+
+ annotation_stub = resources.new({
+ :creator_uri => user_uri,
+ :path => calculate_path(nil, 'application/vnd.wf4ever.annotation'),
+ :data ="" annotation_rdf, # FIXME
+ :content_blob => ContentBlob.new(:data ="" annotation_rdf),
+ :content_type => 'application/vnd.wf4ever.annotation',
+ :is_annotation => true,
+ :ao_body_path => ao_body.path
+ })
+
+ request_links["http://purl.org/ao/annotatesResource"].each do |annotated_resource_uri|
+ annotation_stub.annotation_resources.build(:resource_uri => relative_uri(annotated_resource_uri, uri))
+ links << { :link => annotated_resource_uri, :rel => "http://purl.org/ao/annotatesResource" }
+ end
+
+ annotation_stub.save
+
+ changed_descriptions << annotation_stub.path
+
+ links << { :link => annotation_stub.uri, :rel => "http://purl.org/ao/body" }
+
+ location = uri + annotation_stub.path
+
+ else
+
+ path = calculate_path(slug, content_type, request_links)
+
+ resource = resources.find_by_path(path)
+ resource = resources.new(:path => path) unless resource
+
+ resource.content_blob.destroy if resource.content_blob
+
+ resource.data = "" # FIXME - to be removed
+ resource.content_blob = ContentBlob.new(:data ="" data)
+ resource.creator_uri = user_uri
+ resource.content_type = content_type
+ resource.name = path.split("/").last
+ resource.is_resource = true
+ resource.is_aggregated = true
+
+ resource.save
+
+ changed_descriptions << resource.path
+ end
+
+ if resource && content_type != "application/vnd.wf4ever.proxy" && !resource.is_manifest? && !request_links["http://purl.org/ao/annotatesResource"]
+
+ resource_uri = resource.resource_uri.to_s
+
+ relative_resource_uri = relative_uri(resource_uri, uri)
+
+ proxy = resources.find(:first,
+ :conditions => { :content_type => 'application/vnd.wf4ever.proxy',
+ :proxy_in_path => '.',
+ :proxy_for_path => relative_resource_uri })
+
+ if proxy.nil?
+ proxy_slug = ".ro/proxies/#{SecureRandom.uuid}"
+ else
+ proxy_slug = proxy.path
+ end
+
+ proxy_body = pretty_rdf_xml(RDF::Writer.for(:rdfxml).buffer { |writer| writer << resource.generate_proxy(proxy_slug) } )
+
+ # FIXME - this should be a recursive call
+
+ proxy_attributes = {
+ :data ="" proxy_body,
+ :content_blob => ContentBlob.new(:data ="" proxy_body),
+ :proxy_in_path => '.',
+ :proxy_for_path => relative_resource_uri,
+ :creator_uri => user_uri,
+ :content_type => 'application/vnd.wf4ever.proxy',
+ :is_proxy => true,
+ :path => proxy_slug
+ }
+
+ if proxy.nil?
+ proxy = resources.create(proxy_attributes)
+ else
+ proxy.content_blob.destroy
+ proxy.update_attributes(proxy_attributes)
+ end
+
+ links << { :link => resource_uri, :rel => "http://www.openarchives.org/ore/terms/proxyFor" }
+ location = proxy.uri
+
+ changed_descriptions << proxy_slug
+ end
+
+ location ||= resource_uri
+
+ [:created, nil, location, links, path, changed_descriptions]
+ end
+
+private
+
+ def create_manifest
+
+ resources.create(:path => ResearchObject::MANIFEST_PATH,
+ :data ="" "Dummy content",
+ :content_type => 'application/rdf+xml')
+
+ update_manifest!
+ end
+end
Added: branches/packs/app/models/resource.rb (0 => 3559)
--- branches/packs/app/models/resource.rb (rev 0)
+++ branches/packs/app/models/resource.rb 2013-05-22 13:45:00 UTC (rev 3559)
@@ -0,0 +1,150 @@
+require 'securerandom'
+
+class Resource < ActiveRecord::Base
+
+ include ResearchObjectsHelper
+
+ belongs_to :research_object
+
+ belongs_to :content_blob, :dependent => :destroy
+
+ belongs_to :proxy_for, :primary_key => :path, :foreign_key => :proxy_for_path, :class_name => 'Resource'
+ has_one :proxy, :primary_key => :path, :foreign_key => :proxy_for_path, :class_name => 'Resource'
+
+ belongs_to :proxy_in, :primary_key => :path, :foreign_key => :proxy_in_path, :class_name => 'Resource'
+ has_many :proxies, :primary_key => :path, :foreign_key => :proxy_in_path, :class_name => 'Resource'
+
+ belongs_to :aggregated_by, :primary_key => :path, :foreign_key => :aggregated_by_path, :class_name => 'Resource'
+ has_many :aggregates, :primary_key => :path, :foreign_key => :aggregated_by_path, :class_name => 'Resource'
+
+ belongs_to :ao_body, :primary_key => :path, :foreign_key => :ao_body_path, :class_name => 'Resource'
+ has_one :ao_stub, :primary_key => :path, :foreign_key => :ao_body_path, :class_name => 'Resource'
+
+ belongs_to :resource_map, :primary_key => :path, :foreign_key => :resource_map_path, :class_name => 'Resource'
+ has_one :is_resource_map_to, :primary_key => :path, :foreign_key => :resource_map_path, :class_name => 'Resource'
+
+ 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
+ validates_presence_of :content_blob
+
+ 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, RDF::URI("http://purl.org/wf4ever/ro#Manifest")]
+ graph << [uri, RDF.type, RDF::URI("http://www.openarchives.org/ore/terms/ResourceMap")]
+ graph << [uri, RDF::URI("http://www.openarchives.org/ore/terms/describes"), ro_uri]
+ end
+
+ if is_resource
+ graph << [uri, RDF.type, RDF::URI("http://purl.org/wf4ever/ro#Resource")]
+ end
+
+ if is_aggregated
+ graph << [uri, RDF.type, RDF::URI("http://www.openarchives.org/ore/terms/AggregatedResource")]
+ end
+
+ if is_proxy
+ graph << [uri, RDF.type, RDF::URI("http://www.openarchives.org/ore/terms/Proxy")]
+ graph << [uri, RDF::URI("http://www.openarchives.org/ore/terms/proxyIn"), ro_uri.join(proxy_in_path)]
+ graph << [uri, RDF::URI("http://www.openarchives.org/ore/terms/proxyFor"), ro_uri.join(proxy_for_path)]
+ end
+
+ if is_annotation
+ graph << [uri, RDF.type, RDF::URI("http://purl.org/wf4ever/ro#AggregatedAnnotation")]
+ graph << [uri, RDF.type, RDF::URI("http://purl.org/ao/Annotation")]
+ graph << [uri, RDF::URI("http://purl.org/ao/body"), ro_uri.join(ao_body_path)]
+
+ annotation_resources.each do |resource|
+ graph << [uri, RDF::URI("http://purl.org/wf4ever/ro#annotatesAggregatedResource"), RDF::URI(resource.resource_uri)]
+ 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, RDF::DC.created, folder.created_at.to_datetime]
+ graph << [folder_uri, RDF::DC.creator, RDF::URI(folder.creator_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, RDF::URI("http://purl.org/wf4ever/ro#Resource")]
+ graph << [uri, RDF.type, RDF::URI("http://purl.org/wf4ever/ro#Folder")]
+ graph << [uri, RDF.type, RDF::URI("http://www.openarchives.org/ore/terms/AggregatedResource")]
+ graph << [uri, RDF.type, RDF::URI("http://www.openarchives.org/ore/terms/Aggregation")]
+
+ resource_map = Resource.find(:first, :conditions => { :path => resource_map_path })
+
+ graph << [uri, RDF::URI("http://www.openarchives.org/ore/terms/isDescribedBy"), ro_uri.join(resource_map_path)]
+ end
+
+ graph << [uri, RDF::DC.created, created_at.to_datetime]
+ graph << [uri, RDF::DC.creator, RDF::URI(creator_uri)] if creator_uri
+ graph << [uri, RDF::URI("http://purl.org/wf4ever/ro#name"), name] if name
+
+ if content_blob && !is_manifest?
+ graph << [uri, RDF::URI("http://purl.org/wf4ever/ro#filesize"), content_blob.size]
+ graph << [uri, RDF::URI("http://purl.org/wf4ever/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 generate_proxy(proxy_path = ".ro/proxies/#{SecureRandom.uuid}")
+
+ ro_uri = RDF::URI(research_object.uri)
+ proxy_uri = RDF::URI(research_object.uri) + proxy_path
+
+ graph = RDF::Graph.new
+ graph << [proxy_uri, RDF.type, ORE.Proxy]
+ graph << [proxy_uri, ORE.proxyIn, ro_uri]
+ graph << [proxy_uri, ORE.proxyFor, resource_uri]
+
+ graph
+ end
+
+ def update_graph!
+
+ new_description = create_rdf_xml { |graph| graph << description }
+
+ content_blob.destroy if content_blob
+ content_blob = ContentBlob.new(:data ="" new_description)
+ content_blob.save
+
+ update_attribute(:data, new_description) # FIXME - to be removed
+ end
+end
Added: branches/packs/config/initializers/namespaces.rb (0 => 3559)
--- branches/packs/config/initializers/namespaces.rb (rev 0)
+++ branches/packs/config/initializers/namespaces.rb 2013-05-22 13:45:00 UTC (rev 3559)
@@ -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
+
Modified: branches/packs/config/routes.rb (3558 => 3559)
--- branches/packs/config/routes.rb 2013-05-22 13:42:17 UTC (rev 3558)
+++ branches/packs/config/routes.rb 2013-05-22 13:45:00 UTC (rev 3559)
@@ -322,6 +322,41 @@
:controller => 'linked_data', :action ="" 'taggings', :conditions => { :method => :get }
end
+ # RODL routes. There are no HTML pages for 'new' and 'edit' so the routes
+ # are generated without the resource helpers.
+
+ map.research_objects "/rodl/ROs", :controller => "research_objects", :action ="" "index", :conditions => { :method => :get }
+ map.connect "/rodl/ROs", :controller => "research_objects", :action ="" "create", :conditions => { :method => :post }
+
+ map.research_object "/rodl/ROs/:id", :controller => "research_objects", :action ="" "show", :conditions => { :method => :get }
+ map.connect "/rodl/ROs/:research_object_id", :controller => "resources", :action ="" "post", :conditions => { :method => :post }
+ map.connect "/rodl/ROs/:id", :controller => "research_objects", :action ="" "update", :conditions => { :method => :put }
+ map.connect "/rodl/ROs/:id", :controller => "research_objects", :action ="" "destroy", :conditions => { :method => :delete }
+
+ map.named_route "research_object_resource", "/rodl/ROs/:research_object_id/:id",
+ :controller => "resources",
+ :action ="" "show",
+ :conditions => { :method => :get },
+ :requirements => { :id => /.*/ }
+
+ map.connect "/rodl/ROs/:research_object_id/:id",
+ :controller => "resources",
+ :action ="" "update",
+ :conditions => { :method => :put },
+ :requirements => { :id => /.*/ }
+
+ map.connect "/rodl/ROs/:research_object_id/:id",
+ :controller => "resources",
+ :action ="" "delete",
+ :conditions => { :method => :delete },
+ :requirements => { :id => /.*/ }
+
+ map.connect "/rodl/ROs/:research_object_id/:path",
+ :controller => "resources",
+ :action ="" "post",
+ :conditions => { :method => :post },
+ :requirements => { :path => /.*/ }
+
# Install the default route as the lowest priority.
map.connect ':controller/:action/:id'
end
Added: branches/packs/db/migrate/20130520145900_create_research_objects.rb (0 => 3559)
--- branches/packs/db/migrate/20130520145900_create_research_objects.rb (rev 0)
+++ branches/packs/db/migrate/20130520145900_create_research_objects.rb 2013-05-22 13:45:00 UTC (rev 3559)
@@ -0,0 +1,59 @@
+# 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 "slug"
+ t.integer "version"
+ t.string "version_type"
+ t.integer "user_id"
+
+ t.timestamps
+ end
+
+ create_table "resources" do |t|
+ t.string "name"
+ t.integer "research_object_id"
+ t.integer "content_blob_id"
+ t.string "sha1", :limit => 40
+ t.binary "data", :limit => 2147483647
+ t.integer "size"
+ t.string "content_type"
+ t.text "path"
+ t.string "creator_uri"
+ 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 "annotation_id"
+ t.string "resource_uri"
+ end
+
+ end
+
+ def self.down
+ drop_table :research_objects
+ drop_table :resources
+ drop_table :annotation_resources
+ end
+
+end
Modified: branches/packs/db/schema.rb (3558 => 3559)
--- branches/packs/db/schema.rb 2013-05-22 13:42:17 UTC (rev 3558)
+++ branches/packs/db/schema.rb 2013-05-22 13:45:00 UTC (rev 3559)
@@ -9,7 +9,7 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20130423091433) do
+ActiveRecord::Schema.define(:version => 20130520145900) do
create_table "activities", :force => true do |t|
t.string "subject_type"
@@ -42,6 +42,11 @@
t.datetime "promote_after"
end
+ create_table "annotation_resources", :force => true do |t|
+ t.integer "annotation_id"
+ t.string "resource_uri"
+ end
+
create_table "announcements", :force => true do |t|
t.string "title"
t.integer "user_id"
@@ -155,9 +160,10 @@
end
create_table "content_blobs", :force => true do |t|
- t.binary "data", :limit => 2147483647
- t.string "md5", :limit => 32
- t.string "sha1", :limit => 40
+ t.binary "data", :limit => 2147483647
+ t.string "md5", :limit => 32
+ t.string "sha1", :limit => 40
+ t.integer "size"
end
add_index "content_blobs", ["md5"], :name => "index_content_blobs_on_md5"
@@ -595,6 +601,42 @@
t.string "workflow_uri"
end
+ create_table "research_objects", :force => true do |t|
+ t.string "slug"
+ t.integer "version"
+ t.string "version_type"
+ t.integer "user_id"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ create_table "resources", :force => true do |t|
+ t.string "name"
+ t.integer "research_object_id"
+ t.integer "content_blob_id"
+ t.string "sha1", :limit => 40
+ t.binary "data", :limit => 2147483647
+ t.integer "size"
+ t.string "content_type"
+ t.text "path"
+ t.string "creator_uri"
+ 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.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
create_table "reviews", :force => true do |t|
t.string "title", :default => ""
t.text "review"
Added: branches/packs/test/functional/research_objects_test.rb (0 => 3559)
--- branches/packs/test/functional/research_objects_test.rb (rev 0)
+++ branches/packs/test/functional/research_objects_test.rb 2013-05-22 13:45:00 UTC (rev 3559)
@@ -0,0 +1,419 @@
+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 is empty.
+
+ get research_objects_path
+
+ assert_response :ok
+ assert_equal "", @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 "#{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 :ok
+ assert_equal "application/vnd.wf4ever.proxy", @response.content_type.to_s
+
+ 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