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.
- checksums.yaml +4 -4
- data/config/config_defaults.yml +24 -41
- data/config/dev_console_env.rb.example +0 -9
- data/lib/dor-services.rb +6 -9
- data/lib/dor/config.rb +2 -126
- data/lib/dor/datastreams/content_metadata_ds.rb +7 -0
- data/lib/dor/datastreams/embargo_metadata_ds.rb +1 -1
- data/lib/dor/datastreams/role_metadata_ds.rb +1 -1
- data/lib/dor/datastreams/workflow_definition_ds.rb +0 -22
- data/lib/dor/datastreams/workflow_ds.rb +2 -64
- data/lib/dor/indexers/workflows_indexer.rb +7 -1
- data/lib/dor/models/abstract.rb +2 -4
- data/lib/dor/models/workflow_object.rb +0 -46
- data/lib/dor/release_tags.rb +13 -0
- data/lib/dor/release_tags/identity_metadata.rb +202 -0
- data/lib/dor/release_tags/purl.rb +50 -0
- data/lib/dor/release_tags/purl_client.rb +44 -0
- data/lib/dor/services/release_tag_service.rb +9 -179
- data/lib/dor/services/state_service.rb +23 -0
- data/lib/dor/static_config.rb +108 -0
- data/lib/dor/static_config/fedora_config.rb +36 -0
- data/lib/dor/static_config/solr_config.rb +21 -0
- data/lib/dor/static_config/ssl_config.rb +33 -0
- data/lib/dor/static_config/stacks_config.rb +39 -0
- data/lib/dor/static_config/suri_config.rb +45 -0
- data/lib/dor/static_config/workflow_config.rb +51 -0
- data/lib/dor/version.rb +1 -1
- data/lib/dor/workflow/document.rb +0 -10
- metadata +26 -66
- data/lib/dor/services/cleanup_service.rb +0 -63
- data/lib/dor/services/create_workflow_service.rb +0 -53
- data/lib/dor/services/delete_service.rb +0 -60
- data/lib/dor/services/metadata_handlers/catalog_handler.rb +0 -27
- data/lib/dor/services/metadata_service.rb +0 -64
- data/lib/dor/services/mods2dc.xslt +0 -474
- data/lib/dor/services/public_desc_metadata_service.rb +0 -184
- data/lib/dor/services/purl_client.rb +0 -42
- 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
|
-
|
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
|
data/lib/dor/models/abstract.rb
CHANGED
@@ -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
|
-
|
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,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 [
|
65
|
-
|
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
|
-
|
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
|
-
|
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
|