agraph 0.1.0.beta1
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/README.rdoc +137 -0
- data/Rakefile +48 -0
- data/lib/allegro_graph.rb +5 -0
- data/lib/allegro_graph/catalog.rb +39 -0
- data/lib/allegro_graph/federation.rb +72 -0
- data/lib/allegro_graph/proxy/geo.rb +117 -0
- data/lib/allegro_graph/proxy/mapping.rb +34 -0
- data/lib/allegro_graph/proxy/query.rb +37 -0
- data/lib/allegro_graph/proxy/statements.rb +59 -0
- data/lib/allegro_graph/repository.rb +89 -0
- data/lib/allegro_graph/server.rb +68 -0
- data/lib/allegro_graph/session.rb +64 -0
- data/lib/allegro_graph/transport.rb +169 -0
- data/lib/code_smells.reek +3 -0
- data/spec/fake_transport.yml +287 -0
- data/spec/fake_transport_helper.rb +38 -0
- data/spec/integration/basic_spec.rb +364 -0
- data/spec/lib/allegro_graph/catalog_spec.rb +85 -0
- data/spec/lib/allegro_graph/federation_spec.rb +113 -0
- data/spec/lib/allegro_graph/proxy/geo_spec.rb +89 -0
- data/spec/lib/allegro_graph/proxy/mapping_spec.rb +39 -0
- data/spec/lib/allegro_graph/proxy/query_spec.rb +62 -0
- data/spec/lib/allegro_graph/proxy/statements_spec.rb +58 -0
- data/spec/lib/allegro_graph/repository_spec.rb +224 -0
- data/spec/lib/allegro_graph/server_spec.rb +75 -0
- data/spec/lib/allegro_graph/session_spec.rb +76 -0
- data/spec/lib/allegro_graph/transport_spec.rb +116 -0
- data/spec/spec_helper.rb +26 -0
- metadata +92 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
|
2
|
+
module AllegroGraph
|
3
|
+
|
4
|
+
module Proxy
|
5
|
+
|
6
|
+
class Query
|
7
|
+
|
8
|
+
LANGUAGES = [ :sparql, :prolog ].freeze unless defined?(LANGUAGES)
|
9
|
+
|
10
|
+
attr_reader :server
|
11
|
+
attr_reader :resource
|
12
|
+
attr_reader :language
|
13
|
+
|
14
|
+
def initialize(resource)
|
15
|
+
@resource = resource
|
16
|
+
@language = :sparql
|
17
|
+
end
|
18
|
+
|
19
|
+
def path
|
20
|
+
@resource.path
|
21
|
+
end
|
22
|
+
|
23
|
+
def language=(value)
|
24
|
+
raise NotImplementedError, "query langauge [#{value}] is not implemented" unless LANGUAGES.include?(value.to_sym)
|
25
|
+
@language = value.to_sym
|
26
|
+
end
|
27
|
+
|
28
|
+
def perform(query)
|
29
|
+
parameters = { :query => query, :queryLn => @language.to_s }
|
30
|
+
@resource.request :get, self.path, :parameters => parameters, :expected_status_code => 200
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
|
2
|
+
module AllegroGraph
|
3
|
+
|
4
|
+
module Proxy
|
5
|
+
|
6
|
+
class Statements
|
7
|
+
|
8
|
+
attr_reader :resource
|
9
|
+
|
10
|
+
def initialize(resource)
|
11
|
+
@resource = resource
|
12
|
+
end
|
13
|
+
|
14
|
+
def path
|
15
|
+
"#{@resource.path}/statements"
|
16
|
+
end
|
17
|
+
|
18
|
+
def create(subject, predicate, object, context = nil)
|
19
|
+
statement = [ subject, predicate, object ]
|
20
|
+
statement << context if context
|
21
|
+
@resource.request :post, self.path, :body => [ statement ], :expected_status_code => 204
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
def find(options = { })
|
26
|
+
parameters = { }
|
27
|
+
|
28
|
+
{ :subject => :subj, :predicate => :pred, :object => :obj, :context => :context }.each do |option_key, parameter_key|
|
29
|
+
value = options[option_key]
|
30
|
+
parameters.merge! value.is_a?(Array) ?
|
31
|
+
{ :"#{parameter_key}" => value[0], :"#{parameter_key}End" => value[1] } :
|
32
|
+
{ parameter_key => value } if value
|
33
|
+
end
|
34
|
+
|
35
|
+
[ :offset, :limit, :infer ].each do |key|
|
36
|
+
parameters.merge! key => options[key] if options.has_key?(key)
|
37
|
+
end
|
38
|
+
|
39
|
+
parameters = nil if parameters.empty?
|
40
|
+
|
41
|
+
@resource.request :get, self.path, :parameters => parameters, :expected_status_code => 200
|
42
|
+
end
|
43
|
+
|
44
|
+
def delete(options = { })
|
45
|
+
parameters = { }
|
46
|
+
|
47
|
+
{ :subject => :subj, :predicate => :pred, :object => :obj, :context => :context }.each do |option_key, parameter_key|
|
48
|
+
value = options[option_key]
|
49
|
+
parameters.merge! parameter_key => value if value
|
50
|
+
end
|
51
|
+
|
52
|
+
@resource.request :delete, self.path, :parameters => parameters, :expected_status_code => 200
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "server")
|
2
|
+
require File.join(File.dirname(__FILE__), "session")
|
3
|
+
require File.join(File.dirname(__FILE__), "proxy", "statements")
|
4
|
+
require File.join(File.dirname(__FILE__), "proxy", "query")
|
5
|
+
require File.join(File.dirname(__FILE__), "proxy", "geo")
|
6
|
+
require File.join(File.dirname(__FILE__), "proxy", "mapping")
|
7
|
+
|
8
|
+
module AllegroGraph
|
9
|
+
|
10
|
+
class Repository
|
11
|
+
|
12
|
+
attr_reader :server
|
13
|
+
attr_reader :catalog
|
14
|
+
attr_accessor :name
|
15
|
+
|
16
|
+
attr_reader :statements
|
17
|
+
attr_reader :query
|
18
|
+
attr_reader :geo
|
19
|
+
attr_reader :mapping
|
20
|
+
|
21
|
+
def initialize(server_or_catalog, name, options = { })
|
22
|
+
@catalog = server_or_catalog.is_a?(AllegroGraph::Server) ? server_or_catalog.root_catalog : server_or_catalog
|
23
|
+
@server = @catalog.server
|
24
|
+
@name = name
|
25
|
+
@statements = Proxy::Statements.new self
|
26
|
+
@query = Proxy::Query.new self
|
27
|
+
@geo = Proxy::Geo.new self
|
28
|
+
@mapping = Proxy::Mapping.new self
|
29
|
+
end
|
30
|
+
|
31
|
+
def ==(other)
|
32
|
+
other.is_a?(self.class) && self.catalog == other.catalog && self.name == other.name
|
33
|
+
end
|
34
|
+
|
35
|
+
def path
|
36
|
+
"#{@catalog.path}/repositories/#{@name}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def request(http_method, path, options = { })
|
40
|
+
@server.request http_method, path, options
|
41
|
+
end
|
42
|
+
|
43
|
+
def exists?
|
44
|
+
@catalog.repositories.include? self
|
45
|
+
end
|
46
|
+
|
47
|
+
def create!
|
48
|
+
@server.request :put, self.path, :expected_status_code => 204
|
49
|
+
true
|
50
|
+
rescue ExtendedTransport::UnexpectedStatusCodeError => error
|
51
|
+
return false if error.status_code == 400
|
52
|
+
raise error
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_if_missing!
|
56
|
+
create! unless exists?
|
57
|
+
end
|
58
|
+
|
59
|
+
def delete!
|
60
|
+
@server.request :delete, self.path, :expected_status_code => 200
|
61
|
+
true
|
62
|
+
rescue ExtendedTransport::UnexpectedStatusCodeError => error
|
63
|
+
return false if error.status_code == 400
|
64
|
+
raise error
|
65
|
+
end
|
66
|
+
|
67
|
+
def delete_if_exists!
|
68
|
+
delete! if exists?
|
69
|
+
end
|
70
|
+
|
71
|
+
def size
|
72
|
+
response = @server.request :get, self.path + "/size", :type => :text, :expected_status_code => 200
|
73
|
+
response.to_i
|
74
|
+
end
|
75
|
+
|
76
|
+
def transaction(&block)
|
77
|
+
session = Session.create self
|
78
|
+
begin
|
79
|
+
session.instance_eval &block
|
80
|
+
rescue Object => error
|
81
|
+
session.rollback
|
82
|
+
raise error
|
83
|
+
end
|
84
|
+
session.commit
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "transport")
|
2
|
+
require File.join(File.dirname(__FILE__), "catalog")
|
3
|
+
require File.join(File.dirname(__FILE__), "federation")
|
4
|
+
|
5
|
+
module AllegroGraph
|
6
|
+
|
7
|
+
# The Server class provides methods to retrieve informations about a AllegroGraph server.
|
8
|
+
class Server
|
9
|
+
|
10
|
+
attr_reader :host
|
11
|
+
attr_reader :port
|
12
|
+
attr_reader :username
|
13
|
+
attr_reader :password
|
14
|
+
attr_reader :root_catalog
|
15
|
+
|
16
|
+
def initialize(options = { })
|
17
|
+
@host = options[:host] || "localhost"
|
18
|
+
@port = options[:port] || "10035"
|
19
|
+
@username = options[:username]
|
20
|
+
@password = options[:password]
|
21
|
+
|
22
|
+
@root_catalog = Catalog.new self, "root", :root => true
|
23
|
+
end
|
24
|
+
|
25
|
+
def ==(other)
|
26
|
+
other.is_a?(self.class) && @host == other.host && @port == other.port
|
27
|
+
end
|
28
|
+
|
29
|
+
def request(http_method, path, options = { })
|
30
|
+
ExtendedTransport.request http_method, self.url + path, credentials.merge(options)
|
31
|
+
end
|
32
|
+
|
33
|
+
def version
|
34
|
+
{
|
35
|
+
:version => self.request(:get, "/version", :expected_status_code => 200),
|
36
|
+
:date => self.request(:get, "/version/date", :expected_status_code => 200),
|
37
|
+
:revision => self.request(:get, "/version/revision", :expected_status_code => 200)
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def catalogs
|
42
|
+
result = [ @root_catalog ]
|
43
|
+
catalogs = self.request :get, "/catalogs", :expected_status_code => 200
|
44
|
+
catalogs.each do |catalog|
|
45
|
+
id = catalog["id"]
|
46
|
+
result << Catalog.new(self, id.sub(/^\//, "")) unless id == "/"
|
47
|
+
end
|
48
|
+
result
|
49
|
+
end
|
50
|
+
|
51
|
+
def federations
|
52
|
+
federations = self.request :get, "/federated", :expected_status_code => 200
|
53
|
+
federations.map { |federation| Federation.new self, federation["id"] }
|
54
|
+
end
|
55
|
+
|
56
|
+
def url
|
57
|
+
"http://#{@host}:#{@port}"
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def credentials
|
63
|
+
{ :auth_type => :basic, :username => @username, :password => @password }
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "transport")
|
2
|
+
require File.join(File.dirname(__FILE__), "proxy", "statements")
|
3
|
+
require File.join(File.dirname(__FILE__), "proxy", "query")
|
4
|
+
require File.join(File.dirname(__FILE__), "proxy", "geo")
|
5
|
+
require File.join(File.dirname(__FILE__), "proxy", "mapping")
|
6
|
+
|
7
|
+
module AllegroGraph
|
8
|
+
|
9
|
+
class Session
|
10
|
+
|
11
|
+
attr_reader :url
|
12
|
+
attr_reader :username
|
13
|
+
attr_reader :password
|
14
|
+
|
15
|
+
attr_reader :statements
|
16
|
+
attr_reader :query
|
17
|
+
attr_reader :geo
|
18
|
+
attr_reader :mapping
|
19
|
+
|
20
|
+
def initialize(options = { })
|
21
|
+
@url = options[:url]
|
22
|
+
@username = options[:username]
|
23
|
+
@password = options[:password]
|
24
|
+
|
25
|
+
@statements = Proxy::Statements.new self
|
26
|
+
@query = Proxy::Query.new self
|
27
|
+
@geo = Proxy::Geo.new self
|
28
|
+
@mapping = Proxy::Mapping.new self
|
29
|
+
end
|
30
|
+
|
31
|
+
def path
|
32
|
+
""
|
33
|
+
end
|
34
|
+
|
35
|
+
def request(http_method, path, options = { })
|
36
|
+
ExtendedTransport.request http_method, self.url + path, credentials.merge(options)
|
37
|
+
end
|
38
|
+
|
39
|
+
def commit
|
40
|
+
self.request :post, "/commit", :expected_status_code => 204
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
def rollback
|
45
|
+
self.request :post, "/rollback", :expected_status_code => 204
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.create(repository)
|
50
|
+
url = repository.request :post, repository.path + "/session", :expected_status_code => 200
|
51
|
+
url.sub! /^"/, ""
|
52
|
+
url.sub! /"$/, ""
|
53
|
+
new :url => url, :username => repository.server.username, :password => repository.server.password
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def credentials
|
59
|
+
{ :auth_type => :basic, :username => @username, :password => @password }
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'cgi'
|
3
|
+
require 'net/http'
|
4
|
+
require 'base64'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module AllegroGraph
|
8
|
+
|
9
|
+
# Common transport layer for http transfers.
|
10
|
+
class Transport
|
11
|
+
|
12
|
+
attr_reader :http_method
|
13
|
+
attr_reader :url
|
14
|
+
attr_reader :options
|
15
|
+
attr_reader :headers
|
16
|
+
attr_reader :parameters
|
17
|
+
attr_reader :body
|
18
|
+
attr_reader :response
|
19
|
+
|
20
|
+
def initialize(http_method, url, options = { })
|
21
|
+
@http_method = http_method
|
22
|
+
@uri = URI.parse url
|
23
|
+
@headers = options[:headers] || { }
|
24
|
+
@parameters = options[:parameters] || { }
|
25
|
+
@body = options[:body]
|
26
|
+
end
|
27
|
+
|
28
|
+
def perform
|
29
|
+
initialize_request_class
|
30
|
+
initialize_request_path
|
31
|
+
initialize_request
|
32
|
+
initialize_request_body
|
33
|
+
perform_request
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def initialize_request_class
|
39
|
+
request_class_name = @http_method.capitalize
|
40
|
+
raise NotImplementedError, "the request method #{http_method} is not implemented" unless Net::HTTP.const_defined?(request_class_name)
|
41
|
+
@request_class = Net::HTTP.const_get request_class_name
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize_request_path
|
45
|
+
serialize_parameters
|
46
|
+
@request_path = @uri.path + @serialized_parameters
|
47
|
+
end
|
48
|
+
|
49
|
+
def serialize_parameters
|
50
|
+
quote_parameters
|
51
|
+
@serialized_parameters = if @parameters.nil? || @parameters.empty?
|
52
|
+
""
|
53
|
+
else
|
54
|
+
"?" + @quoted_parameters.collect do |key, value|
|
55
|
+
value.is_a?(Array) ?
|
56
|
+
value.map{ |element| "#{key}=#{element}" }.join("&") :
|
57
|
+
"#{key}=#{value}"
|
58
|
+
end.join("&")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def quote_parameters
|
63
|
+
@quoted_parameters = { }
|
64
|
+
@parameters.each do |key, value|
|
65
|
+
if value.is_a?(Array)
|
66
|
+
@quoted_parameters[ CGI.escape("#{key}") ] = value.map{ |element| CGI.escape element }
|
67
|
+
else
|
68
|
+
@quoted_parameters[ CGI.escape("#{key}") ] = CGI.escape value
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def initialize_request
|
74
|
+
@request = @request_class.new @request_path, @headers
|
75
|
+
end
|
76
|
+
|
77
|
+
def initialize_request_body
|
78
|
+
return unless [ :post, :put ].include?(@http_method.to_sym)
|
79
|
+
@request.body = @body ? @body : @serialized_parameters.sub(/^\?/, "")
|
80
|
+
end
|
81
|
+
|
82
|
+
def perform_request
|
83
|
+
@response = Net::HTTP.start(@uri.host, @uri.port) do |connection|
|
84
|
+
connection.request @request
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.request(http_method, url, options = { })
|
89
|
+
transport = new http_method, url, options
|
90
|
+
transport.perform
|
91
|
+
transport.response
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
class ExtendedTransport < Transport
|
97
|
+
|
98
|
+
# The UnexpectedStatusCodeError is raised if the :expected_status_code option is given to
|
99
|
+
# the :request method and the responded status code is different from the expected one.
|
100
|
+
class UnexpectedStatusCodeError < StandardError
|
101
|
+
|
102
|
+
attr_reader :status_code
|
103
|
+
attr_reader :message
|
104
|
+
|
105
|
+
def initialize(status_code, message)
|
106
|
+
@status_code, @message = status_code, message
|
107
|
+
end
|
108
|
+
|
109
|
+
def to_s
|
110
|
+
"#{super} received status code #{self.status_code}" + (@message ? " [#{@message}]" : "")
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
attr_reader :expected_status_code
|
116
|
+
attr_reader :auth_type
|
117
|
+
attr_reader :username
|
118
|
+
attr_reader :password
|
119
|
+
|
120
|
+
def initialize(http_method, url, options = { })
|
121
|
+
super http_method, url, options
|
122
|
+
@expected_status_code = options[:expected_status_code]
|
123
|
+
@auth_type = options[:auth_type]
|
124
|
+
@username = options[:username]
|
125
|
+
@password = options[:password]
|
126
|
+
end
|
127
|
+
|
128
|
+
def perform
|
129
|
+
initialize_headers
|
130
|
+
super
|
131
|
+
check_status_code
|
132
|
+
parse_response
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def initialize_headers
|
138
|
+
if @auth_type == :basic
|
139
|
+
@headers["Authorization"] = "Basic " + Base64.encode64("#{@username}:#{@password}")
|
140
|
+
elsif !@auth_type.nil?
|
141
|
+
raise NotImplementedError, "the given auth_type [#{@auth_type}] is not implemented"
|
142
|
+
end
|
143
|
+
@headers["Accept"] = "application/json"
|
144
|
+
end
|
145
|
+
|
146
|
+
def initialize_request_body
|
147
|
+
super
|
148
|
+
if @body
|
149
|
+
@request.body = @body.to_json
|
150
|
+
@request["Content-Type"] = "application/json"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def check_status_code
|
155
|
+
return unless @expected_status_code
|
156
|
+
response_code = @response.code
|
157
|
+
response_body = @response.body
|
158
|
+
raise UnexpectedStatusCodeError.new(response_code.to_i, response_body) if @expected_status_code.to_s != response_code
|
159
|
+
end
|
160
|
+
|
161
|
+
def parse_response
|
162
|
+
@response = @response.body.nil? ? nil : JSON.parse(@response.body)
|
163
|
+
rescue JSON::ParserError
|
164
|
+
@response = @response.body.to_s
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|