Class: Dydra::Repository

Inherits:
Resource show all
Defined in:
lib/dydra/repository.rb

Overview

Represents a Dydra.com RDF repository.

See Also:

Constant Summary

SPEC =

/account/repository

%r(^([^/]+)/([^/]+)$)
FORMATS =
{ :json     => 'application/sparql-results+json',
:xml      => 'application/sparql-results+xml',
:columns  => 'application/json',
:rdf      => 'application/rdf+xml',
:sse      => 'application/sparql-query+sse',
:ntriples => 'text/plain',
:n3       => 'text/rdf+n3',
:turtle   => 'text/turtle' }

Constants inherited from Resource

Dydra::Resource::HEADERS

Instance Attribute Summary (collapse)

Attributes inherited from Resource

#url

Class Method Summary (collapse)

Instance Method Summary (collapse)

Methods inherited from Resource

#<=>, #eql?, #exists?, #get, #head, #inspect, #inspect!, new, #path, #to_rdf, #to_uri

Constructor Details

- (Repository) initialize(user, name = nil)

A new instance of Repository

Parameters:

  • user (String, #to_s)
  • name (String, #to_s) (defaults to: nil)


80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/dydra/repository.rb', line 80

def initialize(user, name = nil)
  if name.nil?
    if user =~ /\// # a user/repo form
      (user, name) = user.split(/\//)
    else
      name = user
      user = $dydra[:user]
    end
  end
  if user.nil? && !$dydra[:token].nil?
    raise RepositoryMisspecified, "You must specify a repository owner name when using token-only authentication"
  end
  @account = case user
    when Account then user
    else Account.new(user.to_s)
  end
  @name = name.to_s
  if Dydra::URL.respond_to?(:/')
    super(Dydra::URL / @account.name / @name)    # RDF.rb 0.3.0+
  else
    super(Dydra::URL.join(@account.name, @name)) # RDF.rb 0.2.x
  end
end

Instance Attribute Details

- (Account) account (readonly)

The account the repository belongs to.

Returns:



39
40
41
# File 'lib/dydra/repository.rb', line 39

def 
  @account
end

- (DateTime) created (readonly)

When the repository was first created.

Returns:

  • (DateTime)


63
64
65
# File 'lib/dydra/repository.rb', line 63

def created
  @created
end

- (String) description (readonly)

The long description of the repository.

Returns:

  • (String)


57
58
59
# File 'lib/dydra/repository.rb', line 57

def description
  @description
end

- (String) name (readonly)

The machine-readable name of the repository.

Returns:

  • (String)


45
46
47
# File 'lib/dydra/repository.rb', line 45

def name
  @name
end

- (String) summary (readonly)

The short description of the repository.

Returns:

  • (String)


51
52
53
# File 'lib/dydra/repository.rb', line 51

def summary
  @summary
end

- (DateTime) updated (readonly)

When the repository was last updated.

Returns:

  • (DateTime)


69
70
71
# File 'lib/dydra/repository.rb', line 69

def updated
  @updated
end

Class Method Details

+ (Repository) create!(account, name = nil)

Sugar for creating a repository, as .new instantiates an existing one.

Parameters:

  • repository_name (String)

Returns:



109
110
111
# File 'lib/dydra/repository.rb', line 109

def self.create!(, name = nil)
  self.new(, name).create!
end

+ (Enumerator) each(options = {}) {|repository| ... }

Parameters:

  • options (Hash{Symbol => Object}) (defaults to: {})

Options Hash (options):

  • :account_name (String) — default: nil

Yields:

  • (repository)

Yield Parameters:

Returns:

  • (Enumerator)


25
26
27
28
29
30
31
32
33
# File 'lib/dydra/repository.rb', line 25

def self.each(options = {}, &block)
  if block_given?
    result = Dydra::Client.rpc.call('dydra.repository.list', $dydra[:user] || '')
    result.each do |(, repository_name)|
      block.call(Repository.new(, repository_name))
    end
  end
  enum_for(:each, options)
end

+ (Array<String>) list(user = nil)

List of repository names. Will use the given user if supplied.

Parameters:

  • account (String)

Returns:

  • (Array<String>)

Raises:



118
119
120
121
122
# File 'lib/dydra/repository.rb', line 118

def self.list(user = nil)
  user ||= $dydra[:user]
  raise RepositoryMisspecified, "List requires a user in token-only authentication mode" if user.nil?
  Dydra::Client.get_json(user + '/repositories').map { |r| r['name'] }
end

+ (Object) query_form(query)

Determine if a query is an ASK, SELECT, CONSTRUCT, or DESCRIBE query.

return [:construct, :ask, :select, :describe]

Raises:



334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/dydra/repository.rb', line 334

def self.query_form(query)
  # This algorithm is maybe a little overkill, but tries to avoid weirdness
  # like variables named the same as a query form by finding the first one
  # that appears with a space after it. the space after it makes it an
  # invalid URI in a prefix or base.
  raise MalformedQuery, "Missing query text" if query.nil? || query.empty?
  query_lines = query.to_s.lines.to_a
  forms = %w{construct ask describe select create clear drop delete insert load copy move add}
  form_line = query_lines.shift while form_line !~ /(#{forms.join('|')})/i && query_lines.length > 0

  lowest_spot = result_form = nil
  forms.each do | form |
    # catches the form on a line by itself
    return form.to_sym if form_line.downcase.chomp == form

    # otherwise, look for the form, followed by the space, and mark where it is...
    if !(spot = form_line =~ /#{form}(\s|{)/i).nil?
      result_form = form.to_sym
      result_form = form if !lowest_spot.nil? && lowest_spot < spot
      lowest_spot = spot if lowest_spot.nil? || spot < lowest_spot
    end
  end
  raise MalformedQuery, "Could not determine query form" if result_form.nil?
  result_form
end

Instance Method Details

- (Job) clear!

Deletes all data in this repository.

Returns:



144
145
146
# File 'lib/dydra/repository.rb', line 144

def clear!
  Dydra::Client.delete("#{@account}/#{@name}/statements")
end

- (Integer) count

Returns the number of RDF statements in this repository.

Returns:

  • (Integer)


216
217
218
# File 'lib/dydra/repository.rb', line 216

def count
  Dydra::Client.rpc.call('dydra.repository.count', path)
end

- create!

This method returns an undefined value.

Creates this repository on Dydra.com.



128
129
130
# File 'lib/dydra/repository.rb', line 128

def create!
  Dydra::Client.post("#{}/repositories", { :repository => { :name => name }})
end

- (Object) delete(pattern)

Delete RDF data from this repository



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/dydra/repository.rb', line 166

def delete(pattern)
  pattern = case pattern
    when Hash
      pattern
    when RDF::Query::Pattern, RDF::Statement
      delete(:subject => pattern.subject, :predicate => pattern.predicate, :object => pattern.object, :context => pattern.context)
    when Array
      delete(:subject => pattern[0], :predicate => pattern[1], :object => pattern[2], :context => pattern[3])
    else raise ArgumentError, "Expected Hash, RDF::Query::Pattern, RDF::Statement, or Array, but got #{pattern.class}"
  end

  arguments = []
  arguments << ::URI.escape("subject=#{pattern[:subject]}") if pattern[:subject]
  arguments << ::URI.escape("predicate=#{pattern[:predicate]}") if pattern[:predicate]
  arguments << ::URI.escape("object=#{pattern[:object]}") if pattern[:object]
  arguments << ::URI.escape("context=#{pattern[:context]}") if pattern[:context]

  Dydra::Client.delete "#{@account}/#{@name}/statements?" + arguments.join('&')
end

- (Job) destroy!

Destroys this repository from Dydra.com.

Returns:



136
137
138
# File 'lib/dydra/repository.rb', line 136

def destroy!
  Dydra::Client.delete("#{@account}/#{@name}")
end

- (String) detect_content_type(filepath)

Returns the MIME content type for the given RDF file.

Parameters:

  • filepath (String)

    a local file path

Returns:

  • (String)

    a MIME content type, or nil



445
446
447
448
449
450
451
452
453
454
455
# File 'lib/dydra/repository.rb', line 445

def detect_content_type(filepath)
  case extname = File.extname(filepath)
    when '.ttl'  then 'text/turtle'         # Turtle
    when '.n3'   then 'text/n3'             # N3
    when '.nt'   then 'text/plain'          # N-Triples
    when '.nq'   then 'text/x-nquads'       # N-Quads
    when '.json' then 'application/json'    # RDF/JSON
    when '.rdf'  then 'application/rdf+xml' # RDF/XML
    when '.xml'  then 'application/trix'    # TriX
  end
end

- (Job) import!(url, opts = {})

Imports data from a URL into this repository.

Parameters:

  • url (String, #to_s)

Returns:



191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/dydra/repository.rb', line 191

def import!(url, opts = {})
  base_uri = opts[:base_uri] # || File.dirname(url)
  context = opts[:context] || ''
  if url =~ %r(^(http|https|ftp )://)
    url = url                                     # already a url
    base_uri = opts[:base_uri] || ''              # let the server determine base URI
  else
    base_uri = opts[:base_uri] || url             # Base URI is the file itself unless specified
    url = upload_local_file(self, url)            # local file to be uploaded
  end
  Job.new(Dydra::Client.rpc.call('dydra.repository.import', path, url.to_s, context.to_s, base_uri.to_s))
end

- (Object) insert(*statements)

Insert RDF data into this repository



151
152
153
154
155
156
157
158
159
160
161
# File 'lib/dydra/repository.rb', line 151

def insert(*statements)
  repo = RDF::Repository.new.insert(*statements)
  (repo.each_context.to_a << false).each do |context|
    context_argument = context == false ? "" : "?context=#{context}"
    statements = repo.query(:context => context)
    next if statements.empty?
    Dydra::Client.post "#{@account}/#{@name}/statements#{context_argument}",
                       RDF::Writer.for(:ntriples).dump(statements),
                       :content_type => 'text/plain'
  end
end

- (true, ...) parse_bindings(result)

Parse ASK or SELECT bindings into true/false or RDF::Query::Solutions

Returns:

  • (true, false, RDF::Query::Solutions)


281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/dydra/repository.rb', line 281

def parse_bindings(result)
  results = JSON.parse(result)
  variables = results["columns"].map(&:to_sym)
  nodes = {}
  bindings = results["rows"].map do | row |
    solution = RDF::Query::Solution.new
    row.each_with_index do | binding, index |
      solution[variables[index]] = parse_json_value(binding, nodes)
    end
    solution
  end

  if results["total"] == 1 &&  bindings.first.respond_to?(:result) && [true, false].include?(bindings.first.result.object)
    bindings.first.result.object
  else
    bindings
  end
end

- (Object) parse_json_value(value, nodes = {})

Parse JSON column result values



302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/dydra/repository.rb', line 302

def parse_json_value(value, nodes = {})
  # ASK queries in json-columns look like this
  return value if value.equal?(true) || value.equal?(false)
  # This catches successful queries with no bound variables
  return nil unless value['type']
  case value['type'].to_sym
    when :bnode
      nodes[id = value['value']] ||= RDF::Node.new(id)
    when :uri
      RDF::URI.new(value['value'])
    when :literal
      RDF::Literal.new(value['value'], :language => value['xml:lang'])
    when :typed-literal'
      RDF::Literal.new(value['value'], :datatype => value['datatype'])
    else nil
  end
end

- (RDF::Enumerable) parse_rdf(result)

Parse NTriples data into an RDF::Enumerable

Returns:

  • (RDF::Enumerable)


324
325
326
327
328
329
# File 'lib/dydra/repository.rb', line 324

def parse_rdf(result)
  require 'rdf/ntriples' unless defined?(RDF::NTriples)
  if reader = RDF::Reader.for(:ntriples)
    reader.new(result)
  end
end

- (Job) query(query, opts = {})

Queries this repository.

Parameters:

  • query (String)

Returns:



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/dydra/repository.rb', line 225

def query(query, opts = {})
  form = self.class.query_form(query)
  format = opts[:format] || case form
      when :select, :ask, :insert, :delete, :clear, :drop, :load, :create, :copy, :move, :add
        :json
      when :construct, :describe
        :ntriples
  end
  accept = case
    when FORMATS.has_key?(format)
      FORMATS[format]
    when :parsed
      case form
        when :select, :ask, :insert, :delete, :clear, :drop, :load, :create
          FORMATS[:columns]
        when :construct, :describe
          FORMATS[:ntriples]
      end
    else
      raise ArgumentError, "Unknown result format: #{format}"
  end

  begin
    query_params = { :query => query, :user_id => opts[:user_query_id],
                     :ruleset => opts[:ruleset] }
    result = Dydra::Client.post "#{}/#{name}/sparql", query_params,
       :content_type => 'application/x-www-form-urlencoded',
       :accept => accept

  # Query failure messages should come back JSON encoded. If we can't parse
  # the response as JSON, some other error was raised on the server that we
  # supress here.
  rescue Exception => e
    begin
      # FIXME: what about xml?
      # errors from the server: bad query format of some sort or another
      raise QueryError, JSON.parse(e.response)['error']
    rescue JSON::ParserError
      # errors from network timesout and that kind of thing. RestClient errors are fine.
      raise e
    end
  end

  return result unless format == :parsed
  case form
    when :select, :ask, :insert, :delete, :clear, :drop, :load, :create
      parse_bindings(result)
    when :construct, :describe
      parse_rdf(result)
  end
end

- (Hash) s3_upload_params

Returns params necessary to generate an S3 upload form

Returns:

  • (Hash)


208
209
210
# File 'lib/dydra/repository.rb', line 208

def s3_upload_params
  Dydra::Client.rpc.call('dydra.repository.upload.params', path)
end

- (String) to_s

Returns a string representation of the repository name.

Returns:

  • (String)


364
365
366
# File 'lib/dydra/repository.rb', line 364

def to_s
  [.name, name].join('/')
end

- (String) upload_local_file(repository, filepath)

Uploads a local file to a temporary Amazon S3 bucket.

Parameters:

  • filepath (String)

    a local file path

Returns:

  • (String)

    an Amazon S3 URL



380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
# File 'lib/dydra/repository.rb', line 380

def upload_local_file(repository, filepath)
  abort "file does not exist: #{filepath}"    unless File.exists?(filepath)
  abort "file is not readable: #{filepath}"   unless File.readable?(filepath)
  abort "unknown file extension: #{filepath}" unless content_type = detect_content_type(filepath)

  stdout.puts "Preparing upload...." if self.respond_to?(:verbose) && verbose?

  # Create the boundary used in constructing the form post body
  o =  [('a'..'z'),('A'..'Z'),(0..9)].map{|i| i.to_a}.flatten
  boundary = (0..9).map{ o[rand(o.length)] }.join

  # Grab required form params from the server
  upload_params = repository.s3_upload_params

  # HTTP Setup
  uri = ::URI.parse(upload_params['url'])
  http = Net::HTTP.new(uri.host, uri.port)
  if uri.scheme == 'https'
    http.use_ssl = true
    http.verify_mode = OpenSSL::SSL::VERIFY_NONE
  end

  # Params order is important to AWS.
  form_data = [["key", upload_params['key']],
               ["AWSAccessKeyId", upload_params['AWSAccessKeyId']],
               ["acl", upload_params['acl']],
               ["policy", upload_params['policy']],
               ["signature", upload_params['signature']]]

  # Setup the request,
  request = Net::HTTP::Post.new(uri.request_uri)
  params = []

  # Setup the normal form params,
  form_data.each do |k, v|
    params << "Content-Disposition: form-data; name=\"#{k}\"\r\n\r\n#{v}\r\n"
  end

  # setup the file param,
  File.open(filepath) do |file|
    params << "Content-Disposition: form-data; name=\"file\"; filename=\"#{ File.basename(filepath) }\"\r\n" +
              "Content-Type: #{ content_type }\r\n\r\n#{ file.read }\r\n"
  end

  # setup the request,
  request.content_type = "multipart/form-data; boundary=#{ boundary }"
  request.body = params.collect {|p| "--" + boundary + "\r\n" + p }.join("")  + "--" + boundary + "--"

  # and send it.
  stdout.puts "Uploading your file to S3...." if self.respond_to?(:verbose) && verbose?
  case response = http.request(request)
    when Net::HTTPSuccess
      "#{upload_params['url']}/#{upload_params['key'].gsub('${filename}', File.basename(filepath))}"
    else
      abort "unable to upload file: #{response.code} - #{response.message}"
  end
rescue Exception => e
  abort "error during file upload: #{e.message}"
end