Module: Dor::Releaseable

Extended by:
ActiveSupport::Concern
Includes:
Itemizable
Included in:
BasicItem
Defined in:
lib/dor/models/releaseable.rb

Constant Summary

Constants included from Itemizable

Itemizable::DIFF_FILENAME, Itemizable::DIFF_QUERY

Instance Method Summary collapse

Methods included from Itemizable

#clear_diff_cache, #get_content_diff

Instance Method Details

#add_release_node(release, attrs = {}) ⇒ Nokogiri::XML::Element

Add a release node for the item Will use the current time to add in the timestamp if you do not supply a timestamp, you can supply a timestap for correcting history, etc if desired

Timestamp will be calculated by the function, if no displayType is passed in, it will default to file

Examples:

item.add_tag(true,:release,{:tag=>'Fitch : Batch2',:what=>'self',:to=>'Searchworks',:who=>'petucket', :displayType='filmstrip'})

Returns:

  • (Nokogiri::XML::Element)

    the tag added if successful

Raises:

  • (ArgumentError)

    Raised if attributes are improperly supplied



289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/dor/models/releaseable.rb', line 289

def add_release_node(release, attrs={})
   = self.
  attrs[:when] = Time.now.utc.iso8601 if attrs[:when].nil? #add the timestamp
  attrs[:displayType] = 'file' if attrs[:displayType].nil? #default to file is no display type is passed
  valid_release_attributes(release, attrs)
  
  #Remove the old displayType and then add the one for this tag
  remove_displayTypes
  .add_value(:displayType, attrs[:displayType], {})
  
  return .add_value(:release, release.to_s, attrs)
end

#add_release_nodes_and_start_releaseWF(release_tags) ⇒ Object

Add release tags to an item and initialize the item release workflow

Raises:

  • (ArgumentError)

    Raised if the tags are improperly supplied



16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/dor/models/releaseable.rb', line 16

def add_release_nodes_and_start_releaseWF(release_tags)
  release_tags = [release_tags] if release_tags.class != Array
  
  #Add in each tag
  release_tags.each do |r_tag|
    self.add_release_node(r_tag[:release],r_tag)
  end
  
  #Save the item to dor so the robots work with the latest data
  self.save 
  
  #Intialize the release workflow
  self.initialize_workflow('releaseWF') 
end

#add_tags_from_purl(new_tags) ⇒ Object

This function calls purl and gets a list of all release tags currently in purl. It then compares to the list you have generated. Any tag that is on purl, but not in the newly generated list is added to the new list with a value of false.

params new_tags [Hash] a hash of all new tags in the form of => Boolean, where Project is a string

return [Hash], a hash in the same form as new_tags, with all missing tags not in new_tags, but in current_tag_names, added in with a Boolean value of false



415
416
417
418
419
420
421
422
# File 'lib/dor/models/releaseable.rb', line 415

def add_tags_from_purl(new_tags) 
  tags_currently_in_purl = self.get_release_tags_from_purl
  missing_tags = tags_currently_in_purl.map(&:downcase) - new_tags.keys.map(&:downcase) 
  missing_tags.each do |missing_tag|
    new_tags[missing_tag.capitalize] = {"release"=>false}
  end
  return new_tags
end

#clean_release_tag_for_purl(tag) ⇒ Hash

Take a tag and return only the attributes we want to put into purl

Parameters:

  • tag (Hash)

    a tag

Returns:

  • (Hash)

    a hash of the attributes we want for purl



156
157
158
159
160
161
162
163
# File 'lib/dor/models/releaseable.rb', line 156

def clean_release_tag_for_purl(tag)
  for_purl = ['release']
  return_hash = {}
  for_purl.each do |attr|
    return_hash[attr] = tag[attr]
  end
  return return_hash
end

#combine_two_release_tag_hashes(hash_one, hash_two) ⇒ Hash

Take two hashes of tags and combine them, will not overwrite but will enforce uniqueness of the tags

Parameters:

  • hash_one (Hash)

    a hash of tags obtained via Dor::Item.release_tags or matching format

  • hash_two (Hash)

    a hash of tags obtained via Dor::Item.release_tags or matching format

Returns:

  • (Hash)

    the combined hash with uniquiness enforced



114
115
116
117
118
119
120
# File 'lib/dor/models/releaseable.rb', line 114

def combine_two_release_tag_hashes(hash_one, hash_two)
  hash_two.keys.each do |key|
    hash_one[key] = hash_two[key] if hash_one[key] == nil
    hash_one[key] = (hash_one[key] + hash_two[key]).uniq if hash_one[key] != nil
  end
  return hash_one
end

#does_release_tag_apply(release_tag, admin_tags = false) ⇒ Boolean

Takes a tag and returns true or false if it applies to the specific item

Parameters:

  • release_tag (Hash)

    the tag in a hashed form

  • Optional

    admin_tags [Array] the administrative tags on an item, if not supplied it will attempt to retrieve them

Returns:

  • (Boolean)

    true or false if it applies (not true or false if it is released, that is the release_tag data)



184
185
186
187
188
189
190
# File 'lib/dor/models/releaseable.rb', line 184

def does_release_tag_apply(release_tag, admin_tags=false)
  #Is the tag global or restricted 
  return true if release_tag['tag'] == nil  #there is no specific tag specificied, so that means this tag is global to all members of the collection, it applies, return true
    
  admin_tags = self.tags if ! admin_tags #We use false instead of [], since an item can have no admin_tags that which point we'd be passing down this variable as [] and would not an attempt to retrieve it
  return admin_tags.include?(release_tag['tag'])
end

#form_purl_urlObject

Take the and create the entire purl url that will usable for the open method in open-uri, returns http

params druid [String], the druid without or without the driud prefix

return [String], the full url



381
382
383
384
# File 'lib/dor/models/releaseable.rb', line 381

def form_purl_url
  prefix = "http://" 
  return prefix + Dor::Config.stacks.document_cache_host + "/#{self.remove_druid_prefix}.xml"
end

#generate_release_xmlString

Generate XML structure for inclusion to Purl

Returns:

  • (String)

    The XML release node as a string, with ReleaseDigest as the root document



34
35
36
37
38
39
40
41
42
43
# File 'lib/dor/models/releaseable.rb', line 34

def generate_release_xml
  builder = Nokogiri::XML::Builder.new do |xml|
    xml.releaseData {
      self.released_for.each do |project,released_value|
        xml.release(released_value["release"],:to=>project)
      end  
      }        
    end
  return builder.to_xml
end

#get_newest_release_tag(tags) ⇒ Hash

Take a hash of tags as obtained via Dor::Item.release_tags and returns the newest tag for each namespace

Returns:

  • (Hash)

    a hash of latest tags for each to value



142
143
144
145
146
147
148
149
# File 'lib/dor/models/releaseable.rb', line 142

def get_newest_release_tag(tags)
  return_hash = {}
  tags.keys.each do |key|
    latest_for_key = newest_release_tag_in_an_array(tags[key])
    return_hash[key] = latest_for_key         
  end
  return return_hash
end

#get_release_tags_for_item_and_all_governing_setsHash

Take an item and get all of its release tags and all tags on collections it is a member of it

Returns:

  • (Hash)

    a hash of all tags



100
101
102
103
104
105
106
# File 'lib/dor/models/releaseable.rb', line 100

def get_release_tags_for_item_and_all_governing_sets
  return_tags = self.release_nodes || {}
  self.collections.each do |collection|
    return_tags = combine_two_release_tag_hashes(return_tags, Dor::Item.find(collection.id).get_release_tags_for_item_and_all_governing_sets) #this will function recurvisely so parents of parents are found
  end
  return return_tags  
end

#get_release_tags_from_purlArray

Pull all release nodes from the public xml obtained via the purl query

Returns:

  • (Array)

    An array containing all the release tags



404
405
406
407
# File 'lib/dor/models/releaseable.rb', line 404

def get_release_tags_from_purl
  xml = self.get_xml_from_purl
  return self.get_release_tags_from_purl_xml(xml)
end

#get_release_tags_from_purl_xml(doc) ⇒ Array

Pull all release nodes from the public xml obtained via the purl query

Returns:

  • (Array)

    An array containing all the release tags



391
392
393
394
395
396
397
398
399
# File 'lib/dor/models/releaseable.rb', line 391

def get_release_tags_from_purl_xml(doc)
  nodes = doc.xpath("//html/body/publicobject/releasedata").children 
  #We only want the nodes with a name that isn't text
  return_array = []
  nodes.each do |n|
    return_array << n.attr('to') if n.name != nil and n.name.downcase != "text"
  end
  return return_array.uniq 
end

#get_self_release_tags(tags) ⇒ Hash

Take a hash of tags as obtained via Dor::Item.release_tags and returns all self tags

Parameters:

  • tags (Hash)

    a hash of tags obtained via Dor::Item.release_tags or matching format

Returns:

  • (Hash)

    a hash of self tags for each to value



92
93
94
# File 'lib/dor/models/releaseable.rb', line 92

def get_self_release_tags(tags)
  return get_tags_for_what_value(tags, 'self')
end

#get_tags_for_what_value(tags, what_target) ⇒ Hash

Take a hash of tags and return all tags with the matching what target

Parameters:

  • tags (Hash)

    a hash of tags obtained via Dor::Item.release_tags or matching format

  • what_target (String)

    the target for the ‘what’ key, self or collection

Returns:

  • (Hash)

    a hash of self tags for each to value



128
129
130
131
132
133
134
135
# File 'lib/dor/models/releaseable.rb', line 128

def get_tags_for_what_value(tags, what_target)
  return_hash = {}
  tags.keys.each do |key|
    self_tags =  tags[key].select{|tag| tag['what'] == what_target.downcase}
    return_hash[key] = self_tags if self_tags.size > 0
  end
  return return_hash
end

#get_xml_from_purlNokogiri::HTML::Document

Get a list of all release nodes found in a purl document

Fetches purl xml for a druid

Returns:

  • (Nokogiri::HTML::Document)

    the parsed xml for the druid or an empty document if no purl is found



353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/dor/models/releaseable.rb', line 353

def get_xml_from_purl
    handler = Proc.new do |exception, attempt_number, total_delay|
      #We assume a 404 means the document has never been published before and thus has no purl
      #The strip is needed before the actual message is "404 "
      return Nokogiri::HTML::Document.new if exception.message.strip == "404"
    end

    with_retries(:max_retries => 5, :base_sleep_seconds => 3, :max_sleep_seconds=> 5, :rescue => OpenURI::HTTPError, :handler => handler) {
       #If you change the method used for opening the webpage, you can change the :rescue param to handle the new method's errors
       return Nokogiri::HTML(open(self.form_purl_url))
     }
   
end

#latest_applicable_release_tag_in_array(release_tags, admin_tags) ⇒ Hash

Takes an array of release tags and returns the most recent one that applies to this item

param admin_tags [Array] the administrative tags on an on item

Parameters:

  • release_tags (Array)

    an array of release tags in hashed form

Returns:

  • (Hash)

    the tag



198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/dor/models/releaseable.rb', line 198

def latest_applicable_release_tag_in_array(release_tags, admin_tags)
  newest_tag = newest_release_tag_in_an_array(release_tags)
  return newest_tag if does_release_tag_apply(newest_tag, admin_tags) #Return true if we have it
  
  #The latest tag wasn't applicable, slice it off and try again
  #This could be optimized by reordering on the timestamp and just running down it instead of constantly resorting, at least if we end up getting numerous release tags on an item
  release_tags.slice!(release_tags.index(newest_tag))
  
  return latest_applicable_release_tag_in_array(release_tags, admin_tags) if release_tags.size > 0 #Try again after dropping the one that wasn't applicable 
  
  return nil #We're out of tags, no applicable ones
end

#newest_release_tag_in_an_array(array_of_tags) ⇒ Hash

Takes an array of release tags and returns the most recent one

Returns:

  • (Hash)

    the most recent tag



170
171
172
173
174
175
176
# File 'lib/dor/models/releaseable.rb', line 170

def newest_release_tag_in_an_array(array_of_tags)
  latest_tag_in_array = array_of_tags[0] || {}
  array_of_tags.each do |tag|
    latest_tag_in_array = tag if tag['when'] > latest_tag_in_array['when']
  end
  return latest_tag_in_array
end

#release_nodesNokogiri::XML::NodeSet

helper method to get the release nodes as a nodeset

Returns:

  • (Nokogiri::XML::NodeSet)

    of all release tags and their attributes



330
331
332
333
334
335
336
337
338
339
340
341
342
# File 'lib/dor/models/releaseable.rb', line 330

def release_nodes
  release_tags = self..ng_xml.xpath('//release')
  return_hash = {}
  release_tags.each do |release_tag|
    hashed_node = self.release_tag_node_to_hash(release_tag)
    if return_hash[hashed_node[:to]] != nil
      return_hash[hashed_node[:to]] << hashed_node[:attrs]
    else
       return_hash[hashed_node[:to]] = [hashed_node[:attrs]]
    end
  end
  return return_hash
end

#release_tag_node_to_hash(rtag) ⇒ Object

method to convert one release element into an array

return [Hash] in the form of => String :attrs = Hash

Parameters:

  • rtag (Nokogiri::XML::Element)

    the release tag element



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/dor/models/releaseable.rb', line 233

def release_tag_node_to_hash(rtag)
  to = 'to'
  release = 'release'
  when_word = 'when' #TODO: Make to and when_word load from some config file instead of hardcoded here
  attrs = rtag.attributes
  return_hash = { :to => attrs[to].value }
  attrs.tap { |a| a.delete(to)}
  attrs[release] = rtag.text.downcase == "true" #save release as a boolean
  return_hash[:attrs] = attrs

  #convert all the attrs beside :to to strings, they are currently Nokogiri::XML::Attr
  (return_hash[:attrs].keys-[to]).each do |a|
    return_hash[:attrs][a] =  return_hash[:attrs][a].to_s if a != release
  end

  return_hash[:attrs][when_word] = Time.parse(return_hash[:attrs][when_word]) #convert when to a datetime

  return return_hash
end

#release_tagsNokogiri::XML::NodeSet

helper method to get the release tags as a nodeset

Returns:

  • (Nokogiri::XML::NodeSet)

    of all release tags and their attributes



214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/dor/models/releaseable.rb', line 214

def release_tags
  release_tags = self..ng_xml.xpath('//release')
  return_hash = {}
  release_tags.each do |release_tag|
    hashed_node = self.release_tag_node_to_hash(release_tag)
    if return_hash[hashed_node[:to]] != nil
      return_hash[hashed_node[:to]] << hashed_node[:attrs]
    else
       return_hash[hashed_node[:to]] = [hashed_node[:attrs]]
    end
  end
  return return_hash
end

#released_forHash

Determine which projects an item is released for

Returns:

  • (Hash)

    all namespaces in the form of => Boolean



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/dor/models/releaseable.rb', line 48

def released_for
  released_hash = {}
  
  #Get release tags on the item itself 
  release_tags_on_this_item = self.release_nodes
  
  #Get any self tags on this item
  self_release_tags = self.get_self_release_tags(release_tags_on_this_item)
  
  #Get the most recent self tag for all targets and save their result since most recent self always trumps any other non self tags
  latest_self_tags = self.get_newest_release_tag(self_release_tags)
  latest_self_tags.keys.each do |target|
    released_hash[target] =  self.clean_release_tag_for_purl(latest_self_tags[target])
  end
  
  #With Self Tags Resolved We Now need to deal with tags on all sets this object is part of 
  
  potential_applicable_release_tags = {}  #This will be where we store all tags that apply, regardless of their timestamp
   
  #Get all release tags on the item and strip out the what = self ones, we've already processed all the self tags on this item 
  potential_applicable_release_tags = get_tags_for_what_value(self.get_release_tags_for_item_and_all_governing_sets, 'collection')
  
  administrative_tags = self.tags  #Get them once here and pass them down
  
  #We now have the keys for all potential releases, we need to check the tags and the most recent time stamp with an explicit true or false wins, in a nil case, the lack of an explicit false tag we do nothing
  (potential_applicable_release_tags.keys-released_hash.keys).each do |key|  #don't bother checking the ones already added to the release hash, they were added due to a self tag and that has won
    latest_applicable_tag_for_key = latest_applicable_release_tag_in_array(potential_applicable_release_tags[key], administrative_tags)
    if latest_applicable_tag_for_key != nil #We have a valid tag, record it
      released_hash[key] = self.clean_release_tag_for_purl(latest_applicable_tag_for_key) 
    end
    
  end
  
  #See what the application is currently released for on Purl.  If something is released in purl but not listed here, it needs to be added as a false
  released_hash = self.add_tags_from_purl(released_hash)
    
  return released_hash
end

#remove_druid_prefixString

Since purl does not use the druid: prefix but much of dor does, use this function to strip the druid: if needed

Returns:

  • (String)

    the druid sans the druid: or if there was no druid: prefix, the entire string you passed



370
371
372
373
374
# File 'lib/dor/models/releaseable.rb', line 370

def remove_druid_prefix
  druid_prefix = "druid:"
  return self.id.split(druid_prefix)[1] if self.id.split(druid_prefix).size > 1
  return druid
end

#valid_release_attributes(tag, attrs = {}) ⇒ Boolean

Determine if the supplied tag is a valid release node that meets all requirements

Returns:

  • (Boolean)

    Returns true if no errors found

Raises:

  • (ArgumentError)


309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/dor/models/releaseable.rb', line 309

def valid_release_attributes(tag, attrs={})
  raise ArgumentError, ":when is not iso8601" if attrs[:when].match('\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z') == nil
  [:who, :to, :what].each do |check_attr|
    raise ArgumentError, "#{check_attr} not supplied as a String" if attrs[check_attr].class != String
  end

  what_correct = false
  ['self', 'collection'].each do |allowed_what_value|
    what_correct = true if attrs[:what] == allowed_what_value
  end
  raise ArgumentError, ":what must be self or collection" if ! what_correct
  raise ArgumentError, "the value set for this tag is not a boolean" if !!tag != tag
  raise ArgumentError, ":displayType must be passed in as a String" unless attrs[:displayType].class == String
  
  validate_tag_format(attrs[:tag]) if attrs[:tag] != nil #Will Raise exception if invalid tag
  return true
end

#valid_release_attributes_and_tag(tag, attrs = {}) ⇒ Boolean

Determine if the supplied tag is a valid release tag that meets all requirements

Returns:

  • (Boolean)

    Returns true if no errors found

Raises:

  • (ArgumentError)


260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/dor/models/releaseable.rb', line 260

def valid_release_attributes_and_tag(tag, attrs={})
  raise ArgumentError, ":when is not iso8601" if attrs[:when].match('\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z') == nil
  [:who, :to, :what].each do |check_attr|
    raise ArgumentError, "#{check_attr} not supplied as a String" if attrs[check_attr].class != String
  end

  what_correct = false
  ['self', 'collection'].each do |allowed_what_value|
    what_correct = true if attrs[:what] == allowed_what_value
  end
  raise ArgumentError, ":what must be self or collection" if ! what_correct
  raise ArgumentError, "the value set for this tag is not a boolean" if !!tag != tag
  validate_tag_format(attrs[:tag]) if attrs[:tag] != nil #Will Raise exception if invalid tag
  return true
end