dor-services 5.4.2 → 5.5.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 +4 -4
- data/lib/dor-services.rb +1 -0
- data/lib/dor/config.rb +13 -9
- data/lib/dor/datastreams/workflow_definition_ds.rb +1 -1
- data/lib/dor/datastreams/workflow_ds.rb +4 -4
- data/lib/dor/models/governable.rb +1 -1
- data/lib/dor/models/itemizable.rb +2 -15
- data/lib/dor/models/processable.rb +2 -2
- data/lib/dor/models/releaseable.rb +1 -1
- data/lib/dor/models/shelvable.rb +1 -2
- data/lib/dor/models/versionable.rb +9 -11
- data/lib/dor/models/workflow_object.rb +1 -1
- data/lib/dor/services/cleanup_reset_service.rb +1 -1
- data/lib/dor/services/cleanup_service.rb +2 -2
- data/lib/dor/services/merge_service.rb +1 -1
- data/lib/dor/services/registration_service.rb +1 -1
- data/lib/dor/services/sdr_ingest_service.rb +2 -7
- data/lib/dor/services/technical_metadata_service.rb +3 -10
- data/lib/dor/utils/sdr_client.rb +42 -4
- data/lib/dor/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 38084c1790b717b6ad1dacf15e9e6112eb3117bb
|
4
|
+
data.tar.gz: 7980a4ed6fb6fd68d600398871e329a193e7102f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f7f0f82b7290f1e32e655be2378ce21a5ce9bd33c293a0125fc6e4065e6c6c4b594b463f2f5f5d408e5e76228b96e364a0f90571eb5bfc1bf847247973be400c
|
7
|
+
data.tar.gz: 9d95ba69a57d954498f7c22d66c596f18681b9e70962a15e90f944dcb000bc7764503ae685dcd094714d4cd8e94b3a110f1ada4b8b0db0e688e36bd8af00f725
|
data/lib/dor-services.rb
CHANGED
data/lib/dor/config.rb
CHANGED
@@ -25,15 +25,6 @@ module Dor
|
|
25
25
|
ensure
|
26
26
|
$-v = temp_v
|
27
27
|
end
|
28
|
-
params = { :dor_services_url => result.dor_services.url }
|
29
|
-
|
30
|
-
if result.workflow.logfile && result.workflow.shift_age
|
31
|
-
params[:logger] = Logger.new(result.workflow.logfile, result.workflow.shift_age)
|
32
|
-
elsif result.workflow.logfile
|
33
|
-
params[:logger] = Logger.new(result.workflow.logfile)
|
34
|
-
end
|
35
|
-
params[:timeout] = result.workflow.timeout if result.workflow.timeout
|
36
|
-
Dor::WorkflowService.configure result.workflow.url, params
|
37
28
|
result
|
38
29
|
end
|
39
30
|
|
@@ -84,6 +75,19 @@ module Dor
|
|
84
75
|
:stomp => {
|
85
76
|
:connection => Confstruct.deferred { |c| Stomp::Connection.new c.user, c.password, c.host, c.port, true, 5, { 'client-id' => c.client_id }},
|
86
77
|
:client => Confstruct.deferred { |c| Stomp::Client.new c.user, c.password, c.host, c.port }
|
78
|
+
},
|
79
|
+
:workflow => {
|
80
|
+
:client => Confstruct.deferred do |c|
|
81
|
+
Dor::WorkflowService.configure c.url, logger: c.client_logger, timeout: c.timeout, dor_services_url: config.dor_services.url
|
82
|
+
Dor::WorkflowService
|
83
|
+
end,
|
84
|
+
:client_logger => Confstruct.deferred do |c|
|
85
|
+
if c.logfile && c.shift_age
|
86
|
+
Logger.new(c.logfile, c.shift_age)
|
87
|
+
elsif c.logfile
|
88
|
+
Logger.new(c.logfile)
|
89
|
+
end
|
90
|
+
end
|
87
91
|
}
|
88
92
|
})
|
89
93
|
true
|
@@ -70,7 +70,7 @@ class WorkflowDefinitionDs < ActiveFedora::OmDatastream
|
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
73
|
-
# Creates the xml used by Dor::WorkflowService
|
73
|
+
# Creates the xml used by Dor::WorkflowService#create_workflow
|
74
74
|
# @return [String] An object's initial workflow as defined by the <workflow-def> in content
|
75
75
|
def initial_workflow
|
76
76
|
doc = Nokogiri::XML('<workflow/>')
|
@@ -17,7 +17,7 @@ module Dor
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def get_workflow(wf, repo = 'dor')
|
20
|
-
xml = Dor::
|
20
|
+
xml = Dor::Config.workflow.client.get_workflow_xml(repo, pid, wf)
|
21
21
|
xml = Nokogiri::XML(xml)
|
22
22
|
return nil if xml.xpath('workflow').length == 0
|
23
23
|
Workflow::Document.new(xml.to_s)
|
@@ -34,12 +34,12 @@ module Dor
|
|
34
34
|
# service directly
|
35
35
|
def content(refresh = false)
|
36
36
|
@content = nil if refresh
|
37
|
-
@content ||= Dor::
|
38
|
-
rescue Dor::WorkflowException
|
37
|
+
@content ||= Dor::Config.workflow.client.get_workflow_xml 'dor', pid, nil
|
38
|
+
rescue Dor::WorkflowException
|
39
39
|
xml = Nokogiri::XML(%(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<workflows objectId="#{pid}"/>))
|
40
40
|
digital_object.datastreams.keys.each do |dsid|
|
41
41
|
next unless dsid =~ /WF$/
|
42
|
-
ds_content = Nokogiri::XML(Dor::
|
42
|
+
ds_content = Nokogiri::XML(Dor::Config.workflow.client.get_workflow_xml('dor', pid, dsid))
|
43
43
|
xml.root.add_child(ds_content.root)
|
44
44
|
end
|
45
45
|
@content ||= xml.to_xml
|
@@ -10,7 +10,6 @@ module Dor
|
|
10
10
|
end
|
11
11
|
|
12
12
|
DIFF_FILENAME = 'cm_inv_diff'
|
13
|
-
DIFF_QUERY = DIFF_FILENAME.tr('_', '-')
|
14
13
|
|
15
14
|
# Deletes all cm_inv_diff files in the workspace for the Item
|
16
15
|
def clear_diff_cache
|
@@ -31,25 +30,13 @@ module Dor
|
|
31
30
|
if Dor::Config.stacks.local_workspace_root.nil?
|
32
31
|
raise Dor::ParameterError, 'Missing Dor::Config.stacks.local_workspace_root'
|
33
32
|
end
|
34
|
-
unless %w(all shelve preserve publish).include?(subset.to_s)
|
35
|
-
raise Dor::ParameterError, "Invalid subset value: #{subset}"
|
36
|
-
end
|
37
33
|
|
38
|
-
# fetch content metadata inventory difference from SDR
|
39
|
-
if Dor::Config.sdr.rest_client.nil?
|
40
|
-
raise Dor::ParameterError, 'Missing Dor::Config.sdr.rest_client'
|
41
|
-
end
|
42
|
-
sdr_client = Dor::Config.sdr.rest_client
|
43
34
|
current_content = datastreams['contentMetadata'].content
|
44
35
|
if current_content.nil?
|
45
36
|
raise Dor::Exception, 'Missing contentMetadata datastream'
|
46
37
|
end
|
47
|
-
|
48
|
-
|
49
|
-
query_string = URI.encode_www_form(query_string)
|
50
|
-
sdr_query = "objects/#{pid}/#{DIFF_QUERY}?#{query_string}"
|
51
|
-
response = sdr_client[sdr_query].post(current_content, :content_type => 'application/xml')
|
52
|
-
response
|
38
|
+
|
39
|
+
Sdr::Client.get_content_diff(druid, current_content, subset, version)
|
53
40
|
end
|
54
41
|
end
|
55
42
|
end
|
@@ -100,7 +100,7 @@ module Dor
|
|
100
100
|
end
|
101
101
|
|
102
102
|
def milestones
|
103
|
-
Dor::
|
103
|
+
Dor::Config.workflow.client.get_milestones('dor', pid)
|
104
104
|
end
|
105
105
|
|
106
106
|
# @return [Hash{Symbol => Object}] including :current_version, :status_code and :status_time
|
@@ -214,7 +214,7 @@ module Dor
|
|
214
214
|
priority = workflows.current_priority if priority == 0
|
215
215
|
opts = { :create_ds => create_ds, :lane_id => default_workflow_lane }
|
216
216
|
opts[:priority] = priority if priority > 0
|
217
|
-
Dor::
|
217
|
+
Dor::Config.workflow.client.create_workflow(Dor::WorkflowObject.initial_repo(name), pid, name, Dor::WorkflowObject.initial_workflow(name), opts)
|
218
218
|
workflows.content(true) # refresh the copy of the workflows datastream
|
219
219
|
end
|
220
220
|
|
data/lib/dor/models/shelvable.rb
CHANGED
@@ -25,8 +25,7 @@ module Dor
|
|
25
25
|
# retrieve the differences between the current contentMetadata and the previously ingested version
|
26
26
|
# (filtering to select only the files that should be shelved to stacks)
|
27
27
|
def get_shelve_diff
|
28
|
-
|
29
|
-
inventory_diff = Moab::FileInventoryDifference.parse(inventory_diff_xml)
|
28
|
+
inventory_diff = get_content_diff(:shelve)
|
30
29
|
shelve_diff = inventory_diff.group_difference('content')
|
31
30
|
shelve_diff
|
32
31
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'dor/utils/sdr_client'
|
2
|
-
|
3
1
|
module Dor
|
4
2
|
module Versionable
|
5
3
|
extend ActiveSupport::Concern
|
@@ -12,15 +10,15 @@ module Dor
|
|
12
10
|
# Increments the version number and initializes versioningWF for the object
|
13
11
|
# @param [Hash] opts optional params
|
14
12
|
# @option opts [Boolean] :assume_accessioned If true, does not check whether object has been accessioned.
|
15
|
-
# @option opts [Boolean] :create_workflows_ds If false,
|
13
|
+
# @option opts [Boolean] :create_workflows_ds If false, create_workflow() will not initialize the workflows datastream.
|
16
14
|
# @option opts [Hash] :vers_md_upd_info If present, used to add to the events datastream and set the desc and significance on the versionMetadata datastream
|
17
15
|
# @raise [Dor::Exception] if the object hasn't been accessioned, or if a version is already opened
|
18
16
|
def open_new_version(opts = {})
|
19
17
|
# During local development, we need a way to open a new version even if the object has not been accessioned.
|
20
18
|
raise(Dor::Exception, 'Object net yet accessioned') unless
|
21
|
-
opts[:assume_accessioned] || Dor::
|
19
|
+
opts[:assume_accessioned] || Dor::Config.workflow.client.get_lifecycle('dor', pid, 'accessioned')
|
22
20
|
raise Dor::Exception, 'Object already opened for versioning' if new_version_open?
|
23
|
-
raise Dor::Exception, 'Object currently being accessioned' if Dor::
|
21
|
+
raise Dor::Exception, 'Object currently being accessioned' if Dor::Config.workflow.client.get_active_lifecycle('dor', pid, 'submitted')
|
24
22
|
|
25
23
|
sdr_version = Sdr::Client.current_version pid
|
26
24
|
|
@@ -32,9 +30,9 @@ module Dor
|
|
32
30
|
k = :create_workflows_ds
|
33
31
|
if opts.key?(k)
|
34
32
|
# During local development, Hydrus (or another app w/ local Fedora) does not want to initialize workflows datastream.
|
35
|
-
|
33
|
+
create_workflow('versioningWF', opts[k])
|
36
34
|
else
|
37
|
-
|
35
|
+
create_workflow('versioningWF')
|
38
36
|
end
|
39
37
|
|
40
38
|
vmd_upd_info = opts[:vers_md_upd_info]
|
@@ -65,20 +63,20 @@ module Dor
|
|
65
63
|
|
66
64
|
raise Dor::Exception, 'latest version in versionMetadata requires tag and description before it can be closed' unless datastreams['versionMetadata'].current_version_closeable?
|
67
65
|
raise Dor::Exception, 'Trying to close version on an object not opened for versioning' unless new_version_open?
|
68
|
-
raise Dor::Exception, 'accessionWF already created for versioned object' if Dor::
|
66
|
+
raise Dor::Exception, 'accessionWF already created for versioned object' if Dor::Config.workflow.client.get_active_lifecycle('dor', pid, 'submitted')
|
69
67
|
|
70
|
-
Dor::
|
68
|
+
Dor::Config.workflow.client.close_version 'dor', pid, opts.fetch(:start_accession, true) # Default to creating accessionWF when calling close_version
|
71
69
|
end
|
72
70
|
|
73
71
|
# @return [Boolean] true if 'opened' lifecycle is active, false otherwise
|
74
72
|
def new_version_open?
|
75
|
-
return true if Dor::
|
73
|
+
return true if Dor::Config.workflow.client.get_active_lifecycle('dor', pid, 'opened')
|
76
74
|
false
|
77
75
|
end
|
78
76
|
|
79
77
|
# @return [Boolean] true if the object is in a state that allows it to be modified. States that will allow modification are: has not been submitted for accessioning, has an open version or has sdr-ingest set to hold
|
80
78
|
def allows_modification?
|
81
|
-
if Dor::
|
79
|
+
if Dor::Config.workflow.client.get_lifecycle('dor', pid, 'submitted') && !new_version_open? && Dor::Config.workflow.client.get_workflow_status('dor', pid, 'accessionWF', 'sdr-ingest-transfer') != 'hold'
|
82
80
|
false
|
83
81
|
else
|
84
82
|
true
|
@@ -45,7 +45,7 @@ module Dor
|
|
45
45
|
|
46
46
|
def to_solr(solr_doc = {}, *args)
|
47
47
|
super solr_doc, *args
|
48
|
-
solr_doc["#{definition.name}_archived_isi"] = Dor::
|
48
|
+
solr_doc["#{definition.name}_archived_isi"] = Dor::Config.workflow.client.count_archived_for_workflow(definition.name)
|
49
49
|
solr_doc
|
50
50
|
end
|
51
51
|
|
@@ -18,7 +18,7 @@ module Dor
|
|
18
18
|
last_version = druid_obj.current_version.to_i
|
19
19
|
|
20
20
|
# if the current version is still open, avoid this versioned directory
|
21
|
-
if Dor::
|
21
|
+
if Dor::Config.workflow.client.get_lifecycle('dor', druid, 'accessioned').nil?
|
22
22
|
last_version -= 1
|
23
23
|
end
|
24
24
|
last_version
|
@@ -69,8 +69,8 @@ module Dor
|
|
69
69
|
|
70
70
|
def self.remove_active_workflows(druid)
|
71
71
|
%w(dor sdr).each do |repo|
|
72
|
-
dor_wfs = Dor::
|
73
|
-
dor_wfs.each { |wf| Dor::
|
72
|
+
dor_wfs = Dor::Config.workflow.client.get_workflows(druid, repo)
|
73
|
+
dor_wfs.each { |wf| Dor::Config.workflow.client.delete_workflow(repo, druid, wf) }
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
@@ -68,7 +68,7 @@ module Dor
|
|
68
68
|
unshelve
|
69
69
|
unpublish
|
70
70
|
Dor::CleanupService.cleanup_by_druid @current_secondary.pid
|
71
|
-
Dor::
|
71
|
+
Dor::Config.workflow.client.archive_active_workflow 'dor', @current_secondary.pid
|
72
72
|
rescue => e
|
73
73
|
@logger.error "Unable to decomission #{@current_secondary.pid} with primary object #{@primary.pid}: #{e.inspect}"
|
74
74
|
@logger.error e.backtrace.join("\n")
|
@@ -127,7 +127,7 @@ module Dor
|
|
127
127
|
workflow_priority = params[:workflow_priority] ? params[:workflow_priority].to_i : 0
|
128
128
|
|
129
129
|
Array(params[:seed_datastream]).each { |datastream_name| new_item.build_datastream(datastream_name) }
|
130
|
-
Array(params[:initiate_workflow]).each { |workflow_id| new_item.
|
130
|
+
Array(params[:initiate_workflow]).each { |workflow_id| new_item.create_workflow(workflow_id, !new_item.new_object?, workflow_priority)}
|
131
131
|
|
132
132
|
new_item.assert_content_model
|
133
133
|
|
@@ -36,7 +36,7 @@ module Dor
|
|
36
36
|
bagger.create_tagfiles
|
37
37
|
verify_bag_structure(bag_dir)
|
38
38
|
# Now bootstrap SDR workflow. but do not create the workflows datastream
|
39
|
-
dor_item.
|
39
|
+
dor_item.create_workflow('sdrIngestWF', false)
|
40
40
|
rescue Exception => e
|
41
41
|
raise LyberCore::Exceptions::ItemError.new(druid, 'Export failure', e)
|
42
42
|
end
|
@@ -44,12 +44,7 @@ module Dor
|
|
44
44
|
# @param [String] druid The object identifier
|
45
45
|
# @return [Moab::SignatureCatalog] the catalog of all files previously ingested
|
46
46
|
def self.get_signature_catalog(druid)
|
47
|
-
|
48
|
-
url = "objects/#{druid}/manifest/signatureCatalog.xml"
|
49
|
-
response = sdr_client[url].get
|
50
|
-
Moab::SignatureCatalog.parse(response)
|
51
|
-
rescue RestClient::ResourceNotFound
|
52
|
-
Moab::SignatureCatalog.new(:digital_object_id => druid, :version_id => 0)
|
47
|
+
Sdr::Client.get_signature_catalog(druid)
|
53
48
|
end
|
54
49
|
|
55
50
|
# @param [Dor::Item] dor_item The representation of the digital object
|
@@ -48,8 +48,7 @@ module Dor
|
|
48
48
|
# @param [Dor::Item] dor_item The DOR item being processed by the technical metadata robot
|
49
49
|
# @return [FileGroupDifference] The differences between two versions of a group of files
|
50
50
|
def self.get_content_group_diff(dor_item)
|
51
|
-
|
52
|
-
inventory_diff = Moab::FileInventoryDifference.parse(inventory_diff_xml)
|
51
|
+
inventory_diff = dor_item.get_content_diff('all')
|
53
52
|
inventory_diff.group_difference('content')
|
54
53
|
end
|
55
54
|
|
@@ -77,11 +76,7 @@ module Dor
|
|
77
76
|
# @return [String] The technicalMetadata datastream from the previous version of the digital object (fetched from SDR storage)
|
78
77
|
# The data is updated to the latest format.
|
79
78
|
def self.get_sdr_technical_metadata(druid)
|
80
|
-
|
81
|
-
sdr_techmd = get_sdr_metadata(druid, 'technicalMetadata')
|
82
|
-
rescue RestClient::ResourceNotFound
|
83
|
-
return nil
|
84
|
-
end
|
79
|
+
sdr_techmd = get_sdr_metadata(druid, 'technicalMetadata')
|
85
80
|
return sdr_techmd if sdr_techmd =~ /<technicalMetadata/
|
86
81
|
return ::JhoveService.new.upgrade_technical_metadata(sdr_techmd) if sdr_techmd =~ /<jhove/
|
87
82
|
nil
|
@@ -103,9 +98,7 @@ module Dor
|
|
103
98
|
# @param [String] dsname The identifier of the metadata datastream
|
104
99
|
# @return [String] The datastream contents from the previous version of the digital object (fetched from SDR storage)
|
105
100
|
def self.get_sdr_metadata(druid, dsname)
|
106
|
-
|
107
|
-
url = "objects/#{druid}/metadata/#{dsname}.xml"
|
108
|
-
sdr_client[url].get
|
101
|
+
Sdr::Client.get_sdr_metadata(druid, dsname)
|
109
102
|
end
|
110
103
|
|
111
104
|
# @param [DruidTools::Druid] druid A wrapper class for the druid identifier. Used to generate paths
|
data/lib/dor/utils/sdr_client.rb
CHANGED
@@ -1,12 +1,11 @@
|
|
1
|
+
require 'moab'
|
1
2
|
module Sdr
|
2
|
-
|
3
|
+
class Client
|
3
4
|
class << self
|
4
|
-
|
5
5
|
# @param [String] druid id of the object you want the version of
|
6
6
|
# @return [Integer] the current version from SDR
|
7
7
|
def current_version(druid)
|
8
|
-
|
9
|
-
xml = sdr_client["objects/#{druid}/current_version"].get
|
8
|
+
xml = client["objects/#{druid}/current_version"].get
|
10
9
|
|
11
10
|
begin
|
12
11
|
doc = Nokogiri::XML xml
|
@@ -17,6 +16,45 @@ module Sdr
|
|
17
16
|
end
|
18
17
|
end
|
19
18
|
|
19
|
+
# @param [String] dsname The identifier of the metadata datastream
|
20
|
+
# @return [String] The datastream contents from the previous version of the digital object (fetched from SDR storage)
|
21
|
+
def get_sdr_metadata(druid, dsname)
|
22
|
+
client["objects/#{druid}/metadata/#{dsname}.xml"].get
|
23
|
+
rescue RestClient::ResourceNotFound
|
24
|
+
return nil
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param [String] druid The object identifier
|
28
|
+
# @return [Moab::SignatureCatalog] the catalog of all files previously ingested
|
29
|
+
def get_signature_catalog(druid)
|
30
|
+
response = client["objects/#{druid}/manifest/signatureCatalog.xml"].get
|
31
|
+
Moab::SignatureCatalog.parse(response)
|
32
|
+
rescue RestClient::ResourceNotFound
|
33
|
+
Moab::SignatureCatalog.new(:digital_object_id => druid, :version_id => 0)
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_content_diff(druid, current_content, subset = :all, version = nil)
|
37
|
+
unless %w(all shelve preserve publish).include?(subset.to_s)
|
38
|
+
raise Dor::ParameterError, "Invalid subset value: #{subset}"
|
39
|
+
end
|
40
|
+
|
41
|
+
query_string = { :subset => subset.to_s }
|
42
|
+
query_string[:version] = version.to_s unless version.nil?
|
43
|
+
query_string = URI.encode_www_form(query_string)
|
44
|
+
sdr_query = "objects/#{druid}/cm-inv-diff?#{query_string}"
|
45
|
+
response = client[sdr_query].post(current_content, :content_type => 'application/xml')
|
46
|
+
Moab::FileInventoryDifference.parse(response)
|
47
|
+
end
|
48
|
+
|
49
|
+
def client
|
50
|
+
if Dor::Config.sdr.url
|
51
|
+
Dor::Config.sdr.rest_client
|
52
|
+
elsif Dor::Config.dor_services.url
|
53
|
+
Dor::Config.dor_services.rest_client['v1/sdr']
|
54
|
+
else
|
55
|
+
raise Dor::ParameterError, 'Missing Dor::Config.sdr and/or Dor::Config.dor_services configuration'
|
56
|
+
end
|
57
|
+
end
|
20
58
|
end
|
21
59
|
end
|
22
60
|
end
|
data/lib/dor/version.rb
CHANGED
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: 5.
|
4
|
+
version: 5.5.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: 2016-
|
17
|
+
date: 2016-03-01 00:00:00.000000000 Z
|
18
18
|
dependencies:
|
19
19
|
- !ruby/object:Gem::Dependency
|
20
20
|
name: active-fedora
|