dor-workflow-client 3.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dor
4
+ module Workflow
5
+ class Client
6
+ # Makes requests relating to the workflow queues
7
+ class Queues
8
+ def initialize(requestor:)
9
+ @requestor = requestor
10
+ end
11
+
12
+ # Returns all the distinct laneIds for a given workflow step
13
+ #
14
+ # @param [String] repo The repository the object resides in. The service recoginzes "dor" and "sdr" at the moment
15
+ # @param [String] workflow name
16
+ # @param [String] process name
17
+ # @return [Array<String>] all of the distinct laneIds. Array will be empty if no lane ids were found
18
+ def lane_ids(repo, workflow, process)
19
+ uri = "workflow_queue/lane_ids?step=#{repo}:#{workflow}:#{process}"
20
+ doc = Nokogiri::XML(requestor.request(uri))
21
+ nodes = doc.xpath('/lanes/lane')
22
+ nodes.map { |n| n['id'] }
23
+ end
24
+
25
+ # Gets all of the workflow steps that have a status of 'queued' that have a last-updated timestamp older than the number of hours passed in
26
+ # This will enable re-queueing of jobs that have been lost by the job manager
27
+ # @param [String] repository name of the repository you want to query, like 'dor' or 'sdr'
28
+ # @param [Hash] opts optional values for query
29
+ # @option opts [Integer] :hours_ago steps older than this value will be returned by the query. If not passed in, the service defaults to 0 hours,
30
+ # meaning you will get all queued workflows
31
+ # @option opts [Integer] :limit sets the maximum number of workflow steps that can be returned. Defaults to no limit
32
+ # @return [Array[Hash]] each Hash represents a workflow step. It will have the following keys:
33
+ # :workflow, :step, :druid, :lane_id
34
+ def stale_queued_workflows(repository, opts = {})
35
+ uri_string = build_queued_uri(repository, opts)
36
+ parse_queued_workflows_response requestor.request(uri_string)
37
+ end
38
+
39
+ # Returns a count of workflow steps that have a status of 'queued' that have a last-updated timestamp older than the number of hours passed in
40
+ # @param [String] repository name of the repository you want to query, like 'dor' or 'sdr'
41
+ # @param [Hash] opts optional values for query
42
+ # @option opts [Integer] :hours_ago steps older than this value will be returned by the query. If not passed in, the service defaults to 0 hours,
43
+ # meaning you will get all queued workflows
44
+ # @return [Integer] number of stale, queued steps if the :count_only option was set to true
45
+ def count_stale_queued_workflows(repository, opts = {})
46
+ uri_string = build_queued_uri(repository, opts) + '&count-only=true'
47
+ doc = Nokogiri::XML(requestor.request(uri_string))
48
+ doc.at_xpath('/objects/@count').value.to_i
49
+ end
50
+
51
+ # Returns a list of druids from the workflow service that meet the criteria
52
+ # of the passed in completed and waiting params
53
+ #
54
+ # @param [Array<String>, String] completed An array or single String of the completed steps, should use the qualified format: `repository:workflow:step-name`
55
+ # @param [String] waiting name of the waiting step
56
+ # @param [String] repository default repository to use if it isn't passed in the qualified-step-name
57
+ # @param [String] workflow default workflow to use if it isn't passed in the qualified-step-name
58
+ # @param [String] lane_id issue a query for a specific lane_id for the waiting step
59
+ # @param [Hash] options
60
+ # @param options [String] :default_repository repository to query for if not using the qualified format
61
+ # @param options [String] :default_workflow workflow to query for if not using the qualified format
62
+ # @option options [Integer] :limit maximum number of druids to return (nil for no limit)
63
+ # @return [Array<String>] Array of druids
64
+ #
65
+ # @example
66
+ # objects_for_workstep(...)
67
+ # => [
68
+ # "druid:py156ps0477",
69
+ # "druid:tt628cb6479",
70
+ # "druid:ct021wp7863"
71
+ # ]
72
+ #
73
+ # @example
74
+ # objects_for_workstep(..., "lane1")
75
+ # => {
76
+ # "druid:py156ps0477",
77
+ # "druid:tt628cb6479",
78
+ # }
79
+ #
80
+ # @example
81
+ # objects_for_workstep(..., "lane1", limit: 1)
82
+ # => {
83
+ # "druid:py156ps0477",
84
+ # }
85
+ #
86
+ def objects_for_workstep(completed, waiting, lane_id = 'default', options = {})
87
+ waiting_param = qualify_step(options[:default_repository], options[:default_workflow], waiting)
88
+ uri_string = "workflow_queue?waiting=#{waiting_param}"
89
+ if completed
90
+ Array(completed).each do |step|
91
+ completed_param = qualify_step(options[:default_repository], options[:default_workflow], step)
92
+ uri_string += "&completed=#{completed_param}"
93
+ end
94
+ end
95
+
96
+ uri_string += "&limit=#{options[:limit].to_i}" if options[:limit]&.to_i&.positive?
97
+ uri_string += "&lane-id=#{lane_id}"
98
+
99
+ resp = requestor.request uri_string
100
+ #
101
+ # response looks like:
102
+ # <objects count="2">
103
+ # <object id="druid:ab123de4567"/>
104
+ # <object id="druid:ab123de9012"/>
105
+ # </objects>
106
+ #
107
+ # convert into:
108
+ # ['druid:ab123de4567', 'druid:ab123de9012']
109
+ #
110
+ result = Nokogiri::XML(resp).xpath('//object[@id]')
111
+ result.map { |n| n[:id] }
112
+ end
113
+
114
+ # Get a list of druids that have errored out in a particular workflow and step
115
+ #
116
+ # @param [String] workflow name
117
+ # @param [String] step name
118
+ # @param [String] repository -- optional, default=dor
119
+ #
120
+ # @return [Hash] hash of results, with key has a druid, and value as the error message
121
+ # @example
122
+ # client.errored_objects_for_workstep('accessionWF','content-metadata')
123
+ # => {"druid:qd556jq0580"=>"druid:qd556jq0580 - Item error; caused by
124
+ # #<Rubydora::FedoraInvalidRequest: Error modifying datastream contentMetadata for druid:qd556jq0580. See logger for details>"}
125
+ def errored_objects_for_workstep(workflow, step, repository = 'dor')
126
+ resp = requestor.request "workflow_queue?repository=#{repository}&workflow=#{workflow}&error=#{step}"
127
+ result = {}
128
+ Nokogiri::XML(resp).xpath('//object').collect do |node|
129
+ result.merge!(node['id'] => node['errorMessage'])
130
+ end
131
+ result
132
+ end
133
+
134
+ # Used by preservation robots stats reporter
135
+ def count_objects_in_step(workflow, step, type, repo)
136
+ resp = requestor.request "workflow_queue?repository=#{repo}&workflow=#{workflow}&#{type}=#{step}"
137
+ extract_object_count(resp)
138
+ end
139
+
140
+ # Returns the number of objects that have a status of 'error' in a particular workflow and step
141
+ #
142
+ # @param [String] workflow name
143
+ # @param [String] step name
144
+ # @param [String] repository -- optional, default=dor
145
+ #
146
+ # @return [Integer] Number of objects with this repository:workflow:step that have a status of 'error'
147
+ def count_errored_for_workstep(workflow, step, repository = 'dor')
148
+ count_objects_in_step(workflow, step, 'error', repository)
149
+ end
150
+
151
+ # Returns the number of objects that have a status of 'queued' in a particular workflow and step
152
+ #
153
+ # @param [String] workflow name
154
+ # @param [String] step name
155
+ # @param [String] repository -- optional, default=dor
156
+ #
157
+ # @return [Integer] Number of objects with this repository:workflow:step that have a status of 'queued'
158
+ def count_queued_for_workstep(workflow, step, repository = 'dor')
159
+ count_objects_in_step(workflow, step, 'queued', repository)
160
+ end
161
+
162
+ private
163
+
164
+ attr_reader :requestor
165
+
166
+ def build_queued_uri(repository, opts = {})
167
+ uri_string = "workflow_queue/all_queued?repository=#{repository}"
168
+ uri_string += "&hours-ago=#{opts[:hours_ago]}" if opts[:hours_ago]
169
+ uri_string += "&limit=#{opts[:limit]}" if opts[:limit]
170
+ uri_string
171
+ end
172
+
173
+ # Converts repo-workflow-step into repo:workflow:step
174
+ # @param [String] default_repository
175
+ # @param [String] default_workflow
176
+ # @param [String] step if contains colon :, then the value for workflow and/or workflow/repository. For example: 'jp2-create', 'assemblyWF:jp2-create' or 'dor:assemblyWF:jp2-create'
177
+ # @return [String] repo:workflow:step
178
+ # @example
179
+ # dor:assemblyWF:jp2-create
180
+ def qualify_step(default_repository, default_workflow, step)
181
+ current = step.split(/:/, 3)
182
+ current.unshift(default_workflow) if current.length < 3
183
+ current.unshift(default_repository) if current.length < 3
184
+ current.join(':')
185
+ end
186
+
187
+ def parse_queued_workflows_response(xml)
188
+ doc = Nokogiri::XML(xml)
189
+ doc.xpath('/workflows/workflow').collect do |wf_node|
190
+ {
191
+ workflow: wf_node['name'],
192
+ step: wf_node['process'],
193
+ druid: wf_node['druid'],
194
+ lane_id: wf_node['laneId']
195
+ }
196
+ end
197
+ end
198
+
199
+ def extract_object_count(resp)
200
+ node = Nokogiri::XML(resp).at_xpath('/objects')
201
+ raise Dor::WorkflowException, 'Unable to determine count from response' if node.nil?
202
+
203
+ node['count'].to_i
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dor
4
+ module Workflow
5
+ class Client
6
+ # Makes requests to the workflow service and retries them if necessary.
7
+ class Requestor
8
+ def initialize(connection:)
9
+ @connection = connection
10
+ end
11
+
12
+ attr_reader :connection
13
+
14
+ # calls workflow_resource[uri_string]."#{meth}" with variable number of optional arguments
15
+ # The point of this is to wrap ALL remote calls with consistent error handling and logging
16
+ # @param [String] uri_string resource to request
17
+ # @param [String] meth REST method to use on resource (get, put, post, delete, etc.)
18
+ # @param [String] payload body for (e.g. put) request
19
+ # @param [Hash] opts addtional headers options
20
+ # @return [Object] response from method
21
+ def request(uri_string, meth = 'get', payload = '', opts = {})
22
+ response = send_workflow_resource_request(uri_string, meth, payload, opts)
23
+ response.body
24
+ rescue Faraday::Error => e
25
+ msg = "Failed to retrieve resource: #{meth} #{base_url}/#{uri_string}"
26
+ msg += " (HTTP status #{e.response[:status]})" if e.respond_to?(:response) && e.response
27
+ raise Dor::WorkflowException, msg
28
+ end
29
+
30
+ private
31
+
32
+ ##
33
+ # Get the configured URL for the connection
34
+ def base_url
35
+ connection.url_prefix
36
+ end
37
+
38
+ def send_workflow_resource_request(uri_string, meth = 'get', payload = '', opts = {})
39
+ connection.public_send(meth, uri_string) do |req|
40
+ req.body = payload unless meth == 'delete'
41
+ req.params.update opts[:params] if opts[:params]
42
+ req.headers.update opts.except(:params)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dor
4
+ module Workflow
5
+ class Client
6
+ VERSION = '3.0.0.rc1'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dor
4
+ module Workflow
5
+ class Client
6
+ # Makes requests relating to versions
7
+ class VersionRoutes
8
+ def initialize(requestor:)
9
+ @requestor = requestor
10
+ end
11
+
12
+ # Calls the versionClose endpoint of the workflow service:
13
+ #
14
+ # - completes the versioningWF:submit-version and versioningWF:start-accession steps
15
+ # - initiates accesssionWF
16
+ #
17
+ # @param [String] repo The repository the object resides in. The service recoginzes "dor" and "sdr" at the moment
18
+ # @param [String] druid The id of the object to delete the workflow from
19
+ # @param [Boolean] create_accession_wf Option to create accessionWF when closing a version. Defaults to true
20
+ def close_version(repo, druid, create_accession_wf = true)
21
+ uri = "#{repo}/objects/#{druid}/versionClose"
22
+ uri += '?create-accession=false' unless create_accession_wf
23
+ requestor.request(uri, 'post', '')
24
+ true
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :requestor
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dor
4
+ module Workflow
5
+ class Client
6
+ # Makes requests relating to a workflow
7
+ class WorkflowRoutes
8
+ def initialize(requestor:)
9
+ @requestor = requestor
10
+ end
11
+
12
+ # Creates a workflow for a given object in the repository. If this particular workflow for this objects exists,
13
+ # it will replace the old workflow with wf_xml passed to this method. You have the option of creating a datastream or not.
14
+ # Returns true on success. Caller must handle any exceptions
15
+ #
16
+ # @param [String] repo The repository the object resides in. The service recoginzes "dor" and "sdr" at the moment
17
+ # @param [String] druid The id of the object
18
+ # @param [String] workflow_name The name of the workflow you want to create
19
+ # @param [String] wf_xml The xml that represents the workflow
20
+ # @param [Hash] opts optional params
21
+ # @option opts [Boolean] :create_ds if true, a workflow datastream will be created in Fedora. Set to false if you do not want a datastream to be created
22
+ # If you do not pass in an <b>opts</b> Hash, then :create_ds is set to true by default
23
+ # @option opts [String] :lane_id adds laneId attribute to all process elements in the wf_xml workflow xml. Defaults to a value of 'default'
24
+ # @return [Boolean] always true
25
+ #
26
+ def create_workflow(repo, druid, workflow_name, wf_xml, opts = { create_ds: true })
27
+ lane_id = opts.fetch(:lane_id, 'default')
28
+ xml = add_lane_id_to_workflow_xml(lane_id, wf_xml)
29
+ _status = requestor.request "#{repo}/objects/#{druid}/workflows/#{workflow_name}", 'put', xml,
30
+ content_type: 'application/xml',
31
+ params: { 'create-ds' => opts[:create_ds] }
32
+ true
33
+ end
34
+
35
+ # Updates the status of one step in a workflow.
36
+ # Returns true on success. Caller must handle any exceptions
37
+ #
38
+ # @param [String] repo The repository the object resides in. The service recoginzes "dor" and "sdr" at the moment
39
+ # @param [String] druid The id of the object
40
+ # @param [String] workflow The name of the workflow
41
+ # @param [String] process The name of the process step
42
+ # @param [String] status The status that you want to set -- using one of the values in VALID_STATUS
43
+ # @param [Hash] opts optional values for the workflow step
44
+ # @option opts [Float] :elapsed The number of seconds it took to complete this step. Can have a decimal. Is set to 0 if not passed in.
45
+ # @option opts [String] :lifecycle Bookeeping label for this particular workflow step. Examples are: 'registered', 'shelved'
46
+ # @option opts [String] :note Any kind of string annotation that you want to attach to the workflow
47
+ # @option opts [String] :lane_id Id of processing lane used by the job manager. Can convey priority or name of an applicaiton specific processing lane (e.g. 'high', 'critical', 'hydrus')
48
+ # @option opts [String] :current_status Setting this string tells the workflow service to compare the current status to this value. If the current value does not match this value, the update is not performed
49
+ # @return [Boolean] always true
50
+ # Http Call
51
+ # ==
52
+ # The method does an HTTP PUT to the URL defined in `Dor::WF_URI`. As an example:
53
+ #
54
+ # PUT "/dor/objects/pid:123/workflows/GoogleScannedWF/convert"
55
+ # <process name=\"convert\" status=\"completed\" />"
56
+ def update_workflow_status(repo, druid, workflow, process, status, opts = {})
57
+ raise ArgumentError, "Unknown status value #{status}" unless VALID_STATUS.include?(status.downcase)
58
+
59
+ opts = { elapsed: 0, lifecycle: nil, note: nil }.merge!(opts)
60
+ opts[:elapsed] = opts[:elapsed].to_s
61
+ current_status = opts.delete(:current_status)
62
+ xml = create_process_xml({ name: process, status: status.downcase }.merge!(opts))
63
+ uri = "#{repo}/objects/#{druid}/workflows/#{workflow}/#{process}"
64
+ uri += "?current-status=#{current_status.downcase}" if current_status
65
+ response = requestor.request(uri, 'put', xml, content_type: 'application/xml')
66
+
67
+ Workflow::Response::Update.new(json: response)
68
+ end
69
+
70
+ #
71
+ # Retrieves the process status of the given workflow for the given object identifier
72
+ # @param [String] repo The repository the object resides in. Currently recoginzes "dor" and "sdr".
73
+ # @param [String] druid The id of the object
74
+ # @param [String] workflow The name of the workflow
75
+ # @param [String] process The name of the process step
76
+ # @return [String] status for repo-workflow-process-druid
77
+ def workflow_status(repo, druid, workflow, process)
78
+ workflow_md = workflow_xml(repo, druid, workflow)
79
+ doc = Nokogiri::XML(workflow_md)
80
+ raise Dor::WorkflowException, "Unable to parse response:\n#{workflow_md}" if doc.root.nil?
81
+
82
+ processes = doc.root.xpath("//process[@name='#{process}']")
83
+ process = processes.max { |a, b| a.attr('version').to_i <=> b.attr('version').to_i }
84
+ process&.attr('status')
85
+ end
86
+
87
+ #
88
+ # Retrieves the raw XML for the given workflow
89
+ # @param [String] repo The repository the object resides in. Currently recoginzes "dor" and "sdr".
90
+ # @param [String] druid The id of the object
91
+ # @param [String] workflow The name of the workflow
92
+ # @return [String] XML of the workflow
93
+ def workflow_xml(repo, druid, workflow)
94
+ raise ArgumentError, 'missing workflow' unless workflow
95
+
96
+ requestor.request "#{repo}/objects/#{druid}/workflows/#{workflow}"
97
+ end
98
+
99
+ # Updates the status of one step in a workflow to error.
100
+ # Returns true on success. Caller must handle any exceptions
101
+ #
102
+ # @param [String] repo The repository the object resides in. The service recoginzes "dor" and "sdr" at the moment
103
+ # @param [String] druid The id of the object
104
+ # @param [String] workflow The name of the workflow
105
+ # @param [String] error_msg The error message. Ideally, this is a brief message describing the error
106
+ # @param [Hash] opts optional values for the workflow step
107
+ # @option opts [String] :error_text A slot to hold more information about the error, like a full stacktrace
108
+ # @return [Boolean] always true
109
+ #
110
+ # Http Call
111
+ # ==
112
+ # The method does an HTTP PUT to the URL defined in `Dor::WF_URI`.
113
+ #
114
+ # PUT "/dor/objects/pid:123/workflows/GoogleScannedWF/convert"
115
+ # <process name=\"convert\" status=\"error\" />"
116
+ def update_workflow_error_status(repo, druid, workflow, process, error_msg, opts = {})
117
+ opts = { error_text: nil }.merge!(opts)
118
+ xml = create_process_xml({ name: process, status: 'error', errorMessage: error_msg }.merge!(opts))
119
+ requestor.request "#{repo}/objects/#{druid}/workflows/#{workflow}/#{process}", 'put', xml, content_type: 'application/xml'
120
+ true
121
+ end
122
+
123
+ #
124
+ # Retrieves the raw XML for all the workflows for the the given object
125
+ # @param [String] druid The id of the object
126
+ # @return [String] XML of the workflow
127
+ def all_workflows_xml(druid)
128
+ requestor.request "objects/#{druid}/workflows"
129
+ end
130
+
131
+ # Get workflow names into an array for given PID
132
+ # This method only works when this gem is used in a project that is configured to connect to DOR
133
+ #
134
+ # @param [String] pid of druid
135
+ # @param [String] repo repository for the object
136
+ # @return [Array<String>] list of worklows
137
+ # @example
138
+ # client.workflows('druid:sr100hp0609')
139
+ # => ["accessionWF", "assemblyWF", "disseminationWF"]
140
+ def workflows(pid, repo = 'dor')
141
+ xml_doc = Nokogiri::XML(workflow_xml(repo, pid, ''))
142
+ xml_doc.xpath('//workflow').collect { |workflow| workflow['id'] }
143
+ end
144
+
145
+ # @param [String] repo repository of the object
146
+ # @param [String] pid id of object
147
+ # @param [String] workflow_name The name of the workflow
148
+ # @return [Workflow::Response::Workflow]
149
+ def workflow(repo: 'dor', pid:, workflow_name:)
150
+ xml = workflow_xml(repo, pid, workflow_name)
151
+ Workflow::Response::Workflow.new(xml: xml)
152
+ end
153
+
154
+ # Deletes a workflow from a particular repository and druid
155
+ # @param [String] repo The repository the object resides in. The service recoginzes "dor" and "sdr" at the moment
156
+ # @param [String] druid The id of the object to delete the workflow from
157
+ # @param [String] workflow The name of the workflow to be deleted
158
+ # @return [Boolean] always true
159
+ def delete_workflow(repo, druid, workflow)
160
+ requestor.request "#{repo}/objects/#{druid}/workflows/#{workflow}", 'delete'
161
+ true
162
+ end
163
+
164
+ private
165
+
166
+ attr_reader :requestor
167
+
168
+ # Adds laneId attributes to each process of workflow xml
169
+ #
170
+ # @param [String] lane_id to add to each process element
171
+ # @param [String] wf_xml the workflow xml
172
+ # @return [String] wf_xml with lane_id attributes
173
+ def add_lane_id_to_workflow_xml(lane_id, wf_xml)
174
+ doc = Nokogiri::XML(wf_xml)
175
+ doc.xpath('/workflow/process').each { |proc| proc['laneId'] = lane_id }
176
+ doc.to_xml
177
+ end
178
+
179
+ # @param [Hash] params
180
+ # @return [String]
181
+ def create_process_xml(params)
182
+ builder = Nokogiri::XML::Builder.new do |xml|
183
+ attrs = params.reject { |_k, v| v.nil? }
184
+ attrs = Hash[attrs.map { |k, v| [k.to_s.camelize(:lower), v] }] # camelize all the keys in the attrs hash
185
+ xml.process(attrs)
186
+ end
187
+ builder.to_xml
188
+ end
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'active_support/core_ext'
5
+ require 'nokogiri'
6
+ require 'dor/workflow_exception'
7
+ require 'dor/models/response/workflow'
8
+ require 'dor/models/response/update'
9
+
10
+ require 'dor/workflow/client/connection_factory'
11
+ require 'dor/workflow/client/lifecycle_routes'
12
+ require 'dor/workflow/client/queues'
13
+ require 'dor/workflow/client/requestor'
14
+ require 'dor/workflow/client/version_routes'
15
+ require 'dor/workflow/client/workflow_routes'
16
+
17
+ module Dor
18
+ module Workflow
19
+ # TODO: VALID_STATUS should be just another attribute w/ default
20
+ #
21
+ # Create and update workflows
22
+ class Client
23
+ # From Workflow Service's admin/Process.java
24
+ VALID_STATUS = %w[waiting completed error queued skipped hold].freeze
25
+
26
+ attr_accessor :requestor
27
+
28
+ # Configure the workflow service
29
+ # @param [String] :url points to the workflow service
30
+ # @param [Logger] :logger defaults writing to workflow_service.log with weekly rotation
31
+ # @param [Integer] :timeout number of seconds for HTTP timeout
32
+ # @param [Faraday::Connection] :connection the REST client resource
33
+ def initialize(url: nil, logger: default_logger, timeout: nil, connection: nil)
34
+ raise ArgumentError, 'You must provide either a connection or a url' if !url && !connection
35
+
36
+ @requestor = Requestor.new(connection: connection || ConnectionFactory.build_connection(url, timeout: timeout, logger: logger))
37
+ end
38
+
39
+ delegate :create_workflow, :update_workflow_status, :workflow_status, :workflow_xml,
40
+ :update_workflow_error_status, :all_workflows_xml, :workflows,
41
+ :workflow, :delete_workflow, to: :workflow_routes
42
+
43
+ delegate :lifecycle, :active_lifecycle, :milestones, to: :lifecycle_routes
44
+
45
+ delegate :lane_ids, :stale_queued_workflows, :count_stale_queued_workflows,
46
+ :objects_for_workstep, :errored_objects_for_workstep, :count_objects_in_step,
47
+ :count_errored_for_workstep, :count_queued_for_workstep,
48
+ to: :queues
49
+
50
+ delegate :close_version, to: :version_routes
51
+
52
+ def queues
53
+ @queues ||= Queues.new(requestor: requestor)
54
+ end
55
+
56
+ def workflow_routes
57
+ @workflow_routes ||= WorkflowRoutes.new(requestor: requestor)
58
+ end
59
+
60
+ def lifecycle_routes
61
+ @lifecycle_routes ||= LifecycleRoutes.new(requestor: requestor)
62
+ end
63
+
64
+ def version_routes
65
+ @version_routes ||= VersionRoutes.new(requestor: requestor)
66
+ end
67
+
68
+ private
69
+
70
+ # Among other things, a distinct method helps tests mock default logger
71
+ # @return [Logger] default logger object
72
+ def default_logger
73
+ Logger.new('workflow_service.log', 'weekly')
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dor
4
+ class WorkflowException < ::RuntimeError
5
+ end
6
+ end