dor-services 6.6.2 → 6.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 68389296f84eb896aaff7f6916569e2fc2a39fee502218fa49cc6f96ccdf3793
4
- data.tar.gz: d3bb35c8b8b3a5f3554731dca051bdd07a848b0e8721841411b40226e9a6e09e
3
+ metadata.gz: 49efa35d39eee0748121dad21f44e871d6ea38eff28dc1719f7d13061eaab269
4
+ data.tar.gz: d1e0418a853a958966e7247e9c2ca0ceaf0739784ee3da2e3a0ab1a4f8a2f67d
5
5
  SHA512:
6
- metadata.gz: 2d921dfca2a8c757421582696d32c62059fb7723b6350a449a3bbc32cb8c8021b00ed663458cc9d8f97b0c7eef2dc22b0bb0443bc62e957c570bf73467754a1b
7
- data.tar.gz: bdad1ef88346944263a4e39adb245c4843fbae6343ed00339c9889e806bb60008aaa02c8d3079eefae91ca973a9c0e8e5cbf0824982ca473968a643a3cc83ded
6
+ metadata.gz: 37ac6a9d98625fb2272a5e541d6cf99d4b3f6d7e615c000b22aa6c51b49ed917b912a30f77c293da356107f2418fa60d5c70a5a3b84f31bb52fa149b13f3601d
7
+ data.tar.gz: 2401c8bfef4aa070e5672227e67c53a29dee353dbe58fa73922f4a3409d1b23f0ad8baf9d5e30fa4c4e0d87062e0c38d37fbc0ac74cf31fd740d6d33fba84fea
@@ -37,7 +37,7 @@ Dor::Config.configure do
37
37
  end
38
38
 
39
39
  solr.url 'https://host/solr'
40
- workflow.url 'https://host/workflow/'
40
+ workflow.url 'https://workflow.example.edu/'
41
41
  dor_services.url 'https://host/dor/v1'
42
42
 
43
43
  cleanup do
data/lib/dor-services.rb CHANGED
@@ -91,6 +91,7 @@ module Dor
91
91
  autoload :ProcessableIndexer
92
92
  autoload :ReleasableIndexer
93
93
  autoload :WorkflowIndexer
94
+ autoload :WorkflowsIndexer
94
95
  end
95
96
 
96
97
  # datastreams
@@ -144,6 +145,7 @@ module Dor
144
145
  autoload :Collection
145
146
  autoload :AdminPolicyObject
146
147
  autoload :WorkflowObject
148
+ autoload :WorkflowSolrDocument
147
149
  end
148
150
  end
149
151
 
@@ -155,6 +157,7 @@ module Dor
155
157
  autoload :CreateWorkflowService
156
158
  autoload :CreativeCommonsLicenseService
157
159
  autoload :DatastreamBuilder
160
+ autoload :DecommissionService
158
161
  autoload :DigitalStacksService
159
162
  autoload :DublinCoreService
160
163
  autoload :FileMetadataMergeService
@@ -35,7 +35,7 @@ module Dor
35
35
  end
36
36
 
37
37
  def get_workflow(wf, repo = 'dor')
38
- xml = Dor::Config.workflow.client.get_workflow_xml(repo, pid, wf)
38
+ xml = Dor::Config.workflow.client.workflow_xml(repo, pid, wf)
39
39
  xml = Nokogiri::XML(xml)
40
40
  return nil if xml.xpath('workflow').length == 0
41
41
 
@@ -55,14 +55,16 @@ module Dor
55
55
  # service directly
56
56
  def content(refresh = false)
57
57
  @content = nil if refresh
58
- @content ||= Dor::Config.workflow.client.get_workflow_xml 'dor', pid, nil
58
+ @content ||= Dor::Config.workflow.client.all_workflows_xml pid
59
59
  rescue Dor::WorkflowException => e
60
+ # TODO: I don't understand when this would be useful as this block ends up calling the workflow service too.
61
+ # Why not just raise an exception here?
60
62
  Dor.logger.warn "Unable to connect to the workflow service #{e}. Falling back to placeholder XML"
61
63
  xml = Nokogiri::XML(%(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<workflows objectId="#{pid}"/>))
62
64
  digital_object.datastreams.keys.each do |dsid|
63
65
  next unless dsid =~ /WF$/
64
66
 
65
- ds_content = Nokogiri::XML(Dor::Config.workflow.client.get_workflow_xml('dor', pid, dsid))
67
+ ds_content = Nokogiri::XML(Dor::Config.workflow.client.workflow_xml('dor', pid, dsid))
66
68
  xml.root.add_child(ds_content.root)
67
69
  end
68
70
  @content ||= xml.to_xml
@@ -83,7 +85,7 @@ module Dor
83
85
  end
84
86
 
85
87
  def to_solr(solr_doc = {}, *_args)
86
- # noop - indexing is done by the WorkflowIndexer
88
+ # noop - indexing is done by the WorkflowsIndexer
87
89
  solr_doc
88
90
  end
89
91
 
@@ -11,6 +11,9 @@ module Dor
11
11
  # rubocop:disable Lint/InheritException
12
12
  # See https://github.com/rubocop-hq/rubocop/issues/6770
13
13
  class VersionAlreadyOpenError < Exception; end
14
+
15
+ # Raised when we can't get a response from the catalog
16
+ class BadResponseFromCatalog < Exception; end
14
17
  # rubocop:enable Lint/InheritException
15
18
 
16
19
  class DuplicateIdError < RuntimeError
@@ -3,25 +3,82 @@
3
3
  module Dor
4
4
  # Indexes the objects position in workflows
5
5
  class WorkflowIndexer
6
- include SolrDocHelper
6
+ ERROR_OMISSION = '... (continued)'
7
+ private_constant :ERROR_OMISSION
7
8
 
8
- attr_reader :resource
9
- def initialize(resource:)
10
- @resource = resource
9
+ # see https://lucene.apache.org/core/7_3_1/core/org/apache/lucene/util/BytesRefHash.MaxBytesLengthExceededException.html
10
+ MAX_ERROR_LENGTH = 32_768 - 2 - ERROR_OMISSION.length
11
+ private_constant :MAX_ERROR_LENGTH
12
+
13
+ # @param [Dor::WorkflowDocument] document the workflow document to index
14
+ def initialize(document:)
15
+ @document = document
11
16
  end
12
17
 
13
- # @return [Hash] the partial solr document for workflow concerns
18
+ # @return [Hash] the partial solr document for the workflow document
14
19
  def to_solr
15
- {}.tap do |solr_doc|
16
- workflows.each { |wf| solr_doc = wf.to_solr(solr_doc) }
20
+ WorkflowSolrDocument.new do |solr_doc|
21
+ wf_name = document.workflowId.first
22
+ solr_doc.name = wf_name
23
+ errors = processes.count(&:error?)
24
+
25
+ repo = document.repository.first
26
+ solr_doc.status = [wf_name, workflow_status, errors, repo].join('|')
27
+
28
+ processes.each do |process|
29
+ index_process(solr_doc, wf_name, process)
30
+ end
17
31
  end
18
32
  end
19
33
 
20
34
  private
21
35
 
22
- # @return [Array<Dor::WorkflowDocument>]
23
- def workflows
24
- resource.workflows.workflows
36
+ attr_reader :document
37
+ delegate :processes, to: :document
38
+
39
+ def index_process(solr_doc, wf_name, process)
40
+ return unless process.status.present?
41
+
42
+ # add a record of the robot having operated on this item, so we can track robot activity
43
+ solr_doc.add_process_time(wf_name, process.name, Time.parse(process.date_time)) if process_has_time?(process)
44
+
45
+ index_error_message(solr_doc, wf_name, process)
46
+
47
+ # workflow name, process status then process name
48
+ solr_doc.add_wps("#{wf_name}:#{process.status}", "#{wf_name}:#{process.status}:#{process.name}")
49
+
50
+ # workflow name, process name then process status
51
+ solr_doc.add_wps("#{wf_name}:#{process.name}", "#{wf_name}:#{process.name}:#{process.status}")
52
+
53
+ # process status, workflowname then process name
54
+ solr_doc.add_swp(process.status.to_s, "#{process.status}:#{wf_name}", "#{process.status}:#{wf_name}:#{process.name}")
55
+ return if process.state == process.status
56
+
57
+ solr_doc.add_wsp("#{wf_name}:#{process.state}:#{process.name}")
58
+ solr_doc.add_wps("#{wf_name}:#{process.name}:#{process.state}")
59
+ solr_doc.add_swp(process.state.to_s, "#{process.state}:#{wf_name}", "#{process.state}:#{wf_name}:#{process.name}")
60
+ end
61
+
62
+ def process_has_time?(process)
63
+ !process.date_time.blank? && process.status && (process.status == 'completed' || process.status == 'error')
64
+ end
65
+
66
+ def workflow_status
67
+ return 'empty' if processes.empty?
68
+
69
+ workflow_should_show_completed?(processes) ? 'completed' : 'active'
70
+ end
71
+
72
+ def workflow_should_show_completed?(processes)
73
+ processes.all? { |p| ['skipped', 'completed', '', nil].include?(p.status) }
74
+ end
75
+
76
+ # index the error message without the druid so we hopefully get some overlap
77
+ # truncate to avoid org.apache.lucene.util.BytesRefHash$MaxBytesLengthExceededException
78
+ def index_error_message(solr_doc, wf_name, process)
79
+ return unless process.error_message
80
+
81
+ solr_doc.error = "#{wf_name}:#{process.name}:#{process.error_message}".truncate(MAX_ERROR_LENGTH, omission: ERROR_OMISSION)
25
82
  end
26
83
  end
27
84
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dor
4
+ # Indexes the objects position in workflows
5
+ class WorkflowsIndexer
6
+ attr_reader :resource
7
+ def initialize(resource:)
8
+ @resource = resource
9
+ end
10
+
11
+ # @return [Hash] the partial solr document for workflow concerns
12
+ def to_solr
13
+ WorkflowSolrDocument.new do |combined_doc|
14
+ workflows.each do |wf|
15
+ doc = WorkflowIndexer.new(document: wf).to_solr
16
+ combined_doc.merge!(doc)
17
+ end
18
+ end.to_h
19
+ end
20
+
21
+ private
22
+
23
+ # @return [Array<Dor::WorkflowDocument>]
24
+ def workflows
25
+ resource.workflows.workflows
26
+ end
27
+ end
28
+ end
@@ -15,7 +15,7 @@ module Dor
15
15
  EditableIndexer,
16
16
  IdentifiableIndexer,
17
17
  ProcessableIndexer,
18
- WorkflowIndexer
18
+ WorkflowsIndexer
19
19
  )
20
20
 
21
21
  CREATIVE_COMMONS_USE_LICENSES = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('CREATIVE_COMMONS_USE_LICENSES', 'Dor::CreativeCommonsLicenseService')
@@ -12,7 +12,7 @@ module Dor
12
12
  IdentifiableIndexer,
13
13
  ProcessableIndexer,
14
14
  ReleasableIndexer,
15
- WorkflowIndexer
15
+ WorkflowsIndexer
16
16
  )
17
17
  end
18
18
  end
@@ -170,27 +170,16 @@ module Dor
170
170
  # Clears RELS-EXT relationships, sets the isGovernedBy relationship to the SDR Graveyard APO
171
171
  # @param [String] tag optional String of text that is concatenated to the identityMetadata/tag "Decommissioned : "
172
172
  def decommission(tag)
173
- # remove isMemberOf and isMemberOfCollection relationships
174
- clear_relationship :is_member_of
175
- clear_relationship :is_member_of_collection
176
- # remove isGovernedBy relationship
177
- clear_relationship :is_governed_by
178
- # add isGovernedBy to graveyard APO druid:sw909tc7852
179
- # SEARCH BY dc title for 'SDR Graveyard'
180
- add_relationship :is_governed_by, ActiveFedora::Base.find(Dor::SearchService.sdr_graveyard_apo_druid)
181
- # eliminate contentMetadata. set it to <contentMetadata/> ?
182
- contentMetadata.content = '<contentMetadata/>'
183
- # eliminate rightsMetadata. set it to <rightsMetadata/> ?
184
- rightsMetadata.content = '<rightsMetadata/>'
185
- TagService.add self, "Decommissioned : #{tag}"
173
+ DecommissionService.new(self).decommission(tag)
186
174
  end
175
+ deprecation_deprecate decommission: 'Use DecommissionService#decommission instead'
187
176
 
188
- # TODO: Move to Dor-Utils.
189
177
  # Adds a RELS-EXT constituent relationship to the given druid
190
178
  # @param [String] druid the parent druid of the constituent relationship
191
179
  # e.g.: <fedora:isConstituentOf rdf:resource="info:fedora/druid:hj097bm8879" />
192
180
  def add_constituent(druid)
193
181
  add_relationship :is_constituent_of, ActiveFedora::Base.find(druid)
194
182
  end
183
+ deprecation_deprecate add_constituent: 'Use add_relationship :is_constituent_of instead'
195
184
  end
196
185
  end
@@ -49,9 +49,9 @@ module Dor
49
49
  # @return [Boolean] true if the object is in a state that allows it to be modified.
50
50
  # States that will allow modification are: has not been submitted for accessioning, has an open version or has sdr-ingest set to hold
51
51
  def allows_modification?
52
- if Dor::Config.workflow.client.get_lifecycle('dor', pid, 'submitted') &&
52
+ if Dor::Config.workflow.client.lifecycle('dor', pid, 'submitted') &&
53
53
  !VersionService.new(self).open? &&
54
- Dor::Config.workflow.client.get_workflow_status('dor', pid, 'accessionWF', 'sdr-ingest-transfer') != 'hold'
54
+ Dor::Config.workflow.client.workflow_status('dor', pid, 'accessionWF', 'sdr-ingest-transfer') != 'hold'
55
55
  false
56
56
  else
57
57
  true
@@ -19,7 +19,7 @@ module Dor
19
19
  IdentifiableIndexer,
20
20
  ProcessableIndexer,
21
21
  ReleasableIndexer,
22
- WorkflowIndexer
22
+ WorkflowsIndexer
23
23
  )
24
24
 
25
25
  has_metadata name: 'technicalMetadata', type: TechnicalMetadataDS, label: 'Technical Metadata', control_group: 'M'
@@ -12,7 +12,7 @@ module Dor
12
12
  DescribableIndexer,
13
13
  IdentifiableIndexer,
14
14
  ProcessableIndexer,
15
- WorkflowIndexer
15
+ WorkflowsIndexer
16
16
  )
17
17
  end
18
18
  end
@@ -15,7 +15,7 @@ module Dor
15
15
  DescribableIndexer,
16
16
  IdentifiableIndexer,
17
17
  ProcessableIndexer,
18
- WorkflowIndexer
18
+ WorkflowsIndexer
19
19
  )
20
20
 
21
21
  def self.find_by_name(name)
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dor
4
+ # Represents that part of the solr document that holds workflow data
5
+ class WorkflowSolrDocument
6
+ WORKFLOW_SOLR = 'wf_ssim'
7
+ # field that indexes workflow name, process status then process name
8
+ WORKFLOW_WPS_SOLR = 'wf_wps_ssim'
9
+ # field that indexes workflow name, process name then process status
10
+ WORKFLOW_WSP_SOLR = 'wf_wsp_ssim'
11
+ # field that indexes process status, workflowname then process name
12
+ WORKFLOW_SWP_SOLR = 'wf_swp_ssim'
13
+ WORKFLOW_ERROR_SOLR = 'wf_error_ssim'
14
+ WORKFLOW_STATUS_SOLR = 'workflow_status_ssim'
15
+
16
+ KEYS_TO_MERGE = [
17
+ WORKFLOW_SOLR,
18
+ WORKFLOW_WPS_SOLR,
19
+ WORKFLOW_WSP_SOLR,
20
+ WORKFLOW_SWP_SOLR,
21
+ WORKFLOW_STATUS_SOLR,
22
+ WORKFLOW_ERROR_SOLR
23
+ ].freeze
24
+
25
+ def initialize
26
+ @data = empty_document
27
+ yield self if block_given?
28
+ end
29
+
30
+ def name=(wf_name)
31
+ data[WORKFLOW_SOLR] += [wf_name]
32
+ data[WORKFLOW_WPS_SOLR] += [wf_name]
33
+ data[WORKFLOW_WSP_SOLR] += [wf_name]
34
+ end
35
+
36
+ def status=(status)
37
+ data[WORKFLOW_STATUS_SOLR] += [status]
38
+ end
39
+
40
+ def error=(message)
41
+ data[WORKFLOW_ERROR_SOLR] += [message]
42
+ end
43
+
44
+ # Add to the field that indexes workflow name, process status then process name
45
+ def add_wps(*messages)
46
+ data[WORKFLOW_WPS_SOLR] += messages
47
+ end
48
+
49
+ # Add to the field that indexes workflow name, process name then process status
50
+ def add_wsp(*messages)
51
+ data[WORKFLOW_WSP_SOLR] += messages
52
+ end
53
+
54
+ # Add to the field that indexes process status, workflow name then process name
55
+ def add_swp(*messages)
56
+ data[WORKFLOW_SWP_SOLR] += messages
57
+ end
58
+
59
+ # Add the processes data_time attribute to the solr document
60
+ # @param [String] wf_name
61
+ # @param [String] process_name
62
+ # @param [Time] time
63
+ def add_process_time(wf_name, process_name, time)
64
+ data["wf_#{wf_name}_#{process_name}_dttsi"] = time.utc.iso8601
65
+ end
66
+
67
+ def to_h
68
+ KEYS_TO_MERGE.each { |k| data[k].uniq! }
69
+ data
70
+ end
71
+
72
+ delegate :except, :[], to: :data
73
+
74
+ # @param [WorkflowSolrDocument] doc
75
+ def merge!(doc)
76
+ # This is going to get the date fields, e.g. `wf_assemblyWF_jp2-create_dttsi'
77
+ @data.merge!(doc.except(*KEYS_TO_MERGE))
78
+
79
+ # Combine the non-unique fields together
80
+ KEYS_TO_MERGE.each do |k|
81
+ data[k] += doc[k]
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ attr_reader :data
88
+
89
+ def empty_document
90
+ KEYS_TO_MERGE.each_with_object({}) { |k, obj| obj[k] = [] }
91
+ end
92
+ end
93
+ end
@@ -19,7 +19,7 @@ module Dor
19
19
  last_version = druid_obj.current_version.to_i
20
20
 
21
21
  # if the current version is still open, avoid this versioned directory
22
- last_version -= 1 if Dor::Config.workflow.client.get_lifecycle('dor', druid, 'accessioned').nil?
22
+ last_version -= 1 if Dor::Config.workflow.client.lifecycle('dor', druid, 'accessioned').nil?
23
23
  last_version
24
24
  end
25
25
 
@@ -70,7 +70,7 @@ module Dor
70
70
 
71
71
  def self.remove_active_workflows(druid)
72
72
  %w(dor sdr).each do |repo|
73
- dor_wfs = Dor::Config.workflow.client.get_workflows(druid, repo)
73
+ dor_wfs = Dor::Config.workflow.client.workflows(druid, repo)
74
74
  dor_wfs.each { |wf| Dor::Config.workflow.client.delete_workflow(repo, druid, wf) }
75
75
  end
76
76
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dor
4
+ # Responsible for decommissioning objects
5
+ class DecommissionService
6
+ # @param [Dor::Item] object
7
+ def initialize(object)
8
+ @object = object
9
+ end
10
+
11
+ attr_reader :object
12
+
13
+ # Clears RELS-EXT relationships, sets the isGovernedBy relationship to the SDR Graveyard APO
14
+ # @param [String] tag optional String of text that is concatenated to the identityMetadata/tag "Decommissioned : "
15
+ def decommission(tag)
16
+ # remove isMemberOf and isMemberOfCollection relationships
17
+ object.clear_relationship :is_member_of
18
+ object.clear_relationship :is_member_of_collection
19
+ # remove isGovernedBy relationship
20
+ object.clear_relationship :is_governed_by
21
+ # add isGovernedBy to graveyard APO druid:sw909tc7852
22
+ # SEARCH BY dc title for 'SDR Graveyard'
23
+ object.add_relationship :is_governed_by, ActiveFedora::Base.find(Dor::SearchService.sdr_graveyard_apo_druid)
24
+ # eliminate contentMetadata. set it to <contentMetadata/> ?
25
+ object.contentMetadata.content = '<contentMetadata/>'
26
+ # eliminate rightsMetadata. set it to <rightsMetadata/> ?
27
+ object.rightsMetadata.content = '<rightsMetadata/>'
28
+ TagService.add object, "Decommissioned : #{tag}"
29
+ end
30
+ end
31
+ end
@@ -2,12 +2,15 @@
2
2
 
3
3
  require 'rest-client'
4
4
 
5
- handler = Class.new do
5
+ class CatalogHandler
6
6
  def fetch(prefix, identifier)
7
7
  client = RestClient::Resource.new(Dor::Config.metadata.catalog.url,
8
8
  Dor::Config.metadata.catalog.user,
9
9
  Dor::Config.metadata.catalog.pass)
10
- client["?#{prefix.chomp}=#{identifier.chomp}"].get
10
+ params = "?#{prefix.chomp}=#{identifier.chomp}"
11
+ client[params].get
12
+ rescue RestClient::Exception => e
13
+ raise BadResponseFromCatalog, "#{e.class} - when contacting (with BasicAuth hidden): #{Dor::Config.metadata.catalog.url}#{params}"
11
14
  end
12
15
 
13
16
  def label(metadata)
@@ -20,5 +23,3 @@ handler = Class.new do
20
23
  %w(catkey barcode)
21
24
  end
22
25
  end
23
-
24
- Dor::MetadataService.register(handler)
@@ -1,37 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'cache'
4
+ require 'dor/services/metadata_handlers/catalog_handler'
4
5
 
5
6
  module Dor
6
7
  class MetadataError < RuntimeError; end
7
8
 
8
- # class MetadataHandler
9
- #
10
- # def fetch(prefix, identifier)
11
- # ### Return metadata for prefix/identifier combo
12
- # end
13
- #
14
- # def label(metadata)
15
- # ### Return a Fedora-compatible label from the metadata format returned by #fetch
16
- # end
17
- #
18
- # end
19
-
20
9
  class MetadataService
21
10
  class << self
22
11
  @@cache = Cache.new(nil, nil, 250, 300)
23
12
 
24
- def register(handler_class)
25
- %w(fetch label prefixes).each do |method|
26
- raise TypeError, "Metadata handlers must define ##{method}" unless handler_class.instance_methods.include?(method) || handler_class.instance_methods.include?(method.to_sym)
27
- end
28
- handler = handler_class.new
29
- handler.prefixes.each do |prefix|
30
- handlers[prefix.to_sym] = handler
31
- end
32
- handler
33
- end
34
-
35
13
  def known_prefixes
36
14
  handlers.keys
37
15
  end
@@ -62,7 +40,7 @@ module Dor
62
40
 
63
41
  def handler_for(prefix)
64
42
  handler = handlers[prefix.to_sym]
65
- raise MetadataError, "Unkown metadata prefix: #{prefix}" if handler.nil?
43
+ raise MetadataError, "Unknown metadata prefix: #{prefix}" if handler.nil?
66
44
 
67
45
  handler
68
46
  end
@@ -70,12 +48,17 @@ module Dor
70
48
  private
71
49
 
72
50
  def handlers
73
- @handlers ||= {}
51
+ @handlers ||= {}.tap do |md_handlers|
52
+ # There's only one. If additional handlers are added, will need to be registered here.
53
+ register(CatalogHandler.new, md_handlers)
54
+ end
55
+ end
56
+
57
+ def register(handler, md_handlers)
58
+ handler.prefixes.each do |prefix|
59
+ md_handlers[prefix.to_sym] = handler
60
+ end
74
61
  end
75
62
  end
76
63
  end
77
64
  end
78
-
79
- Dir[File.join(File.dirname(__FILE__), 'metadata_handlers', '*.rb')].each do |handler_file|
80
- load handler_file
81
- end
@@ -76,7 +76,7 @@ module Dor
76
76
  end
77
77
 
78
78
  def milestones
79
- @milestones ||= Dor::Config.workflow.client.get_milestones('dor', work.pid)
79
+ @milestones ||= Dor::Config.workflow.client.milestones('dor', work.pid)
80
80
  end
81
81
 
82
82
  private
@@ -24,9 +24,9 @@ module Dor
24
24
  def open(opts = {})
25
25
  # During local development, we need a way to open a new version even if the object has not been accessioned.
26
26
  raise(Dor::Exception, 'Object net yet accessioned') unless
27
- opts[:assume_accessioned] || Dor::Config.workflow.client.get_lifecycle('dor', work.pid, 'accessioned')
27
+ opts[:assume_accessioned] || Dor::Config.workflow.client.lifecycle('dor', work.pid, 'accessioned')
28
28
  raise Dor::VersionAlreadyOpenError, 'Object already opened for versioning' if open?
29
- raise Dor::Exception, 'Object currently being accessioned' if Dor::Config.workflow.client.get_active_lifecycle('dor', work.pid, 'submitted')
29
+ raise Dor::Exception, 'Object currently being accessioned' if Dor::Config.workflow.client.active_lifecycle('dor', work.pid, 'submitted')
30
30
 
31
31
  sdr_version = Sdr::Client.current_version work.pid
32
32
 
@@ -67,14 +67,14 @@ module Dor
67
67
 
68
68
  raise Dor::Exception, 'latest version in versionMetadata requires tag and description before it can be closed' unless work.versionMetadata.current_version_closeable?
69
69
  raise Dor::Exception, 'Trying to close version on an object not opened for versioning' unless open?
70
- raise Dor::Exception, 'accessionWF already created for versioned object' if Dor::Config.workflow.client.get_active_lifecycle('dor', work.pid, 'submitted')
70
+ raise Dor::Exception, 'accessionWF already created for versioned object' if Dor::Config.workflow.client.active_lifecycle('dor', work.pid, 'submitted')
71
71
 
72
72
  Dor::Config.workflow.client.close_version 'dor', work.pid, opts.fetch(:start_accession, true) # Default to creating accessionWF when calling close_version
73
73
  end
74
74
 
75
75
  # @return [Boolean] true if 'opened' lifecycle is active, false otherwise
76
76
  def open?
77
- return true if Dor::Config.workflow.client.get_active_lifecycle('dor', work.pid, 'opened')
77
+ return true if Dor::Config.workflow.client.active_lifecycle('dor', work.pid, 'opened')
78
78
 
79
79
  false
80
80
  end
data/lib/dor/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dor
4
- VERSION = '6.6.2'
4
+ VERSION = '6.7.0'
5
5
  end
@@ -4,21 +4,8 @@ module Dor
4
4
  module Workflow
5
5
  class Document
6
6
  extend Deprecation
7
- include SolrDocHelper
8
7
  include ::OM::XML::Document
9
8
 
10
- ERROR_OMISSION = '... (continued)'
11
- private_constant :ERROR_OMISSION
12
-
13
- # see https://lucene.apache.org/core/7_3_1/core/org/apache/lucene/util/BytesRefHash.MaxBytesLengthExceededException.html
14
- MAX_ERROR_LENGTH = 32_768 - 2 - ERROR_OMISSION.length
15
- private_constant :MAX_ERROR_LENGTH
16
-
17
- WF_SOLR_TYPE = :string
18
- private_constant :WF_SOLR_TYPE
19
- WF_SOLR_ATTRS = [:symbol].freeze
20
- private_constant :WF_SOLR_ATTRS
21
-
22
9
  set_terminology do |t|
23
10
  t.root(path: 'workflow')
24
11
  t.repository(path: { attribute: 'repository' })
@@ -93,69 +80,9 @@ module Dor
93
80
  end
94
81
  end
95
82
 
96
- def workflow_should_show_completed?(processes)
97
- processes.all? { |p| ['skipped', 'completed', '', nil].include?(p.status) }
98
- end
99
-
100
- def to_solr(solr_doc = {}, *_args)
101
- wf_name = workflowId.first
102
- repo = repository.first
103
-
104
- add_solr_value(solr_doc, 'wf', wf_name, WF_SOLR_TYPE, WF_SOLR_ATTRS)
105
- add_solr_value(solr_doc, 'wf_wps', wf_name, WF_SOLR_TYPE, WF_SOLR_ATTRS)
106
- add_solr_value(solr_doc, 'wf_wsp', wf_name, WF_SOLR_TYPE, WF_SOLR_ATTRS)
107
- status = processes.empty? ? 'empty' : (workflow_should_show_completed?(processes) ? 'completed' : 'active')
108
- errors = processes.count(&:error?)
109
- add_solr_value(solr_doc, 'workflow_status', [wf_name, status, errors, repo].join('|'), WF_SOLR_TYPE, WF_SOLR_ATTRS)
110
-
111
- processes.each do |process|
112
- next unless process.status.present?
113
-
114
- # add a record of the robot having operated on this item, so we can track robot activity
115
- if !process.date_time.blank? && process.status && (process.status == 'completed' || process.status == 'error')
116
- solr_doc["wf_#{wf_name}_#{process.name}_dttsi"] = Time.parse(process.date_time).utc.iso8601
117
- end
118
-
119
- index_error_message(solr_doc, wf_name, process)
120
-
121
- add_solr_value(solr_doc, 'wf_wsp', "#{wf_name}:#{process.status}", WF_SOLR_TYPE, WF_SOLR_ATTRS)
122
- add_solr_value(solr_doc, 'wf_wsp', "#{wf_name}:#{process.status}:#{process.name}", WF_SOLR_TYPE, WF_SOLR_ATTRS)
123
- add_solr_value(solr_doc, 'wf_wps', "#{wf_name}:#{process.name}", WF_SOLR_TYPE, WF_SOLR_ATTRS)
124
- add_solr_value(solr_doc, 'wf_wps', "#{wf_name}:#{process.name}:#{process.status}", WF_SOLR_TYPE, WF_SOLR_ATTRS)
125
- add_solr_value(solr_doc, 'wf_swp', process.status.to_s, WF_SOLR_TYPE, WF_SOLR_ATTRS)
126
- add_solr_value(solr_doc, 'wf_swp', "#{process.status}:#{wf_name}", WF_SOLR_TYPE, WF_SOLR_ATTRS)
127
- add_solr_value(solr_doc, 'wf_swp', "#{process.status}:#{wf_name}:#{process.name}", WF_SOLR_TYPE, WF_SOLR_ATTRS)
128
- next unless process.state != process.status
129
-
130
- add_solr_value(solr_doc, 'wf_wsp', "#{wf_name}:#{process.state}:#{process.name}", WF_SOLR_TYPE, WF_SOLR_ATTRS)
131
- add_solr_value(solr_doc, 'wf_wps', "#{wf_name}:#{process.name}:#{process.state}", WF_SOLR_TYPE, WF_SOLR_ATTRS)
132
- add_solr_value(solr_doc, 'wf_swp', process.state.to_s, WF_SOLR_TYPE, WF_SOLR_ATTRS)
133
- add_solr_value(solr_doc, 'wf_swp', "#{process.state}:#{wf_name}", WF_SOLR_TYPE, WF_SOLR_ATTRS)
134
- add_solr_value(solr_doc, 'wf_swp', "#{process.state}:#{wf_name}:#{process.name}", WF_SOLR_TYPE, WF_SOLR_ATTRS)
135
- end
136
-
137
- solr_doc[Solrizer.solr_name('wf_wps', :symbol)]&.uniq!
138
- solr_doc[Solrizer.solr_name('wf_wsp', :symbol)]&.uniq!
139
- solr_doc[Solrizer.solr_name('wf_swp', :symbol)]&.uniq!
140
- solr_doc['workflow_status']&.uniq!
141
-
142
- solr_doc
143
- end
144
-
145
83
  def inspect
146
84
  "#<#{self.class.name}:#{object_id}>"
147
85
  end
148
-
149
- private
150
-
151
- # index the error message without the druid so we hopefully get some overlap
152
- # truncate to avoid org.apache.lucene.util.BytesRefHash$MaxBytesLengthExceededException
153
- def index_error_message(solr_doc, wf_name, process)
154
- return unless process.error_message
155
-
156
- error_message = "#{wf_name}:#{process.name}:#{process.error_message}".truncate(MAX_ERROR_LENGTH, omission: ERROR_OMISSION)
157
- add_solr_value(solr_doc, 'wf_error', error_message, WF_SOLR_TYPE, WF_SOLR_ATTRS)
158
- end
159
86
  end
160
87
  end
161
88
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dor-services
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.6.2
4
+ version: 6.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Klein
@@ -14,7 +14,7 @@ authors:
14
14
  autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
- date: 2019-03-13 00:00:00.000000000 Z
17
+ date: 2019-04-03 00:00:00.000000000 Z
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
20
20
  name: active-fedora
@@ -290,20 +290,6 @@ dependencies:
290
290
  - - "~>"
291
291
  - !ruby/object:Gem::Version
292
292
  version: '2.6'
293
- - !ruby/object:Gem::Dependency
294
- name: uuidtools
295
- requirement: !ruby/object:Gem::Requirement
296
- requirements:
297
- - - "~>"
298
- - !ruby/object:Gem::Version
299
- version: 2.1.4
300
- type: :runtime
301
- prerelease: false
302
- version_requirements: !ruby/object:Gem::Requirement
303
- requirements:
304
- - - "~>"
305
- - !ruby/object:Gem::Version
306
- version: 2.1.4
307
293
  - !ruby/object:Gem::Dependency
308
294
  name: retries
309
295
  requirement: !ruby/object:Gem::Requirement
@@ -344,20 +330,14 @@ dependencies:
344
330
  requirements:
345
331
  - - "~>"
346
332
  - !ruby/object:Gem::Version
347
- version: '2.0'
348
- - - ">="
349
- - !ruby/object:Gem::Version
350
- version: 2.0.1
333
+ version: '2.11'
351
334
  type: :runtime
352
335
  prerelease: false
353
336
  version_requirements: !ruby/object:Gem::Requirement
354
337
  requirements:
355
338
  - - "~>"
356
339
  - !ruby/object:Gem::Version
357
- version: '2.0'
358
- - - ">="
359
- - !ruby/object:Gem::Version
360
- version: 2.0.1
340
+ version: '2.11'
361
341
  - !ruby/object:Gem::Dependency
362
342
  name: druid-tools
363
343
  requirement: !ruby/object:Gem::Requirement
@@ -624,6 +604,7 @@ files:
624
604
  - lib/dor/indexers/processable_indexer.rb
625
605
  - lib/dor/indexers/releasable_indexer.rb
626
606
  - lib/dor/indexers/workflow_indexer.rb
607
+ - lib/dor/indexers/workflows_indexer.rb
627
608
  - lib/dor/models/abstract.rb
628
609
  - lib/dor/models/admin_policy_object.rb
629
610
  - lib/dor/models/agreement.rb
@@ -647,6 +628,7 @@ files:
647
628
  - lib/dor/models/item.rb
648
629
  - lib/dor/models/set.rb
649
630
  - lib/dor/models/workflow_object.rb
631
+ - lib/dor/models/workflow_solr_document.rb
650
632
  - lib/dor/rest_resource_factory.rb
651
633
  - lib/dor/services/ability.rb
652
634
  - lib/dor/services/cleanup_reset_service.rb
@@ -654,6 +636,7 @@ files:
654
636
  - lib/dor/services/create_workflow_service.rb
655
637
  - lib/dor/services/creative_commons_license_service.rb
656
638
  - lib/dor/services/datastream_builder.rb
639
+ - lib/dor/services/decommission_service.rb
657
640
  - lib/dor/services/digital_stacks_service.rb
658
641
  - lib/dor/services/dublin_core_service.rb
659
642
  - lib/dor/services/file_metadata_merge_service.rb
@@ -667,7 +650,6 @@ files:
667
650
  - lib/dor/services/public_desc_metadata_service.rb
668
651
  - lib/dor/services/public_xml_service.rb
669
652
  - lib/dor/services/publish_metadata_service.rb
670
- - lib/dor/services/registration_service.rb
671
653
  - lib/dor/services/release_tag_service.rb
672
654
  - lib/dor/services/reset_workspace_service.rb
673
655
  - lib/dor/services/sdr_ingest_service.rb
@@ -1,202 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'uuidtools'
4
-
5
- module Dor
6
- class RegistrationService
7
- class << self
8
- # @TODO: Why isn't all this logic in, for example, Dor::Item.create? or Dor::Base.create? or Dor::Creatable.create?
9
- # @TODO: these duplicate checks could be combined into 1 query
10
-
11
- # @param [String] pid an ID to check, if desired. If not passed (or nil), a new ID is minted
12
- # @return [String] a pid you can use immidately, either freshly minted or your checked value
13
- # @raise [Dor::DuplicateIdError]
14
- def unduplicated_pid(pid = nil)
15
- return Dor::SuriService.mint_id unless pid
16
-
17
- existing_pid = SearchService.query_by_id(pid).first
18
- raise Dor::DuplicateIdError.new(existing_pid), "An object with the PID #{pid} has already been registered." unless existing_pid.nil?
19
-
20
- pid
21
- end
22
-
23
- # @param [String] source_id_string a fully qualified source:val or empty string
24
- # @return [String] the same qualified source:id for immediate use
25
- # @raise [Dor::DuplicateIdError]
26
- def check_source_id(source_id_string)
27
- return '' if source_id_string == ''
28
- unless SearchService.query_by_id(source_id_string.to_s).first.nil?
29
- raise Dor::DuplicateIdError.new(source_id_string), "An object with the source ID '#{source_id_string}' has already been registered."
30
- end
31
-
32
- source_id_string
33
- end
34
-
35
- # @param [Hash{Symbol => various}] params
36
- # @option params [String] :object_type required
37
- # @option params [String] :label required
38
- # @option params [String] :admin_policy required
39
- # @option params [String] :metadata_source
40
- # @option params [String] :rights
41
- # @option params [String] :collection
42
- # @option params [Hash{String => String}] :source_id Primary ID from another system, max one key/value pair!
43
- # @option params [Hash] :other_ids including :uuid if known
44
- # @option params [String] :pid Fully qualified PID if you don't want one generated for you
45
- # @option params [Integer] :workflow_priority]
46
- # @option params [Array<String>] :seed_datastream datastream_names
47
- # @option params [Array<String>] :initiate_workflow workflow_ids
48
- # @option params [Array] :tags
49
- def register_object(params = {})
50
- %i[object_type label].each do |required_param|
51
- raise Dor::ParameterError, "#{required_param.inspect} must be specified in call to #{name}.register_object" unless params[required_param]
52
- end
53
- metadata_source = params[:metadata_source]
54
- raise Dor::ParameterError, "label cannot be empty to call #{name}.register_object" if params[:label].length < 1 && %w[label none].include?(metadata_source)
55
-
56
- object_type = params[:object_type]
57
- item_class = Dor.registered_classes[object_type]
58
- raise Dor::ParameterError, "Unknown item type: '#{object_type}'" if item_class.nil?
59
-
60
- # content_model = params[:content_model]
61
- # parent = params[:parent]
62
- label = params[:label]
63
- source_id = params[:source_id] || {}
64
- other_ids = params[:other_ids] || {}
65
- tags = params[:tags] || []
66
- collection = params[:collection]
67
-
68
- # Check for sourceId conflict *before* potentially minting PID
69
- source_id_string = check_source_id [source_id.keys.first, source_id[source_id.keys.first]].compact.join(':')
70
- pid = unduplicated_pid(params[:pid])
71
-
72
- raise ArgumentError, ":source_id Hash can contain at most 1 pair: recieved #{source_id.size}" if source_id.size > 1
73
-
74
- rights = nil
75
- if params[:rights]
76
- rights = params[:rights]
77
- raise Dor::ParameterError, "Unknown rights setting '#{rights}' when calling #{name}.register_object" unless rights == 'default' || RightsMetadataDS.valid_rights_type?(rights)
78
- end
79
-
80
- other_ids[:uuid] = UUIDTools::UUID.timestamp_create.to_s if (other_ids.key?(:uuid) || other_ids.key?('uuid')) == false
81
- apo_object = Dor.find(params[:admin_policy])
82
- new_item = item_class.new(pid: pid)
83
- new_item.label = label.length > 254 ? label[0, 254] : label
84
- idmd = new_item.identityMetadata
85
- idmd.sourceId = source_id_string
86
- idmd.add_value(:objectId, pid)
87
- idmd.add_value(:objectCreator, 'DOR')
88
- idmd.add_value(:objectLabel, label)
89
- idmd.add_value(:objectType, object_type)
90
- other_ids.each_pair { |name, value| idmd.add_otherId("#{name}:#{value}") }
91
- tags.each { |tag| idmd.add_value(:tag, tag) }
92
- new_item.admin_policy_object = apo_object
93
-
94
- apo_object.administrativeMetadata.ng_xml.xpath('/administrativeMetadata/relationships/*').each do |rel|
95
- short_predicate = ActiveFedora::RelsExtDatastream.short_predicate rel.namespace.href + rel.name
96
- if short_predicate.nil?
97
- ix = 0
98
- ix += 1 while ActiveFedora::Predicates.predicate_mappings[rel.namespace.href].key?(short_predicate = :"extra_predicate_#{ix}")
99
- ActiveFedora::Predicates.predicate_mappings[rel.namespace.href][short_predicate] = rel.name
100
- end
101
- new_item.add_relationship short_predicate, rel['rdf:resource']
102
- end
103
- new_item.add_collection(collection) if collection
104
- if rights && %w(item collection).include?(object_type)
105
- rights_xml = apo_object.defaultObjectRights.ng_xml
106
- new_item.datastreams['rightsMetadata'].content = rights_xml.to_s
107
- new_item.set_read_rights(rights) unless rights == 'default' # already defaulted to default!
108
- end
109
- # create basic mods from the label
110
- build_desc_metadata_from_label(new_item, label) if metadata_source == 'label'
111
-
112
- workflow_priority = params[:workflow_priority] ? params[:workflow_priority].to_i : 0
113
-
114
- seed_datastreams(Array(params[:seed_datastream]), new_item)
115
- initiate_workflow(workflows: Array(params[:initiate_workflow]), item: new_item, priority: workflow_priority)
116
-
117
- new_item.class.ancestors.select { |x| x.respond_to?(:to_class_uri) && x != ActiveFedora::Base }.each do |parent_class|
118
- new_item.add_relationship(:has_model, parent_class.to_class_uri)
119
- end
120
-
121
- new_item.save
122
- new_item
123
- end
124
-
125
- # @param [Hash] params
126
- # @see register_object similar but different
127
- def create_from_request(params)
128
- other_ids = Array(params[:other_id]).map do |id|
129
- if id =~ /^symphony:(.+)$/
130
- "#{$1.length < 14 ? 'catkey' : 'barcode'}:#{$1}"
131
- else
132
- id
133
- end
134
- end
135
-
136
- if params[:label] == ':auto'
137
- params.delete(:label)
138
- params.delete('label')
139
- metadata_id = Dor::MetadataService.resolvable(other_ids).first
140
- params[:label] = Dor::MetadataService.label_for(metadata_id)
141
- end
142
-
143
- dor_params = {
144
- pid: params[:pid],
145
- admin_policy: params[:admin_policy],
146
- content_model: params[:model],
147
- label: params[:label],
148
- object_type: params[:object_type],
149
- other_ids: ids_to_hash(other_ids),
150
- parent: params[:parent],
151
- source_id: ids_to_hash(params[:source_id]),
152
- tags: params[:tag] || [],
153
- seed_datastream: params[:seed_datastream],
154
- initiate_workflow: Array(params[:initiate_workflow]) + Array(params[:workflow_id]),
155
- rights: params[:rights],
156
- metadata_source: params[:metadata_source],
157
- collection: params[:collection],
158
- workflow_priority: params[:workflow_priority]
159
- }
160
- dor_params.delete_if { |_k, v| v.nil? }
161
-
162
- dor_obj = register_object(dor_params)
163
- pid = dor_obj.pid
164
- location = URI.parse(Dor::Config.fedora.safeurl.sub(/\/*$/, '/')).merge("objects/#{pid}").to_s
165
- dor_params.dup.merge(location: location, pid: pid)
166
- end
167
-
168
- private
169
-
170
- def ids_to_hash(ids)
171
- return nil if ids.nil?
172
-
173
- Hash[Array(ids).map { |id| id.split(':', 2) }]
174
- end
175
-
176
- def seed_datastreams(names, item)
177
- names.each do |datastream_name|
178
- item.build_datastream(datastream_name)
179
- end
180
- end
181
-
182
- def initiate_workflow(workflows:, item:, priority:)
183
- workflows.each do |workflow_id|
184
- Dor::CreateWorkflowService.create_workflow(item, name: workflow_id,
185
- create_ds: !item.new_record?,
186
- priority: priority)
187
- end
188
- end
189
-
190
- def build_desc_metadata_from_label(new_item, label)
191
- builder = Nokogiri::XML::Builder.new do |xml|
192
- xml.mods(Dor::DescMetadataDS::MODS_HEADER_CONFIG) do
193
- xml.titleInfo do
194
- xml.title label
195
- end
196
- end
197
- end
198
- new_item.descMetadata.content = builder.to_xml
199
- end
200
- end
201
- end
202
- end