jruby-memcached-thoughtworks 0.6.0

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.
@@ -0,0 +1,511 @@
1
+ package com.openfeint.memcached;
2
+
3
+ import com.openfeint.memcached.error.Error;
4
+ import com.openfeint.memcached.transcoder.MarshalTranscoder;
5
+ import com.openfeint.memcached.transcoder.MarshalZlibTranscoder;
6
+ import net.spy.memcached.AddrUtil;
7
+ import net.spy.memcached.ConnectionFactoryBuilder;
8
+ import net.spy.memcached.ConnectionFactoryBuilder.Locator;
9
+ import net.spy.memcached.ConnectionFactoryBuilder.Protocol;
10
+ import net.spy.memcached.DefaultHashAlgorithm;
11
+ import net.spy.memcached.MemcachedClient;
12
+ import net.spy.memcached.OperationTimeoutException;
13
+ import net.spy.memcached.transcoders.Transcoder;
14
+ import org.jruby.Ruby;
15
+ import org.jruby.RubyArray;
16
+ import org.jruby.RubyBoolean;
17
+ import org.jruby.RubyClass;
18
+ import org.jruby.RubyHash;
19
+ import org.jruby.RubyObject;
20
+ import org.jruby.RubyString;
21
+ import org.jruby.anno.JRubyClass;
22
+ import org.jruby.anno.JRubyMethod;
23
+ import org.jruby.exceptions.RaiseException;
24
+ import org.jruby.runtime.ThreadContext;
25
+ import org.jruby.runtime.builtin.IRubyObject;
26
+
27
+ import java.io.IOException;
28
+ import java.net.InetSocketAddress;
29
+ import java.net.SocketAddress;
30
+ import java.util.ArrayList;
31
+ import java.util.List;
32
+ import java.util.HashMap;
33
+ import java.util.Map;
34
+ import java.util.concurrent.ExecutionException;
35
+ import java.util.Collection;
36
+
37
+ @JRubyClass(name = "Memcached")
38
+ public class Memcached extends RubyObject {
39
+ private MemcachedClient client;
40
+
41
+ private Transcoder transcoder;
42
+
43
+ private int ttl;
44
+
45
+ private int timeout;
46
+
47
+ private int exceptionRetryLimit;
48
+
49
+ private String prefixKey;
50
+
51
+ public Memcached(final Ruby ruby, RubyClass rubyClass) {
52
+ super(ruby, rubyClass);
53
+
54
+ ttl = 604800;
55
+ timeout = -1;
56
+ exceptionRetryLimit = 5;
57
+ prefixKey = "";
58
+ }
59
+
60
+ @JRubyMethod(name = "initialize", optional = 2)
61
+ public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
62
+ Ruby ruby = context.getRuntime();
63
+ Map<String, String> options = new HashMap<String, String>();
64
+ if (args.length > 1) {
65
+ RubyHash arguments = args[1].convertToHash();
66
+ for (Object key : arguments.keySet()) {
67
+ if (arguments.get(key) != null) {
68
+ options.put(key.toString(), arguments.get(key).toString());
69
+ }
70
+ }
71
+ }
72
+ List<String> servers = new ArrayList<String>();
73
+ if (args.length > 0) {
74
+ if (args[0] instanceof RubyString) {
75
+ servers.add(args[0].toString());
76
+ } else if (args[0] instanceof RubyArray) {
77
+ servers.addAll((List<String>) args[0].convertToArray());
78
+ }
79
+ }
80
+ if (servers.isEmpty()) {
81
+ servers.add("127.0.0.1:11211");
82
+ }
83
+ return init(context, servers, options);
84
+ }
85
+
86
+ @JRubyMethod
87
+ public IRubyObject servers(ThreadContext context) {
88
+ Ruby ruby = context.getRuntime();
89
+ List<IRubyObject> addresses = new ArrayList<IRubyObject>();
90
+ for (SocketAddress address : client.getAvailableServers()) {
91
+ String addressStr = address.toString();
92
+ if (addressStr.indexOf("/") == 0) {
93
+ addressStr = addressStr.replace("/", "");
94
+ }
95
+ addresses.add(ruby.newString(addressStr));
96
+ }
97
+ return ruby.newArray(addresses);
98
+ }
99
+
100
+ @JRubyMethod(name = "add", required = 2, optional = 3)
101
+ public IRubyObject add(ThreadContext context, IRubyObject[] args) {
102
+ Ruby ruby = context.getRuntime();
103
+ String key = getFullKey(args[0].toString());
104
+ IRubyObject value = args[1];
105
+ int expiry = getExpiry(args);
106
+ int retry = 0;
107
+ while (true) {
108
+ try {
109
+ Boolean result = (Boolean) client.add(key, expiry, value, transcoder).get();
110
+ if (!result) {
111
+ throw Error.newNotStored(ruby, "not stored");
112
+ }
113
+ return context.nil;
114
+ } catch (RaiseException e) {
115
+ throw e;
116
+ } catch (ExecutionException e) {
117
+ if ("net.spy.memcached.internal.CheckedOperationTimeoutException".equals(e.getCause().getClass().getName())) {
118
+ if (retry == exceptionRetryLimit) {
119
+ throw Error.newATimeoutOccurred(ruby, e.getLocalizedMessage());
120
+ }
121
+ retry++;
122
+ } else {
123
+ throw ruby.newRuntimeError(e.getLocalizedMessage());
124
+ }
125
+ } catch (RuntimeException e) {
126
+ if (e.getCause() != null &&
127
+ "net.spy.memcached.internal.CheckedOperationTimeoutException".equals(e.getCause().getClass().getName())) {
128
+ if (retry == exceptionRetryLimit) {
129
+ throw Error.newATimeoutOccurred(ruby, e.getLocalizedMessage());
130
+ }
131
+ retry++;
132
+ } else {
133
+ throw ruby.newRuntimeError(e.getLocalizedMessage());
134
+ }
135
+ } catch (InterruptedException e) {
136
+ throw ruby.newThreadError(e.getLocalizedMessage());
137
+ }
138
+ }
139
+ }
140
+
141
+ @JRubyMethod(name = "replace", required = 2, optional = 3)
142
+ public IRubyObject replace(ThreadContext context, IRubyObject [] args) {
143
+ Ruby ruby = context.getRuntime();
144
+ String key = getFullKey(args[0].toString());
145
+ IRubyObject value = args[1];
146
+ int expiry = getExpiry(args);
147
+ int retry = 0;
148
+ while (true) {
149
+ try {
150
+ Boolean result = (Boolean) client.replace(key, expiry, value, transcoder).get();
151
+ if (!result) {
152
+ throw Error.newNotStored(ruby, "not stored");
153
+ }
154
+ return context.nil;
155
+ } catch (RaiseException e) {
156
+ throw e;
157
+ } catch (ExecutionException e) {
158
+ if ("net.spy.memcached.internal.CheckedOperationTimeoutException".equals(e.getCause().getClass().getName())) {
159
+ if (retry == exceptionRetryLimit) {
160
+ throw Error.newATimeoutOccurred(ruby, e.getLocalizedMessage());
161
+ }
162
+ retry++;
163
+ } else {
164
+ throw ruby.newRuntimeError(e.getLocalizedMessage());
165
+ }
166
+ } catch (RuntimeException e) {
167
+ if (e.getCause() != null &&
168
+ "net.spy.memcached.internal.CheckedOperationTimeoutException".equals(e.getCause().getClass().getName())) {
169
+ if (retry == exceptionRetryLimit) {
170
+ throw Error.newATimeoutOccurred(ruby, e.getLocalizedMessage());
171
+ }
172
+ retry++;
173
+ } else {
174
+ throw ruby.newRuntimeError(e.getLocalizedMessage());
175
+ }
176
+ } catch (InterruptedException e) {
177
+ throw ruby.newThreadError(e.getLocalizedMessage());
178
+ }
179
+ }
180
+ }
181
+
182
+ @JRubyMethod(name = "set", required = 2, optional = 3)
183
+ public IRubyObject set(ThreadContext context, IRubyObject[] args) {
184
+ Ruby ruby = context.getRuntime();
185
+ String key = getFullKey(args[0].toString());
186
+ IRubyObject value = args[1];
187
+ int expiry = getExpiry(args);
188
+ int retry = 0;
189
+ while (true) {
190
+ try {
191
+ Boolean result = (Boolean) client.set(key, expiry, value, transcoder).get();
192
+ if (!result) {
193
+ throw Error.newNotStored(ruby, "not stored");
194
+ }
195
+ return context.nil;
196
+ } catch (RaiseException e) {
197
+ throw e;
198
+ } catch (ExecutionException e) {
199
+ if ("net.spy.memcached.internal.CheckedOperationTimeoutException".equals(e.getCause().getClass().getName())) {
200
+ if (retry == exceptionRetryLimit) {
201
+ throw Error.newATimeoutOccurred(ruby, e.getLocalizedMessage());
202
+ }
203
+ retry++;
204
+ } else {
205
+ throw ruby.newRuntimeError(e.getLocalizedMessage());
206
+ }
207
+ } catch (RuntimeException e) {
208
+ if (e.getCause() != null &&
209
+ "net.spy.memcached.internal.CheckedOperationTimeoutException".equals(e.getCause().getClass().getName())) {
210
+ if (retry == exceptionRetryLimit) {
211
+ throw Error.newATimeoutOccurred(ruby, e.getLocalizedMessage());
212
+ }
213
+ retry++;
214
+ } else {
215
+ throw ruby.newRuntimeError(e.getLocalizedMessage());
216
+ }
217
+ } catch (InterruptedException e) {
218
+ throw ruby.newThreadError(e.getLocalizedMessage());
219
+ }
220
+ }
221
+ }
222
+
223
+ @JRubyMethod(name = "get", required = 1, optional = 1)
224
+ public IRubyObject get(ThreadContext context, IRubyObject[] args) {
225
+ Ruby ruby = context.getRuntime();
226
+ IRubyObject keys = args[0];
227
+ int retry = 0;
228
+ while (true) {
229
+ try {
230
+ if (keys instanceof RubyString) {
231
+ Object ret = client.get(getFullKey(keys.toString()), transcoder);
232
+ if (ret == null) {
233
+ throw Error.newNotFound(ruby, "not found");
234
+ }
235
+ IRubyObject value;
236
+ if (ret instanceof IRubyObject) {
237
+ value = (IRubyObject) ret;
238
+ } else {
239
+ value = ruby.newFixnum((Long) ret);
240
+ }
241
+ return value;
242
+ } else if (keys instanceof RubyArray) {
243
+ RubyHash results = RubyHash.newHash(ruby);
244
+
245
+ Map<String, IRubyObject> bulkResults = (Map<String, IRubyObject>) client.getBulk(getFullKeys(keys.convertToArray()), transcoder);
246
+ for (String key : (List<String>) keys.convertToArray()) {
247
+ if (bulkResults.containsKey(getFullKey(key))) {
248
+ results.put(key, bulkResults.get(getFullKey(key)));
249
+ }
250
+ }
251
+ return results;
252
+ }
253
+ } catch (RaiseException e) {
254
+ throw e;
255
+ } catch (OperationTimeoutException e) {
256
+ if (retry == exceptionRetryLimit) {
257
+ throw Error.newATimeoutOccurred(ruby, e.getLocalizedMessage());
258
+ }
259
+ retry++;
260
+ } catch (RuntimeException e) {
261
+ throw ruby.newRuntimeError(e.getLocalizedMessage());
262
+ }
263
+ }
264
+ }
265
+
266
+ @JRubyMethod(name = { "increment", "incr" }, required = 1, optional = 2)
267
+ public IRubyObject incr(ThreadContext context, IRubyObject[] args) {
268
+ Ruby ruby = context.getRuntime();
269
+ String key = getFullKey(args[0].toString());
270
+ int by = getIncrDecrBy(args);
271
+ int expiry = getExpiry(args);
272
+ int retry = 0;
273
+ while (true) {
274
+ try {
275
+ long result = client.incr(key, by, 1, expiry);
276
+ return ruby.newFixnum(result);
277
+ } catch (OperationTimeoutException e) {
278
+ if (retry == exceptionRetryLimit) {
279
+ throw Error.newATimeoutOccurred(ruby, e.getLocalizedMessage());
280
+ }
281
+ retry++;
282
+ }
283
+ }
284
+ }
285
+
286
+ @JRubyMethod(name = { "decrement", "decr" }, required = 1, optional = 2)
287
+ public IRubyObject decr(ThreadContext context, IRubyObject[] args) {
288
+ Ruby ruby = context.getRuntime();
289
+ String key = getFullKey(args[0].toString());
290
+ int by = getIncrDecrBy(args);
291
+ int expiry = getExpiry(args);
292
+ int retry = 0;
293
+ while (true) {
294
+ try {
295
+ long result = client.decr(key, by, 0, expiry);
296
+ return ruby.newFixnum(result);
297
+ } catch (OperationTimeoutException e) {
298
+ if (retry == exceptionRetryLimit) {
299
+ throw Error.newATimeoutOccurred(ruby, e.getLocalizedMessage());
300
+ }
301
+ retry++;
302
+ }
303
+ }
304
+ }
305
+
306
+ @JRubyMethod(name = "delete")
307
+ public IRubyObject delete(ThreadContext context, IRubyObject key) {
308
+ Ruby ruby = context.getRuntime();
309
+ int retry = 0;
310
+ while (true) {
311
+ try {
312
+ boolean result = client.delete(getFullKey(key.toString())).get();
313
+ if (!result) {
314
+ throw Error.newNotFound(ruby, "not found");
315
+ }
316
+ return context.nil;
317
+ } catch (RaiseException e) {
318
+ throw e;
319
+ } catch (ExecutionException e) {
320
+ if ("net.spy.memcached.internal.CheckedOperationTimeoutException".equals(e.getCause().getClass().getName())) {
321
+ if (retry == exceptionRetryLimit) {
322
+ throw Error.newATimeoutOccurred(ruby, e.getLocalizedMessage());
323
+ }
324
+ retry++;
325
+ } else {
326
+ throw ruby.newRuntimeError(e.getLocalizedMessage());
327
+ }
328
+ } catch (RuntimeException e) {
329
+ if (e.getCause() != null &&
330
+ "net.spy.memcached.internal.CheckedOperationTimeoutException".equals(e.getCause().getClass().getName())) {
331
+ if (retry == exceptionRetryLimit) {
332
+ throw Error.newATimeoutOccurred(ruby, e.getLocalizedMessage());
333
+ }
334
+ retry++;
335
+ } else {
336
+ throw ruby.newRuntimeError(e.getLocalizedMessage());
337
+ }
338
+ } catch (InterruptedException e) {
339
+ throw ruby.newThreadError(e.getLocalizedMessage());
340
+ }
341
+ }
342
+ }
343
+
344
+ @JRubyMethod
345
+ public IRubyObject flush(ThreadContext context) {
346
+ Ruby ruby = context.getRuntime();
347
+ try {
348
+ client.flush().get();
349
+ return context.nil;
350
+ } catch (OperationTimeoutException e) {
351
+ throw Error.newATimeoutOccurred(ruby, e.getLocalizedMessage());
352
+ } catch (ExecutionException e) {
353
+ throw ruby.newRuntimeError(e.getLocalizedMessage());
354
+ } catch (InterruptedException e) {
355
+ throw ruby.newThreadError(e.getLocalizedMessage());
356
+ }
357
+ }
358
+
359
+ @JRubyMethod
360
+ public IRubyObject stats(ThreadContext context) {
361
+ Ruby ruby = context.getRuntime();
362
+ RubyHash results = RubyHash.newHash(ruby);
363
+ for(Map.Entry<SocketAddress, Map<String, String>> entry : client.getStats().entrySet()) {
364
+ RubyHash serverHash = RubyHash.newHash(ruby);
365
+ for(Map.Entry<String, String> server : entry.getValue().entrySet()) {
366
+ serverHash.op_aset(context, ruby.newString(server.getKey()), ruby.newString(server.getValue()));
367
+ }
368
+ results.op_aset(context, ruby.newString(entry.getKey().toString()), serverHash);
369
+ }
370
+ return results;
371
+ }
372
+
373
+ @JRubyMethod(name = {"quit", "shutdown"})
374
+ public IRubyObject shutdown(ThreadContext context) {
375
+ client.shutdown();
376
+
377
+ return context.nil;
378
+ }
379
+
380
+ @JRubyMethod(name = "active?")
381
+ public IRubyObject isActive(ThreadContext context) {
382
+ Ruby ruby = context.getRuntime();
383
+ Collection addresses = client.getAvailableServers();
384
+ return ruby.newBoolean(!addresses.isEmpty());
385
+ }
386
+
387
+ protected int getDefaultTTL() {
388
+ return ttl;
389
+ }
390
+
391
+ protected IRubyObject init(ThreadContext context, List<String> servers, Map<String, String> opts) {
392
+ Ruby ruby = context.getRuntime();
393
+ List<InetSocketAddress> addresses = AddrUtil.getAddresses(servers);
394
+ try {
395
+ ConnectionFactoryBuilder builder = new ConnectionFactoryBuilder();
396
+
397
+ String distributionValue = "ketama";
398
+ String hashValue = "fnv1_32";
399
+ boolean binaryValue = false;
400
+ boolean shouldOptimize = false;
401
+ String transcoderValue = null;
402
+ if (!opts.isEmpty()) {
403
+ if (opts.containsKey("distribution")) {
404
+ distributionValue = opts.get("distribution");
405
+ }
406
+ if (opts.containsKey("hash")) {
407
+ hashValue = opts.get("hash");
408
+ }
409
+ if (opts.containsKey("binary_protocol")) {
410
+ binaryValue = Boolean.parseBoolean(opts.get("binary_protocol"));
411
+ }
412
+ if (opts.containsKey("should_optimize")) {
413
+ shouldOptimize = Boolean.parseBoolean(opts.get("should_optimize"));
414
+ }
415
+ if (opts.containsKey("default_ttl")) {
416
+ ttl = Integer.parseInt(opts.get("default_ttl"));
417
+ }
418
+ if (opts.containsKey("timeout")) {
419
+ timeout = Integer.parseInt(opts.get("timeout"));
420
+ }
421
+ if (opts.containsKey("exception_retry_limit")) {
422
+ exceptionRetryLimit = Integer.parseInt(opts.get("exception_retry_limit"));
423
+ }
424
+ if (opts.containsKey("namespace")) {
425
+ prefixKey = opts.get("namespace");
426
+ }
427
+ if (opts.containsKey("prefix_key")) {
428
+ prefixKey = opts.get("prefix_key");
429
+ }
430
+ if (opts.containsKey("transcoder")) {
431
+ transcoderValue = opts.get("transcoder");
432
+ }
433
+ }
434
+
435
+ if ("array_mod".equals(distributionValue)) {
436
+ builder.setLocatorType(Locator.ARRAY_MOD);
437
+ } else if ("ketama".equals(distributionValue) || "consistent_ketama".equals(distributionValue)) {
438
+ builder.setLocatorType(Locator.CONSISTENT);
439
+ } else {
440
+ throw Error.newNotSupport(ruby, "distribution not support");
441
+ }
442
+ if ("native".equals(hashValue)) {
443
+ builder.setHashAlg(DefaultHashAlgorithm.NATIVE_HASH);
444
+ } else if ("crc".equals(hashValue)) {
445
+ builder.setHashAlg(DefaultHashAlgorithm.CRC_HASH);
446
+ } else if ("fnv1_64".equals(hashValue)) {
447
+ builder.setHashAlg(DefaultHashAlgorithm.FNV1_64_HASH);
448
+ } else if ("fnv1a_64".equals(hashValue)) {
449
+ builder.setHashAlg(DefaultHashAlgorithm.FNV1A_64_HASH);
450
+ } else if ("fnv1_32".equals(hashValue)) {
451
+ builder.setHashAlg(DefaultHashAlgorithm.FNV1_32_HASH);
452
+ } else if ("fnv1a_32".equals(hashValue)) {
453
+ builder.setHashAlg(DefaultHashAlgorithm.FNV1A_32_HASH);
454
+ } else if ("ketama".equals(hashValue)) {
455
+ builder.setHashAlg(DefaultHashAlgorithm.KETAMA_HASH);
456
+ } else {
457
+ throw Error.newNotSupport(ruby, "hash not support");
458
+ }
459
+
460
+ if (binaryValue) {
461
+ builder.setProtocol(Protocol.BINARY);
462
+ }
463
+ if (shouldOptimize) {
464
+ builder.setShouldOptimize(true);
465
+ }
466
+
467
+ if (timeout != -1) {
468
+ builder.setOpTimeout(timeout);
469
+ }
470
+ builder.setDaemon(true);
471
+ if ("marshal_zlib".equals(transcoderValue)) {
472
+ transcoder = new MarshalZlibTranscoder(ruby);
473
+ } else {
474
+ transcoder = new MarshalTranscoder(ruby);
475
+ }
476
+ builder.setTranscoder(transcoder);
477
+
478
+ client = new MemcachedClient(builder.build(), addresses);
479
+
480
+ return context.nil;
481
+ } catch (IOException e) {
482
+ throw ruby.newIOErrorFromException(e);
483
+ }
484
+ }
485
+
486
+ private int getExpiry(IRubyObject[] args) {
487
+ if (args.length > 2) {
488
+ return (int) args[2].convertToInteger().getLongValue();
489
+ }
490
+ return ttl;
491
+ }
492
+
493
+ private int getIncrDecrBy(IRubyObject[] args) {
494
+ if (args.length > 1) {
495
+ return (int) args[1].convertToInteger().getLongValue();
496
+ }
497
+ return 1;
498
+ }
499
+
500
+ private List<String> getFullKeys(List<String> keys) {
501
+ List<String> fullKeys = new ArrayList<String>();
502
+ for (String key : keys) {
503
+ fullKeys.add(getFullKey(key));
504
+ }
505
+ return fullKeys;
506
+ }
507
+
508
+ private String getFullKey(String key) {
509
+ return prefixKey + key;
510
+ }
511
+ }