valkyrie 1.2.0.rc1 → 1.2.0.rc2

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.
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