dor-services 7.2.4 → 8.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/config/config_defaults.yml +24 -41
  3. data/config/dev_console_env.rb.example +0 -9
  4. data/lib/dor-services.rb +6 -9
  5. data/lib/dor/config.rb +2 -126
  6. data/lib/dor/datastreams/content_metadata_ds.rb +7 -0
  7. data/lib/dor/datastreams/embargo_metadata_ds.rb +1 -1
  8. data/lib/dor/datastreams/role_metadata_ds.rb +1 -1
  9. data/lib/dor/datastreams/workflow_definition_ds.rb +0 -22
  10. data/lib/dor/datastreams/workflow_ds.rb +2 -64
  11. data/lib/dor/indexers/workflows_indexer.rb +7 -1
  12. data/lib/dor/models/abstract.rb +2 -4
  13. data/lib/dor/models/workflow_object.rb +0 -46
  14. data/lib/dor/release_tags.rb +13 -0
  15. data/lib/dor/release_tags/identity_metadata.rb +202 -0
  16. data/lib/dor/release_tags/purl.rb +50 -0
  17. data/lib/dor/release_tags/purl_client.rb +44 -0
  18. data/lib/dor/services/release_tag_service.rb +9 -179
  19. data/lib/dor/services/state_service.rb +23 -0
  20. data/lib/dor/static_config.rb +108 -0
  21. data/lib/dor/static_config/fedora_config.rb +36 -0
  22. data/lib/dor/static_config/solr_config.rb +21 -0
  23. data/lib/dor/static_config/ssl_config.rb +33 -0
  24. data/lib/dor/static_config/stacks_config.rb +39 -0
  25. data/lib/dor/static_config/suri_config.rb +45 -0
  26. data/lib/dor/static_config/workflow_config.rb +51 -0
  27. data/lib/dor/version.rb +1 -1
  28. data/lib/dor/workflow/document.rb +0 -10
  29. metadata +26 -66
  30. data/lib/dor/services/cleanup_service.rb +0 -63
  31. data/lib/dor/services/create_workflow_service.rb +0 -53
  32. data/lib/dor/services/delete_service.rb +0 -60
  33. data/lib/dor/services/metadata_handlers/catalog_handler.rb +0 -27
  34. data/lib/dor/services/metadata_service.rb +0 -64
  35. data/lib/dor/services/mods2dc.xslt +0 -474
  36. data/lib/dor/services/public_desc_metadata_service.rb +0 -184
  37. data/lib/dor/services/purl_client.rb +0 -42
  38. data/lib/dor/services/thumbnail_service.rb +0 -59
@@ -22,7 +22,13 @@ module Dor
22
22
 
23
23
  # @return [Array<Dor::WorkflowDocument>]
24
24
  def workflows
25
- resource.workflows.workflows
25
+ # TODO: this could use the models in dor-workflow-service: https://github.com/sul-dlss/dor-workflow-client/pull/101
26
+ nodeset = Nokogiri::XML(all_workflows_xml).xpath('/workflows/workflow')
27
+ nodeset.map { |wf_node| Workflow::Document.new wf_node.to_xml }
28
+ end
29
+
30
+ def all_workflows_xml
31
+ @all_workflows_xml ||= Dor::Config.workflow.client.all_workflows_xml resource.pid
26
32
  end
27
33
  end
28
34
  end
@@ -108,11 +108,9 @@ module Dor
108
108
  # States that will allow modification are: has not been submitted for accessioning, has an open version or has sdr-ingest set to hold
109
109
  # @todo this could be a workflow service endpoint
110
110
  def allows_modification?
111
- client = Dor::Config.workflow.client
112
- !client.lifecycle('dor', pid, 'submitted') ||
113
- client.active_lifecycle('dor', pid, 'opened') ||
114
- client.workflow_status('dor', pid, 'accessionWF', 'sdr-ingest-transfer') == 'hold'
111
+ Dor::StateService.new(pid).allows_modification?
115
112
  end
113
+ deprecation_deprecate allows_modification?: 'use Dor::StateService#allows_modification? instead'
116
114
 
117
115
  def current_version
118
116
  versionMetadata.current_version_id
@@ -4,9 +4,6 @@ require 'dor/datastreams/workflow_definition_ds'
4
4
 
5
5
  module Dor
6
6
  class WorkflowObject < Dor::Abstract
7
- @@xml_cache = {}
8
- @@repo_cache = {}
9
-
10
7
  has_object_type 'workflow'
11
8
  has_metadata name: 'workflowDefinition', type: Dor::WorkflowDefinitionDs, label: 'Workflow Definition'
12
9
 
@@ -22,52 +19,9 @@ module Dor
22
19
  Dor::WorkflowObject.where(Solrizer.solr_name('workflow_name', :symbol) => name).first
23
20
  end
24
21
 
25
- # Searches for the workflow definition object in DOR, then
26
- # returns an object's initial workflow as defined in the worfklowDefinition datastream
27
- # It will cache the result for subsequent requests
28
- # @param [String] name the name of the workflow
29
- # @return [String] the initial workflow xml
30
- def self.initial_workflow(name)
31
- Deprecation.warn(self, 'WorkflowObject#initial_workflow is deprecated and will be removed in dor-services 8.0')
32
- return @@xml_cache[name] if @@xml_cache.include?(name)
33
-
34
- find_and_cache_workflow_xml_and_repo name
35
- @@xml_cache[name]
36
- end
37
-
38
- # Returns the repository attribute from the workflow definition
39
- # It will cache the result for subsequent requests
40
- # @param [String] name the name of the workflow
41
- # @return [String] the initial workflow xml
42
- def self.initial_repo(name)
43
- Deprecation.warn(self, 'WorkflowObject#initial_repo is deprecated and will be removed in dor-services 8.0')
44
- return @@repo_cache[name] if @@repo_cache.include?(name)
45
-
46
- find_and_cache_workflow_xml_and_repo name
47
- @@repo_cache[name]
48
- end
49
-
50
22
  # @return [Dor::WorkflowDefinitionDs]
51
23
  def definition
52
24
  datastreams['workflowDefinition']
53
25
  end
54
-
55
- def generate_initial_workflow
56
- datastreams['workflowDefinition'].initial_workflow
57
- end
58
-
59
- alias generate_intial_workflow generate_initial_workflow
60
-
61
- # Searches DOR for the workflow definition object. It then caches the workflow repository and xml
62
- # @param [String] name the name of the workflow
63
- # @return [Object] a Dor::xxxx object, e.g. a Dor::Item object
64
- def self.find_and_cache_workflow_xml_and_repo(name)
65
- wobj = find_by_name(name)
66
- raise "Failed to find workflow via find_by_name('#{name}')" if wobj.nil?
67
-
68
- @@repo_cache[name] = wobj.definition.repo
69
- @@xml_cache[name] = wobj.generate_initial_workflow
70
- wobj
71
- end
72
26
  end
73
27
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dor
4
+ module ReleaseTags
5
+ extend ActiveSupport::Autoload
6
+
7
+ eager_autoload do
8
+ autoload :PurlClient
9
+ autoload :Purl
10
+ autoload :IdentityMetadata
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,202 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dor
4
+ module ReleaseTags
5
+ class IdentityMetadata
6
+ # Determine projects in which an item is released
7
+ # @param [Dor::Item] item to get the release tags for
8
+ # @return [Hash{String => Boolean}] all namespaces, keys are Project name Strings, values are Boolean
9
+ def self.for(item)
10
+ new(item)
11
+ end
12
+
13
+ def initialize(item)
14
+ @item = item
15
+ end
16
+
17
+ # Called in Dor::UpdateMarcRecordService (in dor-services-app too)
18
+ # Determine projects in which an item is released
19
+ # @param [Hash{String => Boolean}] the released hash to add tags to
20
+ # @return [Hash{String => Boolean}] all namespaces, keys are Project name Strings, values are Boolean
21
+ def released_for(released_hash)
22
+ # Get the most recent self tag for all targets and retain their result since most recent self always trumps any other non self tags
23
+ latest_self_tags = newest_release_tag self_release_tags(release_tags)
24
+ latest_self_tags.each do |key, payload|
25
+ released_hash[key] = { 'release' => payload['release'] }
26
+ end
27
+
28
+ # With Self Tags resolved we now need to deal with tags on all sets this object is part of.
29
+ # 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.
30
+ # This will be where we store all tags that apply, regardless of their timestamp:
31
+ potential_applicable_release_tags = tags_for_what_value(release_tags_for_item_and_all_governing_sets, 'collection')
32
+ administrative_tags = item.tags # Get admin tags once here and pass them down
33
+
34
+ # We now have the keys for all potential releases, we need to check the tags: the most recent timestamp with an explicit true or false wins.
35
+ # In a nil case, the lack of an explicit false tag we do nothing.
36
+ # Don't bother checking if already added to the release hash, they were added due to a self tag so that has won
37
+ (potential_applicable_release_tags.keys - released_hash.keys).each do |key|
38
+ latest_tag = latest_applicable_release_tag_in_array(potential_applicable_release_tags[key], administrative_tags)
39
+ next if latest_tag.nil? # Otherwise, we have a valid tag, record it
40
+
41
+ released_hash[key] = { 'release' => latest_tag['release'] }
42
+ end
43
+ released_hash
44
+ end
45
+
46
+ # Take an item and get all of its release tags and all tags on collections it is a member of it
47
+ # @return [Hash] a hash of all tags
48
+ def release_tags_for_item_and_all_governing_sets
49
+ return_tags = release_tags || {}
50
+ item.collections.each do |collection|
51
+ next if collection.id == item.id # recursive, so parents of parents are found, but we need to avoid an infinite loop if the collection references itself (i.e. bad data)
52
+
53
+ release_service = self.class.for(collection)
54
+ return_tags = combine_two_release_tag_hashes(return_tags, release_service.release_tags_for_item_and_all_governing_sets)
55
+ end
56
+ return_tags
57
+ end
58
+
59
+ # Helper method to get the release tags as a nodeset
60
+ # @return [Nokogiri::XML::NodeSet] all release tags and their attributes
61
+ def release_tags
62
+ release_tags = item.identityMetadata.ng_xml.xpath('//release')
63
+ return_hash = {}
64
+ release_tags.each do |release_tag|
65
+ hashed_node = release_tag_node_to_hash(release_tag)
66
+ if !return_hash[hashed_node[:to]].nil?
67
+ return_hash[hashed_node[:to]] << hashed_node[:attrs]
68
+ else
69
+ return_hash[hashed_node[:to]] = [hashed_node[:attrs]]
70
+ end
71
+ end
72
+ return_hash
73
+ end
74
+
75
+ # Take a hash of tags as obtained via Dor::Item.release_tags and returns the newest tag for each namespace
76
+ # @param tags [Hash] a hash of tags obtained via Dor::Item.release_tags or matching format
77
+ # @return [Hash] a hash of latest tags for each to value
78
+ def newest_release_tag(tags)
79
+ Hash[tags.map { |key, val| [key, newest_release_tag_in_an_array(val)] }]
80
+ end
81
+
82
+ private
83
+
84
+ # Convert one release element into a Hash
85
+ # @param rtag [Nokogiri::XML::Element] the release tag element
86
+ # @return [Hash{:to, :attrs => String, Hash}] in the form of !{:to => String :attrs = Hash}
87
+ def release_tag_node_to_hash(rtag)
88
+ to = 'to'
89
+ release = 'release'
90
+ when_word = 'when' # TODO: Make to and when_word load from some config file instead of hardcoded here
91
+ attrs = rtag.attributes
92
+ return_hash = { to: attrs[to].value }
93
+ attrs.tap { |a| a.delete(to) }
94
+ attrs[release] = rtag.text.casecmp('true') == 0 # save release as a boolean
95
+ return_hash[:attrs] = attrs
96
+
97
+ # convert all the attrs beside :to to strings, they are currently Nokogiri::XML::Attr
98
+ (return_hash[:attrs].keys - [to]).each do |a|
99
+ return_hash[:attrs][a] = return_hash[:attrs][a].to_s if a != release
100
+ end
101
+
102
+ return_hash[:attrs][when_word] = Time.parse(return_hash[:attrs][when_word]) # convert when to a datetime
103
+ return_hash
104
+ end
105
+
106
+ # Take a hash of tags as obtained via Dor::Item.release_tags and returns all self tags
107
+ # @param tags [Hash] a hash of tags obtained via Dor::Item.release_tags or matching format
108
+ # @return [Hash] a hash of self tags for each to value
109
+ def self_release_tags(tags)
110
+ tags_for_what_value(tags, 'self')
111
+ end
112
+
113
+ # Take a hash of tags and return all tags with the matching what target
114
+ # @param tags [Hash] a hash of tags obtained via Dor::Item.release_tags or matching format
115
+ # @param what_target [String] the target for the 'what' key, self or collection
116
+ # @return [Hash] a hash of self tags for each to value
117
+ def tags_for_what_value(tags, what_target)
118
+ return_hash = {}
119
+ tags.keys.each do |key|
120
+ self_tags = tags[key].select { |tag| tag['what'].casecmp(what_target) == 0 }
121
+ return_hash[key] = self_tags unless self_tags.empty?
122
+ end
123
+ return_hash
124
+ end
125
+
126
+ # Take two hashes of tags and combine them, will not overwrite but will enforce uniqueness of the tags
127
+ # @param hash_one [Hash] a hash of tags obtained via Dor::Item.release_tags or matching format
128
+ # @param hash_two [Hash] a hash of tags obtained via Dor::Item.release_tags or matching format
129
+ # @return [Hash] the combined hash with uniquiness enforced
130
+ def combine_two_release_tag_hashes(hash_one, hash_two)
131
+ hash_two.keys.each do |key|
132
+ hash_one[key] = hash_two[key] if hash_one[key].nil?
133
+ hash_one[key] = (hash_one[key] + hash_two[key]).uniq unless hash_one[key].nil?
134
+ end
135
+ hash_one
136
+ end
137
+
138
+ # Takes an array of release tags and returns the most recent one
139
+ # @param array_of_tags [Array] an array of hashes, each hash a release tag
140
+ # @return [Hash] the most recent tag
141
+ def newest_release_tag_in_an_array(array_of_tags)
142
+ latest_tag_in_array = array_of_tags[0] || {}
143
+ array_of_tags.each do |tag|
144
+ latest_tag_in_array = tag if tag['when'] > latest_tag_in_array['when']
145
+ end
146
+ latest_tag_in_array
147
+ end
148
+
149
+ # Takes a tag and returns true or false if it applies to the specific item
150
+ # @param release_tag [Hash] the tag in a hashed form
151
+ # @param admin_tags [Array] the administrative tags on an item, if not supplied it will attempt to retrieve them
152
+ # @return [Boolean] true or false if it applies (not true or false if it is released, that is the release_tag data)
153
+ def does_release_tag_apply(release_tag, admin_tags = false)
154
+ # Is the tag global or restricted
155
+ return true if release_tag['tag'].nil? # no specific tag specificied means this tag is global to all members of the collection
156
+
157
+ admin_tags ||= item.tags # We use false instead of [], since an item can have no admin_tags at which point we'd be passing this var as [] and would not attempt to retrieve it
158
+ admin_tags.include?(release_tag['tag'])
159
+ end
160
+
161
+ # Takes an array of release tags and returns the most recent one that applies to this item
162
+ # @param release_tags [Array] an array of release tags in hashed form
163
+ # @param admin_tags [Array] the administrative tags on an on item
164
+ # @return [Hash] the tag, or nil if none applicable
165
+ def latest_applicable_release_tag_in_array(release_tags, admin_tags)
166
+ newest_tag = newest_release_tag_in_an_array(release_tags)
167
+ return newest_tag if does_release_tag_apply(newest_tag, admin_tags)
168
+
169
+ # The latest tag wasn't applicable, slice it off and try again
170
+ # 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
171
+ release_tags.slice!(release_tags.index(newest_tag))
172
+
173
+ return latest_applicable_release_tag_in_array(release_tags, admin_tags) unless release_tags.empty? # Try again after dropping the inapplicable
174
+
175
+ nil # We're out of tags, no applicable ones
176
+ end
177
+
178
+ # This function calls purl and gets a list of all release tags currently in purl. It then compares to the list you have generated.
179
+ # Any tag that is on purl, but not in the newly generated list is added to the new list with a value of false.
180
+ # @param new_tags [Hash{String => Boolean}] all new tags in the form of !{"Project" => Boolean}
181
+ # @return [Hash] 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
182
+ def add_tags_from_purl(new_tags)
183
+ missing_tags = release_tags_from_purl.map(&:downcase) - new_tags.keys.map(&:downcase)
184
+ missing_tags.each do |missing_tag|
185
+ new_tags[missing_tag.capitalize] = { 'release' => false }
186
+ end
187
+ new_tags
188
+ end
189
+
190
+ # Pull all release nodes from the public xml obtained via the purl query
191
+ # @param doc [Nokogiri::HTML::Document] The druid of the object you want
192
+ # @return [Array] An array containing all the release tags
193
+ def release_tags_from_purl_xml(doc)
194
+ nodes = doc.xpath('//publicObject/releaseData').children
195
+ # We only want the nodes with a name that isn't text
196
+ nodes.reject { |n| n.name.nil? || n.name.casecmp('text') == 0 }.map { |n| n.attr('to') }.uniq
197
+ end
198
+
199
+ attr_reader :item
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dor
4
+ module ReleaseTags
5
+ class Purl
6
+ # Determine projects in which an item is released
7
+ # @param [String] pid identifier of the item to get the release tags for
8
+ def initialize(pid:, purl_host:)
9
+ @pid = pid
10
+ @purl_host = purl_host
11
+ end
12
+
13
+ # This function calls purl and gets a list of all release tags currently in purl. It then compares to the list you have generated.
14
+ # Any tag that is on purl, but not in the newly generated list is added to the new list with a value of false.
15
+ # @param new_tags [Hash{String => Boolean}] all new tags in the form of !{"Project" => Boolean}
16
+ # @return [Hash{String => Boolean}] all namespaces, keys are Project name Strings, values are Boolean
17
+ def released_for(new_tags)
18
+ missing_tags = release_tags_from_purl.map(&:downcase) - new_tags.keys.map(&:downcase)
19
+ missing_tags.each do |missing_tag|
20
+ new_tags[missing_tag.capitalize] = { 'release' => false }
21
+ end
22
+ new_tags
23
+ end
24
+
25
+ private
26
+
27
+ # Pull all release nodes from the public xml obtained via the purl query
28
+ # @param doc [Nokogiri::HTML::Document] The druid of the object you want
29
+ # @return [Array] An array containing all the release tags
30
+ def release_tags_from_purl_xml(doc)
31
+ nodes = doc.xpath('//publicObject/releaseData').children
32
+ # We only want the nodes with a name that isn't text
33
+ nodes.reject { |n| n.name.nil? || n.name.casecmp('text') == 0 }.map { |n| n.attr('to') }.uniq
34
+ end
35
+
36
+ # Pull all release nodes from the public xml obtained via the purl query
37
+ # @return [Array] An array containing all the release tags
38
+ def release_tags_from_purl
39
+ release_tags_from_purl_xml(purl_client.fetch)
40
+ end
41
+
42
+ def purl_client
43
+ PurlClient.new(host: @purl_host,
44
+ pid: pid)
45
+ end
46
+
47
+ attr_reader :pid
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dor
4
+ module ReleaseTags
5
+ # Calls the purl service and returns the XML document
6
+ class PurlClient
7
+ def initialize(host:, pid:)
8
+ @host = host
9
+ @pid = pid
10
+ end
11
+
12
+ # Get XML from the purl service
13
+ # Fetches purl xml for a druid
14
+ # @raise [OpenURI::HTTPError]
15
+ # @return [Nokogiri::HTML::Document] parsed XML for the druid or an empty document if no purl is found
16
+ def fetch
17
+ handler = proc do |exception, attempt_number, total_delay|
18
+ # We assume a 404 means the document has never been published before and thus has no purl
19
+ Dor.logger.warn "[Attempt #{attempt_number}] GET #{url} -- #{exception.class}: #{exception.message}; #{total_delay} seconds elapsed."
20
+ raise exception unless exception.is_a? OpenURI::HTTPError
21
+ return Nokogiri::HTML::Document.new if exception.io.status.first == '404' # ["404", "Not Found"] from OpenURI::Meta.status
22
+ end
23
+
24
+ with_retries(max_retries: 3, base_sleep_seconds: 3, max_sleep_seconds: 5, handler: handler) do |attempt|
25
+ # If you change the method used for opening the webpage, you can change the :rescue param to handle the new method's errors
26
+ Dor.logger.debug "[Attempt #{attempt}] GET #{url}"
27
+ return Nokogiri::XML(OpenURI.open_uri(url))
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ # Take the and create the entire purl url that will usable for the open method in open-uri, returns http
34
+ # @return [String] the full url
35
+ def url
36
+ @url ||= "https://#{@host}/#{druid_without_prefix}.xml"
37
+ end
38
+
39
+ def druid_without_prefix
40
+ PidUtils.remove_druid_prefix(@pid)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -10,202 +10,32 @@ module Dor
10
10
  end
11
11
 
12
12
  def initialize(item)
13
+ @identity_metadata_service = ReleaseTags::IdentityMetadata.new(item)
14
+ @purl_service = ReleaseTags::Purl.new(pid: item.pid, purl_host: Dor::Config.stacks.document_cache_host)
15
+
13
16
  @item = item
14
- @purl_client = PurlClient.new(host: Dor::Config.stacks.document_cache_host,
15
- pid: item.pid)
16
17
  end
17
18
 
18
19
  # Called in Dor::UpdateMarcRecordService (in dor-services-app too)
19
20
  # Determine projects in which an item is released
20
21
  # @return [Hash{String => Boolean}] all namespaces, keys are Project name Strings, values are Boolean
21
22
  def released_for(skip_live_purl:)
22
- released_hash = {}
23
-
24
- # Get the most recent self tag for all targets and retain their result since most recent self always trumps any other non self tags
25
- latest_self_tags = newest_release_tag self_release_tags(release_tags)
26
- latest_self_tags.each do |key, payload|
27
- released_hash[key] = { 'release' => payload['release'] }
28
- end
29
-
30
- # With Self Tags resolved we now need to deal with tags on all sets this object is part of.
31
- # 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.
32
- # This will be where we store all tags that apply, regardless of their timestamp:
33
- potential_applicable_release_tags = tags_for_what_value(release_tags_for_item_and_all_governing_sets, 'collection')
34
- administrative_tags = item.tags # Get admin tags once here and pass them down
35
-
36
- # We now have the keys for all potential releases, we need to check the tags: the most recent timestamp with an explicit true or false wins.
37
- # In a nil case, the lack of an explicit false tag we do nothing.
38
- (potential_applicable_release_tags.keys - released_hash.keys).each do |key| # don't bother checking if already added to the release hash, they were added due to a self tag so that has won
39
- latest_tag = latest_applicable_release_tag_in_array(potential_applicable_release_tags[key], administrative_tags)
40
- next if latest_tag.nil? # Otherwise, we have a valid tag, record it
41
-
42
- released_hash[key] = { 'release' => latest_tag['release'] }
43
- end
44
-
45
- # See what the application is currently released for on Purl. If released in purl but not listed here, it needs to be added as a false
46
- add_tags_from_purl(released_hash) unless skip_live_purl
23
+ released_hash = identity_metadata_service.released_for({})
24
+ released_hash = purl_service.released_for(released_hash) unless skip_live_purl
47
25
  released_hash
48
26
  end
49
27
 
50
- # Take an item and get all of its release tags and all tags on collections it is a member of it
51
- # @return [Hash] a hash of all tags
52
- def release_tags_for_item_and_all_governing_sets
53
- return_tags = release_tags || {}
54
- item.collections.each do |collection|
55
- next if collection.id == item.id # recursive, so parents of parents are found, but we need to avoid an infinite loop if the collection references itself (i.e. bad data)
56
-
57
- release_service = self.class.for(collection)
58
- return_tags = combine_two_release_tag_hashes(return_tags, release_service.release_tags_for_item_and_all_governing_sets)
59
- end
60
- return_tags
61
- end
62
-
63
28
  # Helper method to get the release tags as a nodeset
64
- # @return [Nokogiri::XML::NodeSet] all release tags and their attributes
65
- def release_tags
66
- release_tags = item.identityMetadata.ng_xml.xpath('//release')
67
- return_hash = {}
68
- release_tags.each do |release_tag|
69
- hashed_node = release_tag_node_to_hash(release_tag)
70
- if !return_hash[hashed_node[:to]].nil?
71
- return_hash[hashed_node[:to]] << hashed_node[:attrs]
72
- else
73
- return_hash[hashed_node[:to]] = [hashed_node[:attrs]]
74
- end
75
- end
76
- return_hash
77
- end
29
+ # @return [Hash] all release tags and their attributes
30
+ delegate :release_tags, to: :identity_metadata_service
78
31
 
79
32
  # Take a hash of tags as obtained via Dor::Item.release_tags and returns the newest tag for each namespace
80
33
  # @param tags [Hash] a hash of tags obtained via Dor::Item.release_tags or matching format
81
34
  # @return [Hash] a hash of latest tags for each to value
82
- def newest_release_tag(tags)
83
- Hash[tags.map { |key, val| [key, newest_release_tag_in_an_array(val)] }]
84
- end
35
+ delegate :newest_release_tag, to: :identity_metadata_service
85
36
 
86
37
  private
87
38
 
88
- # Convert one release element into a Hash
89
- # @param rtag [Nokogiri::XML::Element] the release tag element
90
- # @return [Hash{:to, :attrs => String, Hash}] in the form of !{:to => String :attrs = Hash}
91
- def release_tag_node_to_hash(rtag)
92
- to = 'to'
93
- release = 'release'
94
- when_word = 'when' # TODO: Make to and when_word load from some config file instead of hardcoded here
95
- attrs = rtag.attributes
96
- return_hash = { to: attrs[to].value }
97
- attrs.tap { |a| a.delete(to) }
98
- attrs[release] = rtag.text.casecmp('true') == 0 # save release as a boolean
99
- return_hash[:attrs] = attrs
100
-
101
- # convert all the attrs beside :to to strings, they are currently Nokogiri::XML::Attr
102
- (return_hash[:attrs].keys - [to]).each do |a|
103
- return_hash[:attrs][a] = return_hash[:attrs][a].to_s if a != release
104
- end
105
-
106
- return_hash[:attrs][when_word] = Time.parse(return_hash[:attrs][when_word]) # convert when to a datetime
107
- return_hash
108
- end
109
-
110
- # Take a hash of tags as obtained via Dor::Item.release_tags and returns all self tags
111
- # @param tags [Hash] a hash of tags obtained via Dor::Item.release_tags or matching format
112
- # @return [Hash] a hash of self tags for each to value
113
- def self_release_tags(tags)
114
- tags_for_what_value(tags, 'self')
115
- end
116
-
117
- # Take a hash of tags and return all tags with the matching what target
118
- # @param tags [Hash] a hash of tags obtained via Dor::Item.release_tags or matching format
119
- # @param what_target [String] the target for the 'what' key, self or collection
120
- # @return [Hash] a hash of self tags for each to value
121
- def tags_for_what_value(tags, what_target)
122
- return_hash = {}
123
- tags.keys.each do |key|
124
- self_tags = tags[key].select { |tag| tag['what'].casecmp(what_target) == 0 }
125
- return_hash[key] = self_tags if self_tags.size > 0
126
- end
127
- return_hash
128
- end
129
-
130
- # Take two hashes of tags and combine them, will not overwrite but will enforce uniqueness of the tags
131
- # @param hash_one [Hash] a hash of tags obtained via Dor::Item.release_tags or matching format
132
- # @param hash_two [Hash] a hash of tags obtained via Dor::Item.release_tags or matching format
133
- # @return [Hash] the combined hash with uniquiness enforced
134
- def combine_two_release_tag_hashes(hash_one, hash_two)
135
- hash_two.keys.each do |key|
136
- hash_one[key] = hash_two[key] if hash_one[key].nil?
137
- hash_one[key] = (hash_one[key] + hash_two[key]).uniq unless hash_one[key].nil?
138
- end
139
- hash_one
140
- end
141
-
142
- # Takes an array of release tags and returns the most recent one
143
- # @param array_of_tags [Array] an array of hashes, each hash a release tag
144
- # @return [Hash] the most recent tag
145
- def newest_release_tag_in_an_array(array_of_tags)
146
- latest_tag_in_array = array_of_tags[0] || {}
147
- array_of_tags.each do |tag|
148
- latest_tag_in_array = tag if tag['when'] > latest_tag_in_array['when']
149
- end
150
- latest_tag_in_array
151
- end
152
-
153
- # Takes a tag and returns true or false if it applies to the specific item
154
- # @param release_tag [Hash] the tag in a hashed form
155
- # @param admin_tags [Array] the administrative tags on an item, if not supplied it will attempt to retrieve them
156
- # @return [Boolean] true or false if it applies (not true or false if it is released, that is the release_tag data)
157
- def does_release_tag_apply(release_tag, admin_tags = false)
158
- # Is the tag global or restricted
159
- return true if release_tag['tag'].nil? # no specific tag specificied means this tag is global to all members of the collection
160
-
161
- admin_tags ||= item.tags # We use false instead of [], since an item can have no admin_tags at which point we'd be passing this var as [] and would not attempt to retrieve it
162
- admin_tags.include?(release_tag['tag'])
163
- end
164
-
165
- # Takes an array of release tags and returns the most recent one that applies to this item
166
- # @param release_tags [Array] an array of release tags in hashed form
167
- # @param admin_tags [Array] the administrative tags on an on item
168
- # @return [Hash] the tag, or nil if none applicable
169
- def latest_applicable_release_tag_in_array(release_tags, admin_tags)
170
- newest_tag = newest_release_tag_in_an_array(release_tags)
171
- return newest_tag if does_release_tag_apply(newest_tag, admin_tags)
172
-
173
- # The latest tag wasn't applicable, slice it off and try again
174
- # 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
175
- release_tags.slice!(release_tags.index(newest_tag))
176
-
177
- return latest_applicable_release_tag_in_array(release_tags, admin_tags) if release_tags.size > 0 # Try again after dropping the inapplicable
178
-
179
- nil # We're out of tags, no applicable ones
180
- end
181
-
182
- # This function calls purl and gets a list of all release tags currently in purl. It then compares to the list you have generated.
183
- # Any tag that is on purl, but not in the newly generated list is added to the new list with a value of false.
184
- # @param new_tags [Hash{String => Boolean}] all new tags in the form of !{"Project" => Boolean}
185
- # @return [Hash] 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
186
- def add_tags_from_purl(new_tags)
187
- missing_tags = release_tags_from_purl.map(&:downcase) - new_tags.keys.map(&:downcase)
188
- missing_tags.each do |missing_tag|
189
- new_tags[missing_tag.capitalize] = { 'release' => false }
190
- end
191
- new_tags
192
- end
193
-
194
- # Pull all release nodes from the public xml obtained via the purl query
195
- # @param doc [Nokogiri::HTML::Document] The druid of the object you want
196
- # @return [Array] An array containing all the release tags
197
- def release_tags_from_purl_xml(doc)
198
- nodes = doc.xpath('//publicObject/releaseData').children
199
- # We only want the nodes with a name that isn't text
200
- nodes.reject { |n| n.name.nil? || n.name.casecmp('text') == 0 }.map { |n| n.attr('to') }.uniq
201
- end
202
-
203
- # Pull all release nodes from the public xml obtained via the purl query
204
- # @return [Array] An array containing all the release tags
205
- def release_tags_from_purl
206
- release_tags_from_purl_xml(@purl_client.fetch)
207
- end
208
-
209
- attr_reader :item
39
+ attr_reader :identity_metadata_service, :purl_service
210
40
  end
211
41
  end