valkyrie 2.1.0 → 3.0.0.pre.beta.1
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 +71 -36
- data/.lando.yml +58 -0
- data/.rubocop.yml +11 -1
- data/.tool-versions +1 -1
- data/CHANGELOG.md +94 -13
- data/CONTRIBUTING.md +30 -8
- data/README.md +24 -48
- data/Rakefile +26 -20
- data/db/config.yml +3 -10
- data/lib/generators/valkyrie/resource_generator.rb +3 -3
- data/lib/valkyrie/change_set.rb +3 -3
- data/lib/valkyrie/id.rb +12 -19
- data/lib/valkyrie/indexers/access_controls_indexer.rb +17 -17
- data/lib/valkyrie/persistence/buffered_persister.rb +2 -2
- data/lib/valkyrie/persistence/composite_persister.rb +3 -3
- data/lib/valkyrie/persistence/custom_query_container.rb +8 -16
- data/lib/valkyrie/persistence/fedora/list_node.rb +43 -43
- data/lib/valkyrie/persistence/fedora/metadata_adapter.rb +5 -1
- 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 +1 -1
- data/lib/valkyrie/persistence/fedora/persister/model_converter.rb +15 -16
- data/lib/valkyrie/persistence/fedora/persister/orm_converter.rb +14 -19
- data/lib/valkyrie/persistence/fedora/persister.rb +83 -83
- data/lib/valkyrie/persistence/fedora/query_service.rb +39 -41
- data/lib/valkyrie/persistence/memory/persister.rb +51 -35
- data/lib/valkyrie/persistence/memory/query_service.rb +26 -30
- data/lib/valkyrie/persistence/postgres/orm_converter.rb +52 -52
- data/lib/valkyrie/persistence/postgres/persister.rb +4 -1
- data/lib/valkyrie/persistence/postgres/query_service.rb +34 -34
- data/lib/valkyrie/persistence/shared/json_value_mapper.rb +1 -1
- data/lib/valkyrie/persistence/solr/metadata_adapter.rb +15 -3
- data/lib/valkyrie/persistence/solr/model_converter.rb +323 -340
- data/lib/valkyrie/persistence/solr/orm_converter.rb +4 -4
- data/lib/valkyrie/persistence/solr/persister.rb +16 -4
- data/lib/valkyrie/persistence/solr/queries/find_by_alternate_identifier_query.rb +1 -1
- data/lib/valkyrie/persistence/solr/queries/find_by_id_query.rb +1 -1
- data/lib/valkyrie/persistence/solr/queries/find_members_query.rb +1 -1
- data/lib/valkyrie/persistence/solr/query_service.rb +12 -12
- data/lib/valkyrie/persistence/solr/repository.rb +17 -7
- data/lib/valkyrie/resource/access_controls.rb +1 -1
- data/lib/valkyrie/resource.rb +0 -1
- data/lib/valkyrie/specs/shared_specs/change_set.rb +1 -1
- data/lib/valkyrie/specs/shared_specs/file.rb +1 -0
- data/lib/valkyrie/specs/shared_specs/persister.rb +22 -4
- data/lib/valkyrie/specs/shared_specs/queries.rb +7 -0
- data/lib/valkyrie/specs/shared_specs/resource.rb +1 -1
- data/lib/valkyrie/specs/shared_specs/storage_adapter.rb +19 -0
- data/lib/valkyrie/specs/shared_specs/write_only/metadata_adapter.rb +62 -0
- data/lib/valkyrie/specs/shared_specs.rb +2 -0
- data/lib/valkyrie/storage/disk.rb +24 -1
- data/lib/valkyrie/storage/fedora.rb +17 -17
- data/lib/valkyrie/storage_adapter.rb +12 -12
- data/lib/valkyrie/types.rb +1 -1
- data/lib/valkyrie/version.rb +1 -1
- data/lib/valkyrie/vocab/pcdm_use.rb +12 -0
- data/lib/valkyrie.rb +13 -27
- data/tasks/dev.rake +14 -51
- data/valkyrie.gemspec +3 -6
- metadata +25 -63
- data/.docker-stack/valkyrie-development/docker-compose.yml +0 -53
- data/.docker-stack/valkyrie-test/docker-compose.yml +0 -53
- data/tasks/docker.rake +0 -31
@@ -313,7 +313,6 @@ module Valkyrie::Persistence::Fedora
|
|
313
313
|
end
|
314
314
|
|
315
315
|
# Generate a new RDF hash URI for the "child" graph for the ModelConverter::Property
|
316
|
-
# @see https://github.com/fcrepo4/fcrepo4/blob/master/fcrepo-kernel-modeshape/src/main/java/org/fcrepo/kernel/modeshape/rdf/JcrRdfTools.java#L455
|
317
316
|
# @return [RDF::Graph]
|
318
317
|
def subject_uri
|
319
318
|
@subject_uri ||= ::RDF::URI(RDF::Node.new.to_s.gsub("_:", "#"))
|
@@ -325,21 +324,21 @@ module Valkyrie::Persistence::Fedora
|
|
325
324
|
class MappedFedoraValue < ::Valkyrie::ValueMapper
|
326
325
|
private
|
327
326
|
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
327
|
+
# Map a default Ruby data type
|
328
|
+
# (This maps the existing Property to a FedoraValue first)
|
329
|
+
# @param [Object] converted_value
|
330
|
+
# @return [Valkyrie::Persistence::Fedora::Persister::ModelConverter::Property]
|
331
|
+
def map_value(converted_value:)
|
332
|
+
calling_mapper.for(
|
333
|
+
Property.new(
|
334
|
+
value.subject,
|
335
|
+
value.key,
|
336
|
+
converted_value,
|
337
|
+
value.adapter,
|
338
|
+
value.resource
|
339
|
+
)
|
340
|
+
).result
|
341
|
+
end
|
343
342
|
end
|
344
343
|
|
345
344
|
# Class mapping Property objects for Valkyrie IDs using typed RDF literals
|
@@ -97,11 +97,10 @@ module Valkyrie::Persistence::Fedora
|
|
97
97
|
end
|
98
98
|
end
|
99
99
|
|
100
|
-
# Class for handling cases where
|
101
|
-
class
|
100
|
+
# Class for handling cases where deny listed values should not be mapped
|
101
|
+
class DenylistedValue < ::Valkyrie::ValueMapper
|
102
102
|
FedoraValue.register(self)
|
103
|
-
|
104
|
-
# Determines whether or not the value has a blacklisted namespace for the RDF statement object
|
103
|
+
# Determines whether or not the value has a denied namespace for the RDF statement object
|
105
104
|
# (i. e. avoid attempting to map any RDF statements making assertions about LDP containers or resource internal to Fedora)
|
106
105
|
# @param [Property] value
|
107
106
|
# @return [Boolean]
|
@@ -109,7 +108,7 @@ module Valkyrie::Persistence::Fedora
|
|
109
108
|
value.statement.object.to_s.start_with?("http://www.w3.org/ns/ldp", "http://fedora.info")
|
110
109
|
end
|
111
110
|
|
112
|
-
# Provide the NullApplicator Class for any Property in a
|
111
|
+
# Provide the NullApplicator Class for any Property in a deny listed namespace
|
113
112
|
def result
|
114
113
|
NullApplicator
|
115
114
|
end
|
@@ -318,7 +317,7 @@ module Valkyrie::Persistence::Fedora
|
|
318
317
|
# Casts the value of the RDF literal into an Applicator for DateTime values
|
319
318
|
# @return [Applicator]
|
320
319
|
def result
|
321
|
-
value.statement.object = ::DateTime.iso8601(value.statement.object.to_s).
|
320
|
+
value.statement.object = ::DateTime.iso8601(value.statement.object.to_s).new_offset(0)
|
322
321
|
calling_mapper.for(Property.new(statement: value.statement, scope: value.scope, adapter: value.adapter)).result
|
323
322
|
end
|
324
323
|
end
|
@@ -394,7 +393,7 @@ module Valkyrie::Persistence::Fedora
|
|
394
393
|
# Casts the value of the RDF literal into an Applicator for DateTime values
|
395
394
|
# @return [Applicator]
|
396
395
|
def result
|
397
|
-
value.statement.object =
|
396
|
+
value.statement.object = DateTime.parse(value.statement.object.to_s).new_offset(0)
|
398
397
|
calling_mapper.for(Property.new(statement: value.statement, scope: value.scope, adapter: value.adapter)).result
|
399
398
|
end
|
400
399
|
end
|
@@ -522,7 +521,7 @@ module Valkyrie::Persistence::Fedora
|
|
522
521
|
# @param [Hash] hsh a new or existing Hash of attribute for Valkyrie resource attributes
|
523
522
|
# @return [Hash]
|
524
523
|
def apply_to(hsh)
|
525
|
-
return if
|
524
|
+
return if deny?(key)
|
526
525
|
hsh[key.to_sym] = if hsh.key?(key.to_sym)
|
527
526
|
Array.wrap(hsh[key.to_sym]) + cast_array(values)
|
528
527
|
else
|
@@ -541,13 +540,13 @@ module Valkyrie::Persistence::Fedora
|
|
541
540
|
key
|
542
541
|
end
|
543
542
|
|
544
|
-
# Determines whether or not a key is
|
543
|
+
# Determines whether or not a key is on the deny list for mapping
|
545
544
|
# (For example <http://fedora.info/definitions> assertions are not mapped to Valkyrie attributes)
|
546
545
|
# @param [Symbol] key
|
547
546
|
# @return [Boolean]
|
548
|
-
def
|
549
|
-
|
550
|
-
return true if key.start_with?(
|
547
|
+
def deny?(key)
|
548
|
+
denylist.each do |denylist_item|
|
549
|
+
return true if key.start_with?(denylist_item)
|
551
550
|
end
|
552
551
|
false
|
553
552
|
end
|
@@ -556,16 +555,12 @@ module Valkyrie::Persistence::Fedora
|
|
556
555
|
# @param [Object] values
|
557
556
|
# @return [Array<Object>]
|
558
557
|
def cast_array(values)
|
559
|
-
|
560
|
-
[values]
|
561
|
-
else
|
562
|
-
Array(values)
|
563
|
-
end
|
558
|
+
Array(values)
|
564
559
|
end
|
565
560
|
|
566
|
-
# Retrieve a list of
|
561
|
+
# Retrieve a list of denied URIs for predicates
|
567
562
|
# @return [Array<String>]
|
568
|
-
def
|
563
|
+
def denylist
|
569
564
|
[
|
570
565
|
"http://fedora.info/definitions",
|
571
566
|
"http://www.iana.org/assignments/relation/last"
|
@@ -13,7 +13,7 @@ module Valkyrie::Persistence::Fedora
|
|
13
13
|
end
|
14
14
|
|
15
15
|
# (see Valkyrie::Persistence::Memory::Persister#save)
|
16
|
-
def save(resource:)
|
16
|
+
def save(resource:, external_resource: false)
|
17
17
|
initialize_repository
|
18
18
|
internal_resource = resource.dup
|
19
19
|
internal_resource.created_at ||= Time.current
|
@@ -35,6 +35,8 @@ module Valkyrie::Persistence::Fedora
|
|
35
35
|
alternate_resources ? save_reference_to_resource(persisted_resource, alternate_resources) : persisted_resource
|
36
36
|
rescue Ldp::PreconditionFailed
|
37
37
|
raise Valkyrie::Persistence::StaleObjectError, "The object #{internal_resource.id} has been updated by another process."
|
38
|
+
rescue Ldp::Gone
|
39
|
+
raise Valkyrie::Persistence::ObjectNotFoundError, "The object #{resource.id} is previously persisted but not found at save time."
|
38
40
|
end
|
39
41
|
|
40
42
|
# (see Valkyrie::Persistence::Memory::Persister#save_all)
|
@@ -86,97 +88,95 @@ module Valkyrie::Persistence::Fedora
|
|
86
88
|
|
87
89
|
private
|
88
90
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
adapter.persister.save(resource: alternate_resource)
|
101
|
-
end
|
102
|
-
end
|
91
|
+
# Ensure that all alternate IDs for a given resource are persisted
|
92
|
+
# @param [Valkyrie::Resource] resource
|
93
|
+
# @return [Array<Valkyrie::Persistence::Fedora::AlternateIdentifier>]
|
94
|
+
def find_or_create_alternate_ids(resource)
|
95
|
+
return nil unless resource.try(:alternate_ids)
|
96
|
+
|
97
|
+
resource.alternate_ids.map do |alternate_identifier|
|
98
|
+
adapter.query_service.find_by(id: alternate_identifier)
|
99
|
+
rescue ::Valkyrie::Persistence::ObjectNotFoundError
|
100
|
+
alternate_resource = ::Valkyrie::Persistence::Fedora::AlternateIdentifier.new(id: alternate_identifier)
|
101
|
+
adapter.persister.save(resource: alternate_resource)
|
103
102
|
end
|
103
|
+
end
|
104
104
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
105
|
+
# Ensure that any Resources referenced by alternate IDs are deleted when a Resource has these IDs deleted
|
106
|
+
# @param [Valkyrie::Resource] updated_resource
|
107
|
+
def cleanup_alternate_resources(updated_resource)
|
108
|
+
persisted_resource = adapter.query_service.find_by(id: updated_resource.id)
|
109
|
+
removed_identifiers = persisted_resource.alternate_ids - updated_resource.alternate_ids
|
110
110
|
|
111
|
-
|
112
|
-
|
113
|
-
end
|
111
|
+
removed_identifiers.each do |removed_id|
|
112
|
+
adapter.persister.delete(resource: adapter.query_service.find_by(id: removed_id))
|
114
113
|
end
|
114
|
+
end
|
115
115
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
end
|
125
|
-
|
126
|
-
resource
|
116
|
+
# Ensure that any Resources referenced by alternate IDs are persisted when a Resource has these IDs added
|
117
|
+
# @param [Valkyrie::Resource] resource
|
118
|
+
# @param [Array<Valkyrie::Persistence::Fedora::AlternateIdentifier>] alternate_resources
|
119
|
+
# @return [Valkyrie::Resource]
|
120
|
+
def save_reference_to_resource(resource, alternate_resources)
|
121
|
+
alternate_resources.each do |alternate_resource|
|
122
|
+
alternate_resource.references = resource.id
|
123
|
+
adapter.persister.save(resource: alternate_resource)
|
127
124
|
end
|
128
125
|
|
129
|
-
|
130
|
-
|
131
|
-
# @return [Valkyrie::Persistence::OptimisticLockToken]
|
132
|
-
# @see https://github.com/samvera-labs/valkyrie/wiki/Optimistic-Locking
|
133
|
-
# @note Fedora's last modified response is not granular enough to produce an effective lock token
|
134
|
-
# therefore, we use the same implementation as the memory adapter. This could fail to lock a
|
135
|
-
# resource if Fedora updated this resource between the time it was saved and Valkyrie created
|
136
|
-
# the token.
|
137
|
-
def generate_lock_token(resource)
|
138
|
-
return unless resource.optimistic_locking_enabled?
|
139
|
-
token = Valkyrie::Persistence::OptimisticLockToken.new(adapter_id: adapter.id, token: Time.now.to_r)
|
140
|
-
resource.send("#{Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK}=", token)
|
141
|
-
end
|
126
|
+
resource
|
127
|
+
end
|
142
128
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
retrieved_lock_tokens = adapter.query_service.find_by(id: resource.id)[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK]
|
158
|
-
retrieved_lock_token = retrieved_lock_tokens.find { |lock_token| lock_token.adapter_id == adapter.id }
|
159
|
-
return if retrieved_lock_token.blank?
|
160
|
-
|
161
|
-
raise Valkyrie::Persistence::StaleObjectError, "The object #{resource.id} has been updated by another process." unless current_lock_token == retrieved_lock_token
|
162
|
-
end
|
129
|
+
# Generate the lock token for a Resource, and set it for attribute
|
130
|
+
# @param [Valkyrie::Resource] resource
|
131
|
+
# @return [Valkyrie::Persistence::OptimisticLockToken]
|
132
|
+
# @see https://github.com/samvera-labs/valkyrie/wiki/Optimistic-Locking
|
133
|
+
# @note Fedora's last modified response is not granular enough to produce an effective lock token
|
134
|
+
# therefore, we use the same implementation as the memory adapter. This could fail to lock a
|
135
|
+
# resource if Fedora updated this resource between the time it was saved and Valkyrie created
|
136
|
+
# the token.
|
137
|
+
def generate_lock_token(resource)
|
138
|
+
return unless resource.optimistic_locking_enabled?
|
139
|
+
token = Valkyrie::Persistence::OptimisticLockToken.new(adapter_id: adapter.id, token: Time.now.to_r)
|
140
|
+
resource.send("#{Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK}=", token)
|
141
|
+
end
|
163
142
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
143
|
+
# Determine whether or not a lock token is still valid for a persisted Resource
|
144
|
+
# If the persisted Resource has been updated since it was last read into memory,
|
145
|
+
# then the resouce in memory has been invalidated and Valkyrie::Persistence::StaleObjectError
|
146
|
+
# is raised.
|
147
|
+
# @param [Valkyrie::Resource] resource
|
148
|
+
# @see https://github.com/samvera-labs/valkyrie/wiki/Optimistic-Locking
|
149
|
+
# @raise [Valkyrie::Persistence::StaleObjectError]
|
150
|
+
def validate_lock_token(resource)
|
151
|
+
return unless resource.optimistic_locking_enabled?
|
152
|
+
return if resource.id.blank?
|
153
|
+
|
154
|
+
current_lock_token = resource[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK].find { |lock_token| lock_token.adapter_id == adapter.id }
|
155
|
+
return if current_lock_token.blank?
|
156
|
+
|
157
|
+
retrieved_lock_tokens = adapter.query_service.find_by(id: resource.id)[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK]
|
158
|
+
retrieved_lock_token = retrieved_lock_tokens.find { |lock_token| lock_token.adapter_id == adapter.id }
|
159
|
+
return if retrieved_lock_token.blank?
|
160
|
+
|
161
|
+
raise Valkyrie::Persistence::StaleObjectError, "The object #{resource.id} has been updated by another process." unless current_lock_token == retrieved_lock_token
|
162
|
+
end
|
171
163
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
164
|
+
# Retrieve the lock token that holds Fedora's system-managed last-modified date
|
165
|
+
# @param [Valkyrie::Resource] resource
|
166
|
+
# @return [Valkyrie::Persistence::OptimisticLockToken]
|
167
|
+
def native_lock_token(resource)
|
168
|
+
return unless resource.optimistic_locking_enabled?
|
169
|
+
resource[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK].find { |lock_token| lock_token.adapter_id.to_s == "native-#{adapter.id}" }
|
170
|
+
end
|
171
|
+
|
172
|
+
# Set Fedora request headers:
|
173
|
+
# * `Prefer: handling=lenient; received="minimal"` allows us to avoid sending all server-managed triples
|
174
|
+
# * `If-Unmodified-Since` triggers Fedora's server-side optimistic locking
|
175
|
+
# @param request [Faraday::Request]
|
176
|
+
# @param lock_token [Valkyrie::Persistence::OptimisticLockToken]
|
177
|
+
def update_request_headers(request, lock_token)
|
178
|
+
request.headers["Prefer"] = "handling=lenient; received=\"minimal\""
|
179
|
+
request.headers["If-Unmodified-Since"] = lock_token.token if lock_token
|
180
|
+
end
|
181
181
|
end
|
182
182
|
end
|
@@ -31,11 +31,9 @@ module Valkyrie::Persistence::Fedora
|
|
31
31
|
def find_many_by_ids(ids:)
|
32
32
|
ids = ids.uniq
|
33
33
|
ids.map do |id|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
nil
|
38
|
-
end
|
34
|
+
find_by(id: id)
|
35
|
+
rescue ::Valkyrie::Persistence::ObjectNotFoundError
|
36
|
+
nil
|
39
37
|
end.reject(&:nil?)
|
40
38
|
end
|
41
39
|
|
@@ -54,7 +52,7 @@ module Valkyrie::Persistence::Fedora
|
|
54
52
|
# @return [Array<RDF::URI>]
|
55
53
|
def include_uris
|
56
54
|
[
|
57
|
-
adapter.fedora_version
|
55
|
+
[5, 6].include?(adapter.fedora_version) ? "http://fedora.info/definitions/fcrepo#PreferInboundReferences" : ::RDF::Vocab::Fcrepo4.InboundReferences
|
58
56
|
]
|
59
57
|
end
|
60
58
|
|
@@ -137,44 +135,44 @@ module Valkyrie::Persistence::Fedora
|
|
137
135
|
|
138
136
|
private
|
139
137
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
138
|
+
def find_inverse_reference_ids_by_unordered(resource:, property:)
|
139
|
+
content = content_with_inbound(id: resource.id)
|
140
|
+
property_uri = adapter.schema.predicate_for(property: property, resource: nil)
|
141
|
+
content.graph.query([nil, property_uri, adapter.id_to_uri(resource.id)]).map(&:subject).map { |x| x.to_s.gsub(/#.*/, '') }.map { |x| adapter.uri_to_id(x) }
|
142
|
+
end
|
145
143
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
144
|
+
def find_inverse_references_by_ordered(resource:, property:, ignore_ids: [])
|
145
|
+
content = content_with_inbound(id: resource.id)
|
146
|
+
ids = content.graph.query([nil, ::RDF::Vocab::ORE.proxyFor, adapter.id_to_uri(resource.id)]).map(&:subject).map { |x| x.to_s.gsub(/#.*/, '') }.map { |x| adapter.uri_to_id(x) }
|
147
|
+
ids.uniq!
|
148
|
+
ids.delete_if { |id| ignore_ids.include? id }
|
149
|
+
ids.lazy.map { |id| find_by(id: id) }.select { |o| o[property].include?(resource.id) }
|
150
|
+
end
|
153
151
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
152
|
+
# Ensures that an object is (or can be cast into a) Valkyrie::ID
|
153
|
+
# @return [Valkyrie::ID]
|
154
|
+
# @raise [ArgumentError]
|
155
|
+
def validate_id(id)
|
156
|
+
id = Valkyrie::ID.new(id.to_s) if id.is_a?(String)
|
157
|
+
raise ArgumentError, 'id must be a Valkyrie::ID' unless id.is_a? Valkyrie::ID
|
158
|
+
end
|
161
159
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
160
|
+
# Resolve a URI for an LDP resource in Fedora and construct a Valkyrie::Resource
|
161
|
+
# @param uri [RDF::URI]
|
162
|
+
# @return [Valkyrie::Resource]
|
163
|
+
# @raise [Valkyrie::Persistence::ObjectNotFoundError]
|
164
|
+
def resource_from_uri(uri)
|
165
|
+
resource = Ldp::Resource.for(connection, uri, connection.get(uri))
|
166
|
+
resource_factory.to_resource(object: resource)
|
167
|
+
rescue ::Ldp::Gone, ::Ldp::NotFound
|
168
|
+
raise ::Valkyrie::Persistence::ObjectNotFoundError
|
169
|
+
end
|
172
170
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
171
|
+
# Ensures that a Valkyrie::Resource has been persisted
|
172
|
+
# @param resource [Valkyrie::Resource]
|
173
|
+
# @raise [ArgumentError]
|
174
|
+
def ensure_persisted(resource)
|
175
|
+
raise ArgumentError, 'resource is not saved' unless resource.persisted?
|
176
|
+
end
|
179
177
|
end
|
180
178
|
end
|
@@ -5,7 +5,7 @@ module Valkyrie::Persistence::Memory
|
|
5
5
|
# @note Documentation for persisters in general is maintained here.
|
6
6
|
class Persister
|
7
7
|
attr_reader :adapter
|
8
|
-
delegate :cache, to: :adapter
|
8
|
+
delegate :cache, :query_service, to: :adapter
|
9
9
|
|
10
10
|
# @param adapter [Valkyrie::Persistence::Memory::MetadataAdapter] The memory adapter which
|
11
11
|
# holds the cache for this persister.
|
@@ -16,11 +16,18 @@ module Valkyrie::Persistence::Memory
|
|
16
16
|
|
17
17
|
# Save a single resource.
|
18
18
|
# @param resource [Valkyrie::Resource] The resource to save.
|
19
|
+
# @param external_resource [Boolean] Whether the resource to be saved comes
|
20
|
+
# from a different metadata store. Allows a resource to be saved even if
|
21
|
+
# it's not already in the store. For example, if you're indexing a
|
22
|
+
# resource into Solr - it's saved in your primary metadata store, but not
|
23
|
+
# in Solr, so it's okay if it doesn't exist in Solr but is marked as
|
24
|
+
# persisted.
|
19
25
|
# @return [Valkyrie::Resource] The resource with an `#id` value generated by the
|
20
26
|
# persistence backend.
|
21
27
|
# @raise [Valkyrie::Persistence::StaleObjectError]
|
22
|
-
def save(resource:)
|
28
|
+
def save(resource:, external_resource: false)
|
23
29
|
raise Valkyrie::Persistence::StaleObjectError, "The object #{resource.id} has been updated by another process." unless valid_lock?(resource)
|
30
|
+
raise Valkyrie::Persistence::ObjectNotFoundError, "The object #{resource.id} is previously persisted but not found at save time." unless external_resource || valid_for_save?(resource)
|
24
31
|
|
25
32
|
# duplicate the resource so we are not creating side effects on the caller's resource
|
26
33
|
internal_resource = resource.dup
|
@@ -34,6 +41,15 @@ module Valkyrie::Persistence::Memory
|
|
34
41
|
cache[internal_resource.id] = internal_resource
|
35
42
|
end
|
36
43
|
|
44
|
+
# return true if resource is
|
45
|
+
# persisted and found
|
46
|
+
# or
|
47
|
+
# not persisted
|
48
|
+
def valid_for_save?(resource)
|
49
|
+
return true unless resource.persisted? # a new resource
|
50
|
+
query_service.find_by(id: resource.id).present? # a persisted resource must be found
|
51
|
+
end
|
52
|
+
|
37
53
|
# Save a batch of resources.
|
38
54
|
# @param resources [Array<Valkyrie::Resource>] List of resources to save.
|
39
55
|
# @return [Array<Valkyrie::Resource>] List of resources with an `#id` value
|
@@ -62,46 +78,46 @@ module Valkyrie::Persistence::Memory
|
|
62
78
|
|
63
79
|
private
|
64
80
|
|
65
|
-
|
66
|
-
|
67
|
-
|
81
|
+
def generate_id(resource)
|
82
|
+
resource.new(id: SecureRandom.uuid)
|
83
|
+
end
|
68
84
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
85
|
+
# Convert all dates to DateTime in the UTC time zone for consistency.
|
86
|
+
def normalize_dates!(resource)
|
87
|
+
resource.attributes.each { |k, v| resource.send("#{k}=", normalize_date_values(v)) }
|
88
|
+
end
|
73
89
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
90
|
+
def normalize_date_values(v)
|
91
|
+
return v.map { |val| normalize_date_value(val) } if v.is_a?(Array)
|
92
|
+
normalize_date_value(v)
|
93
|
+
end
|
78
94
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
95
|
+
def normalize_date_value(value)
|
96
|
+
return value.new_offset(0) if value.is_a?(DateTime)
|
97
|
+
return value.to_datetime.new_offset(0) if value.is_a?(Time)
|
98
|
+
value
|
99
|
+
end
|
84
100
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
101
|
+
# Create a new lock token based on the current timestamp.
|
102
|
+
def generate_lock_token(resource)
|
103
|
+
return unless resource.optimistic_locking_enabled?
|
104
|
+
token = Valkyrie::Persistence::OptimisticLockToken.new(adapter_id: adapter.id, token: Time.now.to_r)
|
105
|
+
resource.set_value(Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK, token)
|
106
|
+
end
|
91
107
|
|
92
|
-
|
93
|
-
|
94
|
-
|
108
|
+
# Check whether a resource is current.
|
109
|
+
def valid_lock?(resource)
|
110
|
+
return true unless resource.optimistic_locking_enabled?
|
95
111
|
|
96
|
-
|
97
|
-
|
112
|
+
cached_resource = cache[resource.id]
|
113
|
+
return true if cached_resource.blank?
|
98
114
|
|
99
|
-
|
100
|
-
|
101
|
-
|
115
|
+
resource_lock_tokens = resource[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK]
|
116
|
+
resource_value = resource_lock_tokens.find { |lock_token| lock_token.adapter_id == adapter.id }
|
117
|
+
return true if resource_value.blank?
|
102
118
|
|
103
|
-
|
104
|
-
|
105
|
-
|
119
|
+
cached_value = cached_resource[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK].first
|
120
|
+
cached_value == resource_value
|
121
|
+
end
|
106
122
|
end
|
107
123
|
end
|
@@ -51,11 +51,9 @@ module Valkyrie::Persistence::Memory
|
|
51
51
|
def find_many_by_ids(ids:)
|
52
52
|
ids = ids.uniq
|
53
53
|
ids.map do |id|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
nil
|
58
|
-
end
|
54
|
+
find_by(id: id)
|
55
|
+
rescue ::Valkyrie::Persistence::ObjectNotFoundError
|
56
|
+
nil
|
59
57
|
end.reject(&:nil?)
|
60
58
|
end
|
61
59
|
|
@@ -80,7 +78,7 @@ module Valkyrie::Persistence::Memory
|
|
80
78
|
# @return integer. Count objects in the persistence backend
|
81
79
|
# with the given class.
|
82
80
|
def count_all_of_model(model:)
|
83
|
-
cache.values.
|
81
|
+
cache.values.count { |obj| obj.is_a?(model) }
|
84
82
|
end
|
85
83
|
|
86
84
|
# Get all members of a given resource.
|
@@ -105,11 +103,9 @@ module Valkyrie::Persistence::Memory
|
|
105
103
|
# `property` property on `resource`. Not necessarily in order.
|
106
104
|
def find_references_by(resource:, property:, model: nil)
|
107
105
|
refs = Array.wrap(resource[property]).map do |id|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
nil
|
112
|
-
end
|
106
|
+
find_by(id: id)
|
107
|
+
rescue ::Valkyrie::Persistence::ObjectNotFoundError
|
108
|
+
nil
|
113
109
|
end.reject(&:nil?)
|
114
110
|
refs.uniq! unless ordered_property?(resource: resource, property: property)
|
115
111
|
return refs unless model
|
@@ -159,27 +155,27 @@ module Valkyrie::Persistence::Memory
|
|
159
155
|
|
160
156
|
private
|
161
157
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
158
|
+
# @return [Array<Valkyrie::ID>] a list of the identifiers of the member objects
|
159
|
+
def member_ids(resource:)
|
160
|
+
return [] unless resource.respond_to? :member_ids
|
161
|
+
resource.member_ids || []
|
162
|
+
end
|
167
163
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
164
|
+
# Determine whether or not a value is a Valkyrie ID
|
165
|
+
# @param [Object] id
|
166
|
+
# @return [Boolean]
|
167
|
+
def validate_id(id)
|
168
|
+
raise ArgumentError, 'id must be a Valkyrie::ID' unless id.is_a? Valkyrie::ID
|
169
|
+
end
|
174
170
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
171
|
+
# Ensure that a given Valkyrie Resource has been persisted
|
172
|
+
# @param [Valkyrie::Resource] resource
|
173
|
+
def ensure_persisted(resource)
|
174
|
+
raise ArgumentError, 'resource is not saved' unless resource.persisted?
|
175
|
+
end
|
180
176
|
|
181
|
-
|
182
|
-
|
183
|
-
|
177
|
+
def ordered_property?(resource:, property:)
|
178
|
+
resource.ordered_attribute?(property)
|
179
|
+
end
|
184
180
|
end
|
185
181
|
end
|