-
Notifications
You must be signed in to change notification settings - Fork 124
/
persister.rb
136 lines (113 loc) · 5.01 KB
/
persister.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# frozen_string_literal: true
require 'active_fedora/cleaner'
module Wings
module Valkyrie
##
# A valkyrie persister that aims for data consistency/backwards compatibility with ActiveFedora.
#
# The guiding principle of design for this persister is that resources persisted with it should
# be directly readable by `Hydra::Works`-style ActiveFedora models. It aims to be as complete as
# possible as a Valkyrie Persister, given that limitation.
class Persister
attr_reader :adapter
extend Forwardable
def_delegator :adapter, :resource_factory
# @param adapter [Wings::Valkyrie::MetadataAdapter] The adapter which holds the resource_factory for this persister.
# @note Many persister methods are part of Valkyrie's public API, but instantiation itself is not
def initialize(adapter:)
@adapter = adapter
end
# Persists a resource using ActiveFedora
# @param [Valkyrie::Resource] resource
# @return [Valkyrie::Resource] the persisted/updated resource
def save(resource:)
af_object = resource_factory.from_resource(resource: resource)
check_lock_tokens(af_object: af_object, resource: resource)
# the #save! api differs between ActiveFedora::Base and ActiveFedora::File objects,
# if we get a falsey response, we expect we have a File that has failed to save due
# to empty content
# we disable validation on the Active Fedora object
# because we've already done validation as a Valkyrie object
af_object.save!(validate: false) ||
raise(FailedSaveError.new("#{af_object.class}#save! returned non-true. It might be missing required content.", obj: af_object))
resource_factory.to_resource(object: af_object)
rescue ActiveFedora::RecordInvalid, RuntimeError => err
raise MissingOrUnsavedFileError.new(err.message, obj: af_object) if
err.message == 'Save the file first'
raise FailedSaveError.new(err.message, obj: af_object)
end
# Persists a resource using ActiveFedora
# @param [Valkyrie::Resource] resource
# @return [Valkyrie::Resource] the persisted/updated resource
def save_all(resources:)
resources.map { |resource| save(resource: resource) }
rescue ::Valkyrie::Persistence::StaleObjectError => _err
raise(::Valkyrie::Persistence::StaleObjectError,
"One or more resources have been updated by another process.")
end
# Deletes a resource persisted using ActiveFedora
# @param [Valkyrie::Resource] resource
# @return [Valkyrie::Resource] the deleted resource
def delete(resource:)
af_object = ActiveFedora::Base.new
af_object.id = resource.id
af_object.delete
resource
end
# Deletes all resources from Fedora and Solr
def wipe!
Hyrax::SolrService.delete_by_query("*:*")
Hyrax::SolrService.commit
ActiveFedora::Cleaner.clean!
end
class FailedSaveError < RuntimeError
attr_accessor :obj
def initialize(msg = nil, obj:)
self.obj = obj
msg = "Failed to save object #{obj}.\n" + msg
super(msg)
end
end
class MissingOrUnsavedFileError < FailedSaveError
def initialize(msg = nil, obj:)
msg = "Wings tried to save metadata for a file which has not " \
"been saved. Fedora creates a metadata node when the file is " \
"created, so it's not possible to add metadata for a file " \
"until the file contents are persisted.\n Use the " \
"Hyrax.storage_adapter to save the file before trying to " \
"save metadata.\n" + msg
super(msg, obj: obj)
end
end
private
##
# @return [void]
# @raise [::Valkyrie::Persistence::StaleObjectError]
def check_lock_tokens(af_object:, resource:)
return unless resource.optimistic_locking_enabled?
return if af_object.new_record?
return if
etag_lock_token_valid?(af_object: af_object, resource: resource) &&
last_modified_lock_token_valid?(af_object: af_object, resource: resource)
raise(::Valkyrie::Persistence::StaleObjectError,
"The object #{resource.id} has been updated by another process.")
end
##
# @return [Boolean]
def etag_lock_token_valid?(af_object:, resource:)
etag = resource.optimistic_lock_token.find { |t| t.adapter_id == 'wings-fedora-etag' }
return true unless etag
return true if af_object.etag == etag.token
false
end
##
# @return [Boolean]
def last_modified_lock_token_valid?(af_object:, resource:)
modified = resource.optimistic_lock_token.find { |t| t.adapter_id == 'wings-fedora-last-modified' }
return true unless modified
return true if Time.zone.parse(af_object.ldp_source.head.last_modified) <= Time.zone.parse(modified.token)
false
end
end
end
end