module ActiveFedora module SemanticNode include MediaShelfClassLevelInheritableAttributes ms_inheritable_attributes :class_relationships, :internal_uri attr_accessor :internal_uri, :relationships PREDICATE_MAPPINGS = Hash[:is_member_of => "isMemberOf", :has_member => "hasMember", :is_part_of => "isPartOf", :has_part => "hasPart", :is_member_of_collection => "isMemberOfCollection", :has_collection_member => "hasCollectionMember", :is_constituent_of => "isConstituentOf", :has_constituent => "hasConstituent", :is_subset_of => "isSubsetOf", :has_subset => "hasSubset", :is_derivation_of => "isDerivationOf", :has_derivation => "hasDerivation", :is_dependent_of => "isDependentOf", :has_dependent => "hasDependent", :is_description_of => "isDescriptionOf", :has_description => "hasDescription", :is_metadata_for => "isMetadataFor", :has_metadata => "hasMetadata", :is_annotation_of => "isAnnotationOf", :has_annotation => "hasAnnotation", :has_equivalent => "hasEquivalent", :conforms_to => "conformsTo", :has_model => "hasModel"] PREDICATE_MAPPINGS.freeze def self.included(klass) klass.extend(ClassMethods) end def assert_kind_of(n, o,t) raise "Assertion failure: #{n}: #{o} is not of type #{t}" unless o.kind_of?(t) end def add_relationship(relationship) # Only accept ActiveFedora::Relationships as input arguments assert_kind_of 'relationship', relationship, ActiveFedora::Relationship register_triple(relationship.subject, relationship.predicate, relationship.object) end def register_triple(subject, predicate, object) register_subject(subject) register_predicate(subject, predicate) relationships[subject][predicate] << object end def register_subject(subject) if !relationships.has_key?(subject) relationships[subject] = {} end end def register_predicate(subject, predicate) register_subject(subject) if !relationships[subject].has_key?(predicate) relationships[subject][predicate] = [] end end def outbound_relationships() if !internal_uri.nil? && !relationships[internal_uri].nil? return relationships[:self].merge(relationships[internal_uri]) else return relationships[:self] end end def relationships @relationships ||= relationships_from_class end def relationships_from_class rels = {} self.class.relationships.each_pair do |subj, pred| rels[subj] = {} pred.each_key do |pred_key| rels[subj][pred_key] = [] end end #puts "rels from class: #{rels.inspect}" return rels end # Creates a RELS-EXT datastream for insertion into a Fedora Object # @pid # Note: This method is implemented on SemanticNode instead of RelsExtDatastream because SemanticNode contains the relationships array def to_rels_ext(pid, relationships=self.relationships) starter_xml = <<-EOL EOL xml = REXML::Document.new(starter_xml) # Iterate through the hash of predicates, adding an element to the RELS-EXT for each "object" in the predicate's corresponding array. # puts "" # puts "Iterating through a(n) #{self.class}" # puts "=> whose relationships are #{self.relationships.inspect}" # puts "=> and whose outbound relationships are #{self.outbound_relationships.inspect}" self.outbound_relationships.each do |predicate, targets_array| targets_array.each do |target| # puts ". #{predicate} #{target}" xml.root.elements["rdf:Description"].add_element(self.class.predicate_lookup(predicate), {"xmlns" => "info:fedora/fedora-system:def/relations-external#", "rdf:resource"=>target}) end end xml.to_s end module ClassMethods # Anticipates usage of a relationship in classes that include this module # Creates a key in the @relationships array for the predicate provided. Assumes # :self as the subject of the relationship unless :inbound => true, in which case the # predicate is registered under @relationships[:inbound][#{predicate}] # # TODO: # Custom Methods: # A custom finder method will be appended based on the relationship name. # ie. # class Foo # relationship "container", :is_member_of # end # foo = Foo.new # foo.parts # # Special Predicate Short Hand: # These symbols map to the uris of corresponding Fedora RDF predicates # :is_member_of, :has_member, :is_part_of, :has_part def has_relationship(name, predicate, opts = {}) opts = {:singular => nil, :inbound => false}.merge(opts) opts[:inbound] == true ? register_predicate(:inbound, predicate) : register_predicate(:self, predicate) if opts[:inbound] == true create_inbound_relationship_finders(name, predicate, opts) else create_outbound_relationship_finders(name, predicate, opts) end end def create_inbound_relationship_finders(name, predicate, opts = {}) class_eval <<-END def #{name}(opts={}) escaped_uri = self.internal_uri.gsub(/(:)/, '\\:') solr_result = SolrService.instance.conn.query("#{predicate}_s:\#{escaped_uri}") if opts[:response_format] == :solr return solr_result else if opts[:response_format] == :id_array id_array = [] solr_result.hits.each do |hit| id_array << hit[SOLR_DOCUMENT_ID] end return id_array else return ActiveFedora::SolrService.reify_solr_results(solr_result) end end end def #{name}_ids #{name}(:response_format => :id_array) end END end def create_outbound_relationship_finders(name, predicate, opts = {}) class_eval <<-END def #{name}(opts={}) id_array = [] if !outbound_relationships[#{predicate.inspect}].nil? outbound_relationships[#{predicate.inspect}].each do |rel| id_array << rel.gsub("info:fedora/", "") end end if opts[:response_format] == :id_array return id_array else query = ActiveFedora::SolrService.construct_query_for_pids(id_array) solr_result = SolrService.instance.conn.query(query) if opts[:response_format] == :solr return solr_result else return ActiveFedora::SolrService.reify_solr_results(solr_result) end end end def #{name}_ids #{name}(:response_format => :id_array) end END end # relationships are tracked as a hash of structure {subject => {predicate => [object]}} def relationships @class_relationships ||= Hash[:self => {}] end def register_subject(subject) if !relationships.has_key?(subject) relationships[subject] = {} end end def register_predicate(subject, predicate) register_subject(subject) if !relationships[subject].has_key?(predicate) relationships[subject][predicate] = [] end end #alias_method :register_target, :register_object # Creates a RELS-EXT datastream for insertion into a Fedora Object # @pid # Note: This method is implemented on SemanticNode instead of RelsExtDatastream because SemanticNode contains the relationships array def relationships_to_rels_ext(pid, relationships=self.relationships) starter_xml = <<-EOL EOL xml = REXML::Document.new(starter_xml) # Iterate through the hash of predicates, adding an element to the RELS-EXT for each "object" in the predicate's corresponding array. self.outbound_relationships.each do |predicate, targets_array| targets_array.each do |target| #puts ". #{predicate} #{target}" xml.root.elements["rdf:Description"].add_element(predicate_lookup(predicate), {"xmlns" => "info:fedora/fedora-system:def/relations-external#", "rdf:resource"=>target}) end end xml.to_s end # If predicate is a symbol, looks up the predicate in the PREDICATE_MAPPINGS # If predicate is not a Symbol, returns the predicate untouched # @throws UnregisteredPredicateError if the predicate is a symbol but is not found in the PREDICATE_MAPPINGS def predicate_lookup(predicate) if predicate.class == Symbol if PREDICATE_MAPPINGS.has_key?(predicate) return PREDICATE_MAPPINGS[predicate] else throw UnregisteredPredicateError end end return predicate end end end end