dor-services 6.0.5 → 6.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/dor-services.rb +27 -12
- data/lib/dor/config.rb +45 -42
- data/lib/dor/datastreams/administrative_metadata_ds.rb +137 -44
- data/lib/dor/datastreams/content_metadata_ds.rb +42 -42
- data/lib/dor/datastreams/datastream_spec_solrizer.rb +1 -1
- data/lib/dor/datastreams/default_object_rights_ds.rb +185 -44
- data/lib/dor/datastreams/desc_metadata_ds.rb +36 -28
- data/lib/dor/datastreams/embargo_metadata_ds.rb +12 -14
- data/lib/dor/datastreams/events_ds.rb +10 -10
- data/lib/dor/datastreams/geo_metadata_ds.rb +4 -5
- data/lib/dor/datastreams/identity_metadata_ds.rb +14 -14
- data/lib/dor/datastreams/rights_metadata_ds.rb +23 -23
- data/lib/dor/datastreams/role_metadata_ds.rb +61 -15
- data/lib/dor/datastreams/simple_dublin_core_ds.rb +8 -8
- data/lib/dor/datastreams/version_metadata_ds.rb +10 -12
- data/lib/dor/datastreams/workflow_definition_ds.rb +6 -6
- data/lib/dor/datastreams/workflow_ds.rb +13 -13
- data/lib/dor/exceptions.rb +2 -2
- data/lib/dor/indexers/data_indexer.rb +1 -7
- data/lib/dor/indexers/describable_indexer.rb +1 -1
- data/lib/dor/indexers/identifiable_indexer.rb +0 -2
- data/lib/dor/indexers/processable_indexer.rb +55 -28
- data/lib/dor/indexers/releasable_indexer.rb +2 -2
- data/lib/dor/models/admin_policy_object.rb +4 -4
- data/lib/dor/models/concerns/assembleable.rb +4 -0
- data/lib/dor/models/concerns/contentable.rb +27 -69
- data/lib/dor/models/concerns/describable.rb +14 -29
- data/lib/dor/models/concerns/editable.rb +20 -334
- data/lib/dor/models/concerns/embargoable.rb +7 -11
- data/lib/dor/models/concerns/eventable.rb +5 -1
- data/lib/dor/models/concerns/geoable.rb +4 -4
- data/lib/dor/models/concerns/governable.rb +18 -87
- data/lib/dor/models/concerns/identifiable.rb +15 -75
- data/lib/dor/models/concerns/itemizable.rb +9 -11
- data/lib/dor/models/concerns/preservable.rb +4 -0
- data/lib/dor/models/concerns/processable.rb +30 -129
- data/lib/dor/models/concerns/publishable.rb +6 -55
- data/lib/dor/models/concerns/releaseable.rb +14 -227
- data/lib/dor/models/concerns/rightsable.rb +3 -3
- data/lib/dor/models/concerns/shelvable.rb +4 -49
- data/lib/dor/models/concerns/versionable.rb +21 -44
- data/lib/dor/models/set.rb +1 -1
- data/lib/dor/models/workflow_object.rb +2 -2
- data/lib/dor/services/ability.rb +77 -0
- data/lib/dor/services/cleanup_reset_service.rb +1 -3
- data/lib/dor/services/create_workflow_service.rb +51 -0
- data/lib/dor/services/creative_commons_license_service.rb +31 -0
- data/lib/dor/services/datastream_builder.rb +90 -0
- data/lib/dor/services/digital_stacks_service.rb +3 -21
- data/lib/dor/services/dublin_core_service.rb +40 -0
- data/lib/dor/services/file_metadata_merge_service.rb +67 -0
- data/lib/dor/services/indexing_service.rb +8 -4
- data/lib/dor/services/merge_service.rb +5 -5
- data/lib/dor/services/metadata_handlers/catalog_handler.rb +1 -1
- data/lib/dor/services/metadata_service.rb +6 -8
- data/lib/dor/{models/concerns → services}/mods2dc.xslt +0 -0
- data/lib/dor/services/ontology.rb +35 -0
- data/lib/dor/services/open_data_license_service.rb +20 -0
- data/lib/dor/services/public_desc_metadata_service.rb +21 -14
- data/lib/dor/services/public_xml_service.rb +6 -6
- data/lib/dor/services/publish_metadata_service.rb +100 -0
- data/lib/dor/services/registration_service.rb +43 -46
- data/lib/dor/services/release_tag_service.rb +251 -0
- data/lib/dor/services/reset_workspace_service.rb +1 -3
- data/lib/dor/services/sdr_ingest_service.rb +5 -7
- data/lib/dor/services/search_service.rb +10 -10
- data/lib/dor/services/secondary_file_name_service.rb +10 -0
- data/lib/dor/services/shelving_service.rb +67 -0
- data/lib/dor/services/status_service.rb +121 -0
- data/lib/dor/services/suri_service.rb +3 -5
- data/lib/dor/services/tag_service.rb +100 -0
- data/lib/dor/services/technical_metadata_service.rb +5 -4
- data/lib/dor/services/version_service.rb +84 -0
- data/lib/dor/utils/ng_tidy.rb +1 -1
- data/lib/dor/utils/sdr_client.rb +25 -9
- data/lib/dor/version.rb +1 -1
- data/lib/dor/workflow/document.rb +13 -13
- data/lib/dor/workflow/process.rb +71 -26
- data/lib/tasks/rdoc.rake +1 -1
- metadata +77 -51
- data/config/certs/robots-dor-dev.crt +0 -29
- data/config/certs/robots-dor-dev.key +0 -27
- data/config/dev_console_env.rb +0 -80
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dor
|
4
|
+
class DublinCoreService
|
5
|
+
MODS_TO_DC_XSLT = Nokogiri::XSLT(File.new(File.expand_path(File.dirname(__FILE__) + '/mods2dc.xslt')))
|
6
|
+
XMLNS_OAI_DC = 'http://www.openarchives.org/OAI/2.0/oai_dc/'
|
7
|
+
class CrosswalkError < RuntimeError; end
|
8
|
+
|
9
|
+
def initialize(work, include_collection_as_related_item: true)
|
10
|
+
@work = work
|
11
|
+
@include_collection = include_collection_as_related_item
|
12
|
+
end
|
13
|
+
|
14
|
+
# Generates Dublin Core from the MODS in the descMetadata datastream using the LoC mods2dc stylesheet
|
15
|
+
# Should not be used for the Fedora DC datastream
|
16
|
+
# @raise [CrosswalkError] Raises an Exception if the generated DC is empty or has no children
|
17
|
+
# @return [Nokogiri::Doc] the DublinCore XML document object
|
18
|
+
def to_xml
|
19
|
+
dc_doc = MODS_TO_DC_XSLT.transform(desc_md)
|
20
|
+
dc_doc.xpath('/oai_dc:dc/*[count(text()) = 0]', oai_dc: XMLNS_OAI_DC).remove # Remove empty nodes
|
21
|
+
raise CrosswalkError, "Dor::Item#generate_dublin_core produced incorrect xml (no root):\n#{dc_doc.to_xml}" if dc_doc.root.nil?
|
22
|
+
raise CrosswalkError, "Dor::Item#generate_dublin_core produced incorrect xml (no children):\n#{dc_doc.to_xml}" if dc_doc.root.children.size == 0
|
23
|
+
|
24
|
+
dc_doc
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def desc_md
|
30
|
+
return PublicDescMetadataService.new(work).ng_xml(include_access_conditions: false) if include_collection?
|
31
|
+
|
32
|
+
work.descMetadata.ng_xml
|
33
|
+
end
|
34
|
+
|
35
|
+
def include_collection?
|
36
|
+
@include_collection
|
37
|
+
end
|
38
|
+
attr_reader :work
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dor
|
4
|
+
# Merges contentMetadata from several objects into one.
|
5
|
+
class FileMetadataMergeService
|
6
|
+
# @param [Array<String>] secondary_druids ids of the secondary objects that will get their contentMetadata merged into this one
|
7
|
+
def self.copy_file_resources(primary, secondary_druids)
|
8
|
+
merge_service = FileMetadataMergeService.new primary, secondary_druids
|
9
|
+
merge_service.copy_file_resources
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(primary, secondary_druids)
|
13
|
+
@pid = primary.pid
|
14
|
+
content_metadata = primary.contentMetadata
|
15
|
+
content_metadata.ng_xml_will_change!
|
16
|
+
@primary_cm = content_metadata.ng_xml
|
17
|
+
@secondary_druids = secondary_druids
|
18
|
+
end
|
19
|
+
|
20
|
+
# Appends contentMetadata file resources from the source objects to this object
|
21
|
+
def copy_file_resources
|
22
|
+
base_id = primary_cm.at_xpath('/contentMetadata/@objectId').value
|
23
|
+
max_sequence = primary_cm.at_xpath('/contentMetadata/resource[last()]/@sequence').value.to_i
|
24
|
+
|
25
|
+
secondary_druids.each do |src_pid|
|
26
|
+
source_obj = Dor.find src_pid
|
27
|
+
source_cm = source_obj.contentMetadata.ng_xml
|
28
|
+
|
29
|
+
# Copy the resources from each source object
|
30
|
+
source_cm.xpath('/contentMetadata/resource').each do |old_resource|
|
31
|
+
max_sequence += 1
|
32
|
+
resource_copy = old_resource.clone
|
33
|
+
resource_copy['sequence'] = max_sequence.to_s
|
34
|
+
|
35
|
+
# Append sequence number to each secondary filename, then
|
36
|
+
# look for filename collisions with the primary object
|
37
|
+
resource_copy.xpath('file').each do |secondary_file|
|
38
|
+
secondary_file['id'] = SecondaryFileNameService.create(secondary_file['id'], max_sequence)
|
39
|
+
|
40
|
+
if primary_cm.at_xpath("//file[@id = '#{secondary_file['id']}']")
|
41
|
+
raise Dor::Exception, "File '#{secondary_file['id']}' from secondary object #{src_pid} already exist in primary object: #{pid}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
if old_resource['type']
|
46
|
+
resource_copy['id'] = "#{old_resource['type']}_#{max_sequence}"
|
47
|
+
else
|
48
|
+
resource_copy['id'] = "#{base_id}_#{max_sequence}"
|
49
|
+
end
|
50
|
+
|
51
|
+
lbl = old_resource.at_xpath 'label'
|
52
|
+
resource_copy.at_xpath('label').content = "#{Regexp.last_match(1)} #{max_sequence}" if lbl && lbl.text =~ /^(.*)\s+\d+$/
|
53
|
+
|
54
|
+
primary_cm.at_xpath('/contentMetadata/resource[last()]').add_next_sibling resource_copy
|
55
|
+
attr_node = primary_cm.create_element 'attr', src_pid, name: 'mergedFromPid'
|
56
|
+
resource_copy.first_element_child.add_previous_sibling attr_node
|
57
|
+
attr_node = primary_cm.create_element 'attr', old_resource['id'], name: 'mergedFromResource'
|
58
|
+
resource_copy.first_element_child.add_previous_sibling attr_node
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
attr_reader :secondary_druids, :primary_cm, :pid
|
66
|
+
end
|
67
|
+
end
|
@@ -12,9 +12,13 @@ module Dor
|
|
12
12
|
# entry. a placeholder will be used otherwise. 'request.uuid' might be useful in a Rails app.
|
13
13
|
def self.generate_index_logger(&entry_id_block)
|
14
14
|
index_logger = Logger.new(Config.indexing_svc.log, Config.indexing_svc.log_rotation_interval)
|
15
|
-
index_logger.formatter = proc do |
|
15
|
+
index_logger.formatter = proc do |_severity, datetime, _progname, msg|
|
16
16
|
date_format_str = Config.indexing_svc.log_date_format_str
|
17
|
-
entry_id = begin
|
17
|
+
entry_id = begin begin
|
18
|
+
entry_id_block.call
|
19
|
+
rescue StandardError
|
20
|
+
'---'
|
21
|
+
end end
|
18
22
|
"[#{entry_id}] [#{datetime.utc.strftime(date_format_str)}] #{msg}\n"
|
19
23
|
end
|
20
24
|
index_logger
|
@@ -49,7 +53,7 @@ module Dor
|
|
49
53
|
msg = "failed to reindex #{pid}: #{e}"
|
50
54
|
default_index_logger.error msg
|
51
55
|
raise ReindexError.new(msg)
|
52
|
-
rescue => e
|
56
|
+
rescue StandardError => e
|
53
57
|
default_index_logger.error "failed to reindex #{pid}: #{e}"
|
54
58
|
raise
|
55
59
|
end
|
@@ -72,7 +76,7 @@ module Dor
|
|
72
76
|
options = args.pop if args.last.is_a? Hash
|
73
77
|
|
74
78
|
if args.length > 0
|
75
|
-
warn
|
79
|
+
warn 'Dor::IndexingService.reindex_pid with primitive arguments is deprecated; pass e.g. { logger: logger, raise_errors: bool } instead'
|
76
80
|
index_logger, should_raise_errors = args
|
77
81
|
index_logger ||= default_index_logger
|
78
82
|
should_raise_errors = true if should_raise_errors.nil?
|
@@ -31,7 +31,7 @@ module Dor
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def move_metadata_and_content
|
34
|
-
|
34
|
+
FileMetadataMergeService.copy_file_resources @primary, @secondary_pids
|
35
35
|
@primary.save
|
36
36
|
copy_workspace_content
|
37
37
|
end
|
@@ -50,9 +50,9 @@ module Dor
|
|
50
50
|
primary_resource = primary_cm.at_xpath "//resource[attr[@name = 'mergedFromPid']/text() = '#{secondary.pid}' and
|
51
51
|
attr[@name = 'mergedFromResource']/text() = '#{src_resource['id']}' ]"
|
52
52
|
sequence = primary_resource['sequence']
|
53
|
-
src_resource.xpath('//file/@id').map
|
53
|
+
src_resource.xpath('//file/@id').map(&:value).each do |file_id|
|
54
54
|
copy_path = sec_druid.find_content file_id
|
55
|
-
new_name =
|
55
|
+
new_name = SecondaryFileNameService.create(file_id, sequence)
|
56
56
|
# TODO: verify new_name exists in primary_cm?
|
57
57
|
FileUtils.cp(copy_path, File.join(dest_path, "/#{new_name}"))
|
58
58
|
end
|
@@ -71,13 +71,13 @@ module Dor
|
|
71
71
|
unpublish
|
72
72
|
Dor::CleanupService.cleanup_by_druid @current_secondary.pid
|
73
73
|
Dor::Config.workflow.client.archive_active_workflow 'dor', @current_secondary.pid
|
74
|
-
rescue => e
|
74
|
+
rescue StandardError => e
|
75
75
|
@logger.error "Unable to decommission #{@current_secondary.pid} with primary object #{@primary.pid}: #{e.inspect}"
|
76
76
|
@logger.error e.backtrace.join("\n")
|
77
77
|
end
|
78
78
|
end
|
79
79
|
end
|
80
|
-
|
80
|
+
alias decomission_secondaries decommission_secondaries
|
81
81
|
deprecate decomission_secondaries: 'Use decommission_secondaries instead'
|
82
82
|
|
83
83
|
# Remove content from stacks
|
@@ -13,7 +13,7 @@ handler = Class.new do
|
|
13
13
|
def label(metadata)
|
14
14
|
mods = Nokogiri::XML(metadata)
|
15
15
|
mods.root.add_namespace_definition('mods', 'http://www.loc.gov/mods/v3')
|
16
|
-
mods.xpath('/mods:mods/mods:titleInfo[1]').xpath('mods:title|mods:nonSort').collect
|
16
|
+
mods.xpath('/mods:mods/mods:titleInfo[1]').xpath('mods:title|mods:nonSort').collect(&:text).join(' ').strip
|
17
17
|
end
|
18
18
|
|
19
19
|
def prefixes
|
@@ -3,7 +3,7 @@
|
|
3
3
|
require 'cache'
|
4
4
|
|
5
5
|
module Dor
|
6
|
-
class MetadataError <
|
6
|
+
class MetadataError < RuntimeError; end
|
7
7
|
|
8
8
|
# class MetadataHandler
|
9
9
|
#
|
@@ -23,9 +23,7 @@ module Dor
|
|
23
23
|
|
24
24
|
def register(handler_class)
|
25
25
|
%w(fetch label prefixes).each do |method|
|
26
|
-
unless handler_class.instance_methods.include?(method) || handler_class.instance_methods.include?(method.to_sym)
|
27
|
-
raise TypeError, "Metadata handlers must define ##{method}"
|
28
|
-
end
|
26
|
+
raise TypeError, "Metadata handlers must define ##{method}" unless handler_class.instance_methods.include?(method) || handler_class.instance_methods.include?(method.to_sym)
|
29
27
|
end
|
30
28
|
handler = handler_class.new
|
31
29
|
handler.prefixes.each do |prefix|
|
@@ -40,12 +38,12 @@ module Dor
|
|
40
38
|
|
41
39
|
def can_resolve?(identifier)
|
42
40
|
(prefix, _identifier) = identifier.split(/:/, 2)
|
43
|
-
handlers.
|
41
|
+
handlers.key?(prefix.to_sym)
|
44
42
|
end
|
45
43
|
|
46
44
|
# TODO: Return a prioritized list
|
47
45
|
def resolvable(identifiers)
|
48
|
-
identifiers.select { |identifier|
|
46
|
+
identifiers.select { |identifier| can_resolve?(identifier) }
|
49
47
|
end
|
50
48
|
|
51
49
|
def fetch(identifier)
|
@@ -78,6 +76,6 @@ module Dor
|
|
78
76
|
end
|
79
77
|
end
|
80
78
|
|
81
|
-
Dir[File.join(File.dirname(__FILE__), 'metadata_handlers', '*.rb')].each
|
79
|
+
Dir[File.join(File.dirname(__FILE__), 'metadata_handlers', '*.rb')].each do |handler_file|
|
82
80
|
load handler_file
|
83
|
-
|
81
|
+
end
|
File without changes
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dor
|
4
|
+
class Ontology
|
5
|
+
extend Deprecation
|
6
|
+
self.deprecation_horizon = 'dor-services version 7.0.0'
|
7
|
+
|
8
|
+
def self.[](key)
|
9
|
+
@data[key]
|
10
|
+
end
|
11
|
+
deprecation_deprecate :[] => 'Use property() instead'
|
12
|
+
|
13
|
+
def self.key?(key)
|
14
|
+
@data.key?(key)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.include?(key)
|
18
|
+
@data.include?(key)
|
19
|
+
end
|
20
|
+
deprecation_deprecate include?: 'Use key? instead'
|
21
|
+
|
22
|
+
def self.property(key)
|
23
|
+
Term.new(@data[key])
|
24
|
+
end
|
25
|
+
|
26
|
+
class Term
|
27
|
+
def initialize(uri:, human_readable:)
|
28
|
+
@label = human_readable
|
29
|
+
@uri = uri
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :label, :uri
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dor
|
4
|
+
class OpenDataLicenseService < Ontology
|
5
|
+
# these hashes map short ("machine") license codes to their corresponding URIs and human readable titles. they
|
6
|
+
# also allow for deprecated entries (via optional :deprecation_warning). clients that use these maps are advised to
|
7
|
+
# only display undeprecated entries, except where a deprecated entry is already in use by an object. e.g., an APO
|
8
|
+
# that already specifies "by_sa" for its default license code could continue displaying that in a list of license options
|
9
|
+
# for editing, preferably with the deprecation warning. but other deprecated entries would be omitted in such a
|
10
|
+
# selectbox.
|
11
|
+
@data = {
|
12
|
+
'pddl' => { human_readable: 'Open Data Commons Public Domain Dedication and License 1.0',
|
13
|
+
uri: 'http://opendatacommons.org/licenses/pddl/1.0/' },
|
14
|
+
'odc-by' => { human_readable: 'Open Data Commons Attribution License 1.0',
|
15
|
+
uri: 'http://opendatacommons.org/licenses/by/1.0/' },
|
16
|
+
'odc-odbl' => { human_readable: 'Open Data Commons Open Database License 1.0',
|
17
|
+
uri: 'http://opendatacommons.org/licenses/odbl/1.0/' }
|
18
|
+
}.freeze
|
19
|
+
end
|
20
|
+
end
|
@@ -16,14 +16,21 @@ module Dor
|
|
16
16
|
|
17
17
|
# @return [String] Public descriptive medatada XML
|
18
18
|
def to_xml(include_access_conditions: true)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
19
|
+
ng_xml(include_access_conditions: include_access_conditions).to_xml
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [Nokogiri::XML::Document]
|
23
|
+
def ng_xml(include_access_conditions: true)
|
24
|
+
@ng_xml ||= begin
|
25
|
+
add_collection_reference!
|
26
|
+
add_access_conditions! if include_access_conditions
|
27
|
+
add_constituent_relations!
|
28
|
+
strip_comments!
|
29
|
+
|
30
|
+
new_doc = Nokogiri::XML(doc.to_xml, &:noblanks)
|
31
|
+
new_doc.encoding = 'UTF-8'
|
32
|
+
new_doc
|
33
|
+
end
|
27
34
|
end
|
28
35
|
|
29
36
|
private
|
@@ -37,20 +44,20 @@ module Dor
|
|
37
44
|
# @note this method modifies the passed in doc
|
38
45
|
def add_access_conditions!
|
39
46
|
# clear out any existing accessConditions
|
40
|
-
doc.xpath('//mods:accessCondition', 'mods' => 'http://www.loc.gov/mods/v3').each
|
47
|
+
doc.xpath('//mods:accessCondition', 'mods' => 'http://www.loc.gov/mods/v3').each(&:remove)
|
41
48
|
rights = object.datastreams['rightsMetadata'].ng_xml
|
42
49
|
|
43
50
|
rights.xpath('//use/human[@type="useAndReproduction"]').each do |use|
|
44
51
|
txt = use.text.strip
|
45
52
|
next if txt.empty?
|
46
53
|
|
47
|
-
doc.root.element_children.last.add_next_sibling doc.create_element('accessCondition', txt, :
|
54
|
+
doc.root.element_children.last.add_next_sibling doc.create_element('accessCondition', txt, type: 'useAndReproduction')
|
48
55
|
end
|
49
56
|
rights.xpath('//copyright/human[@type="copyright"]').each do |cr|
|
50
57
|
txt = cr.text.strip
|
51
58
|
next if txt.empty?
|
52
59
|
|
53
|
-
doc.root.element_children.last.add_next_sibling doc.create_element('accessCondition', txt, :
|
60
|
+
doc.root.element_children.last.add_next_sibling doc.create_element('accessCondition', txt, type: 'copyright')
|
54
61
|
end
|
55
62
|
rights.xpath("//use/machine[#{ci_compare('type', 'creativecommons')}]").each do |lic_type|
|
56
63
|
next if lic_type.text =~ /none/i
|
@@ -59,7 +66,7 @@ module Dor
|
|
59
66
|
next if lic_text.empty?
|
60
67
|
|
61
68
|
new_text = "CC #{lic_type.text}: #{lic_text}"
|
62
|
-
doc.root.element_children.last.add_next_sibling doc.create_element('accessCondition', new_text, :
|
69
|
+
doc.root.element_children.last.add_next_sibling doc.create_element('accessCondition', new_text, type: 'license')
|
63
70
|
end
|
64
71
|
rights.xpath("//use/machine[#{ci_compare('type', 'opendatacommons')}]").each do |lic_type|
|
65
72
|
next if lic_type.text =~ /none/i
|
@@ -68,7 +75,7 @@ module Dor
|
|
68
75
|
next if lic_text.empty?
|
69
76
|
|
70
77
|
new_text = "ODC #{lic_type.text}: #{lic_text}"
|
71
|
-
doc.root.element_children.last.add_next_sibling doc.create_element('accessCondition', new_text, :
|
78
|
+
doc.root.element_children.last.add_next_sibling doc.create_element('accessCondition', new_text, type: 'license')
|
72
79
|
end
|
73
80
|
end
|
74
81
|
|
@@ -133,7 +140,7 @@ module Dor
|
|
133
140
|
end
|
134
141
|
end
|
135
142
|
|
136
|
-
def add_related_item_node_for_collection!
|
143
|
+
def add_related_item_node_for_collection!(collection_druid)
|
137
144
|
begin
|
138
145
|
collection_obj = Dor.find(collection_druid)
|
139
146
|
rescue ActiveFedora::ObjectNotFoundError
|
@@ -20,7 +20,7 @@ module Dor
|
|
20
20
|
|
21
21
|
pub.add_child(public_relationships.root) unless public_relationships.nil? # TODO: Should never be nil in practice; working around an ActiveFedora quirk for testing
|
22
22
|
pub.add_child(object.generate_dublin_core.root)
|
23
|
-
pub.add_child(
|
23
|
+
pub.add_child(PublicDescMetadataService.new(object).ng_xml.root)
|
24
24
|
pub.add_child(release_xml.root) unless release_xml.xpath('//release').children.size == 0 # If there are no release_tags, this prevents an empty <releaseData/> from being added
|
25
25
|
# Note we cannot base this on if an individual object has release tags or not, because the collection may cause one to be generated for an item,
|
26
26
|
# so we need to calculate it and then look at the final result.
|
@@ -28,7 +28,7 @@ module Dor
|
|
28
28
|
thumb = ThumbnailService.new(object).thumb
|
29
29
|
pub.add_child(Nokogiri("<thumb>#{thumb}</thumb>").root) unless thumb.nil?
|
30
30
|
|
31
|
-
new_pub = Nokogiri::XML(pub.to_xml
|
31
|
+
new_pub = Nokogiri::XML(pub.to_xml, &:noblanks)
|
32
32
|
new_pub.encoding = 'UTF-8'
|
33
33
|
new_pub.to_xml
|
34
34
|
end
|
@@ -38,11 +38,11 @@ module Dor
|
|
38
38
|
def release_xml
|
39
39
|
@release_xml ||= begin
|
40
40
|
builder = Nokogiri::XML::Builder.new do |xml|
|
41
|
-
xml.releaseData
|
41
|
+
xml.releaseData do
|
42
42
|
object.released_for.each do |project, released_value|
|
43
|
-
xml.release(released_value['release'], :
|
43
|
+
xml.release(released_value['release'], to: project)
|
44
44
|
end
|
45
|
-
|
45
|
+
end
|
46
46
|
end
|
47
47
|
Nokogiri::XML(builder.to_xml)
|
48
48
|
end
|
@@ -83,7 +83,7 @@ module Dor
|
|
83
83
|
src_resource_id = externalFile['resourceId']
|
84
84
|
src_druid = externalFile['objectId']
|
85
85
|
src_file_id = externalFile['fileId']
|
86
|
-
|
86
|
+
raise ArgumentError, "Malformed externalFile data: #{externalFile.inspect}" if [src_resource_id, src_file_id, src_druid].map(&:blank?).any?
|
87
87
|
|
88
88
|
# grab source item
|
89
89
|
src_item = Dor.find(src_druid)
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dor
|
4
|
+
# Merges contentMetadata from several objects into one.
|
5
|
+
class PublishMetadataService
|
6
|
+
# @param [Dor::Item] item the object to be publshed
|
7
|
+
def self.publish(item)
|
8
|
+
new(item).publish
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(item)
|
12
|
+
@item = item
|
13
|
+
end
|
14
|
+
|
15
|
+
# Appends contentMetadata file resources from the source objects to this object
|
16
|
+
def publish
|
17
|
+
return unpublish unless world_discoverable?
|
18
|
+
|
19
|
+
transfer_metadata
|
20
|
+
publish_notify_on_success
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_reader :item
|
26
|
+
|
27
|
+
def transfer_metadata
|
28
|
+
dc_xml = item.generate_dublin_core.to_xml(&:no_declaration)
|
29
|
+
transfer_to_document_store(dc_xml, 'dc')
|
30
|
+
%w(identityMetadata contentMetadata rightsMetadata).each do |stream|
|
31
|
+
transfer_to_document_store(item.datastreams[stream].content.to_s, stream) if item.datastreams[stream]
|
32
|
+
end
|
33
|
+
transfer_to_document_store(PublicXmlService.new(item).to_xml, 'public')
|
34
|
+
transfer_to_document_store(PublicDescMetadataService.new(self).to_xml, 'mods')
|
35
|
+
end
|
36
|
+
|
37
|
+
# Clear out the document cache for this item
|
38
|
+
def unpublish
|
39
|
+
purl_druid.prune!
|
40
|
+
publish_delete_on_success
|
41
|
+
end
|
42
|
+
|
43
|
+
def world_discoverable?
|
44
|
+
rights = item.rightsMetadata.ng_xml.clone.remove_namespaces!
|
45
|
+
rights.at_xpath("//rightsMetadata/access[@type='discover']/machine/world")
|
46
|
+
end
|
47
|
+
|
48
|
+
# Create a file inside the content directory under the stacks.local_document_cache_root
|
49
|
+
# @param [String] content The contents of the file to be created
|
50
|
+
# @param [String] filename The name of the file to be created
|
51
|
+
# @return [void]
|
52
|
+
def transfer_to_document_store(content, filename)
|
53
|
+
new_file = File.join(purl_druid.content_dir, filename)
|
54
|
+
File.open(new_file, 'w') { |f| f.write content }
|
55
|
+
end
|
56
|
+
|
57
|
+
def purl_druid
|
58
|
+
@purl_druid ||= DruidTools::PurlDruid.new item.pid, Config.stacks.local_document_cache_root
|
59
|
+
end
|
60
|
+
|
61
|
+
def prune_purl_dir
|
62
|
+
purl_druid.prune!
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# When publishing a PURL, we notify purl-fetcher of changes.
|
67
|
+
# If the purl service isn't configured, instead we drop a `aa11bb2222` file into the `local_recent_changes` folder
|
68
|
+
# to notify other applications watching the filesystem (i.e., purl-fetcher).
|
69
|
+
# We also remove any .deletes entry that may have left over from a previous removal
|
70
|
+
def publish_notify_on_success
|
71
|
+
id = item.pid.gsub(/^druid:/, '')
|
72
|
+
if Dor::Config.purl_services.url
|
73
|
+
purl_services = Dor::Config.purl_services.rest_client
|
74
|
+
purl_services["purls/#{id}"].post ''
|
75
|
+
else
|
76
|
+
Deprecation.warn(self, 'You have not configured perl-fetcher (Dor::Config.purl_services.url). This will result in an error in dor-services 7 ')
|
77
|
+
local_recent_changes = Config.stacks.local_recent_changes
|
78
|
+
raise ArgumentError, "Missing local_recent_changes directory: #{local_recent_changes}" unless File.directory?(local_recent_changes)
|
79
|
+
|
80
|
+
FileUtils.touch(File.join(local_recent_changes, id))
|
81
|
+
begin
|
82
|
+
DruidTools::Druid.new(id, Dor::Config.stacks.local_document_cache_root).deletes_delete_record
|
83
|
+
rescue Errno::EACCES
|
84
|
+
Dor.logger.warn "Access denied while trying to remove .deletes file for druid:#{id}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# When publishing a PURL, we notify purl-fetcher of changes.
|
91
|
+
def publish_delete_on_success
|
92
|
+
return unless Dor::Config.purl_services.url
|
93
|
+
|
94
|
+
id = item.pid.gsub(/^druid:/, '')
|
95
|
+
|
96
|
+
purl_services = Dor::Config.purl_services.rest_client
|
97
|
+
purl_services["purls/#{id}"].delete
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|