valkyrie 2.0.0 → 2.1.2
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/.circleci/config.yml +60 -56
- data/.lando.yml +40 -0
- data/.rubocop.yml +4 -1
- data/.tool-versions +1 -1
- data/Appraisals +4 -4
- data/CHANGELOG.md +136 -0
- data/README.md +21 -49
- data/Rakefile +21 -20
- data/db/config.yml +3 -10
- data/db/schema.rb +0 -40
- data/gemfiles/activerecord_5_2.gemfile +2 -0
- data/gemfiles/{activerecord_5_1.gemfile → activerecord_6_0.gemfile} +3 -1
- data/lib/generators/valkyrie/resource_generator.rb +3 -3
- data/lib/valkyrie.rb +33 -15
- data/lib/valkyrie/change_set.rb +3 -3
- data/lib/valkyrie/id.rb +26 -3
- data/lib/valkyrie/indexers/access_controls_indexer.rb +17 -17
- data/lib/valkyrie/logging.rb +72 -0
- data/lib/valkyrie/persistence/composite_persister.rb +1 -1
- data/lib/valkyrie/persistence/fedora.rb +2 -0
- data/lib/valkyrie/persistence/fedora/list_node.rb +46 -49
- data/lib/valkyrie/persistence/fedora/metadata_adapter.rb +2 -2
- data/lib/valkyrie/persistence/fedora/ordered_list.rb +90 -90
- data/lib/valkyrie/persistence/fedora/ordered_reader.rb +5 -5
- data/lib/valkyrie/persistence/fedora/permissive_schema.rb +3 -3
- data/lib/valkyrie/persistence/fedora/persister.rb +82 -83
- data/lib/valkyrie/persistence/fedora/persister/model_converter.rb +16 -17
- data/lib/valkyrie/persistence/fedora/persister/orm_converter.rb +38 -18
- data/lib/valkyrie/persistence/fedora/query_service.rb +54 -53
- data/lib/valkyrie/persistence/memory/persister.rb +33 -33
- data/lib/valkyrie/persistence/memory/query_service.rb +52 -34
- data/lib/valkyrie/persistence/postgres/orm_converter.rb +52 -52
- data/lib/valkyrie/persistence/postgres/query_service.rb +86 -33
- data/lib/valkyrie/persistence/postgres/resource_converter.rb +1 -1
- data/lib/valkyrie/persistence/shared/json_value_mapper.rb +4 -2
- data/lib/valkyrie/persistence/solr/model_converter.rb +337 -337
- data/lib/valkyrie/persistence/solr/orm_converter.rb +3 -3
- data/lib/valkyrie/persistence/solr/persister.rb +4 -17
- data/lib/valkyrie/persistence/solr/queries/find_all_query.rb +6 -0
- data/lib/valkyrie/persistence/solr/queries/find_members_query.rb +1 -1
- data/lib/valkyrie/persistence/solr/query_service.rb +42 -53
- data/lib/valkyrie/persistence/solr/repository.rb +2 -1
- data/lib/valkyrie/rdf_patches.rb +2 -2
- data/lib/valkyrie/resource.rb +36 -5
- data/lib/valkyrie/specs/shared_specs/change_set.rb +1 -1
- data/lib/valkyrie/specs/shared_specs/persister.rb +17 -6
- data/lib/valkyrie/specs/shared_specs/queries.rb +112 -9
- data/lib/valkyrie/storage/fedora.rb +17 -17
- data/lib/valkyrie/storage_adapter.rb +16 -13
- data/lib/valkyrie/types.rb +3 -1
- data/lib/valkyrie/version.rb +1 -1
- data/solr/config/solrconfig.xml +0 -10
- data/tasks/dev.rake +14 -51
- data/valkyrie.gemspec +4 -4
- metadata +40 -37
- data/.docker-stack/valkyrie-development/docker-compose.yml +0 -53
- data/.docker-stack/valkyrie-test/docker-compose.yml +0 -53
- data/db/seeds.rb +0 -8
- data/tasks/docker.rake +0 -31
@@ -18,7 +18,6 @@ module Valkyrie::Persistence::Fedora
|
|
18
18
|
class ListNode
|
19
19
|
attr_reader :rdf_subject, :graph
|
20
20
|
attr_writer :next, :prev
|
21
|
-
attr_accessor :target
|
22
21
|
attr_writer :next_uri, :prev_uri
|
23
22
|
attr_accessor :proxy_in, :proxy_for
|
24
23
|
attr_reader :adapter
|
@@ -59,16 +58,15 @@ module Valkyrie::Persistence::Fedora
|
|
59
58
|
def to_graph
|
60
59
|
return RDF::Graph.new if target_id.blank?
|
61
60
|
g = Resource.new(rdf_subject)
|
62
|
-
g.proxy_for =
|
61
|
+
g.proxy_for = target
|
63
62
|
g.proxy_in = proxy_in.try(:uri)
|
64
63
|
g.next = self.next.try(:rdf_subject)
|
65
64
|
g.prev = prev.try(:rdf_subject)
|
66
65
|
g.graph
|
67
66
|
end
|
68
67
|
|
69
|
-
#
|
70
|
-
|
71
|
-
def target_uri
|
68
|
+
# @return [RDF::URI] or [String]
|
69
|
+
def target
|
72
70
|
if target_id.is_a?(Valkyrie::ID)
|
73
71
|
adapter.id_to_uri(target_id.to_s)
|
74
72
|
else
|
@@ -76,7 +74,6 @@ module Valkyrie::Persistence::Fedora
|
|
76
74
|
end
|
77
75
|
end
|
78
76
|
|
79
|
-
# Generates the string ID value for the value in the list expression
|
80
77
|
# @return [String]
|
81
78
|
def target_id
|
82
79
|
if proxy_for.to_s.include?("/") && proxy_for.to_s.start_with?(adapter.connection_prefix)
|
@@ -88,60 +85,60 @@ module Valkyrie::Persistence::Fedora
|
|
88
85
|
|
89
86
|
private
|
90
87
|
|
91
|
-
|
88
|
+
attr_reader :next_uri, :prev_uri, :node_cache
|
92
89
|
|
93
|
-
|
94
|
-
|
95
|
-
|
90
|
+
# Class used to populate the RDF graph structure for the linked lists
|
91
|
+
class Builder
|
92
|
+
attr_reader :uri, :graph
|
96
93
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
94
|
+
# @param uri [RDF::URI] the URI for the linked list in the graph store
|
95
|
+
# @param graph [RDF::Repository] the RDF graph to be populated
|
96
|
+
def initialize(uri, graph)
|
97
|
+
@uri = uri
|
98
|
+
@graph = graph
|
99
|
+
end
|
103
100
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
101
|
+
# Populates attributes for the LinkedNode
|
102
|
+
# @param instance [ListNode]
|
103
|
+
def populate(instance)
|
104
|
+
instance.proxy_for = resource.proxy_for
|
105
|
+
instance.proxy_in = resource.proxy_in
|
106
|
+
instance.next_uri = resource.next
|
107
|
+
instance.prev_uri = resource.prev
|
108
|
+
end
|
112
109
|
|
113
|
-
|
110
|
+
private
|
114
111
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
end
|
112
|
+
# Constructs a set of triples using ActiveTriples as objects
|
113
|
+
# @return [Valkyrie::Persistence::Fedora::ListNode::Resource]
|
114
|
+
def resource
|
115
|
+
@resource ||= Resource.new(uri, graph: graph)
|
120
116
|
end
|
117
|
+
end
|
121
118
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
119
|
+
# Class for providing a set of triples modeling linked list nodes
|
120
|
+
class Resource
|
121
|
+
def self.property(property, predicate:)
|
122
|
+
define_method property do
|
123
|
+
graph.query([uri, predicate, nil]).objects.first
|
124
|
+
end
|
128
125
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
end
|
126
|
+
define_method "#{property}=" do |val|
|
127
|
+
return if val.nil?
|
128
|
+
graph << [uri, predicate, val]
|
133
129
|
end
|
130
|
+
end
|
134
131
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
132
|
+
property :proxy_for, predicate: ::RDF::Vocab::ORE.proxyFor
|
133
|
+
property :proxy_in, predicate: ::RDF::Vocab::ORE.proxyIn
|
134
|
+
property :next, predicate: ::RDF::Vocab::IANA.next
|
135
|
+
property :prev, predicate: ::RDF::Vocab::IANA.prev
|
139
136
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
end
|
137
|
+
attr_reader :graph, :uri
|
138
|
+
def initialize(uri, graph: RDF::Graph.new)
|
139
|
+
@uri = uri
|
140
|
+
@graph = graph
|
145
141
|
end
|
142
|
+
end
|
146
143
|
end
|
147
144
|
end
|
@@ -6,7 +6,7 @@ module Valkyrie::Persistence::Fedora
|
|
6
6
|
# Valkyrie::Persistence::Fedora::MetadataAdapter.new(
|
7
7
|
# connection: ::Ldp::Client.new("http://localhost:8988/rest"),
|
8
8
|
# base_path: "test_fed",
|
9
|
-
# schema: Valkyrie::Persistence::Fedora::PermissiveSchema.new(title: RDF::URI("http://
|
9
|
+
# schema: Valkyrie::Persistence::Fedora::PermissiveSchema.new(title: RDF::URI("http://example.com/title"))
|
10
10
|
# )
|
11
11
|
class MetadataAdapter
|
12
12
|
attr_reader :connection, :base_path, :schema, :fedora_version
|
@@ -15,7 +15,7 @@ module Valkyrie::Persistence::Fedora
|
|
15
15
|
# @param [String] base_path
|
16
16
|
# @param [Valkyrie::Persistence::Fedora::PermissiveSchema] schema
|
17
17
|
# @param [Integer] fedora_version
|
18
|
-
def initialize(connection:, base_path: "/", schema: Valkyrie::Persistence::Fedora::PermissiveSchema.new, fedora_version:
|
18
|
+
def initialize(connection:, base_path: "/", schema: Valkyrie::Persistence::Fedora::PermissiveSchema.new, fedora_version: Valkyrie::Persistence::Fedora::DEFAULT_FEDORA_VERSION)
|
19
19
|
@connection = connection
|
20
20
|
@base_path = base_path
|
21
21
|
@schema = schema
|
@@ -67,111 +67,111 @@ module Valkyrie::Persistence::Fedora
|
|
67
67
|
|
68
68
|
private
|
69
69
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
70
|
+
attr_reader :node_cache
|
71
|
+
|
72
|
+
# Append a node to a linked list
|
73
|
+
# @param source [ListNode] the node being appended
|
74
|
+
# @param append_node [ListNode] the node already in the existing list
|
75
|
+
def append_to(source, append_node)
|
76
|
+
source.prev = append_node
|
77
|
+
append_node.next.prev = source
|
78
|
+
source.next = append_node.next
|
79
|
+
append_node.next = source
|
80
|
+
@changed = true
|
81
|
+
end
|
82
82
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
83
|
+
# Constructs a new OrderedReader for this OrderedList
|
84
|
+
# @return [OrderedReader]
|
85
|
+
def ordered_reader
|
86
|
+
OrderedReader.new(self)
|
87
|
+
end
|
88
88
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
end
|
89
|
+
# Populates the list with constructed ListNode Objects
|
90
|
+
# @param subject [RDF::URI]
|
91
|
+
def build_node(subject = nil)
|
92
|
+
return nil unless subject
|
93
|
+
node_cache.fetch(subject) do
|
94
|
+
ListNode.new(node_cache, subject, adapter, graph)
|
96
95
|
end
|
96
|
+
end
|
97
97
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
98
|
+
# Generates hash URIs for the subject of the LinkedList
|
99
|
+
# Should one of these URIs already be in use, a new URI will be generated
|
100
|
+
# @return [RDF::URI]
|
101
|
+
def new_node_subject
|
102
|
+
node = ::RDF::URI("##{::RDF::Node.new.id}")
|
103
|
+
node = ::RDF::URI("##{::RDF::Node.new.id}") while node_cache.key?(node)
|
104
|
+
node
|
105
|
+
end
|
106
106
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
107
|
+
# Class used for caching LinkedNode objects mapped to URIs
|
108
|
+
class NodeCache
|
109
|
+
def initialize
|
110
|
+
@cache ||= {}
|
111
|
+
end
|
112
112
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
113
|
+
# Retrieve the ListNode for a given URI
|
114
|
+
# If a block is passed, set its output to the cache
|
115
|
+
# @param uri [RDF::URI]
|
116
|
+
# @return [ListNode]
|
117
|
+
def fetch(uri)
|
118
|
+
@cache[uri] ||= yield if block_given?
|
119
|
+
end
|
120
120
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
end
|
121
|
+
# Determines whether or not the cache contains a key
|
122
|
+
# @param key [Object]
|
123
|
+
# @return [Boolean]
|
124
|
+
def key?(key)
|
125
|
+
@cache.key?(key)
|
127
126
|
end
|
127
|
+
end
|
128
128
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
129
|
+
# Class modeling sentinels within the linked list
|
130
|
+
# @see https://en.wikipedia.org/wiki/Sentinel_value
|
131
|
+
class Sentinel
|
132
|
+
attr_reader :parent, :next, :prev
|
133
|
+
attr_writer :next, :prev
|
134
|
+
|
135
|
+
# @param parent [Valkyrie::Persistence::Fedora::OrderedList]
|
136
|
+
# @param next_node [ListNode]
|
137
|
+
# @param prev_node [ListNode]
|
138
|
+
def initialize(parent, next_node: nil, prev_node: nil)
|
139
|
+
@parent = parent
|
140
|
+
@next = next_node
|
141
|
+
@prev = prev_node
|
142
|
+
end
|
143
143
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
144
|
+
# Ensure that this always behaves like a NilClass
|
145
|
+
# @return [TrueClass]
|
146
|
+
def nil?
|
147
|
+
true
|
148
|
+
end
|
149
149
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
end
|
150
|
+
# Ensure that this does not have a URI
|
151
|
+
# @return [NilClass]
|
152
|
+
def rdf_subject
|
153
|
+
nil
|
155
154
|
end
|
155
|
+
end
|
156
156
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
end
|
157
|
+
class HeadSentinel < Sentinel
|
158
|
+
# @param parent [Valkyrie::Persistence::Fedora::OrderedList]
|
159
|
+
# @param next_node [ListNode]
|
160
|
+
# @param prev_node [ListNode]
|
161
|
+
def initialize(*args)
|
162
|
+
super
|
163
|
+
@next ||= TailSentinel.new(parent, prev_node: self)
|
165
164
|
end
|
165
|
+
end
|
166
166
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
end
|
167
|
+
class TailSentinel < Sentinel
|
168
|
+
# @param parent [Valkyrie::Persistence::Fedora::OrderedList]
|
169
|
+
# @param next_node [ListNode]
|
170
|
+
# @param prev_node [ListNode]
|
171
|
+
def initialize(*args)
|
172
|
+
super
|
173
|
+
prev.next = self if prev&.next != self
|
175
174
|
end
|
175
|
+
end
|
176
176
|
end
|
177
177
|
end
|
@@ -26,10 +26,10 @@ module Valkyrie::Persistence::Fedora
|
|
26
26
|
|
27
27
|
private
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
29
|
+
# Access the "first" (head) node for the linked list
|
30
|
+
# @return [Valkyrie::Persistence::Fedora::OrderedList::HeadSentinel]
|
31
|
+
def first_head
|
32
|
+
root.head
|
33
|
+
end
|
34
34
|
end
|
35
35
|
end
|
@@ -6,8 +6,8 @@ module Valkyrie::Persistence::Fedora
|
|
6
6
|
#
|
7
7
|
# @example Passing in a mapping
|
8
8
|
# schema = Valkyrie::Persistence::Fedora::PermissiveSchema.new(member_ids:
|
9
|
-
# RDF::URI("http://
|
10
|
-
# schema.predicate_for(resource: Resource.new, property: :member_ids) # => RDF::URI<"http://
|
9
|
+
# RDF::URI("http://example.com/member_ids"))
|
10
|
+
# schema.predicate_for(resource: Resource.new, property: :member_ids) # => RDF::URI<"http://example.com/member_ids">
|
11
11
|
# schema.predicate_for(resource: Resource.new, property: :unknown) # => RDF::URI<"http://example.com/predicate/unknown">
|
12
12
|
class PermissiveSchema
|
13
13
|
URI_PREFIX = 'http://example.com/predicate/'
|
@@ -92,7 +92,7 @@ module Valkyrie::Persistence::Fedora
|
|
92
92
|
existing_predicates = schema.find { |_k, v| v == RDF::URI(predicate.to_s) }
|
93
93
|
predicate_name = predicate.to_s.gsub(URI_PREFIX, '')
|
94
94
|
|
95
|
-
return predicate_name if existing_predicates.
|
95
|
+
return predicate_name if existing_predicates.blank?
|
96
96
|
existing_predicates.first
|
97
97
|
end
|
98
98
|
end
|
@@ -64,11 +64,12 @@ module Valkyrie::Persistence::Fedora
|
|
64
64
|
# (see Valkyrie::Persistence::Memory::Persister#wipe!)
|
65
65
|
# Deletes Fedora repository resource *and* the tombstone resources which remain
|
66
66
|
# @see https://wiki.duraspace.org/display/FEDORA4x/RESTful+HTTP+API#RESTfulHTTPAPI-RedDELETEDeletearesource
|
67
|
+
# @see Valkyrie::Logging for details concerning log suppression.
|
67
68
|
def wipe!
|
68
69
|
connection.delete(base_path)
|
69
70
|
connection.delete("#{base_path}/fcr:tombstone")
|
70
71
|
rescue => error
|
71
|
-
Valkyrie.logger.debug("Failed to wipe Fedora for some reason: #{error}") unless error.is_a?(::Ldp::NotFound)
|
72
|
+
Valkyrie.logger.debug("Failed to wipe Fedora for some reason: #{error}", logging_context: "Valkyrie::Persistence::Fedora::Persister#wipe") unless error.is_a?(::Ldp::NotFound)
|
72
73
|
end
|
73
74
|
|
74
75
|
# Creates the root LDP Container for the connection with Fedora
|
@@ -85,97 +86,95 @@ module Valkyrie::Persistence::Fedora
|
|
85
86
|
|
86
87
|
private
|
87
88
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
adapter.persister.save(resource: alternate_resource)
|
100
|
-
end
|
101
|
-
end
|
89
|
+
# Ensure that all alternate IDs for a given resource are persisted
|
90
|
+
# @param [Valkyrie::Resource] resource
|
91
|
+
# @return [Array<Valkyrie::Persistence::Fedora::AlternateIdentifier>]
|
92
|
+
def find_or_create_alternate_ids(resource)
|
93
|
+
return nil unless resource.try(:alternate_ids)
|
94
|
+
|
95
|
+
resource.alternate_ids.map do |alternate_identifier|
|
96
|
+
adapter.query_service.find_by(id: alternate_identifier)
|
97
|
+
rescue ::Valkyrie::Persistence::ObjectNotFoundError
|
98
|
+
alternate_resource = ::Valkyrie::Persistence::Fedora::AlternateIdentifier.new(id: alternate_identifier)
|
99
|
+
adapter.persister.save(resource: alternate_resource)
|
102
100
|
end
|
101
|
+
end
|
103
102
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
103
|
+
# Ensure that any Resources referenced by alternate IDs are deleted when a Resource has these IDs deleted
|
104
|
+
# @param [Valkyrie::Resource] updated_resource
|
105
|
+
def cleanup_alternate_resources(updated_resource)
|
106
|
+
persisted_resource = adapter.query_service.find_by(id: updated_resource.id)
|
107
|
+
removed_identifiers = persisted_resource.alternate_ids - updated_resource.alternate_ids
|
109
108
|
|
110
|
-
|
111
|
-
|
112
|
-
end
|
109
|
+
removed_identifiers.each do |removed_id|
|
110
|
+
adapter.persister.delete(resource: adapter.query_service.find_by(id: removed_id))
|
113
111
|
end
|
112
|
+
end
|
114
113
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
end
|
124
|
-
|
125
|
-
resource
|
114
|
+
# Ensure that any Resources referenced by alternate IDs are persisted when a Resource has these IDs added
|
115
|
+
# @param [Valkyrie::Resource] resource
|
116
|
+
# @param [Array<Valkyrie::Persistence::Fedora::AlternateIdentifier>] alternate_resources
|
117
|
+
# @return [Valkyrie::Resource]
|
118
|
+
def save_reference_to_resource(resource, alternate_resources)
|
119
|
+
alternate_resources.each do |alternate_resource|
|
120
|
+
alternate_resource.references = resource.id
|
121
|
+
adapter.persister.save(resource: alternate_resource)
|
126
122
|
end
|
127
123
|
|
128
|
-
|
129
|
-
|
130
|
-
# @return [Valkyrie::Persistence::OptimisticLockToken]
|
131
|
-
# @see https://github.com/samvera-labs/valkyrie/wiki/Optimistic-Locking
|
132
|
-
# @note Fedora's last modified response is not granular enough to produce an effective lock token
|
133
|
-
# therefore, we use the same implementation as the memory adapter. This could fail to lock a
|
134
|
-
# resource if Fedora updated this resource between the time it was saved and Valkyrie created
|
135
|
-
# the token.
|
136
|
-
def generate_lock_token(resource)
|
137
|
-
return unless resource.optimistic_locking_enabled?
|
138
|
-
token = Valkyrie::Persistence::OptimisticLockToken.new(adapter_id: adapter.id, token: Time.now.to_r)
|
139
|
-
resource.send("#{Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK}=", token)
|
140
|
-
end
|
124
|
+
resource
|
125
|
+
end
|
141
126
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
retrieved_lock_tokens = adapter.query_service.find_by(id: resource.id)[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK]
|
157
|
-
retrieved_lock_token = retrieved_lock_tokens.find { |lock_token| lock_token.adapter_id == adapter.id }
|
158
|
-
return if retrieved_lock_token.blank?
|
159
|
-
|
160
|
-
raise Valkyrie::Persistence::StaleObjectError, "The object #{resource.id} has been updated by another process." unless current_lock_token == retrieved_lock_token
|
161
|
-
end
|
127
|
+
# Generate the lock token for a Resource, and set it for attribute
|
128
|
+
# @param [Valkyrie::Resource] resource
|
129
|
+
# @return [Valkyrie::Persistence::OptimisticLockToken]
|
130
|
+
# @see https://github.com/samvera-labs/valkyrie/wiki/Optimistic-Locking
|
131
|
+
# @note Fedora's last modified response is not granular enough to produce an effective lock token
|
132
|
+
# therefore, we use the same implementation as the memory adapter. This could fail to lock a
|
133
|
+
# resource if Fedora updated this resource between the time it was saved and Valkyrie created
|
134
|
+
# the token.
|
135
|
+
def generate_lock_token(resource)
|
136
|
+
return unless resource.optimistic_locking_enabled?
|
137
|
+
token = Valkyrie::Persistence::OptimisticLockToken.new(adapter_id: adapter.id, token: Time.now.to_r)
|
138
|
+
resource.send("#{Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK}=", token)
|
139
|
+
end
|
162
140
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
141
|
+
# Determine whether or not a lock token is still valid for a persisted Resource
|
142
|
+
# If the persisted Resource has been updated since it was last read into memory,
|
143
|
+
# then the resouce in memory has been invalidated and Valkyrie::Persistence::StaleObjectError
|
144
|
+
# is raised.
|
145
|
+
# @param [Valkyrie::Resource] resource
|
146
|
+
# @see https://github.com/samvera-labs/valkyrie/wiki/Optimistic-Locking
|
147
|
+
# @raise [Valkyrie::Persistence::StaleObjectError]
|
148
|
+
def validate_lock_token(resource)
|
149
|
+
return unless resource.optimistic_locking_enabled?
|
150
|
+
return if resource.id.blank?
|
151
|
+
|
152
|
+
current_lock_token = resource[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK].find { |lock_token| lock_token.adapter_id == adapter.id }
|
153
|
+
return if current_lock_token.blank?
|
154
|
+
|
155
|
+
retrieved_lock_tokens = adapter.query_service.find_by(id: resource.id)[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK]
|
156
|
+
retrieved_lock_token = retrieved_lock_tokens.find { |lock_token| lock_token.adapter_id == adapter.id }
|
157
|
+
return if retrieved_lock_token.blank?
|
158
|
+
|
159
|
+
raise Valkyrie::Persistence::StaleObjectError, "The object #{resource.id} has been updated by another process." unless current_lock_token == retrieved_lock_token
|
160
|
+
end
|
170
161
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
162
|
+
# Retrieve the lock token that holds Fedora's system-managed last-modified date
|
163
|
+
# @param [Valkyrie::Resource] resource
|
164
|
+
# @return [Valkyrie::Persistence::OptimisticLockToken]
|
165
|
+
def native_lock_token(resource)
|
166
|
+
return unless resource.optimistic_locking_enabled?
|
167
|
+
resource[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK].find { |lock_token| lock_token.adapter_id.to_s == "native-#{adapter.id}" }
|
168
|
+
end
|
169
|
+
|
170
|
+
# Set Fedora request headers:
|
171
|
+
# * `Prefer: handling=lenient; received="minimal"` allows us to avoid sending all server-managed triples
|
172
|
+
# * `If-Unmodified-Since` triggers Fedora's server-side optimistic locking
|
173
|
+
# @param request [Faraday::Request]
|
174
|
+
# @param lock_token [Valkyrie::Persistence::OptimisticLockToken]
|
175
|
+
def update_request_headers(request, lock_token)
|
176
|
+
request.headers["Prefer"] = "handling=lenient; received=\"minimal\""
|
177
|
+
request.headers["If-Unmodified-Since"] = lock_token.token if lock_token
|
178
|
+
end
|
180
179
|
end
|
181
180
|
end
|