jruby-memcached 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,34 @@
1
+ ## 0.4.0 (Aug 16, 2012)
2
+
3
+ Bugfixes:
4
+
5
+ - set as daemon thread to avoid suspend ruby process (like rake task)
6
+
7
+ Features:
8
+
9
+ - support get with multiple keys
10
+ - add Memcached::Rails as rails cache_store
11
+ - use jruby annotation to reduce method definitions
12
+
13
+ ## 0.3.0 (Aug 7, 2012)
14
+
15
+ Features:
16
+
17
+ - rewrite with pure jruby implementation
18
+
19
+ ## 0.2.0 (Jul 29, 2012)
20
+
21
+ Bugfixes:
22
+
23
+ - set method should not be async
24
+
25
+ Features:
26
+
27
+ - allow to change hash, distribution and binary protocol
28
+
29
+ ## 0.1.0 (Jul 24, 2012)
30
+
31
+ Features:
32
+
33
+ - wrap java library spymemcached
34
+ - compatible hash and distribution algorithms with memcached.gem
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- jruby-memcached (0.3.0)
4
+ jruby-memcached (0.4.0)
5
5
 
6
6
  GEM
7
7
  remote: http://rubygems.org/
data/README.md CHANGED
@@ -35,11 +35,16 @@ You can get multiple values at once:
35
35
  $cache.get ['test', 'test2', 'missing']
36
36
  #=> {"test" => "hello", "test2" => "hello"}
37
37
 
38
+ ## Rails
39
+
40
+ # config/environment.rb
41
+ config.cache_store = Memcached::Rails.new(:servers => ['127.0.0.1'])
42
+
38
43
  ## Benchmarks
39
44
 
40
45
  memcached.gem is the fastest memcached gem in MRI,
41
46
  jruby-memcached is the fastest memcached gem in JRuby.
42
47
  See [benchmark][1]
43
48
 
44
- [0]:"https://github.com/evan/memcached"
45
- [1]:"https://github.com/aurorafeint/jruby-memcached/blob/master/benchmark.rb"
49
+ [0]: https://github.com/evan/memcached
50
+ [1]: https://github.com/aurorafeint/jruby-memcached/blob/master/benchmark.rb
@@ -1,5 +1,12 @@
1
1
  require 'java'
2
2
  require 'memcached/version'
3
3
  require File.join(File.dirname(__FILE__), '../target/spymemcached-ext-0.0.1.jar')
4
+ require 'com/openfeint/memcached/memcached'
4
5
 
5
- com.openfeint.memcached.MemcachedLibrary.new.load(JRuby.runtime, false)
6
+ class Memcached::Rails
7
+ attr_reader :logger
8
+
9
+ def logger=(logger)
10
+ @logger = logger
11
+ end
12
+ end
@@ -1,3 +1,3 @@
1
1
  class Memcached
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -1,16 +1,23 @@
1
1
  require 'spec_helper'
2
2
 
3
- com.openfeint.memcached.MemcachedLibrary.new.load(JRuby.runtime, false)
4
3
  describe Memcached do
5
4
  context "localhost" do
6
5
  before(:all) { @memcached = Memcached.new(["127.0.0.1:11211"]) }
7
6
  after(:all) { @memcached.shutdown }
8
7
 
9
8
  it "should get all servers" do
9
+ @memcached.set "foo", "bar"
10
10
  @memcached.servers.should == ["127.0.0.1:11211"]
11
11
  end
12
12
 
13
13
  context "initialize" do
14
+ it "should connect to 127.0.0.1:11211 if no server defined" do
15
+ memcached = Memcached.new
16
+ memcached.set "foo", "bar"
17
+ memcached.servers.should == ["127.0.0.1:11211"]
18
+ memcached.shutdown
19
+ end
20
+
14
21
  it "should raise error with unsupported option hash" do
15
22
  lambda { Memcached.new("127.0.0.1:11211", :hash => :unknown) }.should raise_error(Memcached::NotSupport)
16
23
  end
@@ -42,6 +49,24 @@ describe Memcached do
42
49
  @memcached.delete "key" rescue nil
43
50
  lambda { @memcached.get "key" }.should raise_error(Memcached::NotFound)
44
51
  end
52
+
53
+ context "multiget" do
54
+ it "should get hash containing multiple key/value pairs" do
55
+ @memcached.set "key1", "value1"
56
+ @memcached.set "key2", "value2"
57
+ @memcached.get(["key1", "key2"]).should == {"key1" => "value1", "key2" => "value2"}
58
+ end
59
+
60
+ it "should get hash containing nil value" do
61
+ @memcached.set "key", nil, 0
62
+ @memcached.get(["key"]).should == {"key" => nil}
63
+ end
64
+
65
+ it "should get empty hash" do
66
+ @memcached.delete "key" rescue nil
67
+ @memcached.get(["key"]).should be_empty
68
+ end
69
+ end
45
70
  end
46
71
 
47
72
  context "set" do
@@ -99,6 +124,14 @@ describe Memcached do
99
124
  @memcached.delete "key" rescue nil
100
125
  lambda { @memcached.delete "key" }.should raise_error(Memcached::NotFound)
101
126
  end
127
+
128
+ #context "incr/decr" do
129
+ #it "should incr key" do
130
+ #@memcached.incr "intkey"
131
+ #@memcached.incr "intkey"
132
+ #@memcached.get("intkey").should == 1
133
+ #end
134
+ #end
102
135
  end
103
136
 
104
137
  context "flush" do
@@ -110,5 +143,50 @@ describe Memcached do
110
143
  lambda { @memcached.get "key2" }.should raise_error(Memcached::NotFound)
111
144
  end
112
145
  end
146
+
147
+ context "namespace/prefix_key" do
148
+ it "should get/set with namespace" do
149
+ memcached = Memcached.new("127.0.0.1:11211", :namespace => "jruby")
150
+ memcached.set "key", "value"
151
+ memcached.get("key").should == "value"
152
+ memcached.shutdown
153
+ @memcached.get("jrubykey").should == "value"
154
+ end
155
+
156
+ context "prefix_key" do
157
+ before(:all) { @prefix_memcached = Memcached.new("127.0.0.1:11211", :prefix_key => "jruby") }
158
+ after(:all) { @prefix_memcached.shutdown }
159
+
160
+ it "should get/set with prefix_key" do
161
+ @prefix_memcached.set "key", "value"
162
+ @prefix_memcached.get("key").should == "value"
163
+ @memcached.get("jrubykey").should == "value"
164
+ end
165
+
166
+ #it "should incr/decr with prefix_key" do
167
+ #@prefix_memcached.incr "intkey"
168
+ #@prefix_memcached.decr "intkey"
169
+ #@memcached.get("jrubyintkey").should == 0
170
+ #end
171
+
172
+ it "should add/replace with prefix_key" do
173
+ @prefix_memcached.add "newkey", "value"
174
+ @prefix_memcached.replace "newkey", "new_value"
175
+ @memcached.get("jrubynewkey").should == "new_value"
176
+ end
177
+
178
+ it "should delete with prefix_key" do
179
+ @prefix_memcached.set "key", "value"
180
+ @prefix_memcached.delete "key"
181
+ lambda { @memcached.get("jrubykey") }.should raise_error(Memcached::NotFound)
182
+ end
183
+
184
+ it "should multiget with prefix_key" do
185
+ @prefix_memcached.set "key1", "value1"
186
+ @prefix_memcached.set "key2", "value2"
187
+ @prefix_memcached.get(["key1", "key2"]).should == {"key1" => "value1", "key2" => "value2"}
188
+ end
189
+ end
190
+ end
113
191
  end
114
192
  end
@@ -0,0 +1,174 @@
1
+ require 'spec_helper'
2
+
3
+ describe Memcached::Rails do
4
+ context "localhost" do
5
+ before(:all) { @memcached = Memcached::Rails.new(:servers => ["127.0.0.1:11211"]) }
6
+ after(:all) { @memcached.shutdown }
7
+
8
+ it "should get all servers" do
9
+ @memcached.set "foo", "bar"
10
+ @memcached.servers.should == ["127.0.0.1:11211"]
11
+ end
12
+
13
+ it "should get logger" do
14
+ require 'logger'
15
+ @memcached.logger = Logger.new(STDOUT)
16
+ @memcached.logger.should_not be_nil
17
+ end
18
+
19
+ it "should be active" do
20
+ @memcached.should be_active
21
+ end
22
+
23
+ context "get" do
24
+ it "should get value" do
25
+ @memcached.set "key", "value"
26
+ @memcached.get("key").should == "value"
27
+ end
28
+
29
+ it "should get nil if key is missing" do
30
+ @memcached.delete "key" rescue nil
31
+ @memcached.get("key").should be_nil
32
+ end
33
+ end
34
+
35
+ context "read" do
36
+ it "should get value" do
37
+ @memcached.set "key", "value"
38
+ @memcached.get("key").should == "value"
39
+ end
40
+
41
+ it "should get nil if key is missing" do
42
+ @memcached.delete "key" rescue nil
43
+ @memcached.get("key").should be_nil
44
+ end
45
+ end
46
+
47
+ context "exist?" do
48
+ it "should return true if key exists" do
49
+ @memcached.set "key", "value"
50
+ @memcached.exist?("key").should be_true
51
+ end
52
+
53
+ it "should return false if key is missing" do
54
+ @memcached.delete "key" rescue nil
55
+ @memcached.exist?("key").should be_false
56
+ end
57
+ end
58
+
59
+ context "get_multi" do
60
+ it "should get hash containing multiple key/value pairs" do
61
+ @memcached.set "key1", "value1"
62
+ @memcached.set "key2", "value2"
63
+ @memcached.get(["key1", "key2"]).should == {"key1" => "value1", "key2" => "value2"}
64
+ end
65
+
66
+ it "should get hash containing nil value" do
67
+ @memcached.set "key", nil, 0
68
+ @memcached.get(["key"]).should == {"key" => nil}
69
+ end
70
+
71
+ it "should get empty hash" do
72
+ @memcached.delete "key" rescue nil
73
+ @memcached.get(["key"]).should be_empty
74
+ end
75
+ end
76
+
77
+ context "set" do
78
+ it "should set successfully" do
79
+ @memcached.set("key", "value").should be_true
80
+ @memcached.get("key").should == "value"
81
+ end
82
+ end
83
+
84
+ context "write" do
85
+ it "should write successfully" do
86
+ @memcached.write("key", "value").should be_true
87
+ @memcached.read("key").should == "value"
88
+ end
89
+ end
90
+
91
+ context "fetch" do
92
+ it "should read if key exists" do
93
+ @memcached.write("key", "value")
94
+ @memcached.fetch("key") { "new value" }.should == "value"
95
+ end
96
+
97
+ it "should write if key is missing" do
98
+ @memcached.delete "key" rescue nil
99
+ @memcached.fetch("key") { "new value" }.should == "new value"
100
+ end
101
+ end
102
+
103
+ context "add" do
104
+ it "should add if key is missing" do
105
+ @memcached.delete "key" rescue nil
106
+ @memcached.add("key", "value").should be_true
107
+ end
108
+
109
+ it "should do nothing if key exists" do
110
+ @memcached.set "key", "value"
111
+ @memcached.add("key", "value").should be_false
112
+ end
113
+
114
+ context "with string_return_types" do
115
+ before(:all) { @string_memcached = Memcached::Rails.new("127.0.0.1:11211", :string_return_types => true) }
116
+ after(:all) { @string_memcached.quit }
117
+
118
+ it "should add if key is missing" do
119
+ @string_memcached.delete "key" rescue nil
120
+ @string_memcached.add("key", "value").should == "STORED\r\n"
121
+ end
122
+
123
+ it "should do nothing if key exists" do
124
+ @string_memcached.set "key", "value"
125
+ @string_memcached.add("key", "value").should == "NOT STORED\r\n"
126
+ end
127
+ end
128
+ end
129
+
130
+ context "delete" do
131
+ it "should delete existing key" do
132
+ @memcached.set "key", "value"
133
+ @memcached.delete "key"
134
+ @memcached.get("key").should be_nil
135
+ end
136
+
137
+ it "should do nothing if delete missing key" do
138
+ @memcached.delete "key" rescue nil
139
+ @memcached.delete "key"
140
+ @memcached.get("key").should be_nil
141
+ end
142
+ end
143
+
144
+ context "alias" do
145
+ it "should respond_to? flush_all" do
146
+ @memcached.should be_respond_to(:flush_all)
147
+ end
148
+
149
+ it "should respond_to? clear" do
150
+ @memcached.should be_respond_to(:clear)
151
+ end
152
+
153
+ it "should respond_to? :[]" do
154
+ @memcached.should be_respond_to(:"[]")
155
+ end
156
+
157
+ it "should respond_to? :[]=" do
158
+ @memcached.should be_respond_to(:"[]=")
159
+ end
160
+ end
161
+
162
+ context "read_multi" do
163
+ it "should read hash containing multiple key/value pairs" do
164
+ @memcached.write "key1", "value1"
165
+ @memcached.write "key2", "value2"
166
+ @memcached.read_multi(["key1", "key2"]).should == {"key1" => "value1", "key2" => "value2"}
167
+ end
168
+
169
+ it "should read empty hash without params" do
170
+ @memcached.read_multi.should be_empty
171
+ end
172
+ end
173
+ end
174
+ end
@@ -1,15 +1,9 @@
1
1
  package com.openfeint.memcached;
2
2
 
3
- import java.io.IOException;
4
- import java.net.InetSocketAddress;
5
- import java.net.SocketAddress;
6
- import java.util.ArrayList;
7
- import java.util.Arrays;
8
- import java.util.List;
9
- import java.util.Map;
10
- import java.util.concurrent.ExecutionException;
3
+ import com.openfeint.memcached.error.Error;
4
+ import com.openfeint.memcached.transcoder.MarshalTranscoder;
5
+ import com.openfeint.memcached.transcoder.MarshalZlibTranscoder;
11
6
  import net.spy.memcached.AddrUtil;
12
- import net.spy.memcached.CachedData;
13
7
  import net.spy.memcached.ConnectionFactoryBuilder;
14
8
  import net.spy.memcached.ConnectionFactoryBuilder.Locator;
15
9
  import net.spy.memcached.ConnectionFactoryBuilder.Protocol;
@@ -17,127 +11,68 @@ import net.spy.memcached.DefaultHashAlgorithm;
17
11
  import net.spy.memcached.MemcachedClient;
18
12
  import net.spy.memcached.transcoders.Transcoder;
19
13
  import org.jruby.Ruby;
14
+ import org.jruby.RubyArray;
15
+ import org.jruby.RubyBoolean;
20
16
  import org.jruby.RubyClass;
21
- import org.jruby.RubyException;
22
17
  import org.jruby.RubyHash;
23
18
  import org.jruby.RubyObject;
19
+ import org.jruby.RubyString;
24
20
  import org.jruby.anno.JRubyClass;
25
21
  import org.jruby.anno.JRubyMethod;
26
- import org.jruby.exceptions.RaiseException;
27
22
  import org.jruby.runtime.ThreadContext;
28
23
  import org.jruby.runtime.builtin.IRubyObject;
29
24
 
30
- public class Memcached extends RubyObject {
31
- private Ruby ruby;
25
+ import java.io.IOException;
26
+ import java.net.InetSocketAddress;
27
+ import java.net.SocketAddress;
28
+ import java.util.ArrayList;
29
+ import java.util.List;
30
+ import java.util.Map;
31
+ import java.util.concurrent.ExecutionException;
32
32
 
33
+ @JRubyClass(name = "Memcached")
34
+ public class Memcached extends RubyObject {
33
35
  private MemcachedClient client;
34
36
 
35
37
  private Transcoder<IRubyObject> transcoder;
36
38
 
37
39
  private int ttl;
38
40
 
41
+ private String prefixKey;
42
+
39
43
  public Memcached(final Ruby ruby, RubyClass rubyClass) {
40
44
  super(ruby, rubyClass);
41
- this.ruby = ruby;
42
- }
43
45
 
44
- @JRubyMethod
45
- public IRubyObject initialize(ThreadContext context, IRubyObject servers) {
46
- return initialize(context, servers, context.nil);
46
+ ttl = 604800;
47
+ prefixKey = "";
47
48
  }
48
- @JRubyMethod
49
- public IRubyObject initialize(ThreadContext context, IRubyObject servers, IRubyObject options) {
50
- List<InetSocketAddress> addresses;
51
- if ("java.lang.String".equals(servers.getJavaClass().getName())) {
52
- addresses = AddrUtil.getAddresses(servers.convertToString().toString());
49
+
50
+ @JRubyMethod(name = "initialize", optional = 2)
51
+ public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
52
+ Ruby ruby = context.getRuntime();
53
+ RubyHash options;
54
+ if (args.length > 1) {
55
+ options = args[1].convertToHash();
53
56
  } else {
54
- addresses = AddrUtil.getAddresses((List<String>)servers.convertToArray());
57
+ options = new RubyHash(ruby);
55
58
  }
56
- try {
57
- ConnectionFactoryBuilder builder = new ConnectionFactoryBuilder();
58
-
59
- String distributionValue = null;
60
- String hashValue = null;
61
- String binaryValue = null;
62
- String defaultTTL = null;
63
- String transcoderValue = null;
64
- if (!options.isNil()) {
65
- RubyHash opts = options.convertToHash();
66
- if (opts.containsKey(ruby.newSymbol("distribution"))) {
67
- distributionValue = opts.get(ruby.newSymbol("distribution")).toString();
68
- }
69
- if (opts.containsKey(ruby.newSymbol("hash"))) {
70
- hashValue = opts.get(ruby.newSymbol("hash")).toString();
71
- }
72
- if (opts.containsKey(ruby.newSymbol("binary_protocol"))) {
73
- binaryValue = opts.get(ruby.newSymbol("binary_protocol")).toString();
74
- }
75
- if (opts.containsKey(ruby.newSymbol("default_ttl"))) {
76
- defaultTTL = opts.get(ruby.newSymbol("default_ttl")).toString();
77
- }
78
- if (opts.containsKey(ruby.newSymbol("transcoder"))) {
79
- transcoderValue = opts.get(ruby.newSymbol("transcoder")).toString();
80
- }
81
- }
82
-
83
- if (distributionValue == null) {
84
- distributionValue = "ketama";
59
+ List<String> servers = new ArrayList<String>();
60
+ if (args.length > 0) {
61
+ if (args[0] instanceof RubyString) {
62
+ servers.add(args[0].toString());
63
+ } else if (args[0] instanceof RubyArray) {
64
+ servers.addAll((List<String>) args[0].convertToArray());
85
65
  }
86
- if ("array_mod".equals(distributionValue)) {
87
- builder.setLocatorType(Locator.ARRAY_MOD);
88
- } else if ("ketama".equals(distributionValue) || "consistent_ketama".equals(distributionValue)) {
89
- builder.setLocatorType(Locator.CONSISTENT);
90
- } else {
91
- throw newNotSupport(ruby, "distribution not support");
92
- }
93
-
94
- if (hashValue == null) {
95
- hashValue = "fnv1_32";
96
- }
97
- if ("native".equals(hashValue)) {
98
- builder.setHashAlg(DefaultHashAlgorithm.NATIVE_HASH);
99
- } else if ("crc".equals(hashValue)) {
100
- builder.setHashAlg(DefaultHashAlgorithm.CRC_HASH);
101
- } else if ("fnv1_64".equals(hashValue)) {
102
- builder.setHashAlg(DefaultHashAlgorithm.FNV1_64_HASH);
103
- } else if ("fnv1a_64".equals(hashValue)) {
104
- builder.setHashAlg(DefaultHashAlgorithm.FNV1A_64_HASH);
105
- } else if ("fnv1_32".equals(hashValue)) {
106
- builder.setHashAlg(DefaultHashAlgorithm.FNV1_32_HASH);
107
- } else if ("fnv1a_32".equals(hashValue)) {
108
- builder.setHashAlg(DefaultHashAlgorithm.FNV1A_32_HASH);
109
- } else if ("ketama".equals(hashValue)) {
110
- builder.setHashAlg(DefaultHashAlgorithm.KETAMA_HASH);
111
- } else {
112
- throw newNotSupport(ruby, "hash not support");
113
- }
114
-
115
- if ("true".equals(binaryValue)) {
116
- builder.setProtocol(Protocol.BINARY);
117
- }
118
-
119
- if (defaultTTL == null) {
120
- ttl = 604800;
121
- } else {
122
- ttl = Integer.parseInt(defaultTTL);
123
- }
124
-
125
- client = new MemcachedClient(builder.build(), addresses);
126
-
127
- if ("marshal_zlib".equals(transcoderValue)) {
128
- transcoder = new MarshalZlibTranscoder(ruby);
129
- } else {
130
- transcoder = new MarshalTranscoder(ruby);
131
- }
132
- } catch (IOException ioe) {
133
- throw context.runtime.newIOErrorFromException(ioe);
134
66
  }
135
-
136
- return context.nil;
67
+ if (servers.isEmpty()) {
68
+ servers.add("127.0.0.1:11211");
69
+ }
70
+ return init(context, servers, options);
137
71
  }
138
72
 
139
73
  @JRubyMethod
140
74
  public IRubyObject servers(ThreadContext context) {
75
+ Ruby ruby = context.getRuntime();
141
76
  List<IRubyObject> addresses = new ArrayList<IRubyObject>();
142
77
  for (SocketAddress address : client.getAvailableServers()) {
143
78
  String addressStr = address.toString();
@@ -149,136 +84,139 @@ public class Memcached extends RubyObject {
149
84
  return ruby.newArray(addresses);
150
85
  }
151
86
 
152
- @JRubyMethod
153
- public IRubyObject add(ThreadContext context, IRubyObject key, IRubyObject value) {
154
- return add(context, key, value, ruby.newFixnum(ttl));
155
- }
156
-
157
- @JRubyMethod
158
- public IRubyObject add(ThreadContext context, IRubyObject key, IRubyObject value, IRubyObject timeout) {
87
+ @JRubyMethod(name = "add", required = 2, optional = 3)
88
+ public IRubyObject add(ThreadContext context, IRubyObject[] args) {
89
+ Ruby ruby = context.getRuntime();
90
+ String key = getFullKey(args[0].toString());
91
+ IRubyObject value = args[1];
92
+ int timeout = getTimeout(args);
159
93
  try {
160
- boolean result = client.add(key.toString(), (int)timeout.convertToInteger().getLongValue(), value, transcoder).get();
94
+ boolean result = client.add(key, timeout, value, transcoder).get();
161
95
  if (result == false) {
162
- throw newNotStored(ruby, "not stored");
96
+ throw Error.newNotStored(ruby, "not stored");
163
97
  }
164
98
  return context.nil;
165
99
  } catch (ExecutionException ee) {
166
- throw context.runtime.newRuntimeError(ee.getLocalizedMessage());
100
+ throw ruby.newRuntimeError(ee.getLocalizedMessage());
167
101
  } catch (InterruptedException ie) {
168
- throw context.runtime.newThreadError(ie.getLocalizedMessage());
102
+ throw ruby.newThreadError(ie.getLocalizedMessage());
169
103
  }
170
104
  }
171
105
 
172
- @JRubyMethod
173
- public IRubyObject replace(ThreadContext context, IRubyObject key, IRubyObject value) {
174
- return replace(context, key, value, ruby.newFixnum(ttl));
175
- }
176
-
177
- @JRubyMethod
178
- public IRubyObject replace(ThreadContext context, IRubyObject key, IRubyObject value, IRubyObject timeout) {
106
+ @JRubyMethod(name = "replace", required = 2, optional = 3)
107
+ public IRubyObject replace(ThreadContext context, IRubyObject [] args) {
108
+ Ruby ruby = context.getRuntime();
109
+ String key = getFullKey(args[0].toString());
110
+ IRubyObject value = args[1];
111
+ int timeout = getTimeout(args);
179
112
  try {
180
- boolean result = client.replace(key.toString(), (int)timeout.convertToInteger().getLongValue(), value, transcoder).get();
113
+ boolean result = client.replace(key, timeout, value, transcoder).get();
181
114
  if (result == false) {
182
- throw newNotStored(ruby, "not stored");
115
+ throw Error.newNotStored(ruby, "not stored");
183
116
  }
184
117
  return context.nil;
185
118
  } catch (ExecutionException ee) {
186
- throw context.runtime.newRuntimeError(ee.getLocalizedMessage());
119
+ throw ruby.newRuntimeError(ee.getLocalizedMessage());
187
120
  } catch (InterruptedException ie) {
188
- throw context.runtime.newThreadError(ie.getLocalizedMessage());
121
+ throw ruby.newThreadError(ie.getLocalizedMessage());
189
122
  }
190
123
  }
191
124
 
192
- @JRubyMethod
193
- public IRubyObject set(ThreadContext context, IRubyObject key, IRubyObject value) {
194
- return set(context, key, value, ruby.newFixnum(ttl));
195
- }
196
-
197
- @JRubyMethod
198
- public IRubyObject set(ThreadContext context, IRubyObject key, IRubyObject value, IRubyObject timeout) {
125
+ @JRubyMethod(name = "set", required = 2, optional = 3)
126
+ public IRubyObject set(ThreadContext context, IRubyObject[] args) {
127
+ Ruby ruby = context.getRuntime();
128
+ String key = getFullKey(args[0].toString());
129
+ IRubyObject value = args[1];
130
+ int timeout = getTimeout(args);
199
131
  try {
200
- boolean result = client.set(key.toString(), (int)timeout.convertToInteger().getLongValue(), value, transcoder).get();
132
+ boolean result = client.set(key, timeout, value, transcoder).get();
201
133
  if (result == false) {
202
- throw newNotStored(ruby, "not stored");
134
+ throw Error.newNotStored(ruby, "not stored");
203
135
  }
204
136
  return context.nil;
205
137
  } catch (ExecutionException ee) {
206
- throw context.runtime.newRuntimeError(ee.getLocalizedMessage());
138
+ throw ruby.newRuntimeError(ee.getLocalizedMessage());
207
139
  } catch (InterruptedException ie) {
208
- throw context.runtime.newThreadError(ie.getLocalizedMessage());
140
+ throw ruby.newThreadError(ie.getLocalizedMessage());
209
141
  }
210
142
  }
211
143
 
212
- @JRubyMethod
213
- public IRubyObject get(ThreadContext context, IRubyObject key) {
214
- IRubyObject value = client.get(key.toString(), transcoder);
215
- if (value == null) {
216
- throw newNotFound(ruby, "not found");
144
+ @JRubyMethod(name = "get", required = 1, optional = 1)
145
+ public IRubyObject get(ThreadContext context, IRubyObject[] args) {
146
+ Ruby ruby = context.getRuntime();
147
+ IRubyObject keys = args[0];
148
+ if (keys instanceof RubyString) {
149
+ IRubyObject value = client.get(getFullKey(keys.toString()), transcoder);
150
+ if (value == null) {
151
+ throw Error.newNotFound(ruby, "not found");
152
+ }
153
+ return value;
154
+ } else if (keys instanceof RubyArray) {
155
+ RubyHash results = RubyHash.newHash(ruby);
156
+
157
+ Map<String, IRubyObject> bulkResults = client.getBulk(getFullKeys(keys.convertToArray()), transcoder);
158
+ for (String key : (List<String>) keys.convertToArray()) {
159
+ if (bulkResults.containsKey(getFullKey(key))) {
160
+ results.put(key, bulkResults.get(getFullKey(key)));
161
+ }
162
+ }
163
+ return results;
217
164
  }
218
- return value;
219
- }
220
-
221
- @JRubyMethod
222
- public IRubyObject incr(ThreadContext context, IRubyObject key) {
223
- return incr(context, key, ruby.newFixnum(1), ruby.newFixnum(ttl));
224
- }
225
-
226
- @JRubyMethod
227
- public IRubyObject incr(ThreadContext context, IRubyObject key, IRubyObject by) {
228
- return incr(context, key, by, ruby.newFixnum(ttl));
165
+ return context.nil;
229
166
  }
230
167
 
231
- @JRubyMethod
232
- public IRubyObject incr(ThreadContext context, IRubyObject key, IRubyObject by, IRubyObject timeout) {
233
- long result = client.incr(key.toString(), (int)by.convertToInteger().getLongValue(), 1, (int)timeout.convertToInteger().getLongValue());
168
+ @JRubyMethod(name = "incr", required = 1, optional = 2)
169
+ public IRubyObject incr(ThreadContext context, IRubyObject[] args) {
170
+ Ruby ruby = context.getRuntime();
171
+ String key = getFullKey(args[0].toString());
172
+ int by = getIncrDecrBy(args);
173
+ int timeout = getTimeout(args);
174
+ long result = client.incr(key, by, 1, timeout);
234
175
  return ruby.newFixnum(result);
235
176
  }
236
177
 
237
- @JRubyMethod
238
- public IRubyObject decr(ThreadContext context, IRubyObject key) {
239
- return decr(context, key, ruby.newFixnum(1), ruby.newFixnum(ttl));
240
- }
241
-
242
- @JRubyMethod
243
- public IRubyObject decr(ThreadContext context, IRubyObject key, IRubyObject by) {
244
- return decr(context, key, by, ruby.newFixnum(ttl));
245
- }
246
-
247
- @JRubyMethod
248
- public IRubyObject decr(ThreadContext context, IRubyObject key, IRubyObject by, IRubyObject timeout) {
249
- long result = client.decr(key.toString(), (int)by.convertToInteger().getLongValue(), 0, (int)timeout.convertToInteger().getLongValue());
178
+ @JRubyMethod(name = "decr", required = 1, optional = 2)
179
+ public IRubyObject decr(ThreadContext context, IRubyObject[] args) {
180
+ Ruby ruby = context.getRuntime();
181
+ String key = getFullKey(args[0].toString());
182
+ int by = getIncrDecrBy(args);
183
+ int timeout = getTimeout(args);
184
+ long result = client.decr(key, by, 0, timeout);
250
185
  return ruby.newFixnum(result);
251
186
  }
252
187
 
253
188
  @JRubyMethod
254
189
  public IRubyObject delete(ThreadContext context, IRubyObject key) {
190
+ Ruby ruby = context.getRuntime();
255
191
  try {
256
- boolean result = client.delete(key.toString()).get();
192
+ boolean result = client.delete(getFullKey(key.toString())).get();
257
193
  if (result == false) {
258
- throw newNotFound(ruby, "not found");
194
+ throw Error.newNotFound(ruby, "not found");
259
195
  }
260
196
  return context.nil;
261
197
  } catch (ExecutionException ee) {
262
- throw context.runtime.newRuntimeError(ee.getLocalizedMessage());
198
+ throw ruby.newRuntimeError(ee.getLocalizedMessage());
263
199
  } catch (InterruptedException ie) {
264
- throw context.runtime.newThreadError(ie.getLocalizedMessage());
200
+ throw ruby.newThreadError(ie.getLocalizedMessage());
265
201
  }
266
202
  }
267
203
 
268
204
  @JRubyMethod
269
205
  public IRubyObject flush(ThreadContext context) {
206
+ Ruby ruby = context.getRuntime();
270
207
  try {
271
208
  client.flush().get();
272
209
  return context.nil;
273
210
  } catch (ExecutionException ee) {
274
- throw context.runtime.newRuntimeError(ee.getLocalizedMessage());
211
+ throw ruby.newRuntimeError(ee.getLocalizedMessage());
275
212
  } catch (InterruptedException ie) {
276
- throw context.runtime.newThreadError(ie.getLocalizedMessage());
213
+ throw ruby.newThreadError(ie.getLocalizedMessage());
277
214
  }
278
215
  }
279
216
 
280
217
  @JRubyMethod
281
218
  public IRubyObject stats(ThreadContext context) {
219
+ Ruby ruby = context.getRuntime();
282
220
  RubyHash results = RubyHash.newHash(ruby);
283
221
  for(Map.Entry<SocketAddress, Map<String, String>> entry : client.getStats().entrySet()) {
284
222
  RubyHash serverHash = RubyHash.newHash(ruby);
@@ -290,36 +228,119 @@ public class Memcached extends RubyObject {
290
228
  return results;
291
229
  }
292
230
 
293
- @JRubyMethod
231
+ @JRubyMethod(name = {"quit", "shutdown"})
294
232
  public IRubyObject shutdown(ThreadContext context) {
295
233
  client.shutdown();
296
234
 
297
235
  return context.nil;
298
236
  }
299
237
 
300
- @JRubyClass(name="Memcached::Error", parent="RuntimeError")
301
- public static class Error {}
302
- @JRubyClass(name="Memcached::NotFound", parent="Memcached::Error")
303
- public static class NotFound extends Error {}
304
- @JRubyClass(name="Memcached::NotStored", parent="Memcached::Error")
305
- public static class NotStored extends Error {}
306
- @JRubyClass(name="Memcached::NotSupport", parent="Memcached::Error")
307
- public static class NotSupport extends Error {}
308
-
309
- static RaiseException newNotFound(Ruby ruby, String message) {
310
- return newMemcachedError(ruby, "NotFound", message);
238
+ protected int getDefaultTTL() {
239
+ return ttl;
311
240
  }
312
241
 
313
- static RaiseException newNotStored(Ruby ruby, String message) {
314
- return newMemcachedError(ruby, "NotStored", message);
242
+ protected IRubyObject init(ThreadContext context, List<String> servers, RubyHash options) {
243
+ Ruby ruby = context.getRuntime();
244
+ List<InetSocketAddress> addresses = AddrUtil.getAddresses(servers);
245
+ try {
246
+ ConnectionFactoryBuilder builder = new ConnectionFactoryBuilder();
247
+
248
+ String distributionValue = "ketama";
249
+ String hashValue = "fnv1_32";
250
+ RubyBoolean binaryValue = ruby.getFalse();
251
+ String transcoderValue = null;
252
+ if (!options.isEmpty()) {
253
+ RubyHash opts = options.convertToHash();
254
+ if (opts.containsKey(ruby.newSymbol("distribution"))) {
255
+ distributionValue = opts.get(ruby.newSymbol("distribution")).toString();
256
+ }
257
+ if (opts.containsKey(ruby.newSymbol("hash"))) {
258
+ hashValue = opts.get(ruby.newSymbol("hash")).toString();
259
+ }
260
+ if (opts.containsKey(ruby.newSymbol("binary_protocol"))) {
261
+ binaryValue = (RubyBoolean) opts.get(ruby.newSymbol("binary_protocol"));
262
+ }
263
+ if (opts.containsKey(ruby.newSymbol("default_ttl"))) {
264
+ ttl = Integer.parseInt(opts.get(ruby.newSymbol("default_ttl")).toString());
265
+ }
266
+ if (opts.containsKey(ruby.newSymbol("namespace"))) {
267
+ prefixKey = opts.get(ruby.newSymbol("namespace")).toString();
268
+ }
269
+ if (opts.containsKey(ruby.newSymbol("prefix_key"))) {
270
+ prefixKey = opts.get(ruby.newSymbol("prefix_key")).toString();
271
+ }
272
+ if (opts.containsKey(ruby.newSymbol("transcoder"))) {
273
+ transcoderValue = opts.get(ruby.newSymbol("transcoder")).toString();
274
+ }
275
+ }
276
+
277
+ if ("array_mod".equals(distributionValue)) {
278
+ builder.setLocatorType(Locator.ARRAY_MOD);
279
+ } else if ("ketama".equals(distributionValue) || "consistent_ketama".equals(distributionValue)) {
280
+ builder.setLocatorType(Locator.CONSISTENT);
281
+ } else {
282
+ throw Error.newNotSupport(ruby, "distribution not support");
283
+ }
284
+ if ("native".equals(hashValue)) {
285
+ builder.setHashAlg(DefaultHashAlgorithm.NATIVE_HASH);
286
+ } else if ("crc".equals(hashValue)) {
287
+ builder.setHashAlg(DefaultHashAlgorithm.CRC_HASH);
288
+ } else if ("fnv1_64".equals(hashValue)) {
289
+ builder.setHashAlg(DefaultHashAlgorithm.FNV1_64_HASH);
290
+ } else if ("fnv1a_64".equals(hashValue)) {
291
+ builder.setHashAlg(DefaultHashAlgorithm.FNV1A_64_HASH);
292
+ } else if ("fnv1_32".equals(hashValue)) {
293
+ builder.setHashAlg(DefaultHashAlgorithm.FNV1_32_HASH);
294
+ } else if ("fnv1a_32".equals(hashValue)) {
295
+ builder.setHashAlg(DefaultHashAlgorithm.FNV1A_32_HASH);
296
+ } else if ("ketama".equals(hashValue)) {
297
+ builder.setHashAlg(DefaultHashAlgorithm.KETAMA_HASH);
298
+ } else {
299
+ throw Error.newNotSupport(ruby, "hash not support");
300
+ }
301
+
302
+ if (ruby.getTrue() == binaryValue) {
303
+ builder.setProtocol(Protocol.BINARY);
304
+ }
305
+
306
+ builder.setDaemon(true);
307
+ client = new MemcachedClient(builder.build(), addresses);
308
+
309
+ if ("marshal_zlib".equals(transcoderValue)) {
310
+ transcoder = new MarshalZlibTranscoder(ruby);
311
+ } else {
312
+ transcoder = new MarshalTranscoder(ruby);
313
+ }
314
+ } catch (IOException ioe) {
315
+ throw ruby.newIOErrorFromException(ioe);
316
+ }
317
+
318
+ return context.nil;
319
+ }
320
+
321
+ private int getTimeout(IRubyObject[] args) {
322
+ if (args.length > 2) {
323
+ return (int) args[2].convertToInteger().getLongValue();
324
+ }
325
+ return ttl;
315
326
  }
316
327
 
317
- static RaiseException newNotSupport(Ruby ruby, String message) {
318
- return newMemcachedError(ruby, "NotSupport", message);
328
+ private int getIncrDecrBy(IRubyObject[] args) {
329
+ if (args.length > 1) {
330
+ return (int) args[1].convertToInteger().getLongValue();
331
+ }
332
+ return 1;
333
+ }
334
+
335
+ private List<String> getFullKeys(List<String> keys) {
336
+ List<String> fullKeys = new ArrayList<String>();
337
+ for (String key : keys) {
338
+ fullKeys.add(getFullKey(key));
339
+ }
340
+ return fullKeys;
319
341
  }
320
342
 
321
- private static RaiseException newMemcachedError(Ruby ruby, String klass, String message) {
322
- RubyClass errorClass = ruby.getModule("Memcached").getClass(klass);
323
- return new RaiseException(RubyException.newException(ruby, errorClass, message), true);
343
+ private String getFullKey(String key) {
344
+ return prefixKey + key;
324
345
  }
325
346
  }