infinispan-ruby-client 0.0.1

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/Gemfile.lock ADDED
@@ -0,0 +1,27 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.2)
5
+ git (1.2.5)
6
+ jeweler (1.6.2)
7
+ bundler (~> 1.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ rake (0.9.1)
11
+ rspec (2.6.0)
12
+ rspec-core (~> 2.6.0)
13
+ rspec-expectations (~> 2.6.0)
14
+ rspec-mocks (~> 2.6.0)
15
+ rspec-core (2.6.3)
16
+ rspec-expectations (2.6.0)
17
+ diff-lcs (~> 1.1.2)
18
+ rspec-mocks (2.6.0)
19
+
20
+ PLATFORMS
21
+ ruby
22
+
23
+ DEPENDENCIES
24
+ bundler
25
+ jeweler
26
+ rake
27
+ rspec (~> 2.1)
data/README.markdown ADDED
@@ -0,0 +1,75 @@
1
+ # Infinispan Ruby Client
2
+
3
+ [Infinispan](http://www.jboss.org/infinispan) is a scalable, high-availability
4
+ data grid. This is a native Ruby client implementation of Infinispan's [Hotrod
5
+ client/server protocol](http://community.jboss.org/docs/DOC-14421), allowing
6
+ Ruby applications to natively store and retrieve strings and Ruby objects in
7
+ the Infinispan data grid.
8
+
9
+
10
+ ## Introduction
11
+
12
+ This is a [Level-1
13
+ client](http://community.jboss.org/wiki/HotRodProtocol#Client_Intelligence_1_byte),
14
+ meaning that it is a basic client and does not participate in the data grid
15
+ clustering. The client currently only supports using the default cache, and
16
+ all operations of the Hot Rod protocol are implemented except the [server
17
+ stats](http://community.jboss.org/wiki/HotRodProtocol#stats_request)
18
+ request.
19
+
20
+ ## Installation
21
+
22
+ $ gem install infinispan-ruby-client
23
+
24
+ ## Usage
25
+
26
+ Using the infinispan-ruby-client requires that you connect to an Infinispan
27
+ server using the hotrod protocol.
28
+
29
+ 1) First start Infinispan, specifying hotrod for the protocol
30
+
31
+ $INFINISPAN_DIR/bin/startServer.sh -r hotrod
32
+
33
+ 2) Then write code
34
+
35
+ require 'infinispan-ruby-client'
36
+ cache = Infinispan::RemoteCache.new => #<Infinispan::RemoteCache:0x100365a78 @name="", @host="localhost", @port=11222>
37
+
38
+ # Store and retrieve a string
39
+ cache.put("Name", "Lance") => true
40
+ cache.get("Name") => "Lance"
41
+
42
+ # Store and retrieve a ruby object
43
+ cache.put("Time", Time.now) => true
44
+ time = cache.get("Time") => Tue Jun 07 17:45:17 -0400 2011
45
+ time.class => Time
46
+
47
+
48
+ ## Todo
49
+ * Support cache names
50
+ * Add ping operation before any call
51
+ * Validate response headers
52
+ * Get server statistics
53
+ * Support for lifespanSeconds and maxIdleTimeSeconds
54
+ * Support for intelligent clients and topologies, and listeners
55
+ * Configuration files support
56
+ * Transaction support
57
+ * Support for Apache Avro Marshaller ??
58
+
59
+
60
+ ## License
61
+ Copyright 2011 Red Hat, Inc.
62
+
63
+ Licensed under the Apache License, Version 2.0 (the "License");
64
+ you may not use this file except in compliance with the License.
65
+ You may obtain a copy of the License at
66
+
67
+ http://www.apache.org/licenses/LICENSE-2.0
68
+
69
+ Unless required by applicable law or agreed to in writing, software
70
+ distributed under the License is distributed on an "AS IS" BASIS,
71
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
72
+ See the License for the specific language governing permissions and
73
+ limitations under the License.
74
+
75
+
@@ -0,0 +1,67 @@
1
+ #
2
+ # Copyright 2011 Red Hat, Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ module Infinispan
18
+ module Constants
19
+ MAGIC = 0xA0, 0xA1
20
+ HT_VERSION = 10
21
+
22
+ PUT = 0x01, 0x02
23
+ GET = 0x03, 0x04
24
+ PUT_IF_ABSENT = 0x05, 0x06
25
+ REPLACE = 0x07, 0x08
26
+ REPLACE_IF = 0x09, 0x0A
27
+ REMOVE = 0x0B, 0x0C
28
+ REMOVE_IF = 0x0D, 0x0E
29
+ CONTAINS = 0x0F, 0x10
30
+ GET_WITH_VERSION = 0x11, 0x12
31
+ CLEAR = 0x13, 0x14
32
+ STATS = 0x15, 0x16
33
+ PING = 0x17, 0x18
34
+ BULK_GET = 0x19, 0x1A
35
+ ERROR = 0x50
36
+
37
+ end
38
+
39
+ module ResponseCode
40
+
41
+ SUCCESS = 0x00
42
+ NO_ACTION = 0x01
43
+ INVALID_KEY = 0x02
44
+ INVALID_MAGIC = 0x81
45
+ UNKNOWN_COMMAND = 0x82
46
+ UNKNOWN_VERSION = 0x83
47
+ REQUEST_PARSE_ERROR = 0x84
48
+ SERVER_ERROR = 0x85
49
+ SERVER_TIME_OUT = 0x86
50
+
51
+ def self.status_message( response_code )
52
+ case response_code
53
+ when SUCCESS : "Success"
54
+ when NO_ACTION : "No action taken"
55
+ when INVALID_KEY : "Invalid key supplied"
56
+ when INVALID_MAGIC : "Invalid magic or message"
57
+ when UNKNOWN_COMMAND : "Unrecognized command"
58
+ when UNKNOWN_VERSION : "Unrecognized version"
59
+ when REQUEST_PARSE_ERROR : "Request parse error"
60
+ when SERVER_ERROR : "Server error"
61
+ when SERVER_TIME_OUT : "Server timeout"
62
+ else
63
+ "Unrecognized response code: #{response_code.chr}"
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,39 @@
1
+ #
2
+ # Copyright 2011 Red Hat, Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'infinispan-ruby-client/unsigned.rb'
18
+
19
+ class HeaderBuilder
20
+ include Infinispan::Constants
21
+ def self.getHeader( op_code,cache_name )
22
+ result=[MAGIC[0].chr, #Magic
23
+ Unsigned.encodeVint(0x04), #Message Id
24
+ HT_VERSION.chr, # Version
25
+ op_code.chr, # Opcode
26
+ cache_name.size==0?[]:Unsigned.encodeVint(cache_name.size)] # Cache Name Length
27
+
28
+ result<<cache_name unless cache_name.size==0 # # Cache Name
29
+
30
+ tale=[0.chr, # Flags
31
+ 0x01.chr, # Client Intelligence
32
+ 0.chr, # Topology Id
33
+ 0.chr, # Transaction Type
34
+ 0.chr] # Transaction Id
35
+
36
+ tale.each{|e| result<<e}
37
+ result
38
+ end
39
+ end
@@ -0,0 +1,239 @@
1
+ #
2
+ # Copyright 2011 Red Hat, Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'socket'
18
+
19
+ module Infinispan
20
+ class RemoteCache
21
+ include Infinispan::Constants
22
+
23
+ attr_accessor :host, :port, :name
24
+
25
+ def initialize( host="localhost", port=11222, name="" )
26
+ @host = host
27
+ @port = port
28
+ @name = name
29
+ end
30
+
31
+ def ping
32
+ do_op( :operation => PING[0] )
33
+ end
34
+
35
+ def clear
36
+ do_op( :operation => CLEAR[0] )
37
+ end
38
+
39
+ def get( key )
40
+ do_op( :operation => GET[0], :key => key )
41
+ end
42
+
43
+ def get_bulk( count = 0 )
44
+ do_op( :operation => BULK_GET[0], :count => count )
45
+ end
46
+
47
+ def put( key, value )
48
+ do_op( :operation => PUT[0], :key => key, :value => value )
49
+ end
50
+
51
+ def put_if_absent( key, value )
52
+ do_op( :operation => PUT_IF_ABSENT[0], :key => key, :value => value )
53
+ end
54
+
55
+ def get_versioned( key )
56
+ do_op( :operation => GET_WITH_VERSION[0], :key => key )
57
+ end
58
+
59
+ def contains_key?( key )
60
+ do_op( :operation => CONTAINS[0], :key => key )
61
+ end
62
+
63
+ alias_method :contains_key, :contains_key?
64
+
65
+ def remove( key )
66
+ do_op( :operation => REMOVE[0], :key => key )
67
+ end
68
+
69
+ def remove_if_unmodified( key, version )
70
+ do_op( :operation => REMOVE_IF[0], :key => key, :version => version )
71
+ end
72
+
73
+ def replace( key, value )
74
+ do_op( :operation => REPLACE[0], :key => key, :value => value )
75
+ end
76
+
77
+ def replace_if_unmodified( key, version, value )
78
+ do_op( :operation => REPLACE_IF[0], :key => key, :value => value, :version => version )
79
+ end
80
+
81
+ private
82
+ def do_op( options )
83
+ options[:cache] ||= @name
84
+
85
+ send_op = Operation.send[ options[:operation] ]
86
+ recv_op = Operation.receive[ options[:operation] ]
87
+
88
+ if (send_op && recv_op)
89
+ TCPSocket.open( @host, @port ) do |connection|
90
+ send_op.call( connection, options )
91
+ recv_op.call( connection )
92
+ end
93
+ else
94
+ raise "Unexpected operation: #{options[:operation]}"
95
+ end
96
+
97
+ end
98
+ end
99
+
100
+ module Operation
101
+
102
+ include Infinispan::Constants
103
+ include Infinispan::ResponseCode
104
+
105
+ def self.send
106
+ {
107
+ GET[0] => KEY_ONLY_SEND,
108
+ GET_WITH_VERSION[0] => KEY_ONLY_SEND,
109
+ BULK_GET[0] => BULK_GET_SEND,
110
+ PUT[0] => KEY_VALUE_SEND,
111
+ REMOVE[0] => KEY_ONLY_SEND,
112
+ REMOVE_IF[0] => REMOVE_IF_SEND,
113
+ CONTAINS[0] => KEY_ONLY_SEND,
114
+ PUT_IF_ABSENT[0] => KEY_VALUE_SEND,
115
+ REPLACE[0] => KEY_VALUE_SEND,
116
+ REPLACE_IF[0] => REPLACE_IF_SEND,
117
+ CLEAR[0] => HEADER_ONLY_SEND,
118
+ PING[0] => HEADER_ONLY_SEND
119
+ }
120
+ end
121
+
122
+ def self.receive
123
+ {
124
+ GET[0] => KEY_ONLY_RECV,
125
+ GET_WITH_VERSION[0] => GET_WITH_VERSION_RECV,
126
+ BULK_GET[0] => BULK_GET_RECV,
127
+ PUT[0] => BASIC_RECV,
128
+ REMOVE[0] => BASIC_RECV,
129
+ REMOVE_IF[0] => BASIC_RECV,
130
+ CONTAINS[0] => BASIC_RECV,
131
+ PUT_IF_ABSENT[0] => BASIC_RECV,
132
+ REPLACE[0] => BASIC_RECV,
133
+ REPLACE_IF[0] => BASIC_RECV,
134
+ CLEAR[0] => BASIC_RECV,
135
+ PING[0] => BASIC_RECV
136
+ }
137
+ end
138
+
139
+ HEADER_ONLY_SEND = lambda { |connection, options|
140
+ connection.write( HeaderBuilder.getHeader(options[:operation], options[:cache]) )
141
+ }
142
+
143
+ BULK_GET_SEND = lambda { |connection, options|
144
+ connection.write( HeaderBuilder.getHeader(options[:operation], options[:cache]) )
145
+ connection.write( Unsigned.encodeVint( options[:count] ) )
146
+ }
147
+
148
+ KEY_ONLY_SEND = lambda { |connection, options|
149
+ connection.write( HeaderBuilder.getHeader(options[:operation], options[:cache]) )
150
+ mkey = Marshal.dump( options[:key] )
151
+ connection.write( Unsigned.encodeVint( mkey.size ) )
152
+ connection.write( mkey )
153
+ }
154
+
155
+ KEY_VALUE_SEND = lambda { |connection, options|
156
+ connection.write( HeaderBuilder.getHeader(options[:operation], options[:cache]) )
157
+ # write key
158
+ mkey = Marshal.dump( options[:key] )
159
+ connection.write( Unsigned.encodeVint( mkey.size ) )
160
+ connection.write( mkey )
161
+
162
+ # lifespan + max_idle (not supported yet)
163
+ connection.write( [0x00.chr,0x00.chr] )
164
+
165
+ # write value
166
+ mkey = Marshal.dump( options[:value] )
167
+ connection.write( Unsigned.encodeVint( mkey.size ) )
168
+ connection.write( mkey )
169
+ }
170
+
171
+ REMOVE_IF_SEND = lambda { |connection, options|
172
+ connection.write( HeaderBuilder.getHeader(options[:operation], options[:cache]) )
173
+
174
+ # write key
175
+ key = Marshal.dump( options[:key] )
176
+ connection.write( Unsigned.encodeVint( key.size ) )
177
+ connection.write( key )
178
+
179
+ # write version
180
+ connection.write( options[:version] )
181
+ }
182
+
183
+ REPLACE_IF_SEND = lambda { |connection, options|
184
+ connection.write( HeaderBuilder.getHeader(options[:operation], options[:cache]) )
185
+
186
+ # write key
187
+ key = Marshal.dump( options[:key] )
188
+ connection.write( Unsigned.encodeVint( key.size ) )
189
+ connection.write( key )
190
+
191
+ # lifespan + max_idle (not supported yet)
192
+ connection.write( [0x00.chr,0x00.chr] )
193
+
194
+ # write version
195
+ connection.write( options[:version] )
196
+
197
+ # write value
198
+ value = Marshal.dump( options[:value] )
199
+ connection.write( Unsigned.encodeVint( value.size ) )
200
+ connection.write( value )
201
+ }
202
+
203
+ KEY_ONLY_RECV = lambda { |connection|
204
+ connection.read( 5 ) # The response header
205
+ response_body_length = Unsigned.decodeVint( connection )
206
+ response_body = connection.read( response_body_length )
207
+ Marshal.load( response_body )
208
+ }
209
+
210
+ GET_WITH_VERSION_RECV = lambda { |connection|
211
+ response_header = connection.read( 5 ) # The response header
212
+ version = connection.read( 8 )
213
+ response_body_length = Unsigned.decodeVint( connection )
214
+ response_body = connection.read( response_body_length )
215
+ [ version, Marshal.load( response_body ) ]
216
+ }
217
+
218
+ BULK_GET_RECV = lambda { |connection|
219
+ response = {}
220
+ response_header = connection.read( 5 ) # The response header
221
+ more = connection.read(1).unpack('c')[0]
222
+ while (more == 1) # The "more" flag
223
+ key_length = Unsigned.decodeVint( connection )
224
+ key = connection.read( key_length )
225
+ value_length = Unsigned.decodeVint( connection )
226
+ value = connection.read( value_length )
227
+ response[Marshal.load(key)] = Marshal.load(value)
228
+ more = connection.read(1).unpack('c')[0]
229
+ end
230
+ response
231
+ }
232
+
233
+ BASIC_RECV = lambda { |connection|
234
+ header = connection.read( 5 ) # Just the response header
235
+ header[3] == SUCCESS
236
+ }
237
+
238
+ end
239
+ end
@@ -0,0 +1,39 @@
1
+ #
2
+ # Copyright 2011 Red Hat, Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ class Unsigned
18
+ def self.encodeVint(i)
19
+ result=[]
20
+ while ((i & ~0x7f) != 0)
21
+ result[result.size]=((i & 0x7f) | 0x80).chr
22
+ i>>=7
23
+ end
24
+ result[result.size]=i.chr
25
+ result
26
+ end
27
+
28
+ def self.decodeVint(input)
29
+ b = input.read(1)
30
+ i = b[0] & 0x7F
31
+ shift = 7
32
+ while ((b[0] & 0x80) != 0)
33
+ b = input.read(1)
34
+ i |= (b[0] & 0x7F) << shift
35
+ shift += 7
36
+ end
37
+ i
38
+ end
39
+ end
@@ -0,0 +1,20 @@
1
+ #
2
+ # Copyright 2011 Red Hat, Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'infinispan-ruby-client/constants.rb'
18
+ require 'infinispan-ruby-client/headerbuilder.rb'
19
+ require 'infinispan-ruby-client/remotecache.rb'
20
+ require 'infinispan-ruby-client/unsigned.rb'
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: infinispan-ruby-client
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Lance Ball
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-06-08 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ type: :runtime
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ name: bundler
33
+ version_requirements: *id001
34
+ prerelease: false
35
+ - !ruby/object:Gem::Dependency
36
+ type: :development
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ name: jeweler
47
+ version_requirements: *id002
48
+ prerelease: false
49
+ - !ruby/object:Gem::Dependency
50
+ type: :development
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ name: rake
61
+ version_requirements: *id003
62
+ prerelease: false
63
+ - !ruby/object:Gem::Dependency
64
+ type: :development
65
+ requirement: &id004 !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ~>
69
+ - !ruby/object:Gem::Version
70
+ hash: 1
71
+ segments:
72
+ - 2
73
+ - 1
74
+ version: "2.1"
75
+ name: rspec
76
+ version_requirements: *id004
77
+ prerelease: false
78
+ description: infinispan-hotrod-client provides native ruby access to the Infinispan Hotrod API
79
+ email: lball@redhat.com
80
+ executables: []
81
+
82
+ extensions: []
83
+
84
+ extra_rdoc_files:
85
+ - README.markdown
86
+ files:
87
+ - Gemfile.lock
88
+ - README.markdown
89
+ - lib/infinispan-ruby-client.rb
90
+ - lib/infinispan-ruby-client/constants.rb
91
+ - lib/infinispan-ruby-client/headerbuilder.rb
92
+ - lib/infinispan-ruby-client/remotecache.rb
93
+ - lib/infinispan-ruby-client/unsigned.rb
94
+ has_rdoc: true
95
+ homepage: https://github.com/lance/infinispan-ruby-client
96
+ licenses:
97
+ - MIT
98
+ post_install_message:
99
+ rdoc_options: []
100
+
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ hash: 3
109
+ segments:
110
+ - 0
111
+ version: "0"
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ hash: 3
118
+ segments:
119
+ - 0
120
+ version: "0"
121
+ requirements: []
122
+
123
+ rubyforge_project:
124
+ rubygems_version: 1.6.2
125
+ signing_key:
126
+ specification_version: 3
127
+ summary: Infinispan Hotrod Client
128
+ test_files: []
129
+