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.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/lib/dor-services.rb +27 -12
  3. data/lib/dor/config.rb +45 -42
  4. data/lib/dor/datastreams/administrative_metadata_ds.rb +137 -44
  5. data/lib/dor/datastreams/content_metadata_ds.rb +42 -42
  6. data/lib/dor/datastreams/datastream_spec_solrizer.rb +1 -1
  7. data/lib/dor/datastreams/default_object_rights_ds.rb +185 -44
  8. data/lib/dor/datastreams/desc_metadata_ds.rb +36 -28
  9. data/lib/dor/datastreams/embargo_metadata_ds.rb +12 -14
  10. data/lib/dor/datastreams/events_ds.rb +10 -10
  11. data/lib/dor/datastreams/geo_metadata_ds.rb +4 -5
  12. data/lib/dor/datastreams/identity_metadata_ds.rb +14 -14
  13. data/lib/dor/datastreams/rights_metadata_ds.rb +23 -23
  14. data/lib/dor/datastreams/role_metadata_ds.rb +61 -15
  15. data/lib/dor/datastreams/simple_dublin_core_ds.rb +8 -8
  16. data/lib/dor/datastreams/version_metadata_ds.rb +10 -12
  17. data/lib/dor/datastreams/workflow_definition_ds.rb +6 -6
  18. data/lib/dor/datastreams/workflow_ds.rb +13 -13
  19. data/lib/dor/exceptions.rb +2 -2
  20. data/lib/dor/indexers/data_indexer.rb +1 -7
  21. data/lib/dor/indexers/describable_indexer.rb +1 -1
  22. data/lib/dor/indexers/identifiable_indexer.rb +0 -2
  23. data/lib/dor/indexers/processable_indexer.rb +55 -28
  24. data/lib/dor/indexers/releasable_indexer.rb +2 -2
  25. data/lib/dor/models/admin_policy_object.rb +4 -4
  26. data/lib/dor/models/concerns/assembleable.rb +4 -0
  27. data/lib/dor/models/concerns/contentable.rb +27 -69
  28. data/lib/dor/models/concerns/describable.rb +14 -29
  29. data/lib/dor/models/concerns/editable.rb +20 -334
  30. data/lib/dor/models/concerns/embargoable.rb +7 -11
  31. data/lib/dor/models/concerns/eventable.rb +5 -1
  32. data/lib/dor/models/concerns/geoable.rb +4 -4
  33. data/lib/dor/models/concerns/governable.rb +18 -87
  34. data/lib/dor/models/concerns/identifiable.rb +15 -75
  35. data/lib/dor/models/concerns/itemizable.rb +9 -11
  36. data/lib/dor/models/concerns/preservable.rb +4 -0
  37. data/lib/dor/models/concerns/processable.rb +30 -129
  38. data/lib/dor/models/concerns/publishable.rb +6 -55
  39. data/lib/dor/models/concerns/releaseable.rb +14 -227
  40. data/lib/dor/models/concerns/rightsable.rb +3 -3
  41. data/lib/dor/models/concerns/shelvable.rb +4 -49
  42. data/lib/dor/models/concerns/versionable.rb +21 -44
  43. data/lib/dor/models/set.rb +1 -1
  44. data/lib/dor/models/workflow_object.rb +2 -2
  45. data/lib/dor/services/ability.rb +77 -0
  46. data/lib/dor/services/cleanup_reset_service.rb +1 -3
  47. data/lib/dor/services/create_workflow_service.rb +51 -0
  48. data/lib/dor/services/creative_commons_license_service.rb +31 -0
  49. data/lib/dor/services/datastream_builder.rb +90 -0
  50. data/lib/dor/services/digital_stacks_service.rb +3 -21
  51. data/lib/dor/services/dublin_core_service.rb +40 -0
  52. data/lib/dor/services/file_metadata_merge_service.rb +67 -0
  53. data/lib/dor/services/indexing_service.rb +8 -4
  54. data/lib/dor/services/merge_service.rb +5 -5
  55. data/lib/dor/services/metadata_handlers/catalog_handler.rb +1 -1
  56. data/lib/dor/services/metadata_service.rb +6 -8
  57. data/lib/dor/{models/concerns → services}/mods2dc.xslt +0 -0
  58. data/lib/dor/services/ontology.rb +35 -0
  59. data/lib/dor/services/open_data_license_service.rb +20 -0
  60. data/lib/dor/services/public_desc_metadata_service.rb +21 -14
  61. data/lib/dor/services/public_xml_service.rb +6 -6
  62. data/lib/dor/services/publish_metadata_service.rb +100 -0
  63. data/lib/dor/services/registration_service.rb +43 -46
  64. data/lib/dor/services/release_tag_service.rb +251 -0
  65. data/lib/dor/services/reset_workspace_service.rb +1 -3
  66. data/lib/dor/services/sdr_ingest_service.rb +5 -7
  67. data/lib/dor/services/search_service.rb +10 -10
  68. data/lib/dor/services/secondary_file_name_service.rb +10 -0
  69. data/lib/dor/services/shelving_service.rb +67 -0
  70. data/lib/dor/services/status_service.rb +121 -0
  71. data/lib/dor/services/suri_service.rb +3 -5
  72. data/lib/dor/services/tag_service.rb +100 -0
  73. data/lib/dor/services/technical_metadata_service.rb +5 -4
  74. data/lib/dor/services/version_service.rb +84 -0
  75. data/lib/dor/utils/ng_tidy.rb +1 -1
  76. data/lib/dor/utils/sdr_client.rb +25 -9
  77. data/lib/dor/version.rb +1 -1
  78. data/lib/dor/workflow/document.rb +13 -13
  79. data/lib/dor/workflow/process.rb +71 -26
  80. data/lib/tasks/rdoc.rake +1 -1
  81. metadata +77 -51
  82. data/config/certs/robots-dor-dev.crt +0 -29
  83. data/config/certs/robots-dor-dev.key +0 -27
  84. 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 |severity, datetime, progname, msg|
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 entry_id_block.call rescue '---' end
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 "Dor::IndexingService.reindex_pid with primitive arguments is deprecated; pass e.g. { logger: logger, raise_errors: bool } instead"
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
- @primary.copy_file_resources @secondary_pids
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 { |id| id.value }.each do |file_id|
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 = secondary.new_secondary_file_name(file_id, sequence)
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
- alias_method :decomission_secondaries, :decommission_secondaries
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 { |n| n.text }.join(' ').strip
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 < Exception; end
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.keys.include?(prefix.to_sym)
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| self.can_resolve?(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 { |handler_file|
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
- add_collection_reference!
20
- add_access_conditions! if include_access_conditions
21
- add_constituent_relations!
22
- strip_comments!
23
-
24
- new_doc = Nokogiri::XML(doc.to_xml) { |x| x.noblanks }
25
- new_doc.encoding = 'UTF-8'
26
- new_doc.to_xml
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 { |n| n.remove }
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, :type => 'useAndReproduction')
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, :type => 'copyright')
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, :type => 'license')
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, :type => 'license')
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! collection_druid
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(Nokogiri::XML(object.generate_public_desc_md).root)
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) { |x| x.noblanks }
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'], :to => project)
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
- fail ArgumentError, "Malformed externalFile data: #{externalFile.inspect}" if [src_resource_id, src_file_id, src_druid].map(&:blank?).any?
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