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