Class: Triannon::LdpWriter

Inherits:
Object
  • Object
show all
Defined in:
app/services/triannon/ldp_writer.rb

Overview

writes data/objects to the LDP server; also does deletes

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(anno, id = nil) ⇒ LdpWriter

Returns a new instance of LdpWriter.

Parameters:

  • anno (Triannon::Annotation)

    a Triannon::Annotation object

  • id (String) (defaults to: nil)

    the unique id for the LDP container for the passed annotation; defaults to nil



131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'app/services/triannon/ldp_writer.rb', line 131

def initialize(anno, id = nil)
  @anno = anno
  @id = id
  base_url = Triannon.config[:ldp]['url'].strip
  base_url.chop! if base_url.end_with?('/')
  container_path = Triannon.config[:ldp]['uber_container']
  if container_path
    container_path.strip!
    container_path = container_path[1..-1] if container_path.start_with?('/')
    container_path.chop! if container_path.end_with?('/')
  end
  @base_uri = "#{base_url}/#{container_path}"
end

Class Method Details

.container_exist?(path) ⇒ Boolean

Returns true if container already exists; false otherwise.

Parameters:

  • path (String)

    the path part of the container url, after the ldp base url

Returns:

  • (Boolean)

    true if container already exists; false otherwise



52
53
54
55
56
57
58
59
60
61
62
63
# File 'app/services/triannon/ldp_writer.rb', line 52

def self.container_exist? path
  base_url = Triannon.config[:ldp]['url'].strip
  path.strip!
  separator = (base_url.end_with?('/') || path.start_with?('/')) ? "" : '/'
  conn = Faraday.new url: base_url + separator + path
  resp = conn.head
  if resp.status.between?(400, 600)
    false
  else
    true
  end
end

.create_anno(anno) ⇒ Object

use LDP protocol to create the OpenAnnotation.Annotation in an RDF store

Parameters:



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'app/services/triannon/ldp_writer.rb', line 9

def self.create_anno(anno)
  if anno && anno.graph
    # TODO:  special case if the Annotation object already has an id --
    #  see https://github.com/sul-dlss/triannon/issues/84
    ldp_writer = Triannon::LdpWriter.new anno
    id = ldp_writer.create_base

    bodies_solns = anno.graph.query([nil, RDF::Vocab::OA.hasBody, nil])
    if bodies_solns.size > 0
      ldp_writer.create_body_container
      ldp_writer.create_body_resources
    end

    targets_solns = anno.graph.query([nil, RDF::Vocab::OA.hasTarget, nil])
    # NOTE:  Annotation is invalid if there are no target statements
    if targets_solns.size > 0
      ldp_writer.create_target_container
      ldp_writer.create_target_resources
    end

    id
  end
end

.create_basic_container(parent_path, slug) ⇒ Boolean

Creates an empty LDP BasicContainer in LDP Storage

Parameters:

  • parent_path (String)

    the path part, after the ldp base url – in essence, the LDP BasicContainer that will be the parent of the to-be-created BasicContainer.

  • slug (String)

    the value to send in Http Header ‘Slug’ – this is appended to the parent container’s path to become the id of the newly created BasicContainer

Returns:

  • (Boolean)

    true if the container was created; false otherwise



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'app/services/triannon/ldp_writer.rb', line 73

def self.create_basic_container parent_path, slug
  if slug.blank?
    puts "create_basic_container called with nil or empty slug, parent_path '#{parent_path}'"
    return false
  end
  base_url = Triannon.config[:ldp]['url'].strip
  base_url.chop! if base_url.end_with?('/')
  slug.strip!
  slug = slug[1..-1] if slug.start_with?('/')
  if parent_path
    parent_path.strip!
    parent_path = parent_path[1..-1] if parent_path.start_with?('/')
    parent_path.chop! if parent_path.end_with?('/')
  end

  full_path = (parent_path ? parent_path + '/' : "") + slug
  full_url = base_url + '/' + full_path

  if container_exist? full_path
    puts "Container #{full_url} already exists."
    false
  else
    g = RDF::Graph.new
    null_rel_uri = RDF::URI.new
    g << [null_rel_uri, RDF.type, RDF::Vocab::LDP.BasicContainer]
    conn = Faraday.new url: base_url + (parent_path ? '/' + parent_path : "")
    resp = conn.post do |req|
      # Note from Fcrepo docs:
      #  https://wiki.duraspace.org/display/FEDORA41/RESTful+HTTP+API+-+Containers#RESTfulHTTPAPI-Containers-BluePOSTCreatenewresourceswithinaLDPcontainer
      #   "If the MIME type corresponds to a supported RDF format or SPARQL-Update, the uploaded content will be
      #   parsed as RDF and used to populate the child node properties.  RDF will be interpreted using the current
      #   resource as the base URI (e.g. <> will be expanded to the current URI)."
      # Thus, the next line is needed even if the body of this POST is empty
      req.headers['Content-Type'] = 'text/turtle'
      req.headers['Slug'] = slug
      req.body = g.to_ttl
    end

    if resp.status == 201
      new_url = resp.headers['Location'] ? resp.headers['Location'] : resp.headers['location']
      if new_url == full_url
        puts "Created Basic Container #{new_url}"
        true
      else
        puts "Created Basic Container #{new_url} instead of #{full_url}"
        false
      end
    else
      puts "Unable to create Basic Container #{full_url}:  LDP Storage response status #{resp.status}; body #{resp.body}"
      false
    end
  end
end

.delete_container(id) ⇒ Object Also known as: delete_anno

deletes the indicated container and all its child containers from the LDP

store

Parameters:

  • id (String)

    the unique id for the LDP container for an annotation. May be a compound id, such as uuid1/t/uuid2, in which case the LDP container object uuid2 and its children are deleted from the LDP store, but LDP containers uuid1/t and uuid1 are not deleted from the LDP store.



40
41
42
43
44
45
# File 'app/services/triannon/ldp_writer.rb', line 40

def self.delete_container id
  if id && id.size > 0
    ldpw = Triannon::LdpWriter.new nil
    ldpw.delete_containers id
  end
end

Instance Method Details

#create_baseString

creates a stored LDP container for this object’s Annotation, without its

targets or bodies (as those are put in descendant containers)
SIDE EFFECT:  assigns the uuid of the container created to @id

Returns:

  • (String)

    the unique id for the LDP container created for this annotation



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'app/services/triannon/ldp_writer.rb', line 150

def create_base
  if @anno.graph.query([nil, RDF::Triannon.externalReference, nil]).count > 0
    fail Triannon::ExternalReferenceError, "Incoming annotations may not have http://triannon.stanford.edu/ns/externalReference as a predicate."
  end

  if @anno.graph.id_as_url && @anno.graph.id_as_url.size > 0
    fail Triannon::ExternalReferenceError, "Incoming new annotations may not have an existing id (yet)."
  end

  # TODO:  special case if the Annotation object already has an id --
  #  see https://github.com/sul-dlss/triannon/issues/84

  # we need to work with a copy of the graph so we don't change @anno.graph
  g = RDF::Graph.new
  @anno.graph.each { |s|
    g << s
  }
  g = OA::Graph.new(g)
  g.remove_non_base_statements
  g.make_null_relative_uri_out_of_blank_node

  @id = create_resource g.to_ttl
end

#create_body_containerObject

creates the LDP container for any and all bodies for this annotation



175
176
177
# File 'app/services/triannon/ldp_writer.rb', line 175

def create_body_container
  create_direct_container RDF::Vocab::OA.hasBody
end

#create_body_resourcesObject

create the body resources inside the (already created) body container



185
186
187
# File 'app/services/triannon/ldp_writer.rb', line 185

def create_body_resources
  create_resources_in_container RDF::Vocab::OA.hasBody
end

#create_target_containerObject

creates the LDP container for any and all targets for this annotation



180
181
182
# File 'app/services/triannon/ldp_writer.rb', line 180

def create_target_container
  create_direct_container RDF::Vocab::OA.hasTarget
end

#create_target_resourcesObject

create the target resources inside the (already created) target container



190
191
192
# File 'app/services/triannon/ldp_writer.rb', line 190

def create_target_resources
  create_resources_in_container RDF::Vocab::OA.hasTarget
end

#delete_containers(ldp_container_uris) ⇒ Object

Returns true if a resource was deleted; false otherwise.

Parameters:

  • ldp_container_uris (Array<String>)

    an Array of ids for LDP containers. (can also be a String) e.g. [@base_uri/(uuid1)/t/(uuid2), @base_uri/(uuid1)/t/(uuid3)] or [@base_uri/(uuid)] or (uuid)

Returns:

  • true if a resource was deleted; false otherwise



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'app/services/triannon/ldp_writer.rb', line 198

def delete_containers ldp_container_uris
  return false if !ldp_container_uris || ldp_container_uris.empty?
  if ldp_container_uris.kind_of? String
    ldp_container_uris = [ldp_container_uris]
  end
  something_deleted = false
  ldp_container_uris.each { |uri|
    ldp_id = uri.to_s.split(@base_uri + '/').last
    resp = conn.delete { |req| req.url ldp_id }
    if resp.status != 204
      fail Triannon::LDPStorageError.new("Unable to delete LDP container #{ldp_id}", resp.status, resp.body)
    end
    something_deleted = true
  }
  something_deleted
end