rdf-sesame 0.3.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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