class RDF::LDP::RDFSource

The base class for all directly usable LDP Resources that *are not* `NonRDFSources`. RDFSources are implemented as a resource with:

- a `#graph` representing the "entire persistent state"
- a `#metagraph` containing internal properties of the RDFSource

Repository implementations must be able to reconstruct both `#graph` and `#metagraph` accurately and separately (e.g., by saving them as distinct named graphs).

The implementations of `#create` and `#update` in `RDF::LDP::Resource` are overloaded to handle the edits to `#graph` within the same transaction as the base `#metagraph` updates. `#to_response` is overloaded to return an unnamed `RDF::Graph`, to be transformed into an HTTP Body by `Rack::LDP::ContentNegotiation`.

@note the contents of `#metagraph`'s are not the same as

LDP-server-managed triples. `#metagraph` contains internal properties of
the RDFSource which are necessary for the server's management purposes,
but MAY be absent from (or in conflict with) the representation of its
state in `#graph`.

@see www.w3.org/TR/ldp/#dfn-linked-data-platform-rdf-source

Definition of ldp:RDFSource in the LDP specification

Public Class Methods

new(subject_uri, data = RDF::Repository.new) click to toggle source

@see RDF::LDP::Resource#initialize

Calls superclass method
# File lib/rdf/ldp/rdf_source.rb, line 44
def initialize(subject_uri, data = RDF::Repository.new)
  @subject_uri = subject_uri
  @data = data
  super
  self
end
to_uri() click to toggle source

@return [RDF::URI] uri with lexical representation

'http://www.w3.org/ns/ldp#RDFSource'

@see www.w3.org/TR/ldp/#dfn-linked-data-platform-rdf-source

# File lib/rdf/ldp/rdf_source.rb, line 37
def to_uri
  RDF::Vocab::LDP.RDFSource
end

Public Instance Methods

create(input, content_type) { |transaction| ... } click to toggle source

Creates the RDFSource, populating its graph from the input given

@example

repository = RDF::Repository.new
content = StringIO.new('<http://ex.org/1> <http://ex.org/p> "moomin" .')

ldprs = RDF::LDP::RDFSource.new('http://example.org/moomin', repository)
ldprs.create(content, 'text/turtle')

@param [IO, File] input input (usually from a Rack env's

`rack.input` key) used to determine the Resource's initial state.

@param [#to_s] content_type a MIME content_type used to read the graph.

@yield gives an in-progress transaction (changeset) to collect changes to

graph, metagraph and other resources' (e.g. containers) graphs.

@yieldparam tx [RDF::Transaction] a transaction targeting `#graph` as the

default graph name

@example altering changes before execution with block syntax

content = '<http://ex.org/1> <http://ex.org/p> "moomin" .'

ldprs.create(StringIO.new(content), 'text/turtle') do |tx|
  tx.insert([RDF::URI('s'), RDF::URI('p'), 'custom'])
  tx.insert([RDF::URI('s'), RDF::URI('p'), 'custom', RDF::URI('g')])
end

@example validating changes before execution with block syntax

content = '<http://ex.org/1> <http://ex.org/p> "moomin" .'

ldprs.create(StringIO.new(content), 'text/turtle') do |tx|
  raise "cannot delete triples on create!" unless tx.deletes.empty?
end

@raise [RDF::LDP::RequestError] @raise [RDF::LDP::UnsupportedMediaType] if no reader can be found for the

graph

@raise [RDF::LDP::BadRequest] if the identified reader can't parse the

graph

@raise [RDF::LDP::Conflict] if the RDFSource already exists

@return [RDF::LDP::Resource] self

Calls superclass method
# File lib/rdf/ldp/rdf_source.rb, line 100
def create(input, content_type, &block)
  super do |transaction|
    transaction.insert(parse_graph(input, content_type))
    yield transaction if block_given?
  end
end
destroy(&block) click to toggle source

Clears the graph and marks as destroyed.

@see RDF::LDP::Resource#destroy

Calls superclass method
# File lib/rdf/ldp/rdf_source.rb, line 147
def destroy(&block)
  super do |tx|
    tx.delete(RDF::Statement(nil, nil, nil, graph_name: subject_uri))
  end
end
graph() click to toggle source

@return [RDF::Graph] a graph representing the current persistent state of

the resource.
# File lib/rdf/ldp/rdf_source.rb, line 54
def graph
  @graph ||= RDF::Graph.new(graph_name: @subject_uri, data: @data)
end
rdf_source?() click to toggle source

@return [Boolean] whether this is an ldp:RDFSource

# File lib/rdf/ldp/rdf_source.rb, line 155
def rdf_source?
  true
end
to_response() click to toggle source

Returns the graph representing this resource's state, without the graph context.

# File lib/rdf/ldp/rdf_source.rb, line 162
def to_response
  RDF::Graph.new << graph
end
update(input, content_type) { |transaction| ... } click to toggle source

Updates the resource. Replaces the contents of `graph` with the parsed input.

@param [IO, File] input input (usually from a Rack env's

`rack.input` key) used to determine the Resource's new state.

@param [#to_s] content_type a MIME content_type used to interpret the

input.

@yield gives an in-progress transaction (changeset) to collect changes to

graph, metagraph and other resources' (e.g. containers) graphs.

@yieldparam tx [RDF::Transaction] a transaction targeting `#graph` as the

default graph name

@example altering changes before execution with block syntax

content = '<http://ex.org/1> <http://ex.org/prop> "moomin" .'

ldprs.update(StringIO.new(content), 'text/turtle') do |tx|
  tx.insert([RDF::URI('s'), RDF::URI('p'), 'custom'])
  tx.insert([RDF::URI('s'), RDF::URI('p'), 'custom', RDF::URI('g')])
end

@raise [RDF::LDP::RequestError] @raise [RDF::LDP::UnsupportedMediaType] if no reader can be found for the

graph

@return [RDF::LDP::Resource] self

Calls superclass method
# File lib/rdf/ldp/rdf_source.rb, line 134
def update(input, content_type, &block)
  super do |transaction|
    transaction
      .delete(RDF::Statement(nil, nil, nil, graph_name: subject_uri))
    transaction.insert parse_graph(input, content_type)
    yield transaction if block_given?
  end
end

Private Instance Methods

check_precondition!(env) click to toggle source

@param [Hash<String, String>] env a rack env @raise [RDF::LDP::PreconditionFailed]

# File lib/rdf/ldp/rdf_source.rb, line 227
def check_precondition!(env)
  raise(PreconditionFailed, 'Etag invalid') if
    env.key?('HTTP_IF_MATCH') && !match?(env['HTTP_IF_MATCH'])
end
ld_patch(input, graph) click to toggle source
# File lib/rdf/ldp/rdf_source.rb, line 195
def ld_patch(input, graph)
  LD::Patch.parse(input.read).execute(graph)
rescue LD::Patch::Error => e
  raise BadRequest, e.message
end
parse_graph(input, content_type) click to toggle source

Finds an {RDF::Reader} appropriate for the given content_type and attempts to parse the graph string.

@param [IO, File] input a (Rack) input stream IO object to parse

@param [#to_s] content_type the content type for the reader

@return [RDF::Enumerable] the statements in the resulting graph

@raise [RDF::LDP::UnsupportedMediaType] if no appropriate reader is found

@see www.rubydoc.info/github/rack/rack/file/SPEC#The_Input_Stream

Documentation on input streams in the Rack SPEC
# File lib/rdf/ldp/rdf_source.rb, line 246
def parse_graph(input, content_type)
  reader = RDF::Reader.for(content_type: content_type.to_s)
  raise(RDF::LDP::UnsupportedMediaType, content_type) if reader.nil?

  begin
    input.rewind
    RDF::Graph.new(graph_name: subject_uri, data: RDF::Repository.new) <<
      reader.new(input.read, base_uri: subject_uri, validate: true)
  rescue RDF::ReaderError => e
    raise RDF::LDP::BadRequest, e.message
  end
end
patch(_status, headers, env) click to toggle source

Process & generate response for PUT requsets.

@note patch is currently not transactional.

@raise [RDF::LDP::UnsupportedMediaType] when a media type other than

LDPatch is used

@raise [RDF::LDP::BadRequest] when an invalid document is given

# File lib/rdf/ldp/rdf_source.rb, line 176
def patch(_status, headers, env)
  check_precondition!(env)
  method = patch_types[env['CONTENT_TYPE']]

  raise UnsupportedMediaType unless method

  send(method, env['rack.input'], graph)
  set_last_modified
  [200, update_headers(headers), self]
end
patch_types() click to toggle source

@return [Hash<String,Symbol>] a hash mapping supported PATCH content types

to the method used to process the PATCH request
# File lib/rdf/ldp/rdf_source.rb, line 190
def patch_types
  { 'text/ldpatch'              => :ld_patch,
    'application/sparql-update' => :sparql_update }
end
put(_status, headers, env) click to toggle source

Process & generate response for PUT requsets.

# File lib/rdf/ldp/rdf_source.rb, line 211
def put(_status, headers, env)
  check_precondition!(env)

  if exists?
    update(env['rack.input'], env['CONTENT_TYPE'])
    headers = update_headers(headers)
    [200, headers, self]
  else
    create(env['rack.input'], env['CONTENT_TYPE'])
    [201, update_headers(headers), self]
  end
end
sparql_update(input, graph) click to toggle source
# File lib/rdf/ldp/rdf_source.rb, line 201
def sparql_update(input, graph)
  SPARQL.execute(input.read, graph,
                 update:   true,
                 base_uri: RDF::URI.intern(graph.name))
rescue SPARQL::MalformedQuery => e
  raise BadRequest, e.message
end