rdf-sesame 0.3.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e40256223226ea6aca7ac314a678ebe951af0782
4
+ data.tar.gz: 9f1e93a0eb52901a3881c50fd93f9a03c27ad715
5
+ SHA512:
6
+ metadata.gz: a92457e1ba0067058fa2c23480c66d692adfdba488cabcfb2a5bb4c1025acc8835fe56ee38e56f8b0d47d743656d7bde37f58a398fdfe64a091326c9a7bd1eef
7
+ data.tar.gz: 3fcf0943353f42ef7981ca80416e93c7014a8412123cd9eb2bf8de2028f138055f9aed76488be131e92e8e95d8b40a7bc8ae47ccb4d3807668b1733b0afb6df0
data/README CHANGED
@@ -82,7 +82,8 @@ Author
82
82
  Contributors
83
83
  ------------
84
84
 
85
- Refer to the accompanying {file:CREDITS} file.
85
+ * [Aymeric Brisse](http://github.com/abrisse)
86
+ * [Slava Kravchenko](http://github.com/cordawyn)
86
87
 
87
88
  License
88
89
  -------
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.0
1
+ 1.1.0
@@ -1,4 +1,12 @@
1
1
  require 'rdf'
2
+ require 'rdf/sesame/exceptions'
3
+ require 'rdf/sesame/connection'
4
+ require 'rdf/sesame/repository'
5
+ require 'rdf/sesame/server'
6
+ require 'rdf/sesame/version'
7
+
8
+ require 'pathname'
9
+ require 'addressable/uri'
2
10
 
3
11
  module RDF
4
12
  ##
@@ -13,9 +21,5 @@ module RDF
13
21
  #
14
22
  # @author [Arto Bendiken](http://ar.to/)
15
23
  module Sesame
16
- autoload :Connection, 'rdf/sesame/connection'
17
- autoload :Repository, 'rdf/sesame/repository'
18
- autoload :Server, 'rdf/sesame/server'
19
- autoload :VERSION, 'rdf/sesame/version'
20
24
  end
21
25
  end
@@ -21,13 +21,13 @@ module RDF::Sesame
21
21
  # call {#close} explicitly.
22
22
  #
23
23
  # @example Opening a connection to a Sesame server (1)
24
- # url = RDF::URI("http://localhost:8080/openrdf-sesame")
24
+ # url = URI.parse("http://localhost:8080/openrdf-sesame")
25
25
  # conn = RDF::Sesame::Connection.open(url)
26
26
  # ...
27
27
  # conn.close
28
28
  #
29
29
  # @example Opening a connection to a Sesame server (2)
30
- # url = RDF::URI("http://localhost:8080/openrdf-sesame")
30
+ # url = URI.parse("http://localhost:8080/openrdf-sesame")
31
31
  # RDF::Sesame::Connection.open(url) do |conn|
32
32
  # ...
33
33
  # end
@@ -41,8 +41,17 @@ module RDF::Sesame
41
41
  #
42
42
  # @see http://ruby-doc.org/core/classes/Net/HTTP.html
43
43
  class Connection
44
- # @return [RDF::URI]
45
- attr_reader :url
44
+ # @return [String]
45
+ attr_reader :user
46
+
47
+ # @return [String]
48
+ attr_reader :pass
49
+
50
+ # @return [String]
51
+ attr_reader :proxy_host
52
+
53
+ # @return [Number]
54
+ attr_reader :proxy_port
46
55
 
47
56
  # @return [Hash{Symbol => Object}]
48
57
  attr_reader :options
@@ -67,7 +76,7 @@ module RDF::Sesame
67
76
  self.new(url, options) do |conn|
68
77
  if conn.open(options) && block_given?
69
78
  case block.arity
70
- when 1 then block.call(conn)
79
+ when 1 then yield conn
71
80
  else conn.instance_eval(&block)
72
81
  end
73
82
  else
@@ -83,24 +92,27 @@ module RDF::Sesame
83
92
  # @param [Hash{Symbol => Object}] options
84
93
  # @yield [connection]
85
94
  # @yieldparam [Connection]
86
- def initialize(url, options = {}, &block)
87
- require 'addressable/uri' unless defined?(Addressable)
88
- @url = case url
89
- when Addressable::URI then url
90
- else Addressable::URI.parse(url.to_s)
91
- end
95
+ def initialize(url = nil, options = {}, &block)
96
+ url ||= "http://localhost:8080/openrdf-sesame"
97
+ parsed = URI.parse(url.to_s)
98
+
99
+ @user = options.delete(:user) || parsed.user || nil
100
+ @pass = options.delete(:pass) || parsed.password || nil
92
101
 
93
102
  # Preserve only those URI components that we actually require for
94
103
  # establishing a connection to the HTTP server in question:
95
- @url = RDF::URI.new(to_hash)
104
+ parsed.user = parsed.password = nil
105
+ @url = parsed
96
106
 
107
+ @proxy_host = options.delete(:proxy_host) || nil
108
+ @proxy_port = options.delete(:proxy_port) || nil
97
109
  @headers = options.delete(:headers) || {}
98
110
  @options = options
99
111
  @connected = false
100
112
 
101
113
  if block_given?
102
114
  case block.arity
103
- when 1 then block.call(self)
115
+ when 1 then yield self
104
116
  else instance_eval(&block)
105
117
  end
106
118
  end
@@ -128,7 +140,7 @@ module RDF::Sesame
128
140
  #
129
141
  # @return [Symbol]
130
142
  def scheme
131
- url.scheme.to_s.to_sym
143
+ @url.scheme.to_s.to_sym
132
144
  end
133
145
 
134
146
  ##
@@ -137,7 +149,7 @@ module RDF::Sesame
137
149
  #
138
150
  # @return [Boolean]
139
151
  def userinfo?
140
- !url.userinfo.nil?
152
+ !@url.userinfo.nil?
141
153
  end
142
154
 
143
155
  ##
@@ -145,7 +157,7 @@ module RDF::Sesame
145
157
  #
146
158
  # @return [String] "username:password"
147
159
  def userinfo
148
- url.userinfo
160
+ @url.userinfo
149
161
  end
150
162
 
151
163
  ##
@@ -153,15 +165,7 @@ module RDF::Sesame
153
165
  #
154
166
  # @return [Boolean]
155
167
  def user?
156
- !url.user.nil?
157
- end
158
-
159
- ##
160
- # Returns any user name information for this connection.
161
- #
162
- # @return [String]
163
- def user
164
- url.user
168
+ !user.nil?
165
169
  end
166
170
 
167
171
  ##
@@ -169,15 +173,7 @@ module RDF::Sesame
169
173
  #
170
174
  # @return [Boolean]
171
175
  def password?
172
- !url.password.nil?
173
- end
174
-
175
- ##
176
- # Returns any password information for this connection.
177
- #
178
- # @return [String]
179
- def password
180
- url.password
176
+ !password.nil?
181
177
  end
182
178
 
183
179
  ##
@@ -185,7 +181,7 @@ module RDF::Sesame
185
181
  #
186
182
  # @return [String]
187
183
  def host
188
- url.host.to_s
184
+ @url.host.to_s
189
185
  end
190
186
 
191
187
  alias_method :hostname, :host
@@ -196,7 +192,7 @@ module RDF::Sesame
196
192
  #
197
193
  # @return [Boolean]
198
194
  def port?
199
- !url.port.nil? && url.port != (insecure? ? 80 : 443)
195
+ !@url.port.nil? && @url.port != (insecure? ? 80 : 443)
200
196
  end
201
197
 
202
198
  ##
@@ -204,20 +200,7 @@ module RDF::Sesame
204
200
  #
205
201
  # @return [Integer]
206
202
  def port
207
- url.port
208
- end
209
-
210
- ##
211
- # Returns a `Hash` representation of this connection.
212
- #
213
- # @return [Hash{Symbol => Object}]
214
- def to_hash
215
- {
216
- :scheme => url.scheme,
217
- :userinfo => url.userinfo,
218
- :host => url.host,
219
- :port => url.port,
220
- }
203
+ @url.port
221
204
  end
222
205
 
223
206
  ##
@@ -225,7 +208,7 @@ module RDF::Sesame
225
208
  #
226
209
  # @return [RDF::URI]
227
210
  def to_uri
228
- url
211
+ @url
229
212
  end
230
213
 
231
214
  ##
@@ -233,7 +216,7 @@ module RDF::Sesame
233
216
  #
234
217
  # @return [String]
235
218
  def to_s
236
- url.to_s
219
+ @url.to_s
237
220
  end
238
221
 
239
222
  ##
@@ -252,14 +235,14 @@ module RDF::Sesame
252
235
  # @yieldparam [Connection] connection
253
236
  # @raise [TimeoutError] if the connection could not be opened
254
237
  # @return [Connection]
255
- def open(options = {}, &block)
238
+ def open(options = {})
256
239
  unless connected?
257
240
  # TODO: support persistent connections
258
241
  @connected = true
259
242
  end
260
243
 
261
244
  if block_given?
262
- result = block.call(self)
245
+ result = yield self
263
246
  close
264
247
  result
265
248
  else
@@ -292,11 +275,13 @@ module RDF::Sesame
292
275
  # @yield [response]
293
276
  # @yieldparam [Net::HTTPResponse] response
294
277
  # @return [Net::HTTPResponse]
295
- def get(path, headers = {}, &block)
296
- Net::HTTP.start(host, port) do |http|
297
- response = http.get(path.to_s, @headers.merge(headers))
278
+ def get(path, headers = {})
279
+ Net::HTTP::Proxy(@proxy_host, @proxy_port).start(host, port, :use_ssl => self.secure?) do |http|
280
+ request = Net::HTTP::Get.new(url(path.to_s), @headers.merge(headers))
281
+ request.basic_auth @user, @pass unless @user.nil? || @pass.nil?
282
+ response = http.request(request)
298
283
  if block_given?
299
- block.call(response)
284
+ yield response
300
285
  else
301
286
  response
302
287
  end
@@ -312,11 +297,14 @@ module RDF::Sesame
312
297
  # @yield [response]
313
298
  # @yieldparam [Net::HTTPResponse] response
314
299
  # @return [Net::HTTPResponse]
315
- def post(path, data, headers = {}, &block)
316
- Net::HTTP.start(host, port) do |http|
317
- response = http.post(path.to_s, data.to_s, @headers.merge(headers))
300
+ def post(path, data, headers = {})
301
+ Net::HTTP::Proxy(@proxy_host, @proxy_port).start(host, port, :use_ssl => self.secure?) do |http|
302
+ request = Net::HTTP::Post.new(url(path.to_s), @headers.merge(headers))
303
+ request.body = data.to_s
304
+ request.basic_auth @user, @pass unless @user.nil? || @pass.nil?
305
+ response = http.request(request)
318
306
  if block_given?
319
- block.call(response)
307
+ yield response
320
308
  else
321
309
  response
322
310
  end
@@ -331,8 +319,20 @@ module RDF::Sesame
331
319
  # @yield [response]
332
320
  # @yieldparam [Net::HTTPResponse] response
333
321
  # @return [Net::HTTPResponse]
334
- def put(path, headers = {}, &block)
335
- raise NotImplementedError, "#{self.class}#put" # TODO
322
+ def put(path, data, headers = {})
323
+ Net::HTTP::Proxy(@proxy_host, @proxy_port).start(host, port, :use_ssl => self.secure?) do |http|
324
+ request = Net::HTTP::Put.new(url(path.to_s), @headers.merge(headers))
325
+ request.body = data.to_s
326
+ request.basic_auth @user, @pass unless @user.nil? || @pass.nil?
327
+ response = http.request(request)
328
+ http.request(request) do |response|
329
+ if block_given?
330
+ yield response
331
+ else
332
+ response
333
+ end
334
+ end
335
+ end
336
336
  end
337
337
 
338
338
  ##
@@ -343,15 +343,25 @@ module RDF::Sesame
343
343
  # @yield [response]
344
344
  # @yieldparam [Net::HTTPResponse] response
345
345
  # @return [Net::HTTPResponse]
346
- def delete(path, headers = {}, &block)
347
- Net::HTTP.start(host, port) do |http|
348
- response = http.delete(path.to_s, @headers.merge(headers))
346
+ def delete(path, headers = {})
347
+ Net::HTTP::Proxy(@proxy_host, @proxy_port).start(host, port, :use_ssl => self.secure?) do |http|
348
+ request = Net::HTTP::Delete.new(url(path.to_s), @headers.merge(headers))
349
+ request.basic_auth @user, @pass unless @user.nil? || @pass.nil?
350
+ response = http.request(request)
349
351
  if block_given?
350
- block.call(response)
352
+ yield response
351
353
  else
352
354
  response
353
355
  end
354
356
  end
355
357
  end
358
+
359
+ def url(path)
360
+ if path
361
+ "#{@url}/#{path}"
362
+ else
363
+ @url.to_s
364
+ end
365
+ end
356
366
  end # class Connection
357
367
  end # module RDF::Sesame
@@ -0,0 +1,5 @@
1
+ module RDF::Sesame
2
+ class ClientError < StandardError; end
3
+ class MalformedQuery < ClientError; end
4
+ class ServerError < StandardError; end
5
+ end
@@ -26,10 +26,6 @@ module RDF::Sesame
26
26
  # @see http://www.openrdf.org/doc/sesame2/system/ch08.html
27
27
  # @see http://rdf.rubyforge.org/RDF/Repository.html
28
28
  class Repository < RDF::Repository
29
- # @return [RDF::URI]
30
- attr_reader :url
31
- alias_method :uri, :url
32
-
33
29
  # @return [String]
34
30
  attr_reader :id
35
31
 
@@ -39,11 +35,20 @@ module RDF::Sesame
39
35
  # @return [Server]
40
36
  attr_reader :server
41
37
 
38
+ # @return [String]
39
+ attr_reader :readable
40
+
41
+ # @return [String]
42
+ attr_reader :writeble
43
+
44
+ # @return [String,Array]
45
+ attr_reader :context
46
+
42
47
  ##
43
48
  # Initializes this `Repository` instance.
44
49
  #
45
50
  # @overload initialize(url)
46
- # @param [String, RDF::URI] url
51
+ # @param [String, URI, RDF::URI] url
47
52
  # @yield [repository]
48
53
  # @yieldparam [Repository]
49
54
  #
@@ -56,73 +61,80 @@ module RDF::Sesame
56
61
  # @yieldparam [Repository]
57
62
  #
58
63
  def initialize(url_or_options, &block)
64
+ options = {}
59
65
  case url_or_options
60
- when String
61
- initialize(RDF::URI.new(url_or_options), &block)
62
-
63
- when RDF::URI
64
- require 'pathname' unless defined?(Pathname)
65
- @uri = url_or_options
66
- @server = Server.new(RDF::URI.new({
67
- :scheme => @uri.scheme,
68
- :userinfo => @uri.userinfo,
69
- :host => @uri.host,
70
- :port => @uri.port,
71
- :path => Pathname.new(@uri.path).parent.parent.to_s, # + '../..'
72
- }))
73
- @options = {}
66
+ when String, RDF::URI, URI
67
+ pathname = Pathname.new(url_or_options.to_s)
68
+ @server = Server.new(pathname.parent.parent.to_s)
69
+ @id = pathname.basename.to_s
74
70
 
75
71
  when Hash
76
72
  raise ArgumentError, "missing options[:server]" unless url_or_options.has_key?(:server)
77
73
  raise ArgumentError, "missing options[:id]" unless url_or_options.has_key?(:id)
78
- @options = url_or_options.dup
79
- @server = @options.delete(:server)
80
- @id = @options.delete(:id)
81
- @uri = @options.delete(:uri) || server.url("repositories/#{@id}")
82
- @title = @options.delete(:title)
74
+ options = url_or_options.dup
75
+ @server = options.delete(:server)
76
+ @id = options.delete(:id)
77
+ @readable = options.delete(:readable)
78
+ @writable = options.delete(:writable)
83
79
 
84
80
  else
85
81
  raise ArgumentError, "expected String, RDF::URI or Hash, but got #{url_or_options.inspect}"
86
82
  end
87
83
 
88
- if block_given?
89
- case block.arity
90
- when 1 then block.call(self)
91
- else instance_eval(&block)
92
- end
93
- end
84
+ super(options)
85
+ end
86
+
87
+ #
88
+ # Returns the URL for the given server-relative `path`.
89
+ #
90
+ # @example Getting a Sesame server's URL
91
+ # server.url #=> "http://localhost:8080/openrdf-sesame"
92
+
93
+ # @example Getting a Sesame server's protocol URL
94
+ # server.url(:protocol) #=> "http://localhost:8080/openrdf-sesame/protocol"
95
+ #
96
+ # @param [String, #to_s] path
97
+ # @return [String]
98
+ def url(relative_path = nil)
99
+ self.server.url(path(relative_path))
94
100
  end
95
101
 
96
102
  ##
97
- # Returns the URL for the given repository-relative `path`.
103
+ # Returns the server-relative path for the given repository-relative `path`.
98
104
  #
99
105
  # @param [String, #to_s] path
100
106
  # @param [Hash, RDF::Statement] query
101
- # @return [RDF::URI]
102
- def url(path = nil, query = {})
103
- url = path ? RDF::URI.new("#{@uri}/#{path}") : @uri.dup # FIXME
107
+ # @return [String]
108
+ def path(path = nil, query = {})
109
+ url = RDF::URI.new(path ? "repositories/#{@id}/#{path}" : "repositories/#{@id}")
104
110
  unless query.nil?
105
111
  case query
106
112
  when RDF::Statement
107
113
  writer = RDF::NTriples::Writer.new
108
- query = {
114
+ q = {
109
115
  :subj => writer.format_value(query.subject),
110
116
  :pred => writer.format_value(query.predicate),
111
- :obj => writer.format_value(query.object),
112
- :context => query.has_context? ? writer.format_value(query.context) : 'null',
117
+ :obj => writer.format_value(query.object)
113
118
  }
114
- url.query_values = query
119
+ q.merge!(:context => writer.format_value(query.context)) if query.has_context?
120
+ url.query_values = q
115
121
  when Hash
116
122
  url.query_values = query unless query.empty?
117
123
  end
118
124
  end
119
- return url
125
+ return url.to_s
120
126
  end
121
127
 
122
128
  alias_method :uri, :url
123
129
 
124
130
  ##
131
+ # A mapping of blank node results for this client
125
132
  # @private
133
+ def nodes
134
+ @nodes ||= {}
135
+ end
136
+
137
+ ##
126
138
  # @see RDF::Repository#supports?
127
139
  def supports?(feature)
128
140
  case feature.to_sym
@@ -132,80 +144,94 @@ module RDF::Sesame
132
144
  end
133
145
 
134
146
  ##
135
- # @private
136
147
  # @see RDF::Durable#durable?
137
148
  def durable?
138
149
  true # TODO: would need to query the SYSTEM repository for this information
139
150
  end
140
151
 
141
152
  ##
142
- # @private
143
153
  # @see RDF::Countable#empty?
144
154
  def empty?
145
155
  count.zero?
146
156
  end
147
157
 
148
158
  ##
149
- # @private
150
159
  # @see RDF::Countable#count
151
160
  # @see http://www.openrdf.org/doc/sesame2/system/ch08.html#d0e569
152
161
  def count
153
- server.get(url(:size)) do |response|
154
- case response
155
- when Net::HTTPSuccess
156
- size = response.body
157
- size.to_i rescue 0
158
- else -1 # FIXME: raise error
159
- end
162
+ response = server.get(path(:size, statements_options))
163
+ begin
164
+ size = response.body
165
+ size.to_i
166
+ rescue
167
+ 0
160
168
  end
161
169
  end
162
170
 
163
171
  ##
164
- # @private
165
172
  # @see RDF::Enumerable#has_triple?
166
173
  def has_triple?(triple)
167
174
  has_statement?(RDF::Statement.from(triple))
168
175
  end
169
176
 
170
177
  ##
171
- # @private
172
178
  # @see RDF::Enumerable#has_quad?
173
179
  def has_quad?(quad)
174
180
  has_statement?(RDF::Statement.new(quad[0], quad[1], quad[2], :context => quad[3]))
175
181
  end
176
182
 
177
183
  ##
178
- # @private
179
184
  # @see RDF::Enumerable#has_statement?
180
185
  # @see http://www.openrdf.org/doc/sesame2/system/ch08.html#d0e304
181
186
  def has_statement?(statement)
182
- server.get(url(:statements, statement), 'Accept' => 'text/plain') do |response|
183
- case response
184
- when Net::HTTPSuccess
185
- !response.body.empty?
186
- else false
187
- end
188
- end
187
+ response = server.get(path(:statements, statement), 'Accept' => 'text/plain')
188
+ !response.body.empty?
189
+ end
190
+
191
+ ##
192
+ # Returns `true` if `self` contains the given RDF subject term.
193
+ #
194
+ # @param [RDF::Resource] value
195
+ # @return [Boolean]
196
+ def has_subject?(value)
197
+ !first([value, nil, nil]).nil?
198
+ end
199
+
200
+ ##
201
+ # Returns `true` if `self` contains the given RDF predicate term.
202
+ #
203
+ # @param [RDF::URI] value
204
+ # @return [Boolean]
205
+ def has_predicate?(value)
206
+ !first([nil, value, nil]).nil?
207
+ end
208
+
209
+ ##
210
+ # Returns `true` if `self` contains the given RDF object term.
211
+ #
212
+ # @param [RDF::Term] value
213
+ # @return [Boolean]
214
+ def has_object?(value)
215
+ !first([nil, nil, value]).nil?
189
216
  end
190
217
 
191
218
  ##
192
- # @private
193
219
  # @see RDF::Enumerable#each_statement
194
220
  # @see http://www.openrdf.org/doc/sesame2/system/ch08.html#d0e304
195
- def each_statement(&block)
221
+ def each_statement
196
222
  return enum_statement unless block_given?
197
223
 
198
- [nil, *enum_context].uniq.each do |context|
199
- ctxt = context ? RDF::NTriples.serialize(context) : 'null'
200
- server.get(url(:statements, :context => ctxt), 'Accept' => 'text/plain') do |response|
201
- case response
202
- when Net::HTTPSuccess
203
- reader = RDF::NTriples::Reader.new(response.body)
204
- reader.each_statement do |statement|
205
- statement.context = context
206
- block.call(statement)
207
- end
208
- end
224
+ # Each context will trigger a query that will be parsed with a NTriples::Reader.
225
+ # This is for performance. Otherwise only one query with TriG::Reader will be
226
+ # necessary
227
+
228
+ ['null', *enum_context].uniq.each do |context|
229
+ query = {}
230
+ query.merge!(:context => serialize_context(context)) if context
231
+ response = server.get(path(:statements, query), 'Accept' => 'text/plain')
232
+ RDF::NTriples::Reader.new(response.body).each_statement do |statement|
233
+ statement.context = context
234
+ yield statement
209
235
  end
210
236
  end
211
237
  end
@@ -213,25 +239,120 @@ module RDF::Sesame
213
239
  alias_method :each, :each_statement
214
240
 
215
241
  ##
216
- # @private
217
242
  # @see RDF::Enumerable#each_context
218
- def each_context(&block)
243
+ def each_context
219
244
  return enum_context unless block_given?
220
245
 
221
246
  require 'json' unless defined?(::JSON)
222
- server.get(url(:contexts), Server::ACCEPT_JSON) do |response|
223
- case response
224
- when Net::HTTPSuccess
225
- json = ::JSON.parse(response.body)
226
- json['results']['bindings'].map { |binding| binding['contextID'] }.each do |context_id|
227
- context = case context_id['type'].to_s.to_sym
228
- when :bnode then RDF::Node.new(context_id['value'])
229
- when :uri then RDF::URI.new(context_id['value'])
230
- end
231
- block.call(context) if context
232
- end
233
- end
247
+ response = server.get(path(:contexts), Server::ACCEPT_JSON)
248
+ json = ::JSON.parse(response.body)
249
+ json['results']['bindings'].map { |binding| binding['contextID'] }.each do |context_id|
250
+ context = case context_id['type'].to_s.to_sym
251
+ when :bnode then RDF::Node.new(context_id['value'])
252
+ when :uri then RDF::URI.new(context_id['value'])
253
+ else
254
+ nil
255
+ end
256
+ yield context if context
257
+ end
258
+ end
259
+
260
+ # Run a raw SPARQL query.
261
+ #
262
+ # @overload sparql_query(query) {|solution| ... }
263
+ # @yield solution
264
+ # @yieldparam [RDF::Query::Solution] solution
265
+ # @yieldreturn [void]
266
+ # @return [void]
267
+ #
268
+ # @overload sparql_query(pattern)
269
+ # @return [Enumerator<RDF::Query::Solution>]
270
+ #
271
+ # @param [String] query The query to run.
272
+ # @param [Hash{Symbol => Object}] options
273
+ # The query options (see build_query).
274
+ # @return [void]
275
+ #
276
+ # @see #build_query
277
+ def sparql_query(query, options={}, &block)
278
+ raw_query(query, 'sparql', options, &block)
279
+ end
280
+
281
+ ##
282
+ # Returns all statements of the given query.
283
+ #
284
+ # @private
285
+ # @param [String, #to_s] query
286
+ # @param [String, #to_s] queryLn
287
+ # @return [RDF::Enumerator]
288
+ def raw_query(query, queryLn = 'sparql', options={}, &block)
289
+ options = { infer: true }.merge(options)
290
+
291
+ response = if query =~ /\b(delete|insert)\b/i
292
+ write_query(query, queryLn, options)
293
+ else
294
+ read_query(query, queryLn, options)
295
+ end
296
+ end
297
+
298
+ def read_query(query, queryLn, options)
299
+ if queryLn == 'sparql' and options[:format].nil? and query =~ /\bconstruct\b/i
300
+ options[:format] = Server::ACCEPT_NTRIPLES
301
+ end
302
+
303
+ options[:format] = Server::ACCEPT_JSON unless options[:format]
304
+
305
+ params = Addressable::URI.form_encode({ :query => query, :queryLn => queryLn, :infer => options[:infer] }).gsub("+", "%20").to_s
306
+ url = Addressable::URI.parse(path)
307
+ unless url.normalize.query.nil?
308
+ url.query = [url.query, params].compact.join('&')
309
+ else
310
+ url.query = [url.query, params].compact.join('?')
311
+ end
312
+ response = server.get(url, options[:format])
313
+
314
+ results = parse_response(response)
315
+ if block_given?
316
+ results.each {|s| yield s }
317
+ else
318
+ results
319
+ end
320
+ end
321
+
322
+ def write_query(query, queryLn, options)
323
+ parameters = {}
324
+ parameters[:update] = query
325
+ response = server.post(path(:statements), Addressable::URI.form_encode(parameters), 'Content-Type' => 'application/x-www-form-urlencoded')
326
+ response.code == "204"
327
+ end
328
+
329
+ # Set a global context that will be used for any statements request
330
+ #
331
+ # @param context the context(s) to use
332
+ def set_context(*context)
333
+ options||={}
334
+ @context = serialize_context(context)
335
+ end
336
+
337
+ ##
338
+ # # Clear all statements from the repository.
339
+ # @see RDF::Mutable#clear
340
+ # @see http://www.openrdf.org/doc/sesame2/system/ch08.html#d0e304
341
+ #
342
+ # @param [Hash] options
343
+ # @option options [String] :subject Match a specific subject
344
+ # @option options [String] :predicate Match a specific predicate
345
+ # @option options [String] :object Match a specific object
346
+ # @option options [String] :context Match a specific graph name.
347
+ # @return [void]
348
+ def clear(options={})
349
+ parameters = {}
350
+ { :subject => :subj, :predicate => :pred, :object => :obj, :context => :context }.each do |option_key, parameter_key|
351
+ value = options[option_key]
352
+ parameters.merge! parameter_key => RDF::NTriples.serialize(RDF::URI.new(value)) if value
234
353
  end
354
+ response = server.delete(path(:statements, statements_options.merge(parameters)))
355
+ response.code == "204"
235
356
  end
236
357
 
237
358
  protected
@@ -239,23 +360,39 @@ module RDF::Sesame
239
360
  ##
240
361
  # @private
241
362
  # @see RDF::Queryable#query
242
- def query_pattern(pattern, &block)
243
- super # TODO
363
+ def query_pattern(pattern)
364
+ writer = RDF::NTriples::Writer.new
365
+ query = {}
366
+ query.merge!(:context => writer.format_value(pattern.context)) if pattern.has_context?
367
+ query.merge!(:subj => writer.format_value(pattern.subject)) unless pattern.subject.is_a?(RDF::Query::Variable) || pattern.subject.nil?
368
+ query.merge!(:pred => writer.format_value(pattern.predicate)) unless pattern.predicate.is_a?(RDF::Query::Variable) || pattern.predicate.nil?
369
+ query.merge!(:obj => writer.format_value(pattern.object)) unless pattern.object.is_a?(RDF::Query::Variable) || pattern.object.nil?
370
+ response = server.get(path(:statements, query), Server::ACCEPT_NTRIPLES)
371
+ RDF::NTriples::Reader.new(response.body).each_statement do |statement|
372
+ statement.context = pattern.context
373
+ yield statement
374
+ end
244
375
  end
245
376
 
377
+ #--------------------------------------------------------------------
378
+ # @group RDF::Mutable methods
379
+
246
380
  ##
247
381
  # @private
248
382
  # @see RDF::Mutable#insert
249
383
  # @see http://www.openrdf.org/doc/sesame2/system/ch08.html#d0e304
250
384
  def insert_statement(statement)
251
- ctxt = statement.has_context? ? RDF::NTriples.serialize(statement.context) : 'null'
252
- data = RDF::NTriples.serialize(statement)
253
- server.post(url(:statements, :context => ctxt), data, 'Content-Type' => 'text/plain') do |response|
254
- case response
255
- when Net::HTTPSuccess then true
256
- else false
257
- end
258
- end
385
+ insert_statements([statement])
386
+ end
387
+
388
+ ##
389
+ # @private
390
+ # @see RDF::Mutable#insert
391
+ # @see http://www.openrdf.org/doc/sesame2/system/ch08.html#d0e304
392
+ def insert_statements(statements)
393
+ data = statements_to_text_plain(statements)
394
+ response = server.post(path(:statements, statements_options), data, 'Content-Type' => 'text/plain')
395
+ response.code == "204"
259
396
  end
260
397
 
261
398
  ##
@@ -263,25 +400,165 @@ module RDF::Sesame
263
400
  # @see RDF::Mutable#delete
264
401
  # @see http://www.openrdf.org/doc/sesame2/system/ch08.html#d0e304
265
402
  def delete_statement(statement)
266
- server.delete(url(:statements, statement)) do |response|
267
- case response
268
- when Net::HTTPSuccess then true
269
- else false
270
- end
403
+ response = server.delete(path(:statements, statement))
404
+ response.code == "204"
405
+ end
406
+
407
+ private
408
+
409
+ # Convert a list of statements to a text-plain-compatible text.
410
+ def statements_to_text_plain(statements)
411
+ graph = RDF::Repository.new
412
+ statements.each do |s|
413
+ graph << s
271
414
  end
415
+ RDF::NTriples::Writer.dump(graph, nil, :encoding => Encoding::ASCII)
272
416
  end
273
417
 
274
418
  ##
419
+ # @param [Net::HTTPSuccess] response
420
+ # @param [Hash{Symbol => Object}] options
421
+ # @return [Object]
422
+ def parse_response(response, options = {})
423
+ case content_type = options[:content_type] || response.content_type
424
+ when Server::RESULT_BOOL
425
+ response.body == 'true'
426
+ when Server::RESULT_JSON
427
+ self.class.parse_json_bindings(response.body, nodes)
428
+ when Server::RESULT_XML
429
+ self.class.parse_xml_bindings(response.body, nodes)
430
+ else
431
+ parse_rdf_serialization(response, options)
432
+ end
433
+ end
434
+
435
+ ##
436
+ # @param [String, Hash] json
437
+ # @return [<RDF::Query::Solutions>]
438
+ # @see http://www.w3.org/TR/rdf-sparql-json-res/#results
439
+ def self.parse_json_bindings(json, nodes = {})
440
+ require 'json' unless defined?(::JSON)
441
+ json = JSON.parse(json.to_s) unless json.is_a?(Hash)
442
+
443
+ case
444
+ when json['boolean']
445
+ json['boolean']
446
+ when json['results']
447
+ solutions = RDF::Query::Solutions()
448
+ json['results']['bindings'].each do |row|
449
+ row = row.inject({}) do |cols, (name, value)|
450
+ cols.merge(name.to_sym => parse_json_value(value))
451
+ end
452
+ solutions << RDF::Query::Solution.new(row)
453
+ end
454
+ solutions
455
+ end
456
+ end
457
+
458
+ ##
459
+ # @param [Hash{String => String}] value
460
+ # @return [RDF::Value]
461
+ # @see http://www.w3.org/TR/rdf-sparql-json-res/#variable-binding-results
462
+ def self.parse_json_value(value, nodes = {})
463
+ case value['type'].to_sym
464
+ when :bnode
465
+ nodes[id = value['value']] ||= RDF::Node.new(id)
466
+ when :uri
467
+ RDF::URI.new(value['value'])
468
+ when :literal
469
+ RDF::Literal.new(value['value'], :language => value['xml:lang'])
470
+ when :'typed-literal'
471
+ RDF::Literal.new(value['value'], :datatype => value['datatype'])
472
+ else nil
473
+ end
474
+ end
475
+
476
+ ##
477
+ # @param [String, REXML::Element] xml
478
+ # @return [<RDF::Query::Solutions>]
479
+ # @see http://www.w3.org/TR/rdf-sparql-json-res/#results
480
+ def self.parse_xml_bindings(xml, nodes = {})
481
+ xml.force_encoding(::Encoding::UTF_8) if xml.respond_to?(:force_encoding)
482
+ require 'rexml/document' unless defined?(::REXML::Document)
483
+ xml = REXML::Document.new(xml).root unless xml.is_a?(REXML::Element)
484
+
485
+ case
486
+ when boolean = xml.elements['boolean']
487
+ boolean.text == 'true'
488
+ when results = xml.elements['results']
489
+ solutions = RDF::Query::Solutions()
490
+ results.elements.each do |result|
491
+ row = {}
492
+ result.elements.each do |binding|
493
+ name = binding.attributes['name'].to_sym
494
+ value = binding.select { |node| node.kind_of?(::REXML::Element) }.first
495
+ row[name] = parse_xml_value(value, nodes)
496
+ end
497
+ solutions << RDF::Query::Solution.new(row)
498
+ end
499
+ solutions
500
+ end
501
+ end
502
+
503
+ ##
504
+ # @param [REXML::Element] value
505
+ # @return [RDF::Value]
506
+ # @see http://www.w3.org/TR/rdf-sparql-json-res/#variable-binding-results
507
+ def self.parse_xml_value(value, nodes = {})
508
+ case value.name.to_sym
509
+ when :bnode
510
+ nodes[id = value.text] ||= RDF::Node.new(id)
511
+ when :uri
512
+ RDF::URI.new(value.text)
513
+ when :literal
514
+ RDF::Literal.new(value.text, {
515
+ :language => value.attributes['xml:lang'],
516
+ :datatype => value.attributes['datatype'],
517
+ })
518
+ else nil
519
+ end
520
+ end
521
+
522
+ ##
523
+ # @param [Net::HTTPSuccess] response
524
+ # @param [Hash{Symbol => Object}] options
525
+ # @return [RDF::Enumerable]
526
+ def parse_rdf_serialization(response, options = {})
527
+ options = {:content_type => response.content_type} if options.empty?
528
+ if reader_for = RDF::Reader.for(options)
529
+ reader_for.new(response.body) do |reader|
530
+ reader # FIXME
531
+ end
532
+ end
533
+ end
534
+
275
535
  # @private
276
- # @see RDF::Mutable#clear
277
- # @see http://www.openrdf.org/doc/sesame2/system/ch08.html#d0e304
278
- def clear_statements
279
- server.delete(url(:statements)) do |response|
280
- case response
281
- when Net::HTTPSuccess then true
282
- else false
536
+ #
537
+ # Serialize the context
538
+ def serialize_context(context)
539
+ context = [context] unless context.is_a?(Enumerable)
540
+ serialization = context.map do |c|
541
+ if c == 'null' or !c
542
+ c
543
+ else
544
+ RDF::NTriples.serialize(RDF::URI.new(c))
283
545
  end
284
546
  end
547
+
548
+ if serialization.size == 1
549
+ serialization.first
550
+ else
551
+ serialization
552
+ end
553
+ end
554
+
555
+ # @private
556
+ #
557
+ # Construct the statements options list
558
+ def statements_options
559
+ options = {}
560
+ options[:context] = @context if @context
561
+ options
285
562
  end
286
563
  end # class Repository
287
564
  end # module RDF::Sesame