iq_triplestorage 0.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.
data/.travis.yml ADDED
@@ -0,0 +1,16 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.3
5
+ - jruby-18mode
6
+ - jruby-19mode
7
+
8
+ script: bundle exec rake test
9
+
10
+ branches:
11
+ only:
12
+ - master
13
+
14
+ notifications:
15
+ recipients:
16
+ - iqvoc@innoq.com
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source :rubygems
2
+
3
+ gem "rake"
4
+ gem "typhoeus"
5
+
6
+ group :test do
7
+ gem "minitest"
8
+ gem "webmock"
9
+ end
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # IqTriplestorage - library for interacting with RDF triplestores / quadstores [![build status](https://secure.travis-ci.org/innoq/iq_triplestorage.png)](http://travis-ci.org/innoq/iq_triplestorage)
2
+
3
+ see tests for usage examples
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "rake/testtask"
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.pattern = "test/*_test.rb"
5
+ end
6
+
7
+ desc "Run tests"
8
+ task :default => :test
@@ -0,0 +1,17 @@
1
+ require File.expand_path("../lib/iq_triplestorage", __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "iq_triplestorage"
5
+ s.version = IqTriplestorage::VERSION
6
+ s.platform = Gem::Platform::RUBY
7
+
8
+ s.summary = "IqTriplestorage - library for interacting with RDF triplestores / quadstores"
9
+ s.homepage = "http://github.com/innoq/iq_triplestorage"
10
+ s.rubyforge_project = s.name
11
+ s.authors = ["FND"]
12
+
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
16
+ s.require_paths = ["lib"]
17
+ end
@@ -0,0 +1,3 @@
1
+ module IqTriplestorage
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,105 @@
1
+ require 'net/http'
2
+ require 'base64'
3
+ require 'typhoeus'
4
+
5
+ module IqTriplestorage
6
+ class VirtuosoAdaptor
7
+
8
+ def initialize(host, port, username, password)
9
+ @host = host
10
+ @port = port
11
+ @username = username
12
+ @password = password
13
+ end
14
+
15
+ def reset(uri)
16
+ return sparql_pull("CLEAR GRAPH <#{uri}>") # XXX: s/CLEAR/DROP/ was rejected (405)
17
+ end
18
+
19
+ # expects a hash of N-Triples by graph URI
20
+ def batch_update(triples_by_graph)
21
+ # apparently Virtuoso gets confused when mixing CLEAR and INSERT queries,
22
+ # so we have to do use separate requests
23
+
24
+ reset_queries = triples_by_graph.keys.map do |graph_uri|
25
+ "CLEAR GRAPH <#{graph_uri}>" # XXX: duplicates `reset`
26
+ end
27
+ success = sparql_query(reset_queries)
28
+ return false unless success
29
+
30
+ insert_queries = triples_by_graph.map do |graph_uri, ntriples|
31
+ "INSERT IN GRAPH <#{graph_uri}> {\n#{ntriples}\n}"
32
+ end
33
+ success = sparql_query(insert_queries)
34
+
35
+ return success
36
+ end
37
+
38
+ # uses push method if `rdf_data` is provided, pull otherwise
39
+ def update(uri, rdf_data=nil, content_type=nil)
40
+ reset(uri)
41
+
42
+ if rdf_data
43
+ res = sparql_push(uri, rdf_data.strip, content_type)
44
+ else
45
+ res = sparql_pull(%{LOAD "#{uri}" INTO GRAPH <#{uri}>})
46
+ end
47
+
48
+ return res
49
+ end
50
+
51
+ def sparql_push(uri, rdf_data, content_type)
52
+ raise TypeError, "missing content type" unless content_type
53
+
54
+ filename = uri.gsub(/[^0-9A-Za-z]/, "_") # XXX: too simplistic?
55
+ path = "/DAV/home/#{@username}/rdf_sink/#{filename}"
56
+
57
+ auth = Base64.encode64([@username, @password].join(":")).strip
58
+ headers = {
59
+ "Authorization" => "Basic #{auth}", # XXX: seems like this should be built into Typhoeus!?
60
+ "Content-Type" => content_type
61
+ }
62
+ res = Typhoeus::Request.put("#{@host}:#{@port}#{path}",
63
+ :headers => headers, :body => rdf_data)
64
+
65
+ return res.code == 201
66
+ end
67
+
68
+ def sparql_pull(query)
69
+ path = "/DAV/home/#{@username}/rdf_sink" # XXX: shouldn't this be /sparql?
70
+ res = http_request("POST", path, query, {
71
+ "Content-Type" => "application/sparql-query"
72
+ })
73
+ return res.code == "200" # XXX: always returns 409
74
+ end
75
+
76
+ # query is a string or an array of strings
77
+ def sparql_query(query)
78
+ query = query.join("\n\n") + "\n" rescue query
79
+
80
+ path = "/DAV/home/#{@username}/query"
81
+
82
+ auth = Base64.encode64([@username, @password].join(":")).strip
83
+ headers = {
84
+ "Authorization" => "Basic #{auth}", # XXX: seems like this should be built into Typhoeus!?
85
+ "Content-Type" => "application/sparql-query"
86
+ }
87
+ res = Typhoeus::Request.put("#{@host}:#{@port}#{path}",
88
+ :headers => headers, :body => query)
89
+
90
+ return res.code == 201
91
+ end
92
+
93
+ def http_request(method, path, body, headers={}) # TODO: switch to Typhoeus
94
+ uri = URI.parse("#{@host}:#{@port}#{path}")
95
+
96
+ req = Net::HTTP.const_get(method.downcase.capitalize).new(uri.to_s)
97
+ req.basic_auth(@username, @password)
98
+ headers.each { |key, value| req[key] = value }
99
+ req.body = body
100
+
101
+ return Net::HTTP.new(uri.host, uri.port).request(req)
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,138 @@
1
+ require 'base64'
2
+ require "minitest/autorun"
3
+ require 'webmock/test_unit'
4
+
5
+ require "iq_triplestorage/virtuoso_adaptor"
6
+
7
+ class VirtuosoTest < MiniTest::Unit::TestCase
8
+
9
+ def setup
10
+ # HTTP request mocking
11
+ @observers = [] # one per request
12
+ WebMock.stub_request(:any, /.*example.org.*/).with do |req|
13
+ # not using WebMock's custom assertions as those didn't seem to provide
14
+ # sufficient flexibility
15
+ fn = @observers.shift
16
+ raise(TypeError, "missing request observer") unless fn
17
+ fn.call(req)
18
+ true
19
+ end.to_return do |req|
20
+ { :status => req.uri.to_s.end_with?("/rdf_sink") ? 200 : 201 }
21
+ end
22
+
23
+ @username = "foo"
24
+ @password = "bar"
25
+ @host = "example.org"
26
+ @port = 80
27
+ @adaptor = IqTriplestorage::VirtuosoAdaptor.new("http://#{@host}", @port,
28
+ @username, @password)
29
+ end
30
+
31
+ def teardown
32
+ WebMock.reset!
33
+ raise(TypeError, "unhandled request observer") unless @observers.length == 0
34
+ end
35
+
36
+ def test_reset
37
+ uri = "http://example.com/foo"
38
+
39
+ @observers << lambda do |req|
40
+ ensure_basics(req)
41
+ assert_equal :post, req.method
42
+ assert_equal "/DAV/home/#{@username}/rdf_sink", req.uri.path
43
+ assert_equal "application/sparql-query", req.headers["Content-Type"]
44
+ assert_equal "CLEAR GRAPH <#{uri}>", req.body
45
+ end
46
+ assert @adaptor.reset(uri)
47
+ end
48
+
49
+ def test_pull
50
+ uri = "http://example.com/bar"
51
+
52
+ @observers << lambda do |req|
53
+ assert_equal "CLEAR GRAPH <#{uri}>", req.body
54
+ end
55
+ @observers << lambda do |req|
56
+ assert_equal :post, req.method
57
+ assert_equal "/DAV/home/#{@username}/rdf_sink", req.uri.path
58
+ assert_equal "application/sparql-query", req.headers["Content-Type"]
59
+ assert_equal %(LOAD "#{uri}" INTO GRAPH <#{uri}>), req.body
60
+ end
61
+ assert @adaptor.update(uri)
62
+ end
63
+
64
+ def test_push
65
+ uri = "http://example.com/baz"
66
+
67
+ rdf_data = <<-EOS.strip
68
+ <?xml version="1.0" encoding="UTF-8"?>
69
+ <rdf:RDF xmlns="http://try.iqvoc.net/"
70
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
71
+ xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
72
+ xmlns:owl="http://www.w3.org/2002/07/owl#"
73
+ xmlns:skos="http://www.w3.org/2004/02/skos/core#"
74
+ xmlns:dct="http://purl.org/dc/terms/"
75
+ xmlns:coll="http://try.iqvoc.net/collections/"
76
+ xmlns:schema="http://try.iqvoc.net/schema#">
77
+ <rdf:Description rdf:about="http://try.iqvoc.net/model_building">
78
+ <rdf:type rdf:resource="http://www.w3.org/2004/02/skos/core#Concept"/>
79
+ <skos:prefLabel xml:lang="en">Model building</skos:prefLabel>
80
+ <skos:narrower rdf:resource="http://try.iqvoc.net/model_rocketry"/>
81
+ <skos:narrower rdf:resource="http://try.iqvoc.net/radio-controlled_modeling"/>
82
+ <skos:narrower rdf:resource="http://try.iqvoc.net/scale_modeling"/>
83
+ <skos:broader rdf:resource="http://try.iqvoc.net/achievement_hobbies"/>
84
+ </rdf:Description>
85
+ </rdf:RDF>
86
+ EOS
87
+
88
+ @observers << lambda do |req|
89
+ assert_equal "CLEAR GRAPH <#{uri}>", req.body
90
+ end
91
+ @observers << lambda do |req|
92
+ assert_equal :put, req.method
93
+ assert req.uri.path.start_with?("/DAV/home/#{@username}/rdf_sink/")
94
+ assert_equal "application/rdf+xml", req.headers["Content-Type"]
95
+ assert_equal rdf_data, req.body
96
+ end
97
+ assert @adaptor.update(uri, rdf_data, "application/rdf+xml")
98
+ end
99
+
100
+ def test_batch
101
+ data = {
102
+ "http://example.com/foo" => "<aaa> <bbb> <ccc> .\n<ddd> <eee> <fff> .",
103
+ "http://example.com/bar" => "<ggg> <hhh> <iii> .\n<jjj> <kkk> <lll> ."
104
+ }
105
+
106
+ @observers << lambda do |req|
107
+ data.keys.each do |graph_uri|
108
+ assert req.body.include?("CLEAR GRAPH <#{graph_uri}>")
109
+ end
110
+ end
111
+ @observers << lambda do |req|
112
+ data.each do |graph_uri, ntriples|
113
+ assert req.body.
114
+ include?(<<-EOS)
115
+ INSERT IN GRAPH <#{graph_uri}> {
116
+ #{ntriples}
117
+ }
118
+ EOS
119
+ end
120
+ end
121
+ assert @adaptor.batch_update(data)
122
+ end
123
+
124
+ def ensure_basics(req) # TODO: rename
125
+ assert_equal "#{@host}:#{@port}", "#{req.uri.hostname}:#{req.uri.port}"
126
+
127
+ if auth_header = req.headers["Authorization"]
128
+ auth = Base64.encode64([@username, @password].join(":")).strip
129
+ assert_equal auth, auth_header
130
+ else
131
+ # MockWeb appears to prevent the Authorization header being set, instead
132
+ # retaining username and password in URI
133
+ assert req.uri.to_s.
134
+ start_with?("#{req.uri.scheme}://#{@username}:#{@password}@")
135
+ end
136
+ end
137
+
138
+ end
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: iq_triplestorage
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - FND
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-23 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description:
15
+ email:
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - .travis.yml
21
+ - Gemfile
22
+ - README.md
23
+ - Rakefile
24
+ - iq_triplestorage.gemspec
25
+ - lib/iq_triplestorage.rb
26
+ - lib/iq_triplestorage/virtuoso_adaptor.rb
27
+ - test/virtuoso_test.rb
28
+ homepage: http://github.com/innoq/iq_triplestorage
29
+ licenses: []
30
+ post_install_message:
31
+ rdoc_options: []
32
+ require_paths:
33
+ - lib
34
+ required_ruby_version: !ruby/object:Gem::Requirement
35
+ none: false
36
+ requirements:
37
+ - - ! '>='
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements: []
47
+ rubyforge_project: iq_triplestorage
48
+ rubygems_version: 1.8.23
49
+ signing_key:
50
+ specification_version: 3
51
+ summary: IqTriplestorage - library for interacting with RDF triplestores / quadstores
52
+ test_files:
53
+ - test/virtuoso_test.rb