iq_triplestorage 0.1.0

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