Class: RDF::LDP::Resource
- Inherits:
-
Object
- Object
- RDF::LDP::Resource
- Defined in:
- lib/rdf/ldp/resource.rb
Overview
The base class for all LDP Resources.
The internal state of a Resource is specific to a given persistent datastore (an ‘RDF::Repository` passed to the initilazer) and is managed through an internal graph (`#metagraph`). A Resource has:
- a `#subject_uri` identifying the Resource.
- a `#metagraph` containing server-internal properties of the Resource.
Resources also define a basic set of CRUD operations, identity and current state, and a ‘#to_response`/`#each` method used by Rack & `Rack::LDP` to generate an appropriate HTTP response body.
‘#metagraph’ holds internal properites used by the server. It is distinct from, and may conflict with, other RDF and non-RDF information about the resource (e.g. representations suitable for a response body). Metagraph contains a canonical ‘rdf:type` statement, which specifies the resource’s interaction model and a (dcterms:modified) last-modified date. If the resource is deleted, a (prov:invalidatedAt) flag in metagraph indicates this.
The contents of ‘#metagraph` should not be confused with LDP server-managed-triples, Those triples are included in the state of the resource as represented by the response body. `#metagraph` is invisible to the client except where a subclass mirrors its contents in the body.
Rack (via ‘RDF::LDP::Rack`) uses the `#request` method to dispatch requests and interpret responses. Disallowed HTTP methods result in `RDF::LDP::MethodNotAllowed`. Individual Resources populate `Link`, `Allow`, `ETag`, `Last-Modified`, and `Accept-*` headers as required by LDP. All subclasses (MUST) return `self` as the Body, and respond to `#each`/ `#respond_to` with the intended body.
Direct Known Subclasses
Constant Summary collapse
- CONTAINS_URI =
RDF::Vocab::LDP.contains.freeze
- INVALIDATED_AT_URI =
RDF::Vocab::PROV.invalidatedAtTime.freeze
- MODIFIED_URI =
RDF::Vocab::DC.modified.freeze
Instance Attribute Summary collapse
-
#metagraph ⇒ Object
a graph representing the server-internal state of the resource.
-
#subject_uri ⇒ Object
readonly
Returns the value of attribute subject_uri.
Class Method Summary collapse
-
.find(uri, data) ⇒ RDF::LDP::Resource
Finds an existing resource and.
-
.gen_id ⇒ String
Creates an unique id (URI Slug) for a resource.
-
.interaction_model(link_header) ⇒ Class
Retrieves the correct interaction model from the Link headers.
-
.metagraph_name(uri) ⇒ Object
Build a graph name URI for the uri passed in.
-
.to_uri ⇒ RDF::URI
Uri with lexical representation ‘www.w3.org/ns/ldp#Resource’.
Instance Method Summary collapse
-
#allowed_methods ⇒ Array<Symbol>
A list of HTTP methods allowed by this resource.
-
#container? ⇒ Boolean
Whether this is an ldp:Container.
-
#containers ⇒ Array<RDF::LDP::Resource>
The container for this resource.
-
#create(_input, _content_type) {|tx| ... } ⇒ RDF::LDP::Resource
abstract
Self.
-
#destroy {|tx| ... } ⇒ RDF::LDP::Resource
Mark the resource as destroyed.
-
#destroyed? ⇒ Boolean
True if resource has been destroyed.
-
#etag ⇒ String
Returns an Etag.
-
#exists? ⇒ Boolean
Gives the status of the resource’s existance.
-
#initialize(subject_uri, data = RDF::Repository.new) {|RDF::Resource| ... } ⇒ Resource
constructor
A new instance of Resource.
-
#last_modified ⇒ DateTime
The time this resource was last modified; ‘nil` if the resource doesn’t exist and has no modified date.
-
#ldp_resource? ⇒ Boolean
Whether this is an ldp:Resource.
-
#match?(tag) ⇒ Boolean
Whether the given tag matches ‘#etag`.
-
#non_rdf_source? ⇒ Boolean
Whether this is an ldp:NonRDFSource.
-
#rdf_source? ⇒ Boolean
Whether this is an ldp:RDFSource.
-
#request(method, status, headers, env) ⇒ Array<Fixnum, Hash<String, String>, #each] a new Rack response array.
Build the response for the HTTP ‘method` given.
-
#to_response ⇒ Object
(also: #each)
Runs the request and returns the object’s desired HTTP response body, conforming to the Rack interfare.
-
#to_uri ⇒ RDF::URI
The subject URI for this resource.
-
#update(input, content_type) {|tx| ... } ⇒ RDF::LDP::Resource
abstract
Self.
Constructor Details
#initialize(subject_uri, data = RDF::Repository.new) {|RDF::Resource| ... } ⇒ Resource
Returns a new instance of Resource.
201 202 203 204 205 206 |
# File 'lib/rdf/ldp/resource.rb', line 201 def initialize(subject_uri, data = RDF::Repository.new) @subject_uri = RDF::URI(subject_uri) @data = data @metagraph = RDF::Graph.new(graph_name: , data: data) yield self if block_given? end |
Instance Attribute Details
#metagraph ⇒ Object
a graph representing the server-internal state of the resource
97 98 99 |
# File 'lib/rdf/ldp/resource.rb', line 97 def @metagraph end |
#subject_uri ⇒ Object (readonly)
Returns the value of attribute subject_uri.
93 94 95 |
# File 'lib/rdf/ldp/resource.rb', line 93 def subject_uri @subject_uri end |
Class Method Details
.find(uri, data) ⇒ RDF::LDP::Resource
Finds an existing resource and
131 132 133 134 135 136 137 138 139 140 |
# File 'lib/rdf/ldp/resource.rb', line 131 def find(uri, data) graph = RDF::Graph.new(graph_name: (uri), data: data) raise NotFound if graph.empty? rdf_class = graph.query([uri, RDF.type, :o]).first klass = INTERACTION_MODELS[rdf_class.object] if rdf_class klass ||= RDFSource klass.new(uri, data) end |
.gen_id ⇒ String
the current implementation uses SecureRandom#uuid.
Creates an unique id (URI Slug) for a resource.
115 116 117 |
# File 'lib/rdf/ldp/resource.rb', line 115 def gen_id SecureRandom.uuid end |
.interaction_model(link_header) ⇒ Class
Retrieves the correct interaction model from the Link headers.
Headers are handled intelligently, e.g. if a client sends a request with Resource, RDFSource, and BasicContainer headers, the server gives a BasicContainer. An error is thrown if the headers contain conflicting types (i.e. NonRDFSource and another Resource class).
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/rdf/ldp/resource.rb', line 155 def interaction_model(link_header) models = LinkHeader.parse(link_header) .links.select { |link| link['rel'].casecmp 'type' } .map { |link| link.href } return RDFSource if models.empty? match = INTERACTION_MODELS.keys.reverse.find { |u| models.include? u } if match == RDF::LDP::NonRDFSource.to_uri raise NotAcceptable if models.include?(RDF::LDP::RDFSource.to_uri) || models.include?(RDF::LDP::Container.to_uri) || models.include?(RDF::LDP::DirectContainer.to_uri) || models.include?(RDF::LDP::IndirectContainer.to_uri) || models.include?(RDF::URI('http://www.w3.org/ns/ldp#BasicContainer')) end INTERACTION_MODELS[match] || RDFSource end |
.metagraph_name(uri) ⇒ Object
Build a graph name URI for the uri passed in
180 181 182 |
# File 'lib/rdf/ldp/resource.rb', line 180 def (uri) uri + '#meta' end |
.to_uri ⇒ RDF::URI
Returns uri with lexical representation ‘www.w3.org/ns/ldp#Resource’.
105 106 107 |
# File 'lib/rdf/ldp/resource.rb', line 105 def to_uri RDF::Vocab::LDP.Resource end |
Instance Method Details
#allowed_methods ⇒ Array<Symbol>
Returns a list of HTTP methods allowed by this resource.
359 360 361 362 363 |
# File 'lib/rdf/ldp/resource.rb', line 359 def allowed_methods [:GET, :POST, :PUT, :DELETE, :PATCH, :OPTIONS, :HEAD].select do |m| respond_to?(m.downcase, true) end end |
#container? ⇒ Boolean
Returns whether this is an ldp:Container.
373 374 375 |
# File 'lib/rdf/ldp/resource.rb', line 373 def container? false end |
#containers ⇒ Array<RDF::LDP::Resource>
Returns the container for this resource.
391 392 393 394 395 |
# File 'lib/rdf/ldp/resource.rb', line 391 def containers @data.query([:s, CONTAINS_URI, subject_uri]).map do |st| RDF::LDP::Resource.find(st.subject, @data) end end |
#create(_input, _content_type) {|tx| ... } ⇒ RDF::LDP::Resource
creates the resource
Returns self.
225 226 227 228 229 230 231 232 233 234 235 |
# File 'lib/rdf/ldp/resource.rb', line 225 def create(_input, _content_type) raise Conflict if exists? @data.transaction(mutable: true) do |transaction| set_interaction_model(transaction) yield transaction if block_given? set_last_modified(transaction) end self end |
#destroy {|tx| ... } ⇒ RDF::LDP::Resource
Use of owl:Nothing is probably problematic. Define an internal
Mark the resource as destroyed.
This adds a statment to the metagraph expressing that the resource has been deleted
namespace and class represeting deletion status as a stateful property.
276 277 278 279 280 281 282 283 284 285 286 |
# File 'lib/rdf/ldp/resource.rb', line 276 def destroy @data.transaction(mutable: true) do |transaction| containers.each { |c| c.remove(self, transaction) if c.container? } transaction.insert RDF::Statement(subject_uri, INVALIDATED_AT_URI, DateTime.now, graph_name: ) yield transaction if block_given? end self end |
#destroyed? ⇒ Boolean
Returns true if resource has been destroyed.
301 302 303 304 |
# File 'lib/rdf/ldp/resource.rb', line 301 def destroyed? times = @metagraph.query([subject_uri, INVALIDATED_AT_URI, nil]) !times.empty? end |
#etag ⇒ String
these etags are weak, but we allow clients to use them in ‘If-Match` headers, and use weak comparison. This is in conflict with tools.ietf.org/html/rfc7232#section-3.1. See: github.com/ruby-rdf/rdf-ldp/issues/68
Returns an Etag. This may be a strong or a weak ETag.
320 321 322 323 |
# File 'lib/rdf/ldp/resource.rb', line 320 def etag return nil unless exists? "W/\"#{last_modified.new_offset(0).iso8601(9)}\"" end |
#exists? ⇒ Boolean
destroyed resources continue to exist in the sense represeted by this method.
Gives the status of the resource’s existance.
295 296 297 |
# File 'lib/rdf/ldp/resource.rb', line 295 def exists? @data.has_graph? .graph_name end |
#last_modified ⇒ DateTime
handle cases where there is more than one RDF::DC.modified. check for the most recent date
Returns the time this resource was last modified; ‘nil` if the resource doesn’t exist and has no modified date.
333 334 335 336 337 338 339 340 341 342 |
# File 'lib/rdf/ldp/resource.rb', line 333 def last_modified results = @metagraph.query([subject_uri, RDF::Vocab::DC.modified, :time]) if results.empty? return nil unless exists? raise(RequestError, "Missing dc:modified date for #{subject_uri}") end results.first.object.object end |
#ldp_resource? ⇒ Boolean
Returns whether this is an ldp:Resource.
367 368 369 |
# File 'lib/rdf/ldp/resource.rb', line 367 def ldp_resource? true end |
#match?(tag) ⇒ Boolean
Returns whether the given tag matches ‘#etag`.
347 348 349 |
# File 'lib/rdf/ldp/resource.rb', line 347 def match?(tag) tag == etag end |
#non_rdf_source? ⇒ Boolean
Returns whether this is an ldp:NonRDFSource.
379 380 381 |
# File 'lib/rdf/ldp/resource.rb', line 379 def non_rdf_source? false end |
#rdf_source? ⇒ Boolean
Returns whether this is an ldp:RDFSource.
385 386 387 |
# File 'lib/rdf/ldp/resource.rb', line 385 def rdf_source? false end |
#request(method, status, headers, env) ⇒ Array<Fixnum, Hash<String, String>, #each] a new Rack response array.
Build the response for the HTTP ‘method` given.
The method passed in is symbolized, downcased, and sent to ‘self` with the other three parameters.
Request methods are expected to return an Array appropriate for a Rack response; to return this object (e.g. for a sucessful GET) the response may be ‘[status, headers, self]`.
If the method given is unimplemented, we understand it to require an HTTP 405 response, and throw the appropriate error.
432 433 434 435 436 437 438 439 |
# File 'lib/rdf/ldp/resource.rb', line 432 def request(method, status, headers, env) raise Gone if destroyed? begin send(method.to_sym.downcase, status, headers, env) rescue NotImplementedError raise MethodNotAllowed, method end end |
#to_response ⇒ Object Also known as: each
Runs the request and returns the object’s desired HTTP response body, conforming to the Rack interfare.
403 404 405 |
# File 'lib/rdf/ldp/resource.rb', line 403 def to_response [] end |
#to_uri ⇒ RDF::URI
Returns the subject URI for this resource.
353 354 355 |
# File 'lib/rdf/ldp/resource.rb', line 353 def to_uri subject_uri end |
#update(input, content_type) {|tx| ... } ⇒ RDF::LDP::Resource
update the resource
Returns self.
252 253 254 255 256 257 258 259 260 261 |
# File 'lib/rdf/ldp/resource.rb', line 252 def update(input, content_type, &block) return create(input, content_type, &block) unless exists? @data.transaction(mutable: true) do |transaction| yield transaction if block_given? set_last_modified(transaction) end self end |