dor-services 6.0.5 → 6.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/dor-services.rb +27 -12
- data/lib/dor/config.rb +45 -42
- data/lib/dor/datastreams/administrative_metadata_ds.rb +137 -44
- data/lib/dor/datastreams/content_metadata_ds.rb +42 -42
- data/lib/dor/datastreams/datastream_spec_solrizer.rb +1 -1
- data/lib/dor/datastreams/default_object_rights_ds.rb +185 -44
- data/lib/dor/datastreams/desc_metadata_ds.rb +36 -28
- data/lib/dor/datastreams/embargo_metadata_ds.rb +12 -14
- data/lib/dor/datastreams/events_ds.rb +10 -10
- data/lib/dor/datastreams/geo_metadata_ds.rb +4 -5
- data/lib/dor/datastreams/identity_metadata_ds.rb +14 -14
- data/lib/dor/datastreams/rights_metadata_ds.rb +23 -23
- data/lib/dor/datastreams/role_metadata_ds.rb +61 -15
- data/lib/dor/datastreams/simple_dublin_core_ds.rb +8 -8
- data/lib/dor/datastreams/version_metadata_ds.rb +10 -12
- data/lib/dor/datastreams/workflow_definition_ds.rb +6 -6
- data/lib/dor/datastreams/workflow_ds.rb +13 -13
- data/lib/dor/exceptions.rb +2 -2
- data/lib/dor/indexers/data_indexer.rb +1 -7
- data/lib/dor/indexers/describable_indexer.rb +1 -1
- data/lib/dor/indexers/identifiable_indexer.rb +0 -2
- data/lib/dor/indexers/processable_indexer.rb +55 -28
- data/lib/dor/indexers/releasable_indexer.rb +2 -2
- data/lib/dor/models/admin_policy_object.rb +4 -4
- data/lib/dor/models/concerns/assembleable.rb +4 -0
- data/lib/dor/models/concerns/contentable.rb +27 -69
- data/lib/dor/models/concerns/describable.rb +14 -29
- data/lib/dor/models/concerns/editable.rb +20 -334
- data/lib/dor/models/concerns/embargoable.rb +7 -11
- data/lib/dor/models/concerns/eventable.rb +5 -1
- data/lib/dor/models/concerns/geoable.rb +4 -4
- data/lib/dor/models/concerns/governable.rb +18 -87
- data/lib/dor/models/concerns/identifiable.rb +15 -75
- data/lib/dor/models/concerns/itemizable.rb +9 -11
- data/lib/dor/models/concerns/preservable.rb +4 -0
- data/lib/dor/models/concerns/processable.rb +30 -129
- data/lib/dor/models/concerns/publishable.rb +6 -55
- data/lib/dor/models/concerns/releaseable.rb +14 -227
- data/lib/dor/models/concerns/rightsable.rb +3 -3
- data/lib/dor/models/concerns/shelvable.rb +4 -49
- data/lib/dor/models/concerns/versionable.rb +21 -44
- data/lib/dor/models/set.rb +1 -1
- data/lib/dor/models/workflow_object.rb +2 -2
- data/lib/dor/services/ability.rb +77 -0
- data/lib/dor/services/cleanup_reset_service.rb +1 -3
- data/lib/dor/services/create_workflow_service.rb +51 -0
- data/lib/dor/services/creative_commons_license_service.rb +31 -0
- data/lib/dor/services/datastream_builder.rb +90 -0
- data/lib/dor/services/digital_stacks_service.rb +3 -21
- data/lib/dor/services/dublin_core_service.rb +40 -0
- data/lib/dor/services/file_metadata_merge_service.rb +67 -0
- data/lib/dor/services/indexing_service.rb +8 -4
- data/lib/dor/services/merge_service.rb +5 -5
- data/lib/dor/services/metadata_handlers/catalog_handler.rb +1 -1
- data/lib/dor/services/metadata_service.rb +6 -8
- data/lib/dor/{models/concerns → services}/mods2dc.xslt +0 -0
- data/lib/dor/services/ontology.rb +35 -0
- data/lib/dor/services/open_data_license_service.rb +20 -0
- data/lib/dor/services/public_desc_metadata_service.rb +21 -14
- data/lib/dor/services/public_xml_service.rb +6 -6
- data/lib/dor/services/publish_metadata_service.rb +100 -0
- data/lib/dor/services/registration_service.rb +43 -46
- data/lib/dor/services/release_tag_service.rb +251 -0
- data/lib/dor/services/reset_workspace_service.rb +1 -3
- data/lib/dor/services/sdr_ingest_service.rb +5 -7
- data/lib/dor/services/search_service.rb +10 -10
- data/lib/dor/services/secondary_file_name_service.rb +10 -0
- data/lib/dor/services/shelving_service.rb +67 -0
- data/lib/dor/services/status_service.rb +121 -0
- data/lib/dor/services/suri_service.rb +3 -5
- data/lib/dor/services/tag_service.rb +100 -0
- data/lib/dor/services/technical_metadata_service.rb +5 -4
- data/lib/dor/services/version_service.rb +84 -0
- data/lib/dor/utils/ng_tidy.rb +1 -1
- data/lib/dor/utils/sdr_client.rb +25 -9
- data/lib/dor/version.rb +1 -1
- data/lib/dor/workflow/document.rb +13 -13
- data/lib/dor/workflow/process.rb +71 -26
- data/lib/tasks/rdoc.rake +1 -1
- metadata +77 -51
- data/config/certs/robots-dor-dev.crt +0 -29
- data/config/certs/robots-dor-dev.key +0 -27
- data/config/dev_console_env.rb +0 -80
@@ -17,18 +17,18 @@ module Dor
|
|
17
17
|
client = Config.fedora.client['risearch']
|
18
18
|
client.options[:timeout] = opts.delete(:timeout)
|
19
19
|
query_params = {
|
20
|
-
:
|
21
|
-
:
|
22
|
-
:
|
23
|
-
:
|
24
|
-
:
|
25
|
-
:
|
20
|
+
type: 'tuples',
|
21
|
+
lang: 'itql',
|
22
|
+
format: 'CSV',
|
23
|
+
limit: '1000',
|
24
|
+
stream: 'on',
|
25
|
+
query: query
|
26
26
|
}.merge(opts)
|
27
27
|
result = client.post(query_params)
|
28
28
|
result.split(/\n/)[1..-1].collect { |pid| pid.chomp.sub(/^info:fedora\//, '') }
|
29
29
|
end
|
30
30
|
|
31
|
-
def iterate_over_pids(opts = {}
|
31
|
+
def iterate_over_pids(opts = {})
|
32
32
|
opts[:query] ||= 'select $object from <#ri> where $object <info:fedora/fedora-system:def/model#label> $label'
|
33
33
|
opts[:in_groups_of] ||= 100
|
34
34
|
opts[:mode] ||= :single
|
@@ -46,7 +46,7 @@ module Dor
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def query(query, args = {})
|
49
|
-
params = args.merge(
|
49
|
+
params = args.merge(q: query)
|
50
50
|
params[:start] ||= 0
|
51
51
|
resp = solr.get 'select', params: params
|
52
52
|
return resp unless block_given?
|
@@ -68,7 +68,7 @@ module Dor
|
|
68
68
|
end
|
69
69
|
q = "{!term f=#{Solrizer.solr_name 'identifier', :symbol}}#{id}"
|
70
70
|
result = []
|
71
|
-
query(q, :
|
71
|
+
query(q, fl: 'id', rows: 1000, defType: 'lucene') do |resp|
|
72
72
|
result += resp['response']['docs'].collect { |doc| doc['id'] }
|
73
73
|
true
|
74
74
|
end
|
@@ -86,7 +86,7 @@ module Dor
|
|
86
86
|
end
|
87
87
|
|
88
88
|
def find_sdr_graveyard_apo_druid
|
89
|
-
r = Dor::SearchService.query('dc_title_tesim:"SDR Graveyard"', :
|
89
|
+
r = Dor::SearchService.query('dc_title_tesim:"SDR Graveyard"', fl: 'id')
|
90
90
|
if r['response']['docs'].empty?
|
91
91
|
nil
|
92
92
|
else
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dor
|
4
|
+
# Merges contentMetadata from several objects into one.
|
5
|
+
class SecondaryFileNameService
|
6
|
+
def self.create(old_name, sequence_num)
|
7
|
+
old_name =~ /^(.*)\.(.*)$/ ? "#{Regexp.last_match(1)}_#{sequence_num}.#{Regexp.last_match(2)}" : "#{old_name}_#{sequence_num}"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dor
|
4
|
+
# Push file changes for shelve-able files into the stacks
|
5
|
+
class ShelvingService
|
6
|
+
def self.shelve(work)
|
7
|
+
new(work).shelve
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(work)
|
11
|
+
@work = work
|
12
|
+
end
|
13
|
+
|
14
|
+
def shelve
|
15
|
+
# retrieve the differences between the current contentMetadata and the previously ingested version
|
16
|
+
diff = shelve_diff
|
17
|
+
stacks_object_pathname = stacks_location
|
18
|
+
# determine the location of the object's files in the stacks area
|
19
|
+
stacks_druid = DruidTools::StacksDruid.new work.id, stacks_object_pathname
|
20
|
+
stacks_object_pathname = Pathname(stacks_druid.path)
|
21
|
+
# determine the location of the object's content files in the workspace area
|
22
|
+
workspace_druid = DruidTools::Druid.new(work.id, Config.stacks.local_workspace_root)
|
23
|
+
workspace_content_pathname = workspace_content_dir(diff, workspace_druid)
|
24
|
+
# delete, rename, or copy files to the stacks area
|
25
|
+
DigitalStacksService.remove_from_stacks(stacks_object_pathname, diff)
|
26
|
+
DigitalStacksService.rename_in_stacks(stacks_object_pathname, diff)
|
27
|
+
DigitalStacksService.shelve_to_stacks(workspace_content_pathname, stacks_object_pathname, diff)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :work
|
33
|
+
|
34
|
+
# retrieve the differences between the current contentMetadata and the previously ingested version
|
35
|
+
# (filtering to select only the files that should be shelved to stacks)
|
36
|
+
def shelve_diff
|
37
|
+
raise Dor::ParameterError, 'Missing Dor::Config.stacks.local_workspace_root' if Config.stacks.local_workspace_root.nil?
|
38
|
+
raise Dor::Exception, 'Missing contentMetadata datastream' if work.contentMetadata.nil?
|
39
|
+
|
40
|
+
inventory_diff = Sdr::Client.get_content_diff(work.pid, work.contentMetadata.content, 'shelve')
|
41
|
+
inventory_diff.group_difference('content')
|
42
|
+
end
|
43
|
+
|
44
|
+
# Find the location of the object's content files in the workspace area
|
45
|
+
# @param [Moab::FileGroupDifference] content_diff The differences between the current contentMetadata and the previously ingested version
|
46
|
+
# @param [DruidTools::Druid] workspace_druid the location of the object's files in the workspace area
|
47
|
+
# @return [Pathname] The location of the object's content files in the workspace area
|
48
|
+
def workspace_content_dir(content_diff, workspace_druid)
|
49
|
+
deltas = content_diff.file_deltas
|
50
|
+
filelist = deltas[:modified] + deltas[:added] + deltas[:copyadded].collect { |_old, new| new }
|
51
|
+
return nil if filelist.empty?
|
52
|
+
|
53
|
+
Pathname(workspace_druid.find_filelist_parent('content', filelist))
|
54
|
+
end
|
55
|
+
|
56
|
+
# get the stack location based on the contentMetadata stacks attribute
|
57
|
+
# or using the default value from the config file if it doesn't exist
|
58
|
+
def stacks_location
|
59
|
+
return Config.stacks.local_stacks_root unless work.contentMetadata&.stacks.present?
|
60
|
+
|
61
|
+
location = work.contentMetadata.stacks[0]
|
62
|
+
return location if location.start_with? '/' # Absolute stacks path
|
63
|
+
|
64
|
+
raise "stacks attribute for item: #{work.id} contentMetadata should start with /. The current value is #{location}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dor
|
4
|
+
# Query the processing status of an item.
|
5
|
+
# This has a dependency on the workflow service (app) to get milestones.
|
6
|
+
class StatusService
|
7
|
+
# verbiage we want to use to describe an item when it has completed a particular step
|
8
|
+
STATUS_CODE_DISP_TXT = {
|
9
|
+
0 => 'Unknown Status', # if there are no milestones for the current version, someone likely messed up the versioning process.
|
10
|
+
1 => 'Registered',
|
11
|
+
2 => 'In accessioning',
|
12
|
+
3 => 'In accessioning (described)',
|
13
|
+
4 => 'In accessioning (described, published)',
|
14
|
+
5 => 'In accessioning (described, published, deposited)',
|
15
|
+
6 => 'Accessioned',
|
16
|
+
7 => 'Accessioned (indexed)',
|
17
|
+
8 => 'Accessioned (indexed, ingested)',
|
18
|
+
9 => 'Opened'
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
# milestones from accessioning and the order they happen in
|
22
|
+
STEPS = {
|
23
|
+
'registered' => 1,
|
24
|
+
'submitted' => 2,
|
25
|
+
'described' => 3,
|
26
|
+
'published' => 4,
|
27
|
+
'deposited' => 5,
|
28
|
+
'accessioned' => 6,
|
29
|
+
'indexed' => 7,
|
30
|
+
'shelved' => 8,
|
31
|
+
'opened' => 9
|
32
|
+
}.freeze
|
33
|
+
|
34
|
+
# @return [Hash{Symbol => Object}] including :current_version, :status_code and :status_time
|
35
|
+
def self.status_info(work)
|
36
|
+
new(work).status_info
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.status(work, include_time = false)
|
40
|
+
new(work).status(include_time)
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize(work)
|
44
|
+
@work = work
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Hash{Symbol => Object}] including :current_version, :status_code and :status_time
|
48
|
+
def status_info
|
49
|
+
status_code = 0
|
50
|
+
status_time = nil
|
51
|
+
# for each milestone in the current version, see if it comes after the current 'last' step, if so, make it the last and record the date/time
|
52
|
+
current_milestones.each do |m|
|
53
|
+
m_name = m[:milestone]
|
54
|
+
m_time = m[:at].utc.xmlschema
|
55
|
+
next unless STEPS.key?(m_name) && (!status_time || m_time > status_time)
|
56
|
+
|
57
|
+
status_code = STEPS[m_name]
|
58
|
+
status_time = m_time
|
59
|
+
end
|
60
|
+
|
61
|
+
{ current_version: current_version, status_code: status_code, status_time: status_time }
|
62
|
+
end
|
63
|
+
|
64
|
+
# @param [Boolean] include_time
|
65
|
+
# @return [String] single composed status from status_info
|
66
|
+
def status(include_time = false)
|
67
|
+
status_info_hash = status_info
|
68
|
+
current_version = status_info_hash[:current_version]
|
69
|
+
status_code = status_info_hash[:status_code]
|
70
|
+
status_time = status_info_hash[:status_time]
|
71
|
+
|
72
|
+
# use the translation table to get the appropriate verbage for the latest step
|
73
|
+
result = "v#{current_version} #{STATUS_CODE_DISP_TXT[status_code]}"
|
74
|
+
result += " #{format_date(status_time)}" if include_time
|
75
|
+
result
|
76
|
+
end
|
77
|
+
|
78
|
+
def milestones
|
79
|
+
@milestones ||= Dor::Config.workflow.client.get_milestones('dor', work.pid)
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
attr_reader :work
|
85
|
+
|
86
|
+
def current_version
|
87
|
+
@current_version ||= begin
|
88
|
+
work.versionMetadata.current_version_id
|
89
|
+
rescue StandardError
|
90
|
+
'1'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def current_milestones
|
95
|
+
current = []
|
96
|
+
# only get steps that are part of accessioning and part of the current version. That can mean they were archived with the current version
|
97
|
+
# number, or they might be active (no version number).
|
98
|
+
milestones.each do |m|
|
99
|
+
if STEPS.key?(m[:milestone]) && (m[:version].nil? || m[:version] == current_version)
|
100
|
+
current << m unless m[:milestone] == 'registered' && current_version.to_i > 1
|
101
|
+
end
|
102
|
+
end
|
103
|
+
current
|
104
|
+
end
|
105
|
+
|
106
|
+
# handles formating utc date/time to human readable
|
107
|
+
# XXX: bad form to hardcode TZ here.
|
108
|
+
def format_date(datetime)
|
109
|
+
d =
|
110
|
+
if datetime.is_a?(Time)
|
111
|
+
datetime
|
112
|
+
else
|
113
|
+
DateTime.parse(datetime).in_time_zone(ActiveSupport::TimeZone.new('Pacific Time (US & Canada)'))
|
114
|
+
end
|
115
|
+
I18n.l(d).strftime('%Y-%m-%d %I:%M%p')
|
116
|
+
rescue StandardError
|
117
|
+
d = datetime.is_a?(Time) ? datetime : Time.parse(datetime.to_s)
|
118
|
+
d.strftime('%Y-%m-%d %I:%M%p')
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -15,18 +15,16 @@ module Dor
|
|
15
15
|
if Config.suri.mint_ids
|
16
16
|
# Post with no body
|
17
17
|
resource = RestClient::Resource.new("#{Config.suri.url}/suri2/namespaces/#{Config.suri.id_namespace}",
|
18
|
-
:
|
18
|
+
user: Config.suri.user, password: Config.suri.pass)
|
19
19
|
ids = resource["identifiers?quantity=#{quantity}"].post('').chomp.split(/\n/).collect { |id| "#{Config.suri.id_namespace}:#{id.strip}" }
|
20
20
|
else
|
21
21
|
repo = ActiveFedora::Base.respond_to?(:connection_for_pid) ? ActiveFedora::Base.connection_for_pid(0) : ActiveFedora.fedora.connection
|
22
22
|
resp = Nokogiri::XML(repo.api.next_pid(numPIDs: quantity))
|
23
|
-
ids = resp.xpath('/pidList/pid').collect
|
23
|
+
ids = resp.xpath('/pidList/pid').collect(&:text)
|
24
24
|
# With modernish (circa 2015/6) dependencies, including Nokogiri and
|
25
25
|
# ActiveFedora/Rubydora, `ids` is `[]` above. If that is the case, try
|
26
26
|
# the XPath that works (confirmed with most recent `hydra_etd` work)
|
27
|
-
if ids.empty? && resp.root.namespaces.any?
|
28
|
-
ids = resp.xpath('/xmlns:pidList/xmlns:pid').collect { |node| node.text }
|
29
|
-
end
|
27
|
+
ids = resp.xpath('/xmlns:pidList/xmlns:pid').collect(&:text) if ids.empty? && resp.root.namespaces.any?
|
30
28
|
end
|
31
29
|
want_array ? ids : ids.first
|
32
30
|
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dor
|
4
|
+
# Manage tags on an object
|
5
|
+
class TagService
|
6
|
+
def self.add(item, tag)
|
7
|
+
new(item).add(tag)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.remove(item, tag)
|
11
|
+
new(item).remove(tag)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.update(item, old_tag, new_tag)
|
15
|
+
new(item).update(old_tag, new_tag)
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(item)
|
19
|
+
@item = item
|
20
|
+
end
|
21
|
+
|
22
|
+
# Add an administrative tag to an item, you will need to seperately save the item to write it to fedora
|
23
|
+
# @param tag [string] The tag you wish to add
|
24
|
+
def add(tag)
|
25
|
+
normalized_tag = validate_and_normalize_tag(tag, identity_metadata.tags)
|
26
|
+
identity_metadata.add_value(:tag, normalized_tag)
|
27
|
+
end
|
28
|
+
|
29
|
+
def remove(tag)
|
30
|
+
normtag = normalize_tag(tag)
|
31
|
+
tag_nodes
|
32
|
+
.select { |node| normalize_tag(node.content) == normtag }
|
33
|
+
.each { identity_metadata.ng_xml_will_change! }
|
34
|
+
.each(&:remove)
|
35
|
+
.any?
|
36
|
+
end
|
37
|
+
|
38
|
+
def update(old_tag, new_tag)
|
39
|
+
normtag = normalize_tag(old_tag)
|
40
|
+
tag_nodes
|
41
|
+
.select { |node| normalize_tag(node.content) == normtag }
|
42
|
+
.each { identity_metadata.ng_xml_will_change! }
|
43
|
+
.each { |node| node.content = normalize_tag(new_tag) }
|
44
|
+
.any?
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
attr_reader :item
|
50
|
+
def identity_metadata
|
51
|
+
item.identityMetadata
|
52
|
+
end
|
53
|
+
|
54
|
+
def tag_nodes
|
55
|
+
identity_metadata.ng_xml.search('//tag')
|
56
|
+
end
|
57
|
+
|
58
|
+
# turns a tag string into an array with one element per tag part.
|
59
|
+
# split on ":", disregard leading and trailing whitespace on tokens.
|
60
|
+
def split_tag_to_arr(tag_str)
|
61
|
+
tag_str.split(':').map(&:strip)
|
62
|
+
end
|
63
|
+
|
64
|
+
# turn a tag array back into a tag string with a standard format
|
65
|
+
def normalize_tag_arr(tag_arr)
|
66
|
+
tag_arr.join(' : ')
|
67
|
+
end
|
68
|
+
|
69
|
+
# take a tag string and return a normalized tag string
|
70
|
+
def normalize_tag(tag_str)
|
71
|
+
normalize_tag_arr(split_tag_to_arr(tag_str))
|
72
|
+
end
|
73
|
+
|
74
|
+
# take a proposed tag string and a list of the existing tags for the object being edited. if
|
75
|
+
# the proposed tag is valid, return it in normalized form. if not, raise an exception with an
|
76
|
+
# explanatory message.
|
77
|
+
def validate_and_normalize_tag(tag_str, existing_tag_list)
|
78
|
+
tag_arr = validate_tag_format(tag_str)
|
79
|
+
|
80
|
+
# note that the comparison for duplicate tags is case-insensitive, but we don't change case as part of the normalized version
|
81
|
+
# we return, because we want to preserve the user's intended case.
|
82
|
+
normalized_tag = normalize_tag_arr(tag_arr)
|
83
|
+
dupe_existing_tag = existing_tag_list.detect { |existing_tag| normalize_tag(existing_tag).casecmp(normalized_tag) == 0 }
|
84
|
+
raise "An existing tag (#{dupe_existing_tag}) is the same, consider using update_tag?" if dupe_existing_tag
|
85
|
+
|
86
|
+
normalized_tag
|
87
|
+
end
|
88
|
+
|
89
|
+
# Ensure that an administrative tag meets the proper mininum format
|
90
|
+
# @param tag_str [String] the tag
|
91
|
+
# @return [Array] the tag split into an array via ':'
|
92
|
+
def validate_tag_format(tag_str)
|
93
|
+
tag_arr = split_tag_to_arr(tag_str)
|
94
|
+
raise ArgumentError, "Invalid tag structure: tag '#{tag_str}' must have at least 2 elements" if tag_arr.length < 2
|
95
|
+
raise ArgumentError, "Invalid tag structure: tag '#{tag_str}' contains empty elements" if tag_arr.detect(&:empty?)
|
96
|
+
|
97
|
+
tag_arr
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -49,10 +49,11 @@ module Dor
|
|
49
49
|
# @param [Dor::Item] dor_item The DOR item being processed by the technical metadata robot
|
50
50
|
# @return [FileGroupDifference] The differences between two versions of a group of files
|
51
51
|
def self.get_content_group_diff(dor_item)
|
52
|
-
|
52
|
+
return Moab::FileGroupDifference.new if dor_item.contentMetadata.nil?
|
53
|
+
raise Dor::ParameterError, 'Missing Dor::Config.stacks.local_workspace_root' if Config.stacks.local_workspace_root.nil?
|
54
|
+
|
55
|
+
inventory_diff = Sdr::Client.get_content_diff(dor_item.pid, dor_item.contentMetadata.content, 'all')
|
53
56
|
inventory_diff.group_difference('content')
|
54
|
-
rescue Dor::Exception # no contentMetadata
|
55
|
-
Moab::FileGroupDifference.new
|
56
57
|
end
|
57
58
|
|
58
59
|
# @param [FileGroupDifference] content_group_diff
|
@@ -92,7 +93,7 @@ module Dor
|
|
92
93
|
# The data is updated to the latest format.
|
93
94
|
def self.get_dor_technical_metadata(dor_item)
|
94
95
|
ds = 'technicalMetadata'
|
95
|
-
return nil unless dor_item.datastreams.
|
96
|
+
return nil unless dor_item.datastreams.key?(ds) && !dor_item.datastreams[ds].new?
|
96
97
|
|
97
98
|
dor_techmd = dor_item.datastreams[ds].content
|
98
99
|
return dor_techmd if dor_techmd =~ /<technicalMetadata/
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dor
|
4
|
+
# Open and close versions
|
5
|
+
class VersionService
|
6
|
+
def self.open(work, opts = {})
|
7
|
+
new(work).open(opts)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.close(work, opts = {})
|
11
|
+
new(work).close(opts)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(work)
|
15
|
+
@work = work
|
16
|
+
end
|
17
|
+
|
18
|
+
# Increments the version number and initializes versioningWF for the object
|
19
|
+
# @param [Hash] opts optional params
|
20
|
+
# @option opts [Boolean] :assume_accessioned If true, does not check whether object has been accessioned.
|
21
|
+
# @option opts [Boolean] :create_workflows_ds If false, create_workflow() will not initialize the workflows datastream.
|
22
|
+
# @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
|
23
|
+
# @raise [Dor::Exception] if the object hasn't been accessioned, or if a version is already opened
|
24
|
+
def open(opts = {})
|
25
|
+
# During local development, we need a way to open a new version even if the object has not been accessioned.
|
26
|
+
raise(Dor::Exception, 'Object net yet accessioned') unless
|
27
|
+
opts[:assume_accessioned] || Dor::Config.workflow.client.get_lifecycle('dor', work.pid, 'accessioned')
|
28
|
+
raise Dor::Exception, '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')
|
30
|
+
|
31
|
+
sdr_version = Sdr::Client.current_version work.pid
|
32
|
+
|
33
|
+
vmd_ds = work.versionMetadata
|
34
|
+
vmd_ds.sync_then_increment_version sdr_version
|
35
|
+
vmd_ds.save unless work.new_record?
|
36
|
+
|
37
|
+
k = :create_workflows_ds
|
38
|
+
if opts.key?(k)
|
39
|
+
# During local development, Hydrus (or another app w/ local Fedora) does not want to initialize workflows datastream.
|
40
|
+
work.create_workflow('versioningWF', opts[k])
|
41
|
+
else
|
42
|
+
work.create_workflow('versioningWF')
|
43
|
+
end
|
44
|
+
|
45
|
+
vmd_upd_info = opts[:vers_md_upd_info]
|
46
|
+
return unless vmd_upd_info
|
47
|
+
|
48
|
+
work.events.add_event('open', vmd_upd_info[:opening_user_name], "Version #{vmd_ds.current_version_id} opened")
|
49
|
+
vmd_ds.update_current_version(description: vmd_upd_info[:description], significance: vmd_upd_info[:significance].to_sym)
|
50
|
+
work.save
|
51
|
+
end
|
52
|
+
|
53
|
+
# Sets versioningWF:submit-version to completed and initiates accessionWF for the object
|
54
|
+
# @param [Hash] opts optional params
|
55
|
+
# @option opts [String] :description describes the version change
|
56
|
+
# @option opts [Symbol] :significance which part of the version tag to increment
|
57
|
+
# :major, :minor, :admin (see Dor::VersionTag#increment)
|
58
|
+
# @option opts [String] :version_num version number to archive rows with. Otherwise, current version is used
|
59
|
+
# @option opts [Boolean] :start_accesion set to true if you want accessioning to start (default), false otherwise
|
60
|
+
# @raise [Dor::Exception] if the object hasn't been opened for versioning, or if accessionWF has
|
61
|
+
# already been instantiated or the current version is missing a tag or description
|
62
|
+
def close(opts = {})
|
63
|
+
unless opts.empty?
|
64
|
+
work.versionMetadata.update_current_version opts
|
65
|
+
work.versionMetadata.save
|
66
|
+
end
|
67
|
+
|
68
|
+
raise Dor::Exception, 'latest version in versionMetadata requires tag and description before it can be closed' unless work.versionMetadata.current_version_closeable?
|
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')
|
71
|
+
|
72
|
+
Dor::Config.workflow.client.close_version 'dor', work.pid, opts.fetch(:start_accession, true) # Default to creating accessionWF when calling close_version
|
73
|
+
end
|
74
|
+
|
75
|
+
# @return [Boolean] true if 'opened' lifecycle is active, false otherwise
|
76
|
+
def open?
|
77
|
+
return true if Dor::Config.workflow.client.get_active_lifecycle('dor', work.pid, 'opened')
|
78
|
+
|
79
|
+
false
|
80
|
+
end
|
81
|
+
|
82
|
+
attr_reader :work
|
83
|
+
end
|
84
|
+
end
|