valkyrie 1.2.0.rc1 → 1.2.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/README.md +12 -4
  4. data/lib/valkyrie/persistence/composite_persister.rb +1 -1
  5. data/lib/valkyrie/persistence/fedora/list_node.rb +42 -3
  6. data/lib/valkyrie/persistence/fedora/metadata_adapter.rb +26 -0
  7. data/lib/valkyrie/persistence/fedora/ordered_list.rb +36 -5
  8. data/lib/valkyrie/persistence/fedora/ordered_reader.rb +6 -0
  9. data/lib/valkyrie/persistence/fedora/permissive_schema.rb +20 -1
  10. data/lib/valkyrie/persistence/fedora/persister.rb +33 -4
  11. data/lib/valkyrie/persistence/fedora/persister/alternate_identifier.rb +6 -0
  12. data/lib/valkyrie/persistence/fedora/persister/model_converter.rb +254 -4
  13. data/lib/valkyrie/persistence/fedora/persister/orm_converter.rb +250 -3
  14. data/lib/valkyrie/persistence/fedora/persister/resource_factory.rb +6 -0
  15. data/lib/valkyrie/persistence/fedora/query_service.rb +22 -4
  16. data/lib/valkyrie/persistence/memory/metadata_adapter.rb +2 -0
  17. data/lib/valkyrie/persistence/memory/persister.rb +11 -3
  18. data/lib/valkyrie/persistence/memory/query_service.rb +11 -0
  19. data/lib/valkyrie/persistence/postgres/metadata_adapter.rb +2 -0
  20. data/lib/valkyrie/persistence/postgres/orm.rb +4 -0
  21. data/lib/valkyrie/persistence/postgres/orm_converter.rb +62 -2
  22. data/lib/valkyrie/persistence/postgres/persister.rb +18 -7
  23. data/lib/valkyrie/persistence/postgres/query_service.rb +103 -11
  24. data/lib/valkyrie/persistence/postgres/resource_converter.rb +10 -0
  25. data/lib/valkyrie/persistence/postgres/resource_factory.rb +3 -0
  26. data/lib/valkyrie/persistence/solr/composite_indexer.rb +10 -0
  27. data/lib/valkyrie/persistence/solr/metadata_adapter.rb +7 -0
  28. data/lib/valkyrie/persistence/solr/model_converter.rb +137 -0
  29. data/lib/valkyrie/persistence/solr/orm_converter.rb +168 -0
  30. data/lib/valkyrie/persistence/solr/persister.rb +13 -5
  31. data/lib/valkyrie/persistence/solr/queries.rb +1 -0
  32. data/lib/valkyrie/persistence/solr/queries/default_paginator.rb +11 -1
  33. data/lib/valkyrie/persistence/solr/queries/find_all_query.rb +12 -0
  34. data/lib/valkyrie/persistence/solr/queries/find_by_alternate_identifier_query.rb +12 -0
  35. data/lib/valkyrie/persistence/solr/queries/find_by_id_query.rb +11 -0
  36. data/lib/valkyrie/persistence/solr/queries/find_inverse_references_query.rb +13 -0
  37. data/lib/valkyrie/persistence/solr/queries/find_many_by_ids_query.rb +9 -0
  38. data/lib/valkyrie/persistence/solr/queries/find_members_query.rb +23 -0
  39. data/lib/valkyrie/persistence/solr/queries/find_ordered_references_query.rb +50 -0
  40. data/lib/valkyrie/persistence/solr/queries/find_references_query.rb +15 -0
  41. data/lib/valkyrie/persistence/solr/query_service.rb +47 -14
  42. data/lib/valkyrie/persistence/solr/repository.rb +21 -4
  43. data/lib/valkyrie/persistence/solr/resource_factory.rb +2 -0
  44. data/lib/valkyrie/resource.rb +1 -0
  45. data/lib/valkyrie/specs/shared_specs.rb +1 -0
  46. data/lib/valkyrie/specs/shared_specs/persister.rb +92 -2
  47. data/lib/valkyrie/specs/shared_specs/queries.rb +12 -0
  48. data/lib/valkyrie/specs/shared_specs/solr_indexer.rb +40 -0
  49. data/lib/valkyrie/storage/fedora.rb +0 -2
  50. data/lib/valkyrie/version.rb +1 -1
  51. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a320a108f82f79f88b27eaeb267b7b134499e346dccaae4f93165ff95e47fa5
4
- data.tar.gz: bebc13ba65b1e1e2d69fd9a9c9408f8af8ef4484984a6de568db4c90c34eb15f
3
+ metadata.gz: 0ce0917b94c4102aec4a20c07ad0a2d9bb8abbec21c87c91ffe3100ee266e5ee
4
+ data.tar.gz: 11ab274a94c6510b0910a5cf16552244eb693daf1bc595c2f9db91480279ca8a
5
5
  SHA512:
6
- metadata.gz: 8a32748fc47406abdb640ce4191e6271c61a0813cb2774a444ed5c6a45a5a34fd8e599f37afab22ac6c76d71d25bcaee28df6801a3c57add7617b064fc5ed3fb
7
- data.tar.gz: b353cc383371599df898f626b6e211294a34f5e351dad0fc0521a11841e79c04da3699c1e867f54e102c09379cba0366fa62e408738c3f7fa0f2637a20d59a14
6
+ metadata.gz: 42c1aa0891d23c99092328e0b9ff7eb8e305c25b3210b9f0d64c0493b6f9ad0c55eb091a2cd481bb4212b3ab8eb148cd3ca949e52963549734dde508a11fa411
7
+ data.tar.gz: 30ed893f5b392f4c765950e003aec192039ce3d53147c8428997df492e64836cd47382414b2cd0141eb5f8f265583ad040823c8037144c498ac8e60307b36611
@@ -1,3 +1,11 @@
1
+ # v1.2.0.RC2 2018-08-10
2
+
3
+ ## Changes since last release
4
+
5
+ * Support for ordered properties.
6
+ [Documentation](https://github.com/samvera-labs/valkyrie/wiki/Using-Types#ordering-values)
7
+ * Shared specs for Solr indexers.
8
+
1
9
  # v1.2.0.RC1 2018-08-09
2
10
 
3
11
  ## Changes since last release
data/README.md CHANGED
@@ -123,8 +123,9 @@ these kinds of classes, you should use these shared specs to test your classes f
123
123
  Valkyrie's API.
124
124
 
125
125
  When breaking changes are introduced, necessitating a major version change, the shared specs will reflect
126
- this. Likewise, non-breaking changes to Valkyrie can be defined as code changes that do not cause any
127
- errors with the current shared specs.
126
+ this. When new features are added and a minor version is released there will be no change to the existing shared
127
+ specs, but there may be new ones. These new shared specs will fail in your
128
+ application if you have custom adapters, but your application will still work.
128
129
 
129
130
  Using the shared specs in your own models is described in more [detail](https://github.com/samvera-labs/valkyrie/wiki/Shared-Specs).
130
131
 
@@ -136,11 +137,18 @@ Define a custom work class:
136
137
  # frozen_string_literal: true
137
138
  class MyModel < Valkyrie::Resource
138
139
  include Valkyrie::Resource::AccessControls
139
- attribute :title, Valkyrie::Types::Set # Sets are unordered
140
- attribute :authors, Valkyrie::Types::Array # Arrays are ordered
140
+ attribute :title, Valkyrie::Types::Set # Sets deduplicate values
141
+ attribute :date, Valkyrie::Types::Array # Arrays can contain duplicate values
141
142
  end
142
143
  ```
143
144
 
145
+ Attributes are unordered by default. Adding `ordered: true` to an attribute definition will preserve the
146
+ order of multiple values.
147
+
148
+ ```
149
+ attribute :authors, Valkyrie::Types::Array.meta(ordered: true)
150
+ ```
151
+
144
152
  Defining resource attributes is explained in greater detail within the [Wiki](https://github.com/samvera-labs/valkyrie/wiki/Using-Types).
145
153
 
146
154
  #### Work Types Generator
@@ -37,7 +37,7 @@ module Valkyrie::Persistence
37
37
  end
38
38
  rescue Valkyrie::Persistence::StaleObjectError
39
39
  # clear out any IDs returned to reduce potential confusion
40
- raise Valkyrie::Persistence::StaleObjectError
40
+ raise Valkyrie::Persistence::StaleObjectError, "One or more resources have been updated by another process."
41
41
  end
42
42
 
43
43
  # (see Valkyrie::Persistence::Memory::Persister#delete)
@@ -1,7 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
  module Valkyrie::Persistence::Fedora
3
3
  # Represents a node in an ORE List. Used for persisting ordered members into
4
- # an RDF Graph for Fedora, to keep order maintained.
4
+ # an Resource Description Framework (RDF) Graph for Fedora, to keep order maintained.
5
+ #
6
+ # RDF graph nodes are used to implement a linked list
7
+ # An RDF hash URI is referenced for the list itself
8
+ # <http://www.iana.org/assignments/relation/first> is the predicate which links to the first element in the list
9
+ # <http://www.iana.org/assignments/relation/last> is the predicate which links to the last element
10
+ # Each element is also referenced using a hash URI
11
+ # <http://www.iana.org/assignments/relation/next> is the predicate which links one element to the next element in the list
12
+ # (This permits unidirectional traversal)
13
+ # <http://www.openarchives.org/ore/terms/proxyFor> is the predicate which links any given element to its value
14
+ # (These can be IRIs or XML literals supported by a graph store)
15
+ #
16
+ # @see http://www.openarchives.org/ore/1.0/datamodel#Proxies
17
+ # @see https://www.iana.org/assignments/link-relations/link-relations.xhtml#link-relations-1
5
18
  class ListNode
6
19
  attr_reader :rdf_subject, :graph
7
20
  attr_writer :next, :prev
@@ -9,6 +22,11 @@ module Valkyrie::Persistence::Fedora
9
22
  attr_writer :next_uri, :prev_uri
10
23
  attr_accessor :proxy_in, :proxy_for
11
24
  attr_reader :adapter
25
+
26
+ # @param node_cache [Hash] structure used to cache the nodes of the graph
27
+ # @param rdf_subject [RDF::URI] the URI for the linked list in the graph store (usually a hash URI)
28
+ # @param adapter []
29
+ # @param graph [RDF::Repository] the RDF graph storing the structure of the RDF statements
12
30
  def initialize(node_cache, rdf_subject, adapter, graph = RDF::Repository.new)
13
31
  @rdf_subject = rdf_subject
14
32
  @graph = graph
@@ -48,25 +66,43 @@ module Valkyrie::Persistence::Fedora
48
66
  g
49
67
  end
50
68
 
69
+ # Resolves the URI for the value of the list expression
70
+ # @return [RDF::URI]
51
71
  def target_uri
52
- adapter.id_to_uri(target_id.to_s)
72
+ if target_id.is_a?(Valkyrie::ID)
73
+ adapter.id_to_uri(target_id.to_s)
74
+ else
75
+ target_id
76
+ end
53
77
  end
54
78
 
79
+ # Generates the string ID value for the value in the list expression
80
+ # @return [String]
55
81
  def target_id
56
- adapter.uri_to_id(proxy_for)
82
+ if proxy_for.to_s.include?("/") && proxy_for.to_s.start_with?(adapter.connection_prefix)
83
+ adapter.uri_to_id(proxy_for)
84
+ else
85
+ proxy_for
86
+ end
57
87
  end
58
88
 
59
89
  private
60
90
 
61
91
  attr_reader :next_uri, :prev_uri, :node_cache
62
92
 
93
+ # Class used to populate the RDF graph structure for the linked lists
63
94
  class Builder
64
95
  attr_reader :uri, :graph
96
+
97
+ # @param uri [RDF::URI] the URI for the linked list in the graph store
98
+ # @param graph [RDF::Repository] the RDF graph to be populated
65
99
  def initialize(uri, graph)
66
100
  @uri = uri
67
101
  @graph = graph
68
102
  end
69
103
 
104
+ # Populates attributes for the LinkedNode
105
+ # @param instance [ListNode]
70
106
  def populate(instance)
71
107
  instance.proxy_for = resource.proxy_for.first
72
108
  instance.proxy_in = resource.proxy_in.first
@@ -76,11 +112,14 @@ module Valkyrie::Persistence::Fedora
76
112
 
77
113
  private
78
114
 
115
+ # Constructs a set of triples using ActiveTriples as objects
116
+ # @return [Valkyrie::Persistence::Fedora::ListNode::Resource]
79
117
  def resource
80
118
  @resource ||= Resource.new(uri, data: graph)
81
119
  end
82
120
  end
83
121
 
122
+ # Class for providing a set of triples modeling linked list nodes
84
123
  class Resource < ActiveTriples::Resource
85
124
  property :proxy_for, predicate: ::RDF::Vocab::ORE.proxyFor, cast: false
86
125
  property :proxy_in, predicate: ::RDF::Vocab::ORE.proxyIn, cast: false
@@ -10,40 +10,66 @@ module Valkyrie::Persistence::Fedora
10
10
  # )
11
11
  class MetadataAdapter
12
12
  attr_reader :connection, :base_path, :schema
13
+
14
+ # @param [Ldp::Client] connection
15
+ # @param [String] base_path
16
+ # @param [Valkyrie::Persistence::Fedora::PermissiveSchema] schema
13
17
  def initialize(connection:, base_path: "/", schema: Valkyrie::Persistence::Fedora::PermissiveSchema.new)
14
18
  @connection = connection
15
19
  @base_path = base_path
16
20
  @schema = schema
17
21
  end
18
22
 
23
+ # Construct the query service object using this adapter
24
+ # @return [Valkyrie::Persistence::Fedora::QueryService]
19
25
  def query_service
20
26
  @query_service ||= Valkyrie::Persistence::Fedora::QueryService.new(adapter: self)
21
27
  end
22
28
 
29
+ # Construct the persister object using this adapter
30
+ # @return [Valkyrie::Persistence::Fedora::Persister]
23
31
  def persister
24
32
  Valkyrie::Persistence::Fedora::Persister.new(adapter: self)
25
33
  end
26
34
 
35
+ # Generate the Valkyrie ID for this unique metadata adapter
36
+ # This uses the URL of the Fedora endpoint to ensure that this is unique
37
+ # @return [Valkyrie::ID]
27
38
  def id
28
39
  @id ||= Valkyrie::ID.new(Digest::MD5.hexdigest(connection_prefix))
29
40
  end
30
41
 
42
+ # Construct the factory object used to construct Valkyrie::Resource objects using this adapter
43
+ # @return [Valkyrie::Persistence::Fedora::Persister::ResourceFactory]
31
44
  def resource_factory
32
45
  Valkyrie::Persistence::Fedora::Persister::ResourceFactory.new(adapter: self)
33
46
  end
34
47
 
48
+ # Generate a Valkyrie ID for a given URI
49
+ # @param [RDF::URI] uri the URI for a Fedora resource
50
+ # @return [Valkyrie::ID]
35
51
  def uri_to_id(uri)
36
52
  Valkyrie::ID.new(CGI.unescape(uri.to_s.gsub(/^.*\//, '')))
37
53
  end
38
54
 
55
+ # Generate a URI for a given Valkyrie ID
56
+ # @param [RDF::URI] id the Valkyrie ID
57
+ # @return [RDF::URI]
39
58
  def id_to_uri(id)
40
59
  RDF::URI("#{connection_prefix}/#{pair_path(id)}/#{CGI.escape(id.to_s)}")
41
60
  end
42
61
 
62
+ # Generate the pairtree path for a given Valkyrie ID
63
+ # @see https://confluence.ucop.edu/display/Curation/PairTree
64
+ # @see https://wiki.duraspace.org/display/FF/Design+-+Identifier+Generation
65
+ # @param [Valkyrie::ID] id the Valkyrie ID
66
+ # @return [Array<String>]
43
67
  def pair_path(id)
44
68
  id.to_s.split(/[-\/]/).first.split("").each_slice(2).map(&:join).join("/")
45
69
  end
46
70
 
71
+ # Generate the prefix used in HTTP requests to the Fedora RESTful endpoint
72
+ # @return [String]
47
73
  def connection_prefix
48
74
  "#{connection.http.url_prefix}/#{base_path}"
49
75
  end
@@ -8,6 +8,7 @@ module Valkyrie::Persistence::Fedora
8
8
  attr_writer :head, :tail
9
9
  delegate :each, to: :ordered_reader
10
10
  delegate :length, to: :to_a
11
+
11
12
  # @param [::RDF::Enumerable] graph Enumerable where ORE statements are
12
13
  # stored.
13
14
  # @param [::RDF::URI] head_subject URI of head node in list.
@@ -68,6 +69,9 @@ module Valkyrie::Persistence::Fedora
68
69
 
69
70
  attr_reader :node_cache
70
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
71
75
  def append_to(source, append_node)
72
76
  source.prev = append_node
73
77
  append_node.next.prev = source
@@ -76,10 +80,14 @@ module Valkyrie::Persistence::Fedora
76
80
  @changed = true
77
81
  end
78
82
 
83
+ # Constructs a new OrderedReader for this OrderedList
84
+ # @return [OrderedReader]
79
85
  def ordered_reader
80
86
  OrderedReader.new(self)
81
87
  end
82
88
 
89
+ # Populates the list with constructed ListNode Objects
90
+ # @param subject [RDF::URI]
83
91
  def build_node(subject = nil)
84
92
  return nil unless subject
85
93
  node_cache.fetch(subject) do
@@ -87,49 +95,69 @@ module Valkyrie::Persistence::Fedora
87
95
  end
88
96
  end
89
97
 
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]
90
101
  def new_node_subject
91
102
  node = ::RDF::URI("##{::RDF::Node.new.id}")
92
103
  node = ::RDF::URI("##{::RDF::Node.new.id}") while node_cache.key?(node)
93
104
  node
94
105
  end
95
106
 
107
+ # Class used for caching LinkedNode objects mapped to URIs
96
108
  class NodeCache
97
109
  def initialize
98
110
  @cache ||= {}
99
111
  end
100
112
 
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]
101
117
  def fetch(uri)
102
118
  @cache[uri] ||= yield if block_given?
103
119
  end
104
120
 
121
+ # Determines whether or not the cache contains a key
122
+ # @param key [Object]
123
+ # @return [Boolean]
105
124
  def key?(key)
106
125
  @cache.key?(key)
107
126
  end
108
127
  end
109
128
 
129
+ # Class modeling sentinels within the linked list
130
+ # @see https://en.wikipedia.org/wiki/Sentinel_value
110
131
  class Sentinel
111
- attr_reader :parent
132
+ attr_reader :parent, :next, :prev
112
133
  attr_writer :next, :prev
134
+
135
+ # @param parent [Valkyrie::Persistence::Fedora::OrderedList]
136
+ # @param next_node [ListNode]
137
+ # @param prev_node [ListNode]
113
138
  def initialize(parent, next_node: nil, prev_node: nil)
114
139
  @parent = parent
115
140
  @next = next_node
116
141
  @prev = prev_node
117
142
  end
118
143
 
119
- attr_reader :next
120
-
121
- attr_reader :prev
122
-
144
+ # Ensure that this always behaves like a NilClass
145
+ # @return [TrueClass]
123
146
  def nil?
124
147
  true
125
148
  end
126
149
 
150
+ # Ensure that this does not have a URI
151
+ # @return [NilClass]
127
152
  def rdf_subject
128
153
  nil
129
154
  end
130
155
  end
131
156
 
132
157
  class HeadSentinel < Sentinel
158
+ # @param parent [Valkyrie::Persistence::Fedora::OrderedList]
159
+ # @param next_node [ListNode]
160
+ # @param prev_node [ListNode]
133
161
  def initialize(*args)
134
162
  super
135
163
  @next ||= TailSentinel.new(parent, prev_node: self)
@@ -137,6 +165,9 @@ module Valkyrie::Persistence::Fedora
137
165
  end
138
166
 
139
167
  class TailSentinel < Sentinel
168
+ # @param parent [Valkyrie::Persistence::Fedora::OrderedList]
169
+ # @param next_node [ListNode]
170
+ # @param prev_node [ListNode]
140
171
  def initialize(*args)
141
172
  super
142
173
  prev.next = self if prev&.next != self
@@ -6,10 +6,14 @@ module Valkyrie::Persistence::Fedora
6
6
  class OrderedReader
7
7
  include Enumerable
8
8
  attr_reader :root
9
+
10
+ # @param root [Valkyrie::Persistence::Fedora::OrderedList]
9
11
  def initialize(root)
10
12
  @root = root
11
13
  end
12
14
 
15
+ # Enumerates through each node in the RDF linked list
16
+ # @yield [Valkyrie::Persistence::Fedora::OrderedList::HeadSentinel, Valkyrie::Persistence::Fedora::ListNode]
13
17
  def each
14
18
  proxy = first_head
15
19
  while proxy
@@ -22,6 +26,8 @@ module Valkyrie::Persistence::Fedora
22
26
 
23
27
  private
24
28
 
29
+ # Access the "first" (head) node for the linked list
30
+ # @return [Valkyrie::Persistence::Fedora::OrderedList::HeadSentinel]
25
31
  def first_head
26
32
  root.head
27
33
  end
@@ -53,6 +53,11 @@ module Valkyrie::Persistence::Fedora
53
53
  uri_for(:valkyrie_datetime)
54
54
  end
55
55
 
56
+ # @return [RDF::URI]
57
+ def self.valkyrie_float
58
+ uri_for(:valkyrie_float)
59
+ end
60
+
56
61
  # @return [RDF::URI]
57
62
  def self.valkyrie_int
58
63
  uri_for(:valkyrie_int)
@@ -76,10 +81,17 @@ module Valkyrie::Persistence::Fedora
76
81
  end
77
82
 
78
83
  attr_reader :schema
84
+
85
+ # @param schema [Hash] the structure used to store the mapping between property names and predicates
79
86
  def initialize(schema = {})
80
87
  @schema = schema
81
88
  end
82
89
 
90
+ # Find the predicate in the schema for the Valkyrie property
91
+ # If this does not exist, a URI using the property name prefixed by URI_PREFIX generates it
92
+ # @param resource [Valkyrie::Resource]
93
+ # @param property [String]
94
+ # @return [RDF::URI]
83
95
  def predicate_for(resource:, property:)
84
96
  schema.fetch(property) { self.class.uri_for(property) }
85
97
  end
@@ -89,8 +101,15 @@ module Valkyrie::Persistence::Fedora
89
101
  # @example:
90
102
  # property_for(resource: nil, predicate: "http://example.com/predicate/internal_resource")
91
103
  # #=> 'internal_resource'
104
+ # @param resource [Valkyrie::Resource]
105
+ # @param predicate [RDF::URI, String]
106
+ # @return [String]
92
107
  def property_for(resource:, predicate:)
93
- (schema.find { |_k, v| v == RDF::URI(predicate.to_s) } || []).first || predicate.to_s.gsub(URI_PREFIX, '')
108
+ existing_predicates = schema.find { |_k, v| v == RDF::URI(predicate.to_s) }
109
+ predicate_name = predicate.to_s.gsub(URI_PREFIX, '')
110
+
111
+ return predicate_name if existing_predicates.nil? || existing_predicates.empty?
112
+ existing_predicates.first
94
113
  end
95
114
  end
96
115
  end
@@ -34,7 +34,7 @@ module Valkyrie::Persistence::Fedora
34
34
 
35
35
  alternate_resources ? save_reference_to_resource(persisted_resource, alternate_resources) : persisted_resource
36
36
  rescue Ldp::PreconditionFailed
37
- raise Valkyrie::Persistence::StaleObjectError, internal_resource.id.to_s
37
+ raise Valkyrie::Persistence::StaleObjectError, "The object #{internal_resource.id} has been updated by another process."
38
38
  end
39
39
 
40
40
  # (see Valkyrie::Persistence::Memory::Persister#save_all)
@@ -44,7 +44,7 @@ module Valkyrie::Persistence::Fedora
44
44
  end
45
45
  rescue Valkyrie::Persistence::StaleObjectError
46
46
  # blank out the message / id
47
- raise Valkyrie::Persistence::StaleObjectError
47
+ raise Valkyrie::Persistence::StaleObjectError, "One or more resources have been updated by another process."
48
48
  end
49
49
 
50
50
  # (see Valkyrie::Persistence::Memory::Persister#delete)
@@ -62,13 +62,18 @@ module Valkyrie::Persistence::Fedora
62
62
  end
63
63
 
64
64
  # (see Valkyrie::Persistence::Memory::Persister#wipe!)
65
+ # Deletes Fedora repository resource *and* the tombstone resources which remain
66
+ # @see https://wiki.duraspace.org/display/FEDORA4x/RESTful+HTTP+API#RESTfulHTTPAPI-RedDELETEDeletearesource
65
67
  def wipe!
66
68
  connection.delete(base_path)
67
69
  connection.delete("#{base_path}/fcr:tombstone")
68
70
  rescue => error
69
- Valkyrie.logger.debug("Failed to wipe Fedora for some reason.") unless error.is_a?(::Ldp::NotFound)
71
+ Valkyrie.logger.debug("Failed to wipe Fedora for some reason: #{error}") unless error.is_a?(::Ldp::NotFound)
70
72
  end
71
73
 
74
+ # Creates the root LDP Container for the connection with Fedora
75
+ # @see https://www.w3.org/TR/ldp/#ldpc
76
+ # @return [Ldp::Container::Basic]
72
77
  def initialize_repository
73
78
  @initialized ||=
74
79
  begin
@@ -80,6 +85,9 @@ module Valkyrie::Persistence::Fedora
80
85
 
81
86
  private
82
87
 
88
+ # Ensure that all alternate IDs for a given resource are persisted
89
+ # @param [Valkyrie::Resource] resource
90
+ # @return [Array<Valkyrie::Persistence::Fedora::AlternateIdentifier>]
83
91
  def find_or_create_alternate_ids(resource)
84
92
  return nil unless resource.try(:alternate_ids)
85
93
 
@@ -93,6 +101,8 @@ module Valkyrie::Persistence::Fedora
93
101
  end
94
102
  end
95
103
 
104
+ # Ensure that any Resources referenced by alternate IDs are deleted when a Resource has these IDs deleted
105
+ # @param [Valkyrie::Resource] updated_resource
96
106
  def cleanup_alternate_resources(updated_resource)
97
107
  persisted_resource = adapter.query_service.find_by(id: updated_resource.id)
98
108
  removed_identifiers = persisted_resource.alternate_ids - updated_resource.alternate_ids
@@ -102,6 +112,10 @@ module Valkyrie::Persistence::Fedora
102
112
  end
103
113
  end
104
114
 
115
+ # Ensure that any Resources referenced by alternate IDs are persisted when a Resource has these IDs added
116
+ # @param [Valkyrie::Resource] resource
117
+ # @param [Array<Valkyrie::Persistence::Fedora::AlternateIdentifier>] alternate_resources
118
+ # @return [Valkyrie::Resource]
105
119
  def save_reference_to_resource(resource, alternate_resources)
106
120
  alternate_resources.each do |alternate_resource|
107
121
  alternate_resource.references = resource.id
@@ -111,6 +125,10 @@ module Valkyrie::Persistence::Fedora
111
125
  resource
112
126
  end
113
127
 
128
+ # Generate the lock token for a Resource, and set it for attribute
129
+ # @param [Valkyrie::Resource] resource
130
+ # @return [Valkyrie::Persistence::OptimisticLockToken]
131
+ # @see https://github.com/samvera-labs/valkyrie/wiki/Optimistic-Locking
114
132
  # @note Fedora's last modified response is not granular enough to produce an effective lock token
115
133
  # therefore, we use the same implementation as the memory adapter. This could fail to lock a
116
134
  # resource if Fedora updated this resource between the time it was saved and Valkyrie created
@@ -121,6 +139,13 @@ module Valkyrie::Persistence::Fedora
121
139
  resource.send("#{Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK}=", token)
122
140
  end
123
141
 
142
+ # Determine whether or not a lock token is still valid for a persisted Resource
143
+ # If the persisted Resource has been updated since it was last read into memory,
144
+ # then the resouce in memory has been invalidated and Valkyrie::Persistence::StaleObjectError
145
+ # is raised.
146
+ # @param [Valkyrie::Resource] resource
147
+ # @see https://github.com/samvera-labs/valkyrie/wiki/Optimistic-Locking
148
+ # @raise [Valkyrie::Persistence::StaleObjectError]
124
149
  def validate_lock_token(resource)
125
150
  return unless resource.optimistic_locking_enabled?
126
151
  return if resource.id.blank?
@@ -132,10 +157,12 @@ module Valkyrie::Persistence::Fedora
132
157
  retrieved_lock_token = retrieved_lock_tokens.find { |lock_token| lock_token.adapter_id == adapter.id }
133
158
  return if retrieved_lock_token.blank?
134
159
 
135
- raise Valkyrie::Persistence::StaleObjectError, resource.id.to_s unless current_lock_token == retrieved_lock_token
160
+ raise Valkyrie::Persistence::StaleObjectError, "The object #{resource.id} has been updated by another process." unless current_lock_token == retrieved_lock_token
136
161
  end
137
162
 
138
163
  # Retrieve the lock token that holds Fedora's system-managed last-modified date
164
+ # @param [Valkyrie::Resource] resource
165
+ # @return [Valkyrie::Persistence::OptimisticLockToken]
139
166
  def native_lock_token(resource)
140
167
  return unless resource.optimistic_locking_enabled?
141
168
  resource[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK].find { |lock_token| lock_token.adapter_id == "native-#{adapter.id}" }
@@ -144,6 +171,8 @@ module Valkyrie::Persistence::Fedora
144
171
  # Set Fedora request headers:
145
172
  # * `Prefer: handling=lenient; received="minimal"` allows us to avoid sending all server-managed triples
146
173
  # * `If-Unmodified-Since` triggers Fedora's server-side optimistic locking
174
+ # @param request [Faraday::Request]
175
+ # @param lock_token [Valkyrie::Persistence::OptimisticLockToken]
147
176
  def update_request_headers(request, lock_token)
148
177
  request.headers["Prefer"] = "handling=lenient; received=\"minimal\""
149
178
  request.headers["If-Unmodified-Since"] = lock_token.token if lock_token