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, root_container, id = nil) ⇒ LdpWriter

Returns a new instance of LdpWriter.

Parameters:

  • anno (Triannon::Annotation)

    a Triannon::Annotation object

  • root_container (String)

    the LDP parent container for the annotation

  • id (String) (defaults to: nil)

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



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

def initialize(anno, root_container, id = nil)
  @anno = anno
  @root_container = root_container
  @id = id
  base_url = Triannon.config[:ldp]['url'].strip
  base_url.chop! if base_url.end_with?('/')
  @uber_container_path = Triannon.config[:ldp]['uber_container'].strip
  if @uber_container_path
    @uber_container_path = @uber_container_path[1..-1] if @uber_container_path.start_with?('/')
    @uber_container_path.chop! if @uber_container_path.end_with?('/')
  end
  @base_uri = "#{base_url}/#{@uber_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



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

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, root_container) ⇒ Object

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

Parameters:

  • anno (Triannon::Annotation)

    a Triannon::Annotation object, from which we use the graph

  • root_container (String)

    the LDP parent container for the annotation



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, root_container)
  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, root_container
    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



72
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
# File 'app/services/triannon/ldp_writer.rb', line 72

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.



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

def self.delete_container id
  if id && id.size > 0
    ldpw = Triannon::LdpWriter.new nil, 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



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

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
  if @root_container.blank?
    fail Triannon::LDPContainerError, "Annotations must be created in a root container."
  end
  unless self.class.container_exist? "#{@uber_container_path}/#{@root_container}"
    fail Triannon::MissingLDPContainerError, "Annotation root container #{@root_container} doesn't exist."
  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
  g << [RDF::URI.new, RDF.type, RDF::Vocab::LDP.BasicContainer]

  @id = create_resource g.to_ttl, @root_container
end

#create_body_containerObject

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



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

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



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

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



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

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



194
195
196
# File 'app/services/triannon/ldp_writer.rb', line 194

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



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'app/services/triannon/ldp_writer.rb', line 202

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
    ldp_id = "#{@root_container}/#{ldp_id}" unless @root_container.blank? || ldp_id.match(@root_container)
    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