vagas-orientdb4r 0.5.2
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.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +26 -0
- data/.travis.yml +18 -0
- data/Gemfile +9 -0
- data/LICENSE +202 -0
- data/README.rdoc +124 -0
- data/Rakefile +43 -0
- data/changelog.txt +60 -0
- data/ci/initialize-ci.sh +36 -0
- data/fstudy/design_v1.dia +0 -0
- data/fstudy/design_v1.png +0 -0
- data/fstudy/domain_model.dia +0 -0
- data/fstudy/domain_model.png +0 -0
- data/fstudy/flat_class_perf.rb +56 -0
- data/fstudy/sample1_object_diagram.dia +0 -0
- data/fstudy/sample1_object_diagram.png +0 -0
- data/fstudy/study_case.rb +87 -0
- data/fstudy/technical_feasibility.rb +256 -0
- data/lib/orientdb4r.rb +115 -0
- data/lib/orientdb4r/bin/client.rb +86 -0
- data/lib/orientdb4r/bin/connection.rb +29 -0
- data/lib/orientdb4r/bin/constants.rb +20 -0
- data/lib/orientdb4r/bin/io.rb +38 -0
- data/lib/orientdb4r/bin/protocol28.rb +101 -0
- data/lib/orientdb4r/bin/protocol_factory.rb +25 -0
- data/lib/orientdb4r/chained_error.rb +37 -0
- data/lib/orientdb4r/client.rb +364 -0
- data/lib/orientdb4r/load_balancing.rb +113 -0
- data/lib/orientdb4r/node.rb +42 -0
- data/lib/orientdb4r/rest/client.rb +517 -0
- data/lib/orientdb4r/rest/excon_node.rb +115 -0
- data/lib/orientdb4r/rest/model.rb +159 -0
- data/lib/orientdb4r/rest/node.rb +43 -0
- data/lib/orientdb4r/rest/restclient_node.rb +77 -0
- data/lib/orientdb4r/rid.rb +54 -0
- data/lib/orientdb4r/utils.rb +203 -0
- data/lib/orientdb4r/version.rb +39 -0
- data/orientdb4r.gemspec +37 -0
- data/test/bin/test_client.rb +21 -0
- data/test/readme_sample.rb +38 -0
- data/test/test_client.rb +93 -0
- data/test/test_database.rb +261 -0
- data/test/test_ddo.rb +237 -0
- data/test/test_dmo.rb +115 -0
- data/test/test_document_crud.rb +184 -0
- data/test/test_gremlin.rb +52 -0
- data/test/test_helper.rb +10 -0
- data/test/test_loadbalancing.rb +81 -0
- data/test/test_utils.rb +67 -0
- metadata +136 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
require 'excon'
|
|
2
|
+
|
|
3
|
+
module Orientdb4r
|
|
4
|
+
|
|
5
|
+
###
|
|
6
|
+
# This class represents a single sever/node in the Distributed Multi-Master Architecture
|
|
7
|
+
# accessible view REST API and 'excon' library on the client side.
|
|
8
|
+
class ExconNode < RestNode
|
|
9
|
+
|
|
10
|
+
attr_accessor :proxy
|
|
11
|
+
|
|
12
|
+
def request(options) #:nodoc:
|
|
13
|
+
verify_options(options, {:user => :mandatory, :password => :mandatory, \
|
|
14
|
+
:uri => :mandatory, :method => :mandatory, :content_type => :optional, :data => :optional,
|
|
15
|
+
:no_session => :optional})
|
|
16
|
+
|
|
17
|
+
opts = options.clone # if not cloned we change original hash map that cannot be used more with load balancing
|
|
18
|
+
|
|
19
|
+
# Auth + Cookie + Content-Type
|
|
20
|
+
opts[:headers] = headers(opts)
|
|
21
|
+
opts.delete :user
|
|
22
|
+
opts.delete :password
|
|
23
|
+
use_session = !opts.delete(:no_session)
|
|
24
|
+
|
|
25
|
+
opts[:body] = opts[:data] if opts.include? :data # just other naming convention
|
|
26
|
+
opts.delete :data
|
|
27
|
+
opts[:path] = opts[:uri] if opts.include? :uri # just other naming convention
|
|
28
|
+
opts.delete :uri
|
|
29
|
+
|
|
30
|
+
was_ok = false
|
|
31
|
+
begin
|
|
32
|
+
response = connection.request opts
|
|
33
|
+
was_ok = (2 == (response.status / 100))
|
|
34
|
+
|
|
35
|
+
# store session ID if received to reuse in next request
|
|
36
|
+
cookies = CGI::Cookie::parse(response.headers['Set-Cookie'])
|
|
37
|
+
sessid = cookies[SESSION_COOKIE_NAME][0]
|
|
38
|
+
if session_id != sessid and use_session
|
|
39
|
+
@session_id = sessid
|
|
40
|
+
Orientdb4r::logger.debug "new session id: #{session_id}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def response.code
|
|
44
|
+
status
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
rescue Excon::Errors::SocketError
|
|
48
|
+
raise NodeError
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# this is workaround for a strange behavior:
|
|
52
|
+
# excon delivered magic response status '1' when previous request was not 20x
|
|
53
|
+
unless was_ok
|
|
54
|
+
connection.reset
|
|
55
|
+
Orientdb4r::logger.debug 'response code not 20x -> connection reset'
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
response
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def post_connect(user, password, http_response) #:nodoc:
|
|
63
|
+
|
|
64
|
+
cookies = CGI::Cookie::parse(http_response.headers['Set-Cookie'])
|
|
65
|
+
@session_id = cookies[SESSION_COOKIE_NAME][0]
|
|
66
|
+
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def cleanup #:nodoc:
|
|
71
|
+
super
|
|
72
|
+
connection.reset
|
|
73
|
+
@connection = nil
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# ---------------------------------------------------------- Assistant Stuff
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
###
|
|
82
|
+
# Gets Excon connection.
|
|
83
|
+
def connection
|
|
84
|
+
return @connection unless @connection.nil?
|
|
85
|
+
|
|
86
|
+
options = {}
|
|
87
|
+
options[:proxy] = proxy unless proxy.nil?
|
|
88
|
+
options[:scheme], options[:host], options[:port]=url.scan(/(.*):\/\/(.*):([0-9]*)/).first
|
|
89
|
+
|
|
90
|
+
@connection ||= Excon::Connection.new(options)
|
|
91
|
+
#:read_timeout => self.class.read_timeout,
|
|
92
|
+
#:write_timeout => self.class.write_timeout,
|
|
93
|
+
#:connect_timeout => self.class.connect_timeout
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
###
|
|
97
|
+
# Get request headers prepared with session ID and Basic Auth.
|
|
98
|
+
def headers(options)
|
|
99
|
+
rslt = {'Authorization' => basic_auth_header(options[:user], options[:password])}
|
|
100
|
+
rslt['Cookie'] = "#{SESSION_COOKIE_NAME}=#{session_id}" if !session_id.nil? and !options[:no_session]
|
|
101
|
+
rslt['Content-Type'] = options[:content_type] if options.include? :content_type
|
|
102
|
+
rslt['User-Agent'] = user_agent unless user_agent.nil?
|
|
103
|
+
rslt
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
###
|
|
107
|
+
# Gets value of the Basic Auth header.
|
|
108
|
+
def basic_auth_header(user, password)
|
|
109
|
+
b64 = Base64.encode64("#{user}:#{password}").delete("\r\n")
|
|
110
|
+
"Basic #{b64}"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
module Orientdb4r
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
###
|
|
5
|
+
# Extends a Hash produced by JSON.parse.
|
|
6
|
+
module HashExtension
|
|
7
|
+
|
|
8
|
+
###
|
|
9
|
+
# Gets an attribute value that has to be presented.
|
|
10
|
+
def get_mandatory_attribute(name)
|
|
11
|
+
key = name.to_s
|
|
12
|
+
raise ::ArgumentError, "unknown attribute, name=#{key}" unless self.include? key
|
|
13
|
+
self[key]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
###
|
|
20
|
+
# This module represents API to OrientDB's class.
|
|
21
|
+
module OClass
|
|
22
|
+
|
|
23
|
+
###
|
|
24
|
+
# Gets name of the class.
|
|
25
|
+
def name
|
|
26
|
+
get_mandatory_attribute :name
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
###
|
|
30
|
+
# Gets properties of the class.
|
|
31
|
+
# Returns nil for a class without properties.
|
|
32
|
+
def properties
|
|
33
|
+
self['properties']
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
###
|
|
37
|
+
# Gets a property with the given name.
|
|
38
|
+
def property(name)
|
|
39
|
+
raise ArgumentError, 'no properties defined on class' if properties.nil?
|
|
40
|
+
props = properties.select { |i| i['name'] == name.to_s }
|
|
41
|
+
raise ::ArgumentError, "unknown property, name=#{name}" if props.empty?
|
|
42
|
+
raise ::ArgumentError, "too many properties found, name=#{name}" if props.size > 1 # just to be sure
|
|
43
|
+
props[0]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
###
|
|
47
|
+
# Gets the super-class.
|
|
48
|
+
def super_class
|
|
49
|
+
get_mandatory_attribute :superClass
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
###
|
|
53
|
+
# Gets clusters of the class.
|
|
54
|
+
def clusters
|
|
55
|
+
get_mandatory_attribute :clusters
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
###
|
|
59
|
+
# Gets the default cluster.
|
|
60
|
+
def default_cluster
|
|
61
|
+
get_mandatory_attribute :defaultCluster
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
###
|
|
65
|
+
# Get flag whether the class is abstract.
|
|
66
|
+
def abstract?
|
|
67
|
+
get_mandatory_attribute :abstract
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
###
|
|
74
|
+
# This module represents API to OrientDB's property.
|
|
75
|
+
module Property
|
|
76
|
+
|
|
77
|
+
###
|
|
78
|
+
# Gets name of the property.
|
|
79
|
+
def name
|
|
80
|
+
get_mandatory_attribute :name
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
###
|
|
84
|
+
# Gets type of the property.
|
|
85
|
+
def type
|
|
86
|
+
get_mandatory_attribute :type
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
###
|
|
90
|
+
# Gets the 'mandatory' flag.
|
|
91
|
+
def mandatory
|
|
92
|
+
get_mandatory_attribute :mandatory
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
###
|
|
96
|
+
# Gets the 'notNull' flag.
|
|
97
|
+
def not_null
|
|
98
|
+
get_mandatory_attribute :notNull
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
###
|
|
102
|
+
# Gets the 'read-only' flag.
|
|
103
|
+
def read_only
|
|
104
|
+
get_mandatory_attribute :readonly
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
###
|
|
108
|
+
# Gets linked class if the property is a link.
|
|
109
|
+
def linked_class
|
|
110
|
+
self['linkedClass']
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
###
|
|
114
|
+
# Gets the minimal allowed value.
|
|
115
|
+
def min
|
|
116
|
+
self['min']
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
###
|
|
120
|
+
# Gets the maximal allowed value.
|
|
121
|
+
def max
|
|
122
|
+
self['max']
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
###
|
|
129
|
+
# This module represents API to document metadata.
|
|
130
|
+
module DocumentMetadata
|
|
131
|
+
|
|
132
|
+
###
|
|
133
|
+
# Gets the document class.
|
|
134
|
+
def doc_class
|
|
135
|
+
self['@class']
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
###
|
|
139
|
+
# Gets the document ID.
|
|
140
|
+
def doc_rid
|
|
141
|
+
@mixedin_rid = Rid.new(self['@rid']) if @mixedin_rid.nil?
|
|
142
|
+
@mixedin_rid
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
###
|
|
146
|
+
# Gets the document version.
|
|
147
|
+
def doc_version
|
|
148
|
+
self['@version']
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
###
|
|
152
|
+
# Gets the document type.
|
|
153
|
+
def doc_type
|
|
154
|
+
self['@type']
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Orientdb4r
|
|
2
|
+
|
|
3
|
+
###
|
|
4
|
+
# This class represents a single sever/node in the Distributed Multi-Master Architecture
|
|
5
|
+
# accessible view REST API.
|
|
6
|
+
class RestNode < Node
|
|
7
|
+
|
|
8
|
+
# Name of cookie that represents a session.
|
|
9
|
+
SESSION_COOKIE_NAME = 'OSESSIONID'
|
|
10
|
+
|
|
11
|
+
attr_reader :ssl
|
|
12
|
+
# HTTP header 'User-Agent'
|
|
13
|
+
attr_accessor :user_agent
|
|
14
|
+
|
|
15
|
+
###
|
|
16
|
+
# Constructor.
|
|
17
|
+
def initialize(host, port, ssl)
|
|
18
|
+
super(host, port)
|
|
19
|
+
raise ArgumentError, 'ssl flag cannot be blank' if blank?(ssl)
|
|
20
|
+
@ssl = ssl
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def url #:nodoc:
|
|
25
|
+
"http#{'s' if ssl}://#{host}:#{port}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ----------------------------------------------------------- RestNode Stuff
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
###
|
|
33
|
+
# Sends a HTTP request to the remote server.
|
|
34
|
+
# Use following if possible:
|
|
35
|
+
# * session_id
|
|
36
|
+
# * Keep-Alive (if possible)
|
|
37
|
+
def request(options)
|
|
38
|
+
raise NotImplementedError, 'this should be overridden by subclass'
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
require 'rest_client'
|
|
2
|
+
|
|
3
|
+
module Orientdb4r
|
|
4
|
+
|
|
5
|
+
###
|
|
6
|
+
# This class represents a single sever/node in the Distributed Multi-Master Architecture
|
|
7
|
+
# accessible via REST API and 'rest-client' library on the client side.
|
|
8
|
+
class RestClientNode < RestNode
|
|
9
|
+
|
|
10
|
+
def request(options) #:nodoc:
|
|
11
|
+
verify_options(options, {:user => :mandatory, :password => :mandatory, \
|
|
12
|
+
:uri => :mandatory, :method => :mandatory, :content_type => :optional, :data => :optional,
|
|
13
|
+
:no_session => :optional})
|
|
14
|
+
|
|
15
|
+
opts = options.clone # if not cloned we change original hash map that cannot be used more with load balancing
|
|
16
|
+
|
|
17
|
+
# URL
|
|
18
|
+
opts[:url] = "#{url}/#{opts[:uri]}"
|
|
19
|
+
opts.delete :uri
|
|
20
|
+
|
|
21
|
+
if content_type = opts.delete(:content_type)
|
|
22
|
+
opts[:headers] ||= {}
|
|
23
|
+
opts[:headers][:content_type] = content_type
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# data
|
|
27
|
+
data = opts.delete :data
|
|
28
|
+
data = '' if data.nil? and :post == opts[:method] # POST has to have data
|
|
29
|
+
opts[:payload] = data unless data.nil?
|
|
30
|
+
|
|
31
|
+
# cookies
|
|
32
|
+
use_session = !opts.delete(:no_session)
|
|
33
|
+
opts[:cookies] = { SESSION_COOKIE_NAME => session_id} if use_session and !session_id.nil?
|
|
34
|
+
# headers
|
|
35
|
+
opts[:headers] = { 'User-Agent' => user_agent } unless user_agent.nil?
|
|
36
|
+
|
|
37
|
+
begin
|
|
38
|
+
response = ::RestClient::Request.new(opts).execute
|
|
39
|
+
|
|
40
|
+
# store session ID if received to reuse in next request
|
|
41
|
+
sessid = response.cookies[SESSION_COOKIE_NAME]
|
|
42
|
+
if sessid and session_id != sessid and use_session
|
|
43
|
+
@session_id = sessid
|
|
44
|
+
Orientdb4r::logger.debug "new session id: #{session_id}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
rescue Errno::ECONNREFUSED
|
|
48
|
+
raise NodeError
|
|
49
|
+
rescue ::RestClient::ServerBrokeConnection
|
|
50
|
+
raise NodeError
|
|
51
|
+
rescue ::RestClient::Exception => e
|
|
52
|
+
response = transform_error2_response(e)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
response
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
###
|
|
62
|
+
# Fakes an error thrown by the library into a response object with methods
|
|
63
|
+
# 'code' and 'body'.
|
|
64
|
+
def transform_error2_response(error)
|
|
65
|
+
response = ["#{error.message}: #{error.http_body}", error.http_code]
|
|
66
|
+
def response.body
|
|
67
|
+
self[0]
|
|
68
|
+
end
|
|
69
|
+
def response.code
|
|
70
|
+
self[1]
|
|
71
|
+
end
|
|
72
|
+
response
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Orientdb4r
|
|
2
|
+
|
|
3
|
+
###
|
|
4
|
+
# This class represents encapsulation of RecordID.
|
|
5
|
+
class Rid
|
|
6
|
+
include Utils
|
|
7
|
+
|
|
8
|
+
# Format validation regexp.
|
|
9
|
+
RID_REGEXP_PATTERN = /^#?\d+:\d+$/
|
|
10
|
+
|
|
11
|
+
attr_reader :cluster_id, :document_id
|
|
12
|
+
|
|
13
|
+
###
|
|
14
|
+
# Constructor.
|
|
15
|
+
def initialize(rid)
|
|
16
|
+
raise ArgumentError, 'RID cannot be blank' if blank? rid
|
|
17
|
+
raise ArgumentError, 'RID is not String' unless rid.is_a? String
|
|
18
|
+
raise ArgumentError, "bad RID format, rid=#{rid}" unless rid =~ RID_REGEXP_PATTERN
|
|
19
|
+
|
|
20
|
+
rid = rid[1..-1] if rid.start_with? '#'
|
|
21
|
+
ids = rid.split ':'
|
|
22
|
+
self.cluster_id = ids[0].to_i
|
|
23
|
+
self.document_id = ids[1].to_i
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
###
|
|
27
|
+
# Setter fo cluster ID.
|
|
28
|
+
def cluster_id=(cid)
|
|
29
|
+
@cluster_id = cid.to_i
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
###
|
|
33
|
+
# Setter fo document ID.
|
|
34
|
+
def document_id=(did)
|
|
35
|
+
@document_id = did.to_i
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def to_s #:nodoc:
|
|
39
|
+
"##{cluster_id}:#{document_id}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
###
|
|
43
|
+
# Gets RID's string representation with no prefix.
|
|
44
|
+
def unprefixed
|
|
45
|
+
"#{cluster_id}:#{document_id}"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def ==(another_rid) #:nodoc:
|
|
49
|
+
self.cluster_id == another_rid.cluster_id and self.document_id == another_rid.document_id
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
end
|