myexperiment-hackers
[Top][All Lists]
Advanced

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

[myexperiment-hackers] [3785] branches/ro: merged in basic ro functional


From: noreply
Subject: [myexperiment-hackers] [3785] branches/ro: merged in basic ro functionality
Date: Wed, 13 Nov 2013 13:33:27 +0000 (UTC)

Revision
3785
Author
dgc
Date
2013-11-13 13:33:26 +0000 (Wed, 13 Nov 2013)

Log Message

merged in basic ro functionality

Modified Paths

Added Paths

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

reply via email to

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