rdf-ldp 0.7.0 → 0.8.0
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/CHANGELOG.md +8 -0
- data/README.md +1 -1
- data/VERSION +1 -1
- data/lib/rdf/ldp.rb +12 -12
- data/lib/rdf/ldp/container.rb +41 -44
- data/lib/rdf/ldp/direct_container.rb +38 -38
- data/lib/rdf/ldp/indirect_container.rb +33 -29
- data/lib/rdf/ldp/non_rdf_source.rb +42 -131
- data/lib/rdf/ldp/rdf_source.rb +57 -55
- data/lib/rdf/ldp/resource.rb +46 -42
- data/lib/rdf/ldp/storage_adapters/file_storage_adapter.rb +102 -0
- metadata +13 -13
@@ -1,23 +1,26 @@
|
|
1
1
|
module RDF::LDP
|
2
2
|
##
|
3
|
-
# An extension of `RDF::LDP::DirectContainer` implementing indirect
|
4
|
-
# containment. Adds the concept of an inserted content relation to the
|
3
|
+
# An extension of `RDF::LDP::DirectContainer` implementing indirect
|
4
|
+
# containment. Adds the concept of an inserted content relation to the
|
5
5
|
# features of the direct container.
|
6
6
|
#
|
7
7
|
# Clients MUST provide exactly one `ldp:insertedContentRelation` statement in
|
8
|
-
# each Indirect Container. If no `#inserted_content_relation` is given by the
|
8
|
+
# each Indirect Container. If no `#inserted_content_relation` is given by the
|
9
9
|
# client, we default to `ldp:MemberSubject`. If more than one is present,
|
10
10
|
#
|
11
|
-
# Attempts to POST resources without the appropriate content relation (or
|
12
|
-
# with more than one) to an Indirect Container will fail with `Not
|
11
|
+
# Attempts to POST resources without the appropriate content relation (or
|
12
|
+
# with more than one) to an Indirect Container will fail with `Not
|
13
13
|
# Acceptable`. LDP-NR's cannot be added since indirect membership is not well
|
14
14
|
# defined for them, per _LDP 5.5.1.2_.
|
15
15
|
#
|
16
|
-
# @see http://www.w3.org/TR/ldp/#h-ldpic-indirectmbr for an explanation if
|
16
|
+
# @see http://www.w3.org/TR/ldp/#h-ldpic-indirectmbr for an explanation if
|
17
17
|
# indirect membership and limitiations surrounding LDP-NRs.
|
18
18
|
# @see http://www.w3.org/TR/ldp/#dfn-linked-data-platform-indirect-container
|
19
19
|
# definition of LDP Indirect Container
|
20
20
|
class IndirectContainer < DirectContainer
|
21
|
+
INSERTED_CONTENT_REL_URI = RDF::Vocab::LDP.insertedContentRelation.freeze
|
22
|
+
MEMBER_SUBJECT_URI = RDF::Vocab::LDP.MemberSubject.freeze
|
23
|
+
|
21
24
|
def self.to_uri
|
22
25
|
RDF::Vocab::LDP.IndirectContainer
|
23
26
|
end
|
@@ -31,16 +34,17 @@ module RDF::LDP
|
|
31
34
|
##
|
32
35
|
# Creates and inserts default relation triples if none are given.
|
33
36
|
#
|
34
|
-
# @see DirectContainer#create for information about the behavior and
|
37
|
+
# @see DirectContainer#create for information about the behavior and
|
35
38
|
# transactionality of this method.
|
36
|
-
def create(input, content_type
|
39
|
+
def create(input, content_type)
|
37
40
|
super
|
38
41
|
|
39
42
|
graph.transaction(mutable: true) do |tx|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
43
|
+
if inserted_content_statements.empty?
|
44
|
+
tx.insert RDF::Statement(subject_uri,
|
45
|
+
INSERTED_CONTENT_REL_URI,
|
46
|
+
MEMBER_SUBJECT_URI)
|
47
|
+
end
|
44
48
|
end
|
45
49
|
|
46
50
|
self
|
@@ -48,13 +52,13 @@ module RDF::LDP
|
|
48
52
|
|
49
53
|
##
|
50
54
|
# Gives the inserted content relation for the indirect container. If none is
|
51
|
-
# present in the container state, we add `ldp:MemberSubject`, effectively
|
55
|
+
# present in the container state, we add `ldp:MemberSubject`, effectively
|
52
56
|
# treating this LDP-IC as an LDP-DC.
|
53
57
|
#
|
54
58
|
# @return [RDF::URI] the inserted content relation; either a predicate term
|
55
59
|
# or `ldp:MemberSubject`
|
56
60
|
#
|
57
|
-
# @raise [RDF::LDP::NotAcceptable] if multiple inserted content relations
|
61
|
+
# @raise [RDF::LDP::NotAcceptable] if multiple inserted content relations
|
58
62
|
# exist.
|
59
63
|
#
|
60
64
|
# @see http://www.w3.org/TR/ldp/#dfn-membership-triples
|
@@ -62,36 +66,36 @@ module RDF::LDP
|
|
62
66
|
statements = inserted_content_statements
|
63
67
|
return statements.first.object if statements.count == 1
|
64
68
|
|
65
|
-
raise
|
66
|
-
|
67
|
-
|
69
|
+
raise(NotAcceptable, 'An LDP-IC MUST have exactly ' \
|
70
|
+
'one inserted content relation triple; found ' \
|
71
|
+
"#{statements.count}.")
|
68
72
|
end
|
69
73
|
|
70
74
|
private
|
71
75
|
|
72
76
|
def inserted_content_statements
|
73
|
-
graph.query([subject_uri,
|
74
|
-
|
77
|
+
graph.query([subject_uri, INSERTED_CONTENT_REL_URI, nil])
|
78
|
+
.statements
|
75
79
|
end
|
76
|
-
|
77
|
-
def process_membership_resource(resource
|
80
|
+
|
81
|
+
def process_membership_resource(resource)
|
78
82
|
resource = member_derived_uri(resource)
|
79
|
-
super
|
83
|
+
super
|
80
84
|
end
|
81
|
-
|
85
|
+
|
82
86
|
def member_derived_uri(resource)
|
83
87
|
predicate = inserted_content_relation
|
84
|
-
return resource.to_uri if predicate ==
|
88
|
+
return resource.to_uri if predicate == MEMBER_SUBJECT_URI
|
85
89
|
|
86
|
-
raise
|
87
|
-
|
88
|
-
|
90
|
+
raise(NotAcceptable, "#{resource.to_uri} is an LDP-NR; cannot add " \
|
91
|
+
'it to an IndirectContainer with a content ' \
|
92
|
+
'relation.') if resource.non_rdf_source?
|
89
93
|
|
90
94
|
statements = resource.graph.query([resource.subject_uri, predicate, :o])
|
91
95
|
return statements.first.object if statements.count == 1
|
92
96
|
|
93
|
-
raise
|
94
|
-
|
97
|
+
raise(NotAcceptable, "#{statements.count} inserted content" \
|
98
|
+
"#{predicate} found on #{resource.to_uri}")
|
95
99
|
end
|
96
100
|
end
|
97
101
|
end
|
@@ -1,31 +1,47 @@
|
|
1
|
+
require 'rdf/ldp/storage_adapters/file_storage_adapter'
|
2
|
+
|
1
3
|
module RDF::LDP
|
2
4
|
##
|
3
5
|
# A NonRDFSource describes a `Resource` whose response body is a format other
|
4
|
-
# than an RDF serialization. The persistent state of the resource, as
|
5
|
-
# represented by the body, is persisted to an IO stream provided by a
|
6
|
+
# than an RDF serialization. The persistent state of the resource, as
|
7
|
+
# represented by the body, is persisted to an IO stream provided by a
|
6
8
|
# `RDF::LDP::NonRDFSource::StorageAdapter` given by `#storage`.
|
7
9
|
#
|
8
10
|
# In addition to the properties stored by the `RDF::LDP::Resource#metagraph`,
|
9
|
-
# `NonRDFSource`s also store a content type (format).
|
11
|
+
# `NonRDFSource`s also store a content type (format).
|
10
12
|
#
|
11
|
-
# When a `NonRDFSource` is created, it also creates an `RDFSource` which
|
13
|
+
# When a `NonRDFSource` is created, it also creates an `RDFSource` which
|
12
14
|
# describes it. This resource is created at the URI in `#description_uri`,
|
13
|
-
# the resource itself is returned by `#description`.
|
15
|
+
# the resource itself is returned by `#description`.
|
14
16
|
#
|
15
17
|
# @see RDF::LDP::Resource
|
16
18
|
# @see http://www.w3.org/TR/ldp/#dfn-linked-data-platform-non-rdf-source for
|
17
19
|
# a definition of NonRDFSource in LDP
|
18
20
|
class NonRDFSource < Resource
|
21
|
+
attr_reader :storage
|
22
|
+
|
23
|
+
# Use the default filesystem-based storage adapter
|
24
|
+
DEFAULT_ADAPTER = RDF::LDP::NonRDFSource::FileStorageAdapter
|
25
|
+
|
19
26
|
# Use DC elements format
|
20
|
-
FORMAT_TERM = RDF::Vocab::DC11.format
|
21
|
-
|
22
|
-
|
27
|
+
FORMAT_TERM = RDF::Vocab::DC11.format.freeze
|
28
|
+
|
29
|
+
##
|
30
|
+
# @param [storage_adapter] a class implementing the StorageAdapter interface
|
31
|
+
#
|
32
|
+
# @see RDF::LDP::Resource#initialize
|
33
|
+
def initialize(subject_uri, data = RDF::Repository.new, storage_adapter = DEFAULT_ADAPTER)
|
34
|
+
data ||= RDF::Repository.new # allows explict `nil` pass
|
35
|
+
@storage = storage_adapter.new(self)
|
36
|
+
super(subject_uri, data)
|
37
|
+
end
|
38
|
+
|
23
39
|
##
|
24
|
-
# @return [RDF::URI] uri with lexical representation
|
40
|
+
# @return [RDF::URI] uri with lexical representation
|
25
41
|
# 'http://www.w3.org/ns/ldp#NonRDFSource'
|
26
42
|
#
|
27
43
|
# @see http://www.w3.org/TR/ldp/#dfn-linked-data-platform-non-rdf-source
|
28
|
-
def self.to_uri
|
44
|
+
def self.to_uri
|
29
45
|
RDF::Vocab::LDP.NonRDFSource
|
30
46
|
end
|
31
47
|
|
@@ -36,7 +52,7 @@ module RDF::LDP
|
|
36
52
|
end
|
37
53
|
|
38
54
|
##
|
39
|
-
# @param [IO, File] input input (usually from a Rack env's
|
55
|
+
# @param [IO, File] input input (usually from a Rack env's
|
40
56
|
# `rack.input` key) that will be read into the NonRDFSource
|
41
57
|
# @param [#to_s] c_type a MIME content_type used as a content type
|
42
58
|
# for the created NonRDFSource
|
@@ -52,7 +68,7 @@ module RDF::LDP
|
|
52
68
|
self.content_type = c_type
|
53
69
|
|
54
70
|
RDFSource.new(description_uri, @data)
|
55
|
-
|
71
|
+
.create(StringIO.new, 'application/n-triples')
|
56
72
|
|
57
73
|
self
|
58
74
|
end
|
@@ -67,13 +83,13 @@ module RDF::LDP
|
|
67
83
|
end
|
68
84
|
|
69
85
|
##
|
70
|
-
# Deletes the LDP-NR contents from the storage medium and marks the
|
86
|
+
# Deletes the LDP-NR contents from the storage medium and marks the
|
71
87
|
# resource as destroyed.
|
72
88
|
#
|
73
89
|
# @see RDF::LDP::Resource#destroy
|
74
90
|
def destroy
|
75
|
-
storage.delete
|
76
91
|
super
|
92
|
+
storage.delete
|
77
93
|
end
|
78
94
|
|
79
95
|
##
|
@@ -90,49 +106,43 @@ module RDF::LDP
|
|
90
106
|
subject_uri / '.well-known' / 'desc'
|
91
107
|
end
|
92
108
|
|
93
|
-
##
|
94
|
-
# @return [StorageAdapter] the storage adapter for this LDP-NR
|
95
|
-
def storage
|
96
|
-
@storage_adapter ||= StorageAdapter.new(self)
|
97
|
-
end
|
98
|
-
|
99
109
|
##
|
100
110
|
# Sets the MIME type for the resource in `metagraph`.
|
101
111
|
#
|
102
112
|
# @param [String] a string representing the content type for this LDP-NR.
|
103
113
|
# This SHOULD be a regisered MIME type.
|
104
114
|
#
|
105
|
-
# @return [StorageAdapter] the content type
|
115
|
+
# @return [StorageAdapter] the content type
|
106
116
|
def content_type=(content_type)
|
107
117
|
metagraph.delete([subject_uri, FORMAT_TERM])
|
108
|
-
metagraph <<
|
109
|
-
RDF::Statement(subject_uri,
|
118
|
+
metagraph <<
|
119
|
+
RDF::Statement(subject_uri, FORMAT_TERM, content_type)
|
110
120
|
end
|
111
|
-
|
121
|
+
|
112
122
|
##
|
113
|
-
# @return [StorageAdapter] this resource's content type
|
123
|
+
# @return [StorageAdapter] this resource's content type
|
114
124
|
def content_type
|
115
125
|
format_triple = metagraph.first([subject_uri, FORMAT_TERM, :format])
|
116
126
|
format_triple.nil? ? nil : format_triple.object.object
|
117
127
|
end
|
118
128
|
|
119
129
|
##
|
120
|
-
# @return [#each] the response body. This is normally the StorageAdapter's
|
130
|
+
# @return [#each] the response body. This is normally the StorageAdapter's
|
121
131
|
# IO object in read and binary mode.
|
122
|
-
#
|
132
|
+
#
|
123
133
|
# @raise [RDF::LDP::RequestError] when the request fails
|
124
134
|
def to_response
|
125
135
|
(exists? && !destroyed?) ? storage.io : []
|
126
136
|
end
|
127
137
|
|
128
|
-
private
|
138
|
+
private
|
129
139
|
|
130
140
|
##
|
131
141
|
# Process & generate response for PUT requsets.
|
132
|
-
def put(
|
133
|
-
raise
|
134
|
-
env.
|
135
|
-
|
142
|
+
def put(_status, headers, env)
|
143
|
+
raise(PreconditionFailed, 'Etag invalid') if
|
144
|
+
env.key?('HTTP_IF_MATCH') && !match?(env['HTTP_IF_MATCH'])
|
145
|
+
|
136
146
|
if exists?
|
137
147
|
update(env['rack.input'], env['CONTENT_TYPE'])
|
138
148
|
headers = update_headers(headers)
|
@@ -153,104 +163,5 @@ module RDF::LDP
|
|
153
163
|
def link_headers
|
154
164
|
super << "<#{description_uri}>;rel=\"describedBy\""
|
155
165
|
end
|
156
|
-
|
157
|
-
##
|
158
|
-
# StorageAdapters bundle the logic for mapping a `NonRDFSource` to a
|
159
|
-
# specific IO stream. Implementations must conform to a minimal interface:
|
160
|
-
#
|
161
|
-
# - `#initailize` must accept a `resource` parameter. The input should be
|
162
|
-
# a `NonRDFSource` (LDP-NR).
|
163
|
-
# - `#io` must yield and return a IO object in binary mode that represents
|
164
|
-
# the current state of the LDP-NR.
|
165
|
-
# - If a block is passed to `#io`, the implementation MUST allow return a
|
166
|
-
# writable IO object and that anything written to the stream while
|
167
|
-
# yielding is synced with the source in a thread-safe manner.
|
168
|
-
# - Clients not passing a block to `#io` SHOULD call `#close` on the
|
169
|
-
# object after reading it.
|
170
|
-
# - If the `#io` object responds to `#to_path` it MUST give the location
|
171
|
-
# of a file whose contents are identical the IO object's. This supports
|
172
|
-
# Rack's response body interface.
|
173
|
-
# - `#delete` remove the contents from the corresponding storage. This MAY
|
174
|
-
# be a no-op if is undesirable or impossible to delete the contents
|
175
|
-
# from the storage medium.
|
176
|
-
#
|
177
|
-
# @see http://www.rubydoc.info/github/rack/rack/master/file/SPEC#The_Body
|
178
|
-
# for details about `#to_path` in Rack response bodies.
|
179
|
-
#
|
180
|
-
# @example reading from a `StorageAdapter`
|
181
|
-
# storage = StorageAdapter.new(an_nr_source)
|
182
|
-
# storage.io.read # => [string contents of `an_nr_source`]
|
183
|
-
#
|
184
|
-
# @example writing to a `StorageAdapter`
|
185
|
-
# storage = StorageAdapter.new(an_nr_source)
|
186
|
-
# storage.io { |io| io.write('moomin') }
|
187
|
-
#
|
188
|
-
# Beyond this interface, implementations are permitted to behave as desired.
|
189
|
-
# They may, for instance, reject undesirable content or alter the graph (or
|
190
|
-
# metagraph) of the resource. They should throw appropriate `RDF::LDP`
|
191
|
-
# errors when failing to allow the middleware to handle response codes and
|
192
|
-
# messages.
|
193
|
-
#
|
194
|
-
# The base storage adapter class provides a simple File storage
|
195
|
-
# implementation.
|
196
|
-
#
|
197
|
-
# @todo check thread saftey on write for base implementation
|
198
|
-
class StorageAdapter
|
199
|
-
STORAGE_PATH = '.storage'.freeze
|
200
|
-
|
201
|
-
##
|
202
|
-
# Initializes the storage adapter.
|
203
|
-
#
|
204
|
-
# @param [NonRDFSource] resource
|
205
|
-
def initialize(resource)
|
206
|
-
@resource = resource
|
207
|
-
end
|
208
|
-
|
209
|
-
##
|
210
|
-
# Gives an IO object which represents the current state of @resource.
|
211
|
-
# Opens the file for read-write (mode: r+), if it already exists;
|
212
|
-
# otherwise, creates the file and opens it for read-write (mode: w+).
|
213
|
-
#
|
214
|
-
# @yield [IO] yields a read-writable object conforming to the Ruby IO
|
215
|
-
# interface for storage. The IO object will be closed when the block
|
216
|
-
# ends.
|
217
|
-
#
|
218
|
-
# @return [IO] an object conforming to the Ruby IO interface
|
219
|
-
def io(&block)
|
220
|
-
FileUtils.mkdir_p(path_dir) unless Dir.exists?(path_dir)
|
221
|
-
FileUtils.touch(path) unless file_exists?
|
222
|
-
|
223
|
-
File.open(path, 'r+b', &block)
|
224
|
-
end
|
225
|
-
|
226
|
-
##
|
227
|
-
# @return [Boolean] 1 if the file has been deleted, otherwise false
|
228
|
-
def delete
|
229
|
-
return false unless File.exists?(path)
|
230
|
-
File.delete(path)
|
231
|
-
end
|
232
|
-
|
233
|
-
private
|
234
|
-
|
235
|
-
##
|
236
|
-
# @return [Boolean]
|
237
|
-
def file_exists?
|
238
|
-
File.exists?(path)
|
239
|
-
end
|
240
|
-
|
241
|
-
##
|
242
|
-
# Build the path to the file on disk.
|
243
|
-
# @return [String]
|
244
|
-
def path
|
245
|
-
File.join(STORAGE_PATH, @resource.subject_uri.path)
|
246
|
-
end
|
247
|
-
|
248
|
-
##
|
249
|
-
# Build the path to the file's directory on disk
|
250
|
-
# @return [String]
|
251
|
-
def path_dir
|
252
|
-
File.split(path).first
|
253
|
-
end
|
254
|
-
end
|
255
166
|
end
|
256
167
|
end
|
data/lib/rdf/ldp/rdf_source.rb
CHANGED
@@ -3,39 +3,38 @@ require 'ld/patch'
|
|
3
3
|
|
4
4
|
module RDF::LDP
|
5
5
|
##
|
6
|
-
# The base class for all directly usable LDP Resources that *are not*
|
6
|
+
# The base class for all directly usable LDP Resources that *are not*
|
7
7
|
# `NonRDFSources`. RDFSources are implemented as a resource with:
|
8
8
|
#
|
9
9
|
# - a `#graph` representing the "entire persistent state"
|
10
10
|
# - a `#metagraph` containing internal properties of the RDFSource
|
11
11
|
#
|
12
|
-
# Repository implementations must be able to reconstruct both `#graph` and
|
12
|
+
# Repository implementations must be able to reconstruct both `#graph` and
|
13
13
|
# `#metagraph` accurately and separately (e.g., by saving them as distinct
|
14
14
|
# named graphs).
|
15
|
-
#
|
16
|
-
# The implementations of `#create` and `#update` in `RDF::LDP::Resource` are
|
17
|
-
# overloaded to handle the edits to `#graph` within the same transaction as
|
18
|
-
# the base `#metagraph` updates. `#to_response` is overloaded to return an
|
15
|
+
#
|
16
|
+
# The implementations of `#create` and `#update` in `RDF::LDP::Resource` are
|
17
|
+
# overloaded to handle the edits to `#graph` within the same transaction as
|
18
|
+
# the base `#metagraph` updates. `#to_response` is overloaded to return an
|
19
19
|
# unnamed `RDF::Graph`, to be transformed into an HTTP Body by
|
20
20
|
# `Rack::LDP::ContentNegotiation`.
|
21
21
|
#
|
22
|
-
# @note the contents of `#metagraph`'s are *not* the same as
|
23
|
-
#
|
24
|
-
# RDFSource which are necessary for the server's management purposes,
|
25
|
-
# be absent from (or in conflict with) the representation of its
|
26
|
-
# `#graph`.
|
27
|
-
#
|
28
|
-
# @see http://www.w3.org/TR/ldp/#dfn-linked-data-platform-rdf-source
|
29
|
-
# of ldp:RDFSource in the LDP specification
|
22
|
+
# @note the contents of `#metagraph`'s are *not* the same as
|
23
|
+
# LDP-server-managed triples. `#metagraph` contains internal properties of
|
24
|
+
# the RDFSource which are necessary for the server's management purposes,
|
25
|
+
# but MAY be absent from (or in conflict with) the representation of its
|
26
|
+
# state in `#graph`.
|
27
|
+
#
|
28
|
+
# @see http://www.w3.org/TR/ldp/#dfn-linked-data-platform-rdf-source
|
29
|
+
# Definition of ldp:RDFSource in the LDP specification
|
30
30
|
class RDFSource < Resource
|
31
|
-
|
32
31
|
class << self
|
33
32
|
##
|
34
|
-
# @return [RDF::URI] uri with lexical representation
|
33
|
+
# @return [RDF::URI] uri with lexical representation
|
35
34
|
# 'http://www.w3.org/ns/ldp#RDFSource'
|
36
35
|
#
|
37
36
|
# @see http://www.w3.org/TR/ldp/#dfn-linked-data-platform-rdf-source
|
38
|
-
def to_uri
|
37
|
+
def to_uri
|
39
38
|
RDF::Vocab::LDP.RDFSource
|
40
39
|
end
|
41
40
|
end
|
@@ -50,7 +49,7 @@ module RDF::LDP
|
|
50
49
|
end
|
51
50
|
|
52
51
|
##
|
53
|
-
# @return [RDF::Graph] a graph representing the current persistent state of
|
52
|
+
# @return [RDF::Graph] a graph representing the current persistent state of
|
54
53
|
# the resource.
|
55
54
|
def graph
|
56
55
|
@graph ||= RDF::Graph.new(graph_name: @subject_uri, data: @data)
|
@@ -61,37 +60,39 @@ module RDF::LDP
|
|
61
60
|
#
|
62
61
|
# @example
|
63
62
|
# repository = RDF::Repository.new
|
63
|
+
# content = StringIO.new('<http://ex.org/1> <http://ex.org/p> "moomin" .')
|
64
|
+
#
|
64
65
|
# ldprs = RDF::LDP::RDFSource.new('http://example.org/moomin', repository)
|
65
|
-
# ldprs.create(
|
66
|
+
# ldprs.create(content, 'text/turtle')
|
66
67
|
#
|
67
|
-
# @param [IO, File
|
68
|
+
# @param [IO, File] input input (usually from a Rack env's
|
68
69
|
# `rack.input` key) used to determine the Resource's initial state.
|
69
70
|
# @param [#to_s] content_type a MIME content_type used to read the graph.
|
70
71
|
#
|
71
|
-
# @yield gives an in-progress transaction (changeset) to collect changes to
|
72
|
-
# graph, metagraph and other resources' (e.g. containers) graphs.
|
73
|
-
# @yieldparam tx [RDF::Transaction] a transaction targeting `#graph` as the
|
72
|
+
# @yield gives an in-progress transaction (changeset) to collect changes to
|
73
|
+
# graph, metagraph and other resources' (e.g. containers) graphs.
|
74
|
+
# @yieldparam tx [RDF::Transaction] a transaction targeting `#graph` as the
|
74
75
|
# default graph name
|
75
76
|
#
|
76
77
|
# @example altering changes before execution with block syntax
|
77
|
-
# content = '<http://ex.org/1> <http://ex.org/
|
78
|
+
# content = '<http://ex.org/1> <http://ex.org/p> "moomin" .'
|
78
79
|
#
|
79
|
-
# ldprs.create(content, 'text/turtle') do |tx|
|
80
|
+
# ldprs.create(StringIO.new(content), 'text/turtle') do |tx|
|
80
81
|
# tx.insert([RDF::URI('s'), RDF::URI('p'), 'custom'])
|
81
82
|
# tx.insert([RDF::URI('s'), RDF::URI('p'), 'custom', RDF::URI('g')])
|
82
83
|
# end
|
83
84
|
#
|
84
85
|
# @example validating changes before execution with block syntax
|
85
|
-
# content = '<http://ex.org/1> <http://ex.org/
|
86
|
+
# content = '<http://ex.org/1> <http://ex.org/p> "moomin" .'
|
86
87
|
#
|
87
|
-
# ldprs.create(content, 'text/turtle') do |tx|
|
88
|
+
# ldprs.create(StringIO.new(content), 'text/turtle') do |tx|
|
88
89
|
# raise "cannot delete triples on create!" unless tx.deletes.empty?
|
89
90
|
# end
|
90
91
|
#
|
91
|
-
# @raise [RDF::LDP::RequestError]
|
92
|
-
# @raise [RDF::LDP::UnsupportedMediaType] if no reader can be found for the
|
92
|
+
# @raise [RDF::LDP::RequestError]
|
93
|
+
# @raise [RDF::LDP::UnsupportedMediaType] if no reader can be found for the
|
93
94
|
# graph
|
94
|
-
# @raise [RDF::LDP::BadRequest] if the identified reader can't parse the
|
95
|
+
# @raise [RDF::LDP::BadRequest] if the identified reader can't parse the
|
95
96
|
# graph
|
96
97
|
# @raise [RDF::LDP::Conflict] if the RDFSource already exists
|
97
98
|
#
|
@@ -104,35 +105,36 @@ module RDF::LDP
|
|
104
105
|
end
|
105
106
|
|
106
107
|
##
|
107
|
-
# Updates the resource. Replaces the contents of `graph` with the parsed
|
108
|
+
# Updates the resource. Replaces the contents of `graph` with the parsed
|
108
109
|
# input.
|
109
110
|
#
|
110
|
-
# @param [IO, File
|
111
|
+
# @param [IO, File] input input (usually from a Rack env's
|
111
112
|
# `rack.input` key) used to determine the Resource's new state.
|
112
113
|
# @param [#to_s] content_type a MIME content_type used to interpret the
|
113
114
|
# input.
|
114
115
|
#
|
115
|
-
# @yield gives an in-progress transaction (changeset) to collect changes to
|
116
|
-
# graph, metagraph and other resources' (e.g. containers) graphs.
|
117
|
-
# @yieldparam tx [RDF::Transaction] a transaction targeting `#graph` as the
|
116
|
+
# @yield gives an in-progress transaction (changeset) to collect changes to
|
117
|
+
# graph, metagraph and other resources' (e.g. containers) graphs.
|
118
|
+
# @yieldparam tx [RDF::Transaction] a transaction targeting `#graph` as the
|
118
119
|
# default graph name
|
119
120
|
#
|
120
121
|
# @example altering changes before execution with block syntax
|
121
122
|
# content = '<http://ex.org/1> <http://ex.org/prop> "moomin" .'
|
122
123
|
#
|
123
|
-
# ldprs.update(content, 'text/turtle') do |tx|
|
124
|
+
# ldprs.update(StringIO.new(content), 'text/turtle') do |tx|
|
124
125
|
# tx.insert([RDF::URI('s'), RDF::URI('p'), 'custom'])
|
125
126
|
# tx.insert([RDF::URI('s'), RDF::URI('p'), 'custom', RDF::URI('g')])
|
126
127
|
# end
|
127
128
|
#
|
128
|
-
# @raise [RDF::LDP::RequestError]
|
129
|
-
# @raise [RDF::LDP::UnsupportedMediaType] if no reader can be found for the
|
129
|
+
# @raise [RDF::LDP::RequestError]
|
130
|
+
# @raise [RDF::LDP::UnsupportedMediaType] if no reader can be found for the
|
130
131
|
# graph
|
131
132
|
#
|
132
133
|
# @return [RDF::LDP::Resource] self
|
133
134
|
def update(input, content_type, &block)
|
134
135
|
super do |transaction|
|
135
|
-
transaction
|
136
|
+
transaction
|
137
|
+
.delete(RDF::Statement(nil, nil, nil, graph_name: subject_uri))
|
136
138
|
transaction.insert parse_graph(input, content_type)
|
137
139
|
yield transaction if block_given?
|
138
140
|
end
|
@@ -143,7 +145,7 @@ module RDF::LDP
|
|
143
145
|
#
|
144
146
|
# @see RDF::LDP::Resource#destroy
|
145
147
|
def destroy(&block)
|
146
|
-
super do |tx|
|
148
|
+
super do |tx|
|
147
149
|
tx.delete(RDF::Statement(nil, nil, nil, graph_name: subject_uri))
|
148
150
|
end
|
149
151
|
end
|
@@ -155,7 +157,7 @@ module RDF::LDP
|
|
155
157
|
end
|
156
158
|
|
157
159
|
##
|
158
|
-
# Returns the graph representing this resource's state, without the graph
|
160
|
+
# Returns the graph representing this resource's state, without the graph
|
159
161
|
# context.
|
160
162
|
def to_response
|
161
163
|
RDF::Graph.new << graph
|
@@ -168,10 +170,10 @@ module RDF::LDP
|
|
168
170
|
#
|
169
171
|
# @note patch is currently not transactional.
|
170
172
|
#
|
171
|
-
# @raise [RDF::LDP::UnsupportedMediaType] when a media type other than
|
173
|
+
# @raise [RDF::LDP::UnsupportedMediaType] when a media type other than
|
172
174
|
# LDPatch is used
|
173
175
|
# @raise [RDF::LDP::BadRequest] when an invalid document is given
|
174
|
-
def patch(
|
176
|
+
def patch(_status, headers, env)
|
175
177
|
check_precondition!(env)
|
176
178
|
method = patch_types[env['CONTENT_TYPE']]
|
177
179
|
|
@@ -181,7 +183,7 @@ module RDF::LDP
|
|
181
183
|
set_last_modified
|
182
184
|
[200, update_headers(headers), self]
|
183
185
|
end
|
184
|
-
|
186
|
+
|
185
187
|
##
|
186
188
|
# @return [Hash<String,Symbol>] a hash mapping supported PATCH content types
|
187
189
|
# to the method used to process the PATCH request
|
@@ -189,8 +191,8 @@ module RDF::LDP
|
|
189
191
|
{ 'text/ldpatch' => :ld_patch,
|
190
192
|
'application/sparql-update' => :sparql_update }
|
191
193
|
end
|
192
|
-
|
193
|
-
def ld_patch(input, graph
|
194
|
+
|
195
|
+
def ld_patch(input, graph)
|
194
196
|
LD::Patch.parse(input.read).execute(graph)
|
195
197
|
rescue LD::Patch::Error => e
|
196
198
|
raise BadRequest, e.message
|
@@ -204,7 +206,7 @@ module RDF::LDP
|
|
204
206
|
|
205
207
|
##
|
206
208
|
# Process & generate response for PUT requsets.
|
207
|
-
def put(
|
209
|
+
def put(_status, headers, env)
|
208
210
|
check_precondition!(env)
|
209
211
|
|
210
212
|
if exists?
|
@@ -221,35 +223,35 @@ module RDF::LDP
|
|
221
223
|
# @param [Hash<String, String>] env a rack env
|
222
224
|
# @raise [RDF::LDP::PreconditionFailed]
|
223
225
|
def check_precondition!(env)
|
224
|
-
raise
|
225
|
-
env.
|
226
|
+
raise(PreconditionFailed, 'Etag invalid') if
|
227
|
+
env.key?('HTTP_IF_MATCH') && !match?(env['HTTP_IF_MATCH'])
|
226
228
|
end
|
227
229
|
|
228
230
|
##
|
229
231
|
# Finds an {RDF::Reader} appropriate for the given content_type and attempts
|
230
232
|
# to parse the graph string.
|
231
233
|
#
|
232
|
-
# @param [IO, File
|
233
|
-
#
|
234
|
+
# @param [IO, File] input a (Rack) input stream IO object to parse
|
235
|
+
#
|
234
236
|
# @param [#to_s] content_type the content type for the reader
|
235
237
|
#
|
236
238
|
# @return [RDF::Enumerable] the statements in the resulting graph
|
237
239
|
#
|
238
240
|
# @raise [RDF::LDP::UnsupportedMediaType] if no appropriate reader is found
|
239
241
|
#
|
240
|
-
# @see http://www.rubydoc.info/github/rack/rack/file/SPEC#The_Input_Stream
|
241
|
-
#
|
242
|
+
# @see http://www.rubydoc.info/github/rack/rack/file/SPEC#The_Input_Stream
|
243
|
+
# Documentation on input streams in the Rack SPEC
|
242
244
|
def parse_graph(input, content_type)
|
243
245
|
reader = RDF::Reader.for(content_type: content_type.to_s)
|
244
246
|
raise(RDF::LDP::UnsupportedMediaType, content_type) if reader.nil?
|
245
247
|
|
246
248
|
begin
|
247
249
|
input.rewind
|
248
|
-
RDF::Graph.new(graph_name: subject_uri, data: RDF::Repository.new) <<
|
250
|
+
RDF::Graph.new(graph_name: subject_uri, data: RDF::Repository.new) <<
|
249
251
|
reader.new(input.read, base_uri: subject_uri, validate: true)
|
250
252
|
rescue RDF::ReaderError => e
|
251
253
|
raise RDF::LDP::BadRequest, e.message
|
252
|
-
end
|
254
|
+
end
|
253
255
|
end
|
254
256
|
end
|
255
257
|
end
|