dor-services 6.6.2 → 6.7.0

Sign up to get free protection for your applications and to get access to all the features.
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